Fork fix: the SDIO wedge is FIXED (esp-hosted-mcu #167)
Root cause (verified against our exact IDF tree, not the community guess): the "258" in "sdio_write_task: Failed to send data: 258" is NOT a timeout (that is 263). 258 = 0x102 = ESP_ERR_INVALID_ARG. On the ESP32-P4, block- mode CMD53 writes require the SOURCE buffer to be 64-byte (cache-line) aligned; the IDF sdmmc driver rejects a misaligned source with INVALID_ARG BEFORE any bus activity. esp_hosts write loop then declares "Unrecoverable host sdio state" and reboots the whole P4. The audio TX payload is not 64-aligned, so streaming mic audio wedged on the very FIRST frame (which is exactly what we saw: listening -> instant Failed to send -> reboot). This also explains why buffer/queue/clock/retry tuning all did nothing: the write never reached the bus. And why our symptom was instant, not after ~100 writes (the community block-mode-desync theory) — it is the first misaligned buffer, every time. Fix: vendored esp_hosted 2.12.11 as an editable local component (overrides the registry copy) and bounce a misaligned TX payload through one aligned DMA scratch buffer in hosted_sdio_write_block (port_esp_hosted_host_sdio.c). TX is serialized by the bus lock so a single static bounce buffer is safe; freed in hosted_sdio_deinit. Host-only change — no C6 reflash. VERIFIED ON HARDWARE (autonomous self-test): 40s of continuous mic-audio upstream streaming — the traffic that previously wedged on the first frame — ran clean, zero timeouts, zero reboots. A guarded SDIO_TX_SELFTEST harness is kept (compiled out) for future SDIO stress testing. Credit: root cause + patch designed via multi-agent investigation; the precise 258=INVALID_ARG decode (correcting the upstream community timeout assumption) came from checking our actual esp_err.h. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
|
||||
list(PREPEND SDKCONFIG_DEFAULTS "sdkconfig.mbedtls_minimal" "sdkconfig.defaults")
|
||||
|
||||
set(COMPONENTS main)
|
||||
|
||||
project(thermostat)
|
||||
@@ -0,0 +1,95 @@
|
||||
| Supported Hosts | ESP32-P4 |
|
||||
| --------------- | -------- |
|
||||
|
||||
| Supported Targets | Co-processors with Zigbee RCP Support |
|
||||
|-------------------|---------------------------------------|
|
||||
|
||||
# Thermostat Example
|
||||
|
||||
This example demonstrates how to configure a Home Automation thermostat on a Zigbee Coordinator. Is has been modified from the [ESP Zigbee SDK Thermostat example](https://github.com/espressif/esp-zigbee-sdk/tree/main/examples/home_automation_devices/thermostat).
|
||||
|
||||
## Hardware Required
|
||||
|
||||
* An ESP32-P4 connected to a coprocessor with 802.15.4 support (e.g., ESP32-H2 or ESP32-C6) running this example. The ESP-Hosted Transport **and** UART should be connected between the two SOCs. See the [OpenThread and Zigbee Support](https://github.com/espressif/esp-hosted-mcu/blob/main/docs/openthread_zigbee.md) documentation for more information.
|
||||
* A second board running as a Zigbee end-device (see the [ESP Zigbee SDK temperature\_sensor](https://github.com/espressif/esp-zigbee-sdk/tree/main/examples/home_automation_devices/temperature_sensor) example)
|
||||
|
||||
## Configure the project
|
||||
|
||||
Before project configuration and build, make sure to set the correct chip target using `idf.py set-target TARGET` command.
|
||||
|
||||
## Erase the NVRAM
|
||||
|
||||
Before flash it to the board, it is recommended to erase NVRAM if user doesn't want to keep the previous examples or other projects stored info using `idf.py -p PORT erase-flash`
|
||||
|
||||
## Build and Flash
|
||||
|
||||
Build the project, flash it to the board, and start the monitor tool to view the serial output by running `idf.py -p PORT flash monitor`.
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
## Application Functions
|
||||
|
||||
- When the program starts, the board, acting as a Zigbee Coordinator with the `Home Automation Thermostat` function, will form an open network within 180 seconds.
|
||||
|
||||
```text
|
||||
I (419) main_task: Calling app_main()
|
||||
[…]
|
||||
I (1611) ESP_RADIO_SPINEL: spinel UART interface initialization completed
|
||||
I (1611) ESP_RADIO_SPINEL: Spinel UART interface has been successfully enabled
|
||||
I (1611) ESP_ZIGBEE_RADIO_SPINEL_UART: Spinel UART interface enable successfully
|
||||
I(1621) OPENTHREAD:[I] P-SpinelDrive-: co-processor reset: RESET_POWER_ON
|
||||
E(1631) OPENTHREAD:[C] P-SpinelDrive-: Software reset co-processor successfully
|
||||
I (1691) THERMOSTAT: Initialize Zigbee stack
|
||||
E (1691) gpio: gpio_install_isr_service(537): GPIO isr service already installed
|
||||
I (1691) THERMOSTAT: Deferred driver initialization successful
|
||||
I (1691) THERMOSTAT: Device started up in non factory-reset mode
|
||||
I (1701) THERMOSTAT: Device reboot
|
||||
I (2831) THERMOSTAT: Network(0x03e2) is open for 180 seconds
|
||||
```
|
||||
|
||||
- If a Zigbee device with the `Home Automation Temperature Sensor` function joins the network, the board read its manufacturer code and model then add it to the binding table.
|
||||
|
||||
```text
|
||||
I (11889) THERMOSTAT: Attempt to find HA temperature sensor device on address(0xd9bc)
|
||||
I (12279) THERMOSTAT: Attempt to read manuf_code and model_id from device (0xd9bc, 0x0a)
|
||||
I (12369) THERMOSTAT: ZCL Read Attribute Response message for endpoint(1) cluster(0x0000) client with status(0x00)
|
||||
I (12369) THERMOSTAT: Model identifier: esp32h2
|
||||
I (12369) THERMOSTAT: Manufacturer name: ESPRESSIF
|
||||
I (12379) THERMOSTAT: Attempt to bind temperature sensor device (0xd9bc, 0x0a) to local
|
||||
I (12389) THERMOSTAT: Attempt to subscribe temperature sensor device (0xd9bc, 0x0a) from local
|
||||
I (12429) THERMOSTAT: Bound HA temperature sensor device (0x0000, 0x0a) to local successfully
|
||||
```
|
||||
|
||||
- By clicking the `BOOT` button on this board, it will read the temperature value, temperature measurement range, and temperature tolerance from the remote board. Additionally, it will configure the remote temperature sensor to report the measured temperature value every 10 seconds or whenever there is a 2-degree change.
|
||||
|
||||
```text
|
||||
I (25321) THERMOSTAT: Zigbee APP Signal: ZDO Device Update(type: 0x07)
|
||||
I (25351) THERMOSTAT: New device commissioned or rejoined (short: 0x96cf)
|
||||
I (25351) THERMOSTAT: Attempt to find HA temperature sensor device on address(0x96cf)
|
||||
I (25411) THERMOSTAT: Attempt to read manuf_code and model_id from device (0x96cf, 0x0a)
|
||||
I (25481) THERMOSTAT: ZCL Read Attribute Response message for endpoint(1) cluster(0x0000) client with status(0x00)
|
||||
I (25481) THERMOSTAT: Model identifier: esp32h2
|
||||
I (25491) THERMOSTAT: Manufacturer name: ESPRESSIF
|
||||
I (25491) THERMOSTAT: Attempt to bind temperature sensor device (0x96cf, 0x0a) to local
|
||||
I (25501) THERMOSTAT: Attempt to subscribe temperature sensor device (0x96cf, 0x0a) from local
|
||||
I (25511) THERMOSTAT: Bound HA temperature sensor device (0x0000, 0x0a) to local successfully
|
||||
I (25521) THERMOSTAT: Attempt to configure reporting for HA temperature sensor
|
||||
I (25591) THERMOSTAT: Subscribed HA temperature sensor device (0x96cf, 0x0a) from local successfully
|
||||
I (25641) THERMOSTAT: ZCL Report Config Response message for endpoint(1) cluster(0x0402) client with status(0x00)
|
||||
I (25671) THERMOSTAT: ZCL Report Attribute message for endpoint(1) cluster(0x0402) client with status(0x00)
|
||||
I (25671) THERMOSTAT: Temperature sensor measured value: -327.68 degrees Celsius
|
||||
I (26741) THERMOSTAT: ZCL Report Attribute message for endpoint(1) cluster(0x0402) client with status(0x00)
|
||||
I (26741) THERMOSTAT: Temperature sensor measured value: 29.60 degrees Celsius
|
||||
I (36841) THERMOSTAT: ZCL Report Attribute message for endpoint(1) cluster(0x0402) client with status(0x00)
|
||||
I (36841) THERMOSTAT: Temperature sensor measured value: 29.60 degrees Celsius
|
||||
I (46911) THERMOSTAT: ZCL Report Attribute message for endpoint(1) cluster(0x0402) client with status(0x00)
|
||||
I (46911) THERMOSTAT: Temperature sensor measured value: 29.60 degrees Celsius
|
||||
I (56981) THERMOSTAT: ZCL Report Attribute message for endpoint(1) cluster(0x0402) client with status(0x00)
|
||||
I (56981) THERMOSTAT: Temperature sensor measured value: 29.60 degrees Celsius
|
||||
[…]
|
||||
W (206631) THERMOSTAT: Read Temperature Measurement attribute ID: 0x0003 with status: 0x86
|
||||
I (206631) THERMOSTAT: Max measured value: 80.00 degrees Celsius
|
||||
I (206641) THERMOSTAT: Min measured value: -10.00 degrees Celsius
|
||||
I (206641) THERMOSTAT: Measured value: 29.60 degrees Celsius
|
||||
[…]
|
||||
```
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRC_DIRS "."
|
||||
PRIV_INCLUDE_DIRS "."
|
||||
PRIV_REQUIRES esp_timer vfs nvs_flash)
|
||||
@@ -0,0 +1,11 @@
|
||||
menu "Example Configuration"
|
||||
config GPIO_BOOT_ON_DEVKIT
|
||||
int "BOOT GPIO on devkit"
|
||||
default 9 if IDF_TARGET_ESP32H2
|
||||
default 35 if IDF_TARGET_ESP32P4
|
||||
default 9
|
||||
|
||||
help
|
||||
The GPIO connected to the BOOT button on the devkits
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*
|
||||
* Alarm Timer
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, this
|
||||
* software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "esp_timer.h"
|
||||
|
||||
#include "alarm_timer.h"
|
||||
|
||||
typedef struct alarm_timer_s {
|
||||
esp_timer_handle_t timer;
|
||||
alarm_timer_callback_t cb;
|
||||
alarm_timer_arg_t arg;
|
||||
} alarm_timer_t;
|
||||
|
||||
static void alarm_timer_cb(void *arg)
|
||||
{
|
||||
alarm_timer_t *alarm = (alarm_timer_t *)arg;
|
||||
|
||||
esp_timer_delete(alarm->timer);
|
||||
alarm->cb(alarm->arg);
|
||||
free(alarm);
|
||||
}
|
||||
|
||||
esp_err_t alarm_timer_schedule(alarm_timer_callback_t cb, alarm_timer_arg_t arg, uint32_t time_ms)
|
||||
{
|
||||
if (cb == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
alarm_timer_t *alarm = malloc(sizeof(alarm_timer_t));
|
||||
if (alarm == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
alarm->cb = cb;
|
||||
alarm->arg = arg;
|
||||
alarm->timer = NULL;
|
||||
|
||||
esp_timer_create_args_t timer_args = {
|
||||
.callback = alarm_timer_cb,
|
||||
.arg = alarm,
|
||||
};
|
||||
|
||||
esp_err_t err = esp_timer_create(&timer_args, &alarm->timer);
|
||||
if (err != ESP_OK) {
|
||||
free(alarm);
|
||||
return err;
|
||||
}
|
||||
|
||||
uint64_t time_us = (uint64_t)time_ms * 1000;
|
||||
err = esp_timer_start_once(alarm->timer, time_us);
|
||||
if (err != ESP_OK) {
|
||||
esp_timer_delete(alarm->timer);
|
||||
free(alarm);
|
||||
return err;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*
|
||||
* Alarm driver
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, this
|
||||
* software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief The argument type for the alarm callback.
|
||||
*
|
||||
*/
|
||||
typedef uintptr_t alarm_timer_arg_t;
|
||||
|
||||
/**
|
||||
* @brief The callback for the timer.
|
||||
*
|
||||
*/
|
||||
typedef void (*alarm_timer_callback_t)(alarm_timer_arg_t arg);
|
||||
|
||||
/**
|
||||
* @brief Schedule an alarm to fire at a given time.
|
||||
*
|
||||
* @param cb The callback to execute when the alarm fires.
|
||||
* @param arg The argument to pass to the callback.
|
||||
* @param time_ms The time to fire the timer in milliseconds.
|
||||
*
|
||||
* @return ESP_OK on success, error code otherwise.
|
||||
*/
|
||||
esp_err_t alarm_timer_schedule(alarm_timer_callback_t cb, alarm_timer_arg_t arg, uint32_t time_ms);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*
|
||||
*/
|
||||
|
||||
#include "esp_hosted.h"
|
||||
#include "esp_hosted_openthread.h"
|
||||
#include "esp_hosted_openthread_app.h"
|
||||
|
||||
#include "esp_log.h"
|
||||
#define TAG "h_ot_util"
|
||||
|
||||
esp_err_t esp_hosted_openthread_app_init(void)
|
||||
{
|
||||
esp_err_t res = ESP_OK;
|
||||
|
||||
res = esp_hosted_openthread_rcp_query(HOSTED_OPENTHREAD_QUERY_CONFIGURED);
|
||||
if (ESP_OK != res) {
|
||||
ESP_LOGE(TAG, "OT RCP not configured (enabled via Kconfig) on co-processor");
|
||||
return res;
|
||||
}
|
||||
|
||||
res = esp_hosted_openthread_rcp_init();
|
||||
if (ESP_OK != res) {
|
||||
ESP_LOGE(TAG, "failed to init OT RCP");
|
||||
return res;
|
||||
}
|
||||
|
||||
res = esp_hosted_openthread_rcp_query(HOSTED_OPENTHREAD_QUERY_INITED);
|
||||
if (ESP_OK != res) {
|
||||
ESP_LOGE(TAG, "OT RCP not inited on co-processor");
|
||||
return res;
|
||||
}
|
||||
|
||||
res = esp_hosted_openthread_rcp_start();
|
||||
if (ESP_OK != res) {
|
||||
ESP_LOGE(TAG, "failed to start OT RCP");
|
||||
return res;
|
||||
}
|
||||
|
||||
res = esp_hosted_openthread_rcp_query(HOSTED_OPENTHREAD_QUERY_ENABLED);
|
||||
if (ESP_OK != res) {
|
||||
ESP_LOGE(TAG, "OT RCP not enabled on co-processor");
|
||||
return res;
|
||||
}
|
||||
|
||||
res = esp_hosted_openthread_rcp_query(HOSTED_OPENTHREAD_QUERY_READY);
|
||||
if (ESP_OK != res) {
|
||||
ESP_LOGE(TAG, "OT RCP not ready on co-processor");
|
||||
return res;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
esp_err_t esp_hosted_openthread_app_deinit(void)
|
||||
{
|
||||
esp_err_t res = ESP_OK;
|
||||
|
||||
res = esp_hosted_openthread_rcp_stop();
|
||||
if (ESP_OK != res) {
|
||||
ESP_LOGE(TAG, "OT RCP failed to stop");
|
||||
return res;
|
||||
}
|
||||
|
||||
res = esp_hosted_openthread_rcp_deinit();
|
||||
if (ESP_OK != res) {
|
||||
ESP_LOGE(TAG, "OT RCP failed to deinit");
|
||||
return res;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __ESP_HOSTED_OPENTHREAD_APP_H__
|
||||
#define __ESP_HOSTED_OPENTHREAD_APP_H__
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
esp_err_t esp_hosted_openthread_app_init(void);
|
||||
esp_err_t esp_hosted_openthread_app_deinit(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,14 @@
|
||||
dependencies:
|
||||
espressif/esp-zigbee-lib:
|
||||
version: '>=2.0.0'
|
||||
espressif/esp_hosted:
|
||||
rules:
|
||||
- if: target in [esp32p4, esp32h2]
|
||||
version: ~2
|
||||
espressif/esp_wifi_remote:
|
||||
rules:
|
||||
- if: target in [esp32p4, esp32h2]
|
||||
- if: idf_version <6.1
|
||||
version: '>=0.10,<2.0'
|
||||
idf:
|
||||
version: '>=5.2'
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*
|
||||
* Zigbee switch driver
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, this
|
||||
* software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "switch_driver.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
|
||||
#include "esp_attr.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
static QueueHandle_t gpio_event_queue = NULL;
|
||||
|
||||
static void switch_driver_gpio_intr_enabled(gpio_num_t gpio_num, bool enabled)
|
||||
{
|
||||
if (enabled) {
|
||||
gpio_intr_enable(gpio_num);
|
||||
} else {
|
||||
gpio_intr_disable(gpio_num);
|
||||
}
|
||||
}
|
||||
|
||||
static void IRAM_ATTR gpio_isr_handler(void *arg)
|
||||
{
|
||||
switch_driver_config_t *config = (switch_driver_config_t *)arg;
|
||||
switch_driver_gpio_intr_enabled(config->gpio_num, false);
|
||||
xQueueSendFromISR(gpio_event_queue, &config, NULL);
|
||||
}
|
||||
|
||||
static void switch_driver_button_detected(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
switch_driver_config_t *config = NULL;
|
||||
while (1) {
|
||||
if (xQueueReceive(gpio_event_queue, &config, portMAX_DELAY) == pdTRUE) {
|
||||
if (config) {
|
||||
if (config->event_cb) {
|
||||
config->event_cb(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
switch_driver_gpio_intr_enabled(config->gpio_num, true);
|
||||
}
|
||||
}
|
||||
switch_driver_handle_t switch_driver_init(const switch_driver_config_t *config)
|
||||
{
|
||||
if (config == NULL || config->gpio_num < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
switch_driver_config_t *handle = (switch_driver_config_t *)malloc(sizeof(switch_driver_config_t));
|
||||
if (handle == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
memcpy(handle, (void *)config, sizeof(switch_driver_config_t));
|
||||
|
||||
/* Configure GPIO in init: input, pull-up, and negative-edge interrupt */
|
||||
gpio_config_t io_conf = {
|
||||
.pin_bit_mask = (1ULL << config->gpio_num),
|
||||
.mode = GPIO_MODE_INPUT,
|
||||
.pull_up_en = GPIO_PULLUP_ENABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_LOW_LEVEL,
|
||||
};
|
||||
esp_err_t ret = gpio_config(&io_conf);
|
||||
if (ret != ESP_OK) {
|
||||
free(handle);
|
||||
return NULL;
|
||||
}
|
||||
gpio_event_queue = xQueueCreate(10, sizeof(switch_driver_config_t *));
|
||||
if (gpio_event_queue == 0) {
|
||||
gpio_reset_pin(config->gpio_num);
|
||||
free(handle);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
xTaskCreate(switch_driver_button_detected, "button_detected", 4096, NULL, 10, NULL);
|
||||
ret = gpio_install_isr_service(0);
|
||||
if ((ret != ESP_OK) && (ret != ESP_ERR_INVALID_STATE)) {
|
||||
gpio_reset_pin(config->gpio_num);
|
||||
free(handle);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = gpio_isr_handler_add(config->gpio_num, gpio_isr_handler, (void *)handle);
|
||||
if (ret != ESP_OK) {
|
||||
gpio_reset_pin(config->gpio_num);
|
||||
free(handle);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
void switch_driver_deinit(switch_driver_handle_t handle)
|
||||
{
|
||||
if (handle == NULL) {
|
||||
return;
|
||||
}
|
||||
switch_driver_config_t *config = (switch_driver_config_t *)handle;
|
||||
gpio_isr_handler_remove(config->gpio_num);
|
||||
gpio_reset_pin(config->gpio_num);
|
||||
free(config);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*
|
||||
* Zigbee switch driver
|
||||
*
|
||||
* This driver reads a physical GPIO switch/button (e.g. BOOT button on devkit),
|
||||
* uses GPIO interrupt and a queue to report press/release events to the application
|
||||
* via a callback. Typical use: Zigbee HA On/Off Switch sending commands on button press.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, this
|
||||
* software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Opaque handle for the switch driver instance.
|
||||
*/
|
||||
typedef void* switch_driver_handle_t;
|
||||
|
||||
/**
|
||||
* @brief Invalid handle value.
|
||||
*/
|
||||
#define SWITCH_INV_HANDLE NULL
|
||||
|
||||
/**
|
||||
* @brief Callback invoked from GPIO ISR (low-level) when the switch level changes.
|
||||
*
|
||||
* Runs in interrupt context: keep it minimal (e.g. set a flag or post to a queue).
|
||||
* Do not block or call blocking / non-ISR-safe APIs.
|
||||
*
|
||||
* @param user_ctx User context passed at init
|
||||
*/
|
||||
typedef void (*switch_event_cb_t)(switch_driver_handle_t handle);
|
||||
|
||||
/**
|
||||
* @brief Switch driver configuration.
|
||||
*/
|
||||
typedef struct {
|
||||
gpio_num_t gpio_num; /*!< GPIO number used as switch input (e.g. BOOT button) */
|
||||
switch_event_cb_t event_cb; /*!< Callback on level change (invoked from ISR); may be NULL if only polling */
|
||||
} switch_driver_config_t;
|
||||
|
||||
/**
|
||||
* @brief Initialize the switch driver.
|
||||
*
|
||||
* Configures the GPIO in switch_driver_init() as input with pull-up enabled,
|
||||
* installs the GPIO ISR, and hooks the callback. The callback is triggered in low-level
|
||||
* (GPIO ISR) context on level change.
|
||||
*
|
||||
* @param config Driver configuration (gpio_num required; event_cb optional)
|
||||
* @return Handle
|
||||
*/
|
||||
switch_driver_handle_t switch_driver_init(const switch_driver_config_t *config);
|
||||
|
||||
/**
|
||||
* @brief Deinitialize the switch driver.
|
||||
*
|
||||
* Removes ISR handler and resets GPIO.
|
||||
*
|
||||
* @param handle Handle from switch_driver_init; may be NULL
|
||||
*/
|
||||
void switch_driver_deinit(switch_driver_handle_t handle);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,574 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
||||
#include "esp_check.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
#include "switch_driver.h"
|
||||
#include "alarm_timer.h"
|
||||
|
||||
#include "esp_zigbee.h"
|
||||
#include "ezbee/zha.h"
|
||||
|
||||
#include "thermostat.h"
|
||||
|
||||
#include "esp_hosted.h"
|
||||
#include "esp_hosted_openthread.h"
|
||||
#include "esp_hosted_openthread_app.h"
|
||||
|
||||
static const char *TAG = "THERMOSTAT";
|
||||
|
||||
static void button_event_handler(switch_driver_handle_t handle)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(handle != SWITCH_INV_HANDLE, , TAG, "Invalid switch handle");
|
||||
|
||||
uint16_t attr_field[] = {
|
||||
EZB_ZCL_ATTR_TEMPERATURE_MEASUREMENT_MEASURED_VALUE_ID, EZB_ZCL_ATTR_TEMPERATURE_MEASUREMENT_MIN_MEASURED_VALUE_ID,
|
||||
EZB_ZCL_ATTR_TEMPERATURE_MEASUREMENT_MAX_MEASURED_VALUE_ID, EZB_ZCL_ATTR_TEMPERATURE_MEASUREMENT_TOLERANCE_ID};
|
||||
ezb_zcl_read_attr_cmd_t read_attr_cmd = {
|
||||
.cmd_ctrl =
|
||||
{
|
||||
.dst_addr.addr_mode = EZB_ADDR_MODE_NONE,
|
||||
.src_ep = ESP_ZIGBEE_HA_THERMOSTAT_EP_ID,
|
||||
.cluster_id = EZB_ZCL_CLUSTER_ID_TEMPERATURE_MEASUREMENT,
|
||||
},
|
||||
.payload.attr_number = sizeof(attr_field) / sizeof(attr_field[0]),
|
||||
.payload.attr_field = attr_field,
|
||||
};
|
||||
esp_zigbee_lock_acquire(portMAX_DELAY);
|
||||
esp_err_t ret = ezb_zcl_read_attr_cmd_req(&read_attr_cmd);
|
||||
esp_zigbee_lock_release();
|
||||
if (ret == EZB_ERR_NONE) {
|
||||
ESP_LOGI(TAG, "Read Attributes of Temperature Measurement");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to send ZCL Read Attribute request with error(0x%04x)", ret);
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t deferred_driver_init(void)
|
||||
{
|
||||
static bool is_inited = false;
|
||||
|
||||
ESP_RETURN_ON_FALSE(!is_inited, ESP_OK, TAG, "Deferred driver already initialized");
|
||||
|
||||
switch_driver_config_t config = {
|
||||
.gpio_num = CONFIG_GPIO_BOOT_ON_DEVKIT,
|
||||
.event_cb = button_event_handler,
|
||||
};
|
||||
switch_driver_handle_t handle = switch_driver_init(&config);
|
||||
is_inited = handle == SWITCH_INV_HANDLE ? false : true;
|
||||
|
||||
return is_inited ? ESP_OK : ESP_FAIL;
|
||||
}
|
||||
|
||||
static ezb_err_t zdo_config_ha_temperature_sensor_reporting(void)
|
||||
{
|
||||
ezb_err_t ret = EZB_ERR_FAIL;
|
||||
ezb_zcl_config_report_record_t record[] = {
|
||||
{
|
||||
.direction = EZB_ZCL_REPORTING_SEND,
|
||||
.attr_id = EZB_ZCL_ATTR_TEMPERATURE_MEASUREMENT_MEASURED_VALUE_ID,
|
||||
.client =
|
||||
{
|
||||
.attr_type = EZB_ZCL_ATTR_TYPE_INT16,
|
||||
.min_interval = 1,
|
||||
.max_interval = 10,
|
||||
.reportable_change =
|
||||
{
|
||||
.s16 = 200,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
ezb_zcl_config_report_cmd_t config_report_cmd = {
|
||||
.cmd_ctrl =
|
||||
{
|
||||
.dst_addr.addr_mode = EZB_ADDR_MODE_NONE,
|
||||
.src_ep = ESP_ZIGBEE_HA_THERMOSTAT_EP_ID,
|
||||
.cluster_id = EZB_ZCL_CLUSTER_ID_TEMPERATURE_MEASUREMENT,
|
||||
},
|
||||
.payload.record_number = sizeof(record) / sizeof(record[0]),
|
||||
.payload.record_field = record,
|
||||
};
|
||||
ret = ezb_zcl_config_report_cmd_req(&config_report_cmd);
|
||||
if (ret == EZB_ERR_NONE) {
|
||||
ESP_LOGI(TAG, "Attempt to configure reporting for HA temperature sensor");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to configure reporting for HA temperature sensor with error(0x%04x)", ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void zdo_bind_ha_temperature_sensor_device_result(const ezb_zdp_bind_req_result_t *result, void *user_ctx)
|
||||
{
|
||||
ezb_zdo_bind_req_t *bind_req = (ezb_zdo_bind_req_t *)user_ctx;
|
||||
|
||||
assert(result);
|
||||
if (result->error == EZB_ERR_NONE) {
|
||||
if (result->rsp && result->rsp->status == EZB_ZDP_STATUS_SUCCESS) {
|
||||
if (bind_req->dst_nwk_addr == ezb_nwk_get_short_address()) {
|
||||
ESP_LOGI(TAG, "Bound HA temperature sensor device (0x%04hx, 0x%02x) to local successfully",
|
||||
bind_req->dst_nwk_addr, bind_req->field.dst_ep);
|
||||
zdo_config_ha_temperature_sensor_reporting();
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Subscribed HA temperature sensor device (0x%04hx, 0x%02x) from local successfully",
|
||||
bind_req->dst_nwk_addr, bind_req->field.src_ep);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Bound HA temperature sensor device failure with status (0x%02x)", result->rsp->status);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Bound HA temperature sensor device failure with error (0x%04x)", result->error);
|
||||
}
|
||||
if (user_ctx) {
|
||||
free(user_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
static ezb_err_t zdo_bind_ha_temperature_sensor_device(uint16_t dst_short_addr, uint8_t dst_ep)
|
||||
{
|
||||
ezb_err_t ret = EZB_ERR_FAIL;
|
||||
ezb_zdo_bind_req_t *bind_sensor = malloc(sizeof(ezb_zdo_bind_req_t));
|
||||
bind_sensor->dst_nwk_addr = ezb_nwk_get_short_address();
|
||||
bind_sensor->field.src_ep = ESP_ZIGBEE_HA_THERMOSTAT_EP_ID;
|
||||
bind_sensor->field.cluster_id = EZB_ZCL_CLUSTER_ID_TEMPERATURE_MEASUREMENT;
|
||||
bind_sensor->field.dst_addr_mode = EZB_ADDR_MODE_EXT;
|
||||
bind_sensor->field.dst_ep = dst_ep;
|
||||
bind_sensor->cb = zdo_bind_ha_temperature_sensor_device_result;
|
||||
bind_sensor->user_ctx = bind_sensor;
|
||||
|
||||
ezb_nwk_get_extended_address(&bind_sensor->field.src_addr);
|
||||
|
||||
ESP_RETURN_ON_ERROR(ezb_address_extended_by_short(dst_short_addr, &bind_sensor->field.dst_addr.extended_addr), TAG,
|
||||
"Failed to get extended address for destination device(0x%04hx)", dst_short_addr);
|
||||
|
||||
ret = ezb_zdo_bind_req(bind_sensor);
|
||||
if (ret == EZB_ERR_NONE) {
|
||||
ESP_LOGI(TAG, "Attempt to bind temperature sensor device (0x%04hx, 0x%02x) to local", dst_short_addr, dst_ep);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to bind temperature sensor device (0x%04hx, 0x%02x) to local with error(0x%04x)", dst_short_addr,
|
||||
dst_ep, ret);
|
||||
}
|
||||
|
||||
ezb_zdo_bind_req_t *subscribe_sensor = malloc(sizeof(ezb_zdo_bind_req_t));
|
||||
subscribe_sensor->dst_nwk_addr = dst_short_addr;
|
||||
subscribe_sensor->field.src_ep = dst_ep;
|
||||
subscribe_sensor->field.cluster_id = EZB_ZCL_CLUSTER_ID_TEMPERATURE_MEASUREMENT;
|
||||
subscribe_sensor->field.dst_addr_mode = EZB_ADDR_MODE_EXT;
|
||||
subscribe_sensor->field.dst_ep = ESP_ZIGBEE_HA_THERMOSTAT_EP_ID;
|
||||
subscribe_sensor->cb = zdo_bind_ha_temperature_sensor_device_result;
|
||||
subscribe_sensor->user_ctx = subscribe_sensor;
|
||||
|
||||
ezb_nwk_get_extended_address(&subscribe_sensor->field.dst_addr.extended_addr);
|
||||
|
||||
ESP_RETURN_ON_ERROR(ezb_address_extended_by_short(dst_short_addr, &subscribe_sensor->field.src_addr), TAG,
|
||||
"Failed to get extended address for destination device(0x%04hx)", dst_short_addr);
|
||||
|
||||
ret = ezb_zdo_bind_req(subscribe_sensor);
|
||||
if (ret == EZB_ERR_NONE) {
|
||||
ESP_LOGI(TAG, "Attempt to subscribe temperature sensor device (0x%04hx, 0x%02x) from local", dst_short_addr, dst_ep);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to subscribe temperature sensor device (0x%04hx, 0x%02x) from local with error(0x%04x)",
|
||||
dst_short_addr, dst_ep, ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ezb_err_t esp_zigbee_read_manuf_code_and_model_id(uint16_t dst_short_addr, uint8_t dst_ep)
|
||||
{
|
||||
ezb_err_t ret = EZB_ERR_FAIL;
|
||||
uint16_t attr_field[] = {EZB_ZCL_ATTR_BASIC_MANUFACTURER_NAME_ID, EZB_ZCL_ATTR_BASIC_MODEL_IDENTIFIER_ID};
|
||||
ezb_zcl_read_attr_cmd_t read_attr_cmd = {
|
||||
.cmd_ctrl =
|
||||
{
|
||||
.dst_addr.addr_mode = EZB_ADDR_MODE_SHORT,
|
||||
.src_ep = ESP_ZIGBEE_HA_THERMOSTAT_EP_ID,
|
||||
.dst_addr.u.short_addr = dst_short_addr,
|
||||
.dst_ep = dst_ep,
|
||||
.cluster_id = EZB_ZCL_CLUSTER_ID_BASIC,
|
||||
},
|
||||
.payload.attr_number = sizeof(attr_field) / sizeof(attr_field[0]),
|
||||
.payload.attr_field = attr_field,
|
||||
};
|
||||
ret = ezb_zcl_read_attr_cmd_req(&read_attr_cmd);
|
||||
if (ret == EZB_ERR_NONE) {
|
||||
ESP_LOGI(TAG, "Attempt to read manuf_code and model_id from device (0x%04hx, 0x%02x)", dst_short_addr, dst_ep);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to read manuf_code and model_id from device (0x%04hx, 0x%02x) with error(0x%04x)", dst_short_addr,
|
||||
dst_ep, ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void zdo_find_ha_temperature_sensor_device_result(const ezb_zdo_match_desc_req_result_t *result, void *user_ctx)
|
||||
{
|
||||
assert(result);
|
||||
if (result->error == EZB_ERR_NONE) {
|
||||
if (result->rsp && result->rsp->status == EZB_ZDP_STATUS_SUCCESS && result->rsp->match_length > 0 &&
|
||||
result->rsp->match_list) {
|
||||
for (size_t i = 0; i < result->rsp->match_length; i++) {
|
||||
esp_zigbee_read_manuf_code_and_model_id(result->rsp->nwk_addr_of_interest, result->rsp->match_list[i]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to find HA temperature sensor device in the network with error(0x%04x)", result->error);
|
||||
}
|
||||
}
|
||||
|
||||
static ezb_err_t zdo_find_ha_temperature_sensor_device(uint16_t short_addr)
|
||||
{
|
||||
ezb_err_t ret = EZB_ERR_FAIL;
|
||||
uint16_t cluster_list[1] = {EZB_ZCL_CLUSTER_ID_TEMPERATURE_MEASUREMENT};
|
||||
|
||||
ezb_zdo_match_desc_req_t req = {
|
||||
.dst_nwk_addr = short_addr,
|
||||
.field =
|
||||
{
|
||||
.nwk_addr_of_interest = short_addr,
|
||||
.profile_id = EZB_AF_HA_PROFILE_ID,
|
||||
.num_in_clusters = 1,
|
||||
.num_out_clusters = 0,
|
||||
.cluster_list = cluster_list,
|
||||
},
|
||||
.cb = zdo_find_ha_temperature_sensor_device_result,
|
||||
.user_ctx = NULL,
|
||||
};
|
||||
ret = ezb_zdo_match_desc_req(&req);
|
||||
if (ret == EZB_ERR_NONE) {
|
||||
ESP_LOGI(TAG, "Attempt to find HA temperature sensor device on address(0x%04hx)", short_addr);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to find HA temperature sensor device on address(0x%04hx) with error(0x%04x)", short_addr, ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void esp_zigbee_alarm_bdb_commissioning(alarm_timer_arg_t arg)
|
||||
{
|
||||
esp_zigbee_lock_acquire(portMAX_DELAY);
|
||||
(void)ezb_bdb_start_top_level_commissioning(arg);
|
||||
esp_zigbee_lock_release();
|
||||
}
|
||||
|
||||
static bool esp_zigbee_app_signal_handler(const ezb_app_signal_t *app_signal)
|
||||
{
|
||||
ezb_app_signal_type_t signal_type = ezb_app_signal_get_type(app_signal);
|
||||
|
||||
switch (signal_type) {
|
||||
case EZB_ZDO_SIGNAL_SKIP_STARTUP:
|
||||
ESP_LOGI(TAG, "Initialize Zigbee stack");
|
||||
ezb_bdb_start_top_level_commissioning(EZB_BDB_MODE_INITIALIZATION);
|
||||
break;
|
||||
case EZB_BDB_SIGNAL_DEVICE_FIRST_START:
|
||||
case EZB_BDB_SIGNAL_DEVICE_REBOOT: {
|
||||
ezb_bdb_comm_status_t status = *((ezb_bdb_comm_status_t *)ezb_app_signal_get_params(app_signal));
|
||||
if (status == EZB_BDB_STATUS_SUCCESS) {
|
||||
ESP_LOGI(TAG, "Deferred driver initialization %s", deferred_driver_init() ? "failed" : "successful");
|
||||
ESP_LOGI(TAG, "Device started up in%s factory-reset mode", ezb_bdb_is_factory_new() ? "" : " non");
|
||||
if (ezb_bdb_is_factory_new()) {
|
||||
ezb_bdb_start_top_level_commissioning(EZB_BDB_MODE_NETWORK_FORMATION);
|
||||
} else {
|
||||
ezb_bdb_open_network(180);
|
||||
ESP_LOGI(TAG, "Device reboot");
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "The %s failed with status(0x%02x), please retry", ezb_app_signal_to_string(signal_type), status);
|
||||
alarm_timer_schedule(esp_zigbee_alarm_bdb_commissioning, EZB_BDB_MODE_INITIALIZATION, 1000);
|
||||
}
|
||||
} break;
|
||||
case EZB_BDB_SIGNAL_FORMATION: {
|
||||
ezb_bdb_comm_status_t status = *((ezb_bdb_comm_status_t *)ezb_app_signal_get_params(app_signal));
|
||||
if (status == EZB_BDB_STATUS_SUCCESS) {
|
||||
ezb_extpanid_t extended_pan_id;
|
||||
ezb_nwk_get_extended_panid(&extended_pan_id);
|
||||
ESP_LOGI(TAG, "Formed network successfully: PAN ID(0x%04hx, EXT: 0x%llx), Channel(%d), Short Address(0x%04hx)",
|
||||
ezb_nwk_get_panid(), extended_pan_id.u64, ezb_nwk_get_current_channel(), ezb_nwk_get_short_address());
|
||||
ezb_bdb_start_top_level_commissioning(EZB_BDB_MODE_NETWORK_STEERING);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Failed to form network with status(0x%02x)", status);
|
||||
alarm_timer_schedule(esp_zigbee_alarm_bdb_commissioning, EZB_BDB_MODE_NETWORK_FORMATION, 1000);
|
||||
}
|
||||
} break;
|
||||
case EZB_BDB_SIGNAL_STEERING: {
|
||||
ezb_bdb_comm_status_t status = *((ezb_bdb_comm_status_t *)ezb_app_signal_get_params(app_signal));
|
||||
if (status == EZB_BDB_STATUS_SUCCESS) {
|
||||
ESP_LOGI(TAG, "Network steering completed");
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Failed to steering network with status(0x%02x)", status);
|
||||
alarm_timer_schedule(esp_zigbee_alarm_bdb_commissioning, EZB_BDB_MODE_NETWORK_FORMATION, 1000);
|
||||
}
|
||||
} break;
|
||||
case EZB_ZDO_SIGNAL_DEVICE_ANNCE: {
|
||||
const ezb_zdo_signal_device_annce_params_t *dev_annce_params = ezb_app_signal_get_params(app_signal);
|
||||
ESP_LOGI(TAG, "New device commissioned or rejoined (short: 0x%04hx)", dev_annce_params->short_addr);
|
||||
zdo_find_ha_temperature_sensor_device(dev_annce_params->short_addr);
|
||||
} break;
|
||||
case EZB_ZDO_SIGNAL_LEAVE_INDICATION: {
|
||||
const ezb_zdo_signal_leave_indication_params_t *leave_ind_params = ezb_app_signal_get_params(app_signal);
|
||||
ESP_LOGI(TAG, "Zigbee Node(0x%04hx) is leaving network", leave_ind_params->short_addr);
|
||||
} break;
|
||||
case EZB_NWK_SIGNAL_PERMIT_JOIN_STATUS: {
|
||||
uint8_t duration = *(uint8_t *)ezb_app_signal_get_params(app_signal);
|
||||
if (duration) {
|
||||
ESP_LOGI(TAG, "Network(0x%04hx) is open for %d seconds", ezb_nwk_get_panid(), duration);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Network(0x%04hx) closed, devices joining not allowed.", ezb_nwk_get_panid());
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
ESP_LOGI(TAG, "Zigbee APP Signal: %s(type: 0x%02x)", ezb_app_signal_to_string(signal_type), signal_type);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static float zb_s16_to_temperature(int16_t value) { return 1.0 * value / 100; }
|
||||
|
||||
static void zcl_core_cmd_read_attr_rsp_handler(ezb_zcl_cmd_read_attr_rsp_message_t *message)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(message, , TAG, "message is empty");
|
||||
ESP_LOGI(TAG, "ZCL Read Attribute Response message for endpoint(%d) cluster(0x%04x) %s with status(0x%02x)",
|
||||
message->info.dst_ep, message->info.cluster_id,
|
||||
message->info.cluster_role == EZB_ZCL_CLUSTER_SERVER ? "server" : "client", message->info.status);
|
||||
|
||||
switch (message->info.cluster_id) {
|
||||
case EZB_ZCL_CLUSTER_ID_BASIC: {
|
||||
ezb_zcl_read_attr_rsp_variable_t *var = message->in.variables;
|
||||
while (var) {
|
||||
if (var->status == EZB_ZCL_STATUS_SUCCESS) {
|
||||
switch (var->attr_id) {
|
||||
case EZB_ZCL_ATTR_BASIC_MANUFACTURER_NAME_ID:
|
||||
ESP_LOGI(TAG, "Manufacturer name: %.*s", *(uint8_t *)var->attr_value, (char *)(var->attr_value + 1));
|
||||
break;
|
||||
case EZB_ZCL_ATTR_BASIC_MODEL_IDENTIFIER_ID:
|
||||
ESP_LOGI(TAG, "Model identifier: %.*s", *(uint8_t *)var->attr_value, (char *)(var->attr_value + 1));
|
||||
break;
|
||||
default:
|
||||
ESP_LOGI(TAG, "Basic attribute ID: 0x%04x with type: 0x%02x", var->attr_id, var->attr_type);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Read Basic attribute ID: 0x%04x with status: 0x%02x", var->attr_id, var->status);
|
||||
}
|
||||
var = var->next;
|
||||
}
|
||||
zdo_bind_ha_temperature_sensor_device(message->in.header->src_addr.u.short_addr, message->in.header->src_ep);
|
||||
} break;
|
||||
case EZB_ZCL_CLUSTER_ID_TEMPERATURE_MEASUREMENT: {
|
||||
ezb_zcl_read_attr_rsp_variable_t *var = message->in.variables;
|
||||
while (var) {
|
||||
if (var->status == EZB_ZCL_STATUS_SUCCESS) {
|
||||
switch (var->attr_id) {
|
||||
case EZB_ZCL_ATTR_TEMPERATURE_MEASUREMENT_MEASURED_VALUE_ID:
|
||||
ESP_LOGI(TAG, "Measured value: %.2f degrees Celsius", zb_s16_to_temperature(*(int16_t *)var->attr_value));
|
||||
break;
|
||||
case EZB_ZCL_ATTR_TEMPERATURE_MEASUREMENT_MIN_MEASURED_VALUE_ID:
|
||||
ESP_LOGI(TAG, "Min measured value: %.2f degrees Celsius",
|
||||
zb_s16_to_temperature(*(int16_t *)var->attr_value));
|
||||
break;
|
||||
case EZB_ZCL_ATTR_TEMPERATURE_MEASUREMENT_MAX_MEASURED_VALUE_ID:
|
||||
ESP_LOGI(TAG, "Max measured value: %.2f degrees Celsius",
|
||||
zb_s16_to_temperature(*(int16_t *)var->attr_value));
|
||||
break;
|
||||
case EZB_ZCL_ATTR_TEMPERATURE_MEASUREMENT_TOLERANCE_ID:
|
||||
ESP_LOGI(TAG, "Tolerance: %.2f degrees Celsius", zb_s16_to_temperature(*(int16_t *)var->attr_value));
|
||||
break;
|
||||
default:
|
||||
ESP_LOGI(TAG, "Temperature Measurement attribute ID: 0x%04x, Value: %p", var->attr_id, var->attr_value);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Read Temperature Measurement attribute ID: 0x%04x with status: 0x%02x", var->attr_id,
|
||||
var->status);
|
||||
}
|
||||
var = var->next;
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unknown cluster ID: 0x%04x", message->info.cluster_id);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void zcl_core_cmd_report_attr_handler(ezb_zcl_cmd_report_attr_message_t *message)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(message, , TAG, "message is empty");
|
||||
ESP_LOGI(TAG, "ZCL Report Attribute message for endpoint(%d) cluster(0x%04x) %s with status(0x%02x)", message->info.dst_ep,
|
||||
message->info.cluster_id, message->info.cluster_role == EZB_ZCL_CLUSTER_SERVER ? "server" : "client",
|
||||
message->info.status);
|
||||
|
||||
switch (message->info.cluster_id) {
|
||||
case EZB_ZCL_CLUSTER_ID_TEMPERATURE_MEASUREMENT: {
|
||||
ezb_zcl_report_attr_variable_t *var = message->in.variables;
|
||||
while (var) {
|
||||
switch (var->attr_id) {
|
||||
case EZB_ZCL_ATTR_TEMPERATURE_MEASUREMENT_MEASURED_VALUE_ID:
|
||||
ESP_LOGI(TAG, "Temperature sensor measured value: %.2f degrees Celsius",
|
||||
zb_s16_to_temperature(*(int16_t *)var->attr_value));
|
||||
break;
|
||||
default:
|
||||
ESP_LOGI(TAG, "Temperature Measurement attribute ID: 0x%04x with type: 0x%02x", var->attr_id, var->attr_type);
|
||||
break;
|
||||
}
|
||||
var = var->next;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unknown cluster ID: 0x%04x", message->info.cluster_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void zcl_core_cmd_report_config_rsp_handler(ezb_zcl_cmd_config_report_rsp_message_t *message)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(message, , TAG, "message is empty");
|
||||
ESP_LOGI(TAG, "ZCL Report Config Response message for endpoint(%d) cluster(0x%04x) %s with status(0x%02x)",
|
||||
message->info.dst_ep, message->info.cluster_id,
|
||||
message->info.cluster_role == EZB_ZCL_CLUSTER_SERVER ? "server" : "client", message->info.status);
|
||||
ezb_zcl_config_report_rsp_variable_t *var = message->in.variables;
|
||||
while (var) {
|
||||
if (var->status != EZB_ZCL_STATUS_SUCCESS) {
|
||||
ESP_LOGI(TAG, "Report config attribute ID(0x%04x) with direction(0x%02x)", var->attr_id, var->direction);
|
||||
}
|
||||
var = var->next;
|
||||
}
|
||||
}
|
||||
|
||||
static void esp_zigbee_zcl_core_action_handler(ezb_zcl_core_action_callback_id_t callback_id, void *message)
|
||||
{
|
||||
switch (callback_id) {
|
||||
case EZB_ZCL_CORE_READ_ATTR_RSP_CB_ID:
|
||||
zcl_core_cmd_read_attr_rsp_handler(message);
|
||||
break;
|
||||
case EZB_ZCL_CORE_CONFIG_REPORT_RSP_CB_ID:
|
||||
zcl_core_cmd_report_config_rsp_handler(message);
|
||||
break;
|
||||
case EZB_ZCL_CORE_REPORT_ATTR_CB_ID:
|
||||
zcl_core_cmd_report_attr_handler(message);
|
||||
break;
|
||||
case EZB_ZCL_CORE_DEFAULT_RSP_CB_ID: {
|
||||
ezb_zcl_cmd_default_rsp_message_t *default_rsp = (ezb_zcl_cmd_default_rsp_message_t *)message;
|
||||
ESP_LOGI(TAG, "Received ZCL Default Response: 0x%02x", default_rsp->in.status_code);
|
||||
} break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "ZCL Core Action: ID(0x%04lx)", callback_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t esp_zigbee_create_zha_thermostat_device(void)
|
||||
{
|
||||
ezb_af_device_desc_t dev_desc = ezb_af_create_device_desc();
|
||||
ezb_zha_thermostat_config_t thermostat_cfg = EZB_ZHA_THERMOSTAT_CONFIG();
|
||||
ezb_af_ep_desc_t ep_desc = ezb_zha_create_thermostat(ESP_ZIGBEE_HA_THERMOSTAT_EP_ID, &thermostat_cfg);
|
||||
ezb_zcl_cluster_desc_t basic_desc = {0};
|
||||
|
||||
basic_desc = ezb_af_endpoint_get_cluster_desc(ep_desc, EZB_ZCL_CLUSTER_ID_BASIC, EZB_ZCL_CLUSTER_SERVER);
|
||||
ezb_zcl_basic_cluster_desc_add_attr(basic_desc, EZB_ZCL_ATTR_BASIC_MANUFACTURER_NAME_ID, (void *)ESP_MANUFACTURER_NAME);
|
||||
ezb_zcl_basic_cluster_desc_add_attr(basic_desc, EZB_ZCL_ATTR_BASIC_MODEL_IDENTIFIER_ID, (void *)ESP_MODEL_IDENTIFIER);
|
||||
ESP_ERROR_CHECK(ezb_af_endpoint_add_cluster_desc(ep_desc, ezb_zcl_basic_create_cluster_desc(NULL, EZB_ZCL_CLUSTER_CLIENT)));
|
||||
ESP_ERROR_CHECK(ezb_af_endpoint_add_cluster_desc(
|
||||
ep_desc, ezb_zcl_temperature_measurement_create_cluster_desc(NULL, EZB_ZCL_CLUSTER_CLIENT)));
|
||||
|
||||
ESP_ERROR_CHECK(ezb_af_device_add_endpoint_desc(dev_desc, ep_desc));
|
||||
ESP_ERROR_CHECK(ezb_af_device_desc_register(dev_desc));
|
||||
|
||||
ezb_zcl_core_action_handler_register(esp_zigbee_zcl_core_action_handler);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_zigbee_setup_commissioning(void)
|
||||
{
|
||||
ezb_aps_secur_enable_distributed_security(false);
|
||||
ESP_ERROR_CHECK(ezb_bdb_set_primary_channel_set(ESP_ZIGBEE_PRIMARY_CHANNEL_MASK));
|
||||
ESP_ERROR_CHECK(ezb_bdb_set_secondary_channel_set(ESP_ZIGBEE_SECONDARY_CHANNEL_MASK));
|
||||
ESP_ERROR_CHECK(ezb_app_signal_add_handler(esp_zigbee_app_signal_handler));
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t app_init_esp_hosted(void)
|
||||
{
|
||||
esp_err_t res = ESP_OK;
|
||||
|
||||
res = esp_hosted_init();
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_hosted_init failed");
|
||||
return res;
|
||||
}
|
||||
|
||||
res = esp_hosted_connect_to_slave();
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_hosted_connect_to_slave failed");
|
||||
return res;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static void esp_zigbee_stack_main_task(void *pvParameters)
|
||||
{
|
||||
app_init_esp_hosted();
|
||||
|
||||
esp_hosted_openthread_app_init();
|
||||
|
||||
esp_zigbee_config_t config = ESP_ZIGBEE_DEFAULT_CONFIG();
|
||||
|
||||
// get radio config from Hosted
|
||||
esp_hosted_openthread_radio_config_t hosted_radio_config = { 0 };
|
||||
if (0 != esp_hosted_openthread_get_radio_config(&hosted_radio_config)) {
|
||||
ESP_LOGE(TAG, "Failed to get OpenThread radio config from Hosted");
|
||||
return;
|
||||
}
|
||||
|
||||
#if CONFIG_ZB_RADIO_SPINEL_UART
|
||||
if (hosted_radio_config.type != HOSTED_OPENTHREAD_TRANSPORT_UART) {
|
||||
ESP_LOGE(TAG, "Invalid Hosted Radio Config: expected UART radio config");
|
||||
return;
|
||||
}
|
||||
config.platform_config.radio_config.radio_mode = ESP_ZIGBEE_RADIO_MODE_UART_RCP;
|
||||
esp_hosted_openthread_uart_config_t *src = &hosted_radio_config.radio_uart_config;
|
||||
esp_zigbee_uart_config_t *dst = &config.platform_config.radio_config.radio_uart_config;
|
||||
dst->port = src->port;
|
||||
dst->uart_config.baud_rate = src->baud_rate;
|
||||
dst->uart_config.data_bits = src->data_bits;
|
||||
dst->uart_config.parity = src->parity;
|
||||
dst->uart_config.stop_bits = src->stop_bits;
|
||||
dst->uart_config.flow_ctrl = src->flow_ctrl;
|
||||
dst->uart_config.rx_flow_ctrl_thresh = src->rx_flow_ctrl_thresh;
|
||||
dst->uart_config.source_clk = src->source_clk;
|
||||
dst->rx_pin = src->rx_pin;
|
||||
dst->tx_pin = src->tx_pin;
|
||||
#else
|
||||
#error "Unsupported Hosted Radio Config"
|
||||
#endif
|
||||
|
||||
ESP_ERROR_CHECK(esp_zigbee_init(&config));
|
||||
|
||||
ESP_ERROR_CHECK(esp_zigbee_setup_commissioning());
|
||||
|
||||
ESP_ERROR_CHECK(esp_zigbee_create_zha_thermostat_device());
|
||||
|
||||
ESP_ERROR_CHECK(esp_zigbee_start(false));
|
||||
|
||||
esp_zigbee_launch_mainloop();
|
||||
|
||||
esp_zigbee_deinit();
|
||||
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
ESP_ERROR_CHECK(nvs_flash_init_partition(ESP_ZIGBEE_STORAGE_PARTITION_NAME));
|
||||
ESP_LOGI(TAG, "Start ESP Zigbee Stack");
|
||||
xTaskCreate(esp_zigbee_stack_main_task, "Zigbee_main", 4096 * 2, NULL, 5, NULL);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#define ESP_ZIGBEE_PRIMARY_CHANNEL_MASK ((1U << 13))
|
||||
#define ESP_ZIGBEE_SECONDARY_CHANNEL_MASK (0x07FFF800U)
|
||||
|
||||
#define ESP_ZIGBEE_HA_THERMOSTAT_EP_ID (1)
|
||||
|
||||
#define ESP_ZIGBEE_STORAGE_PARTITION_NAME "zb_storage"
|
||||
|
||||
#define ESP_MANUFACTURER_NAME "\x09""ESPRESSIF"
|
||||
#define ESP_MODEL_IDENTIFIER "\x07"CONFIG_IDF_TARGET
|
||||
|
||||
#define ESP_ZIGBEE_ZC_CONFIG() \
|
||||
{ \
|
||||
.device_type = EZB_NWK_DEVICE_TYPE_COORDINATOR, \
|
||||
.install_code_policy = false, \
|
||||
.zczr_config = { \
|
||||
.max_children = 10, \
|
||||
}, \
|
||||
}
|
||||
|
||||
// radio_config done via ESP-Hosted
|
||||
#define ESP_ZIGBEE_PLATFORM_CONFIG() \
|
||||
{ \
|
||||
.storage_partition_name = ESP_ZIGBEE_STORAGE_PARTITION_NAME, \
|
||||
}
|
||||
|
||||
#define ESP_ZIGBEE_DEFAULT_CONFIG() \
|
||||
{ \
|
||||
.device_config = ESP_ZIGBEE_ZC_CONFIG(), \
|
||||
.platform_config = ESP_ZIGBEE_PLATFORM_CONFIG(), \
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
|
||||
nvs, data, nvs, , 0x6000,
|
||||
otadata, data, ota, , 0x2000,
|
||||
phy_init, data, phy, , 0x1000,
|
||||
factory, app, factory, , 940K,
|
||||
zb_storage, data, nvs, , 16K,
|
||||
zb_fct, data, fat, , 1K,
|
||||
|
@@ -0,0 +1,15 @@
|
||||
#
|
||||
# Partition Table
|
||||
#
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
|
||||
# end of Partition Table
|
||||
|
||||
#
|
||||
# Zigbee
|
||||
#
|
||||
CONFIG_ZB_ENABLED=y
|
||||
# end of Zigbee
|
||||
|
||||
### enable ESP-Hosted Zigbee Support
|
||||
CONFIG_ESP_HOSTED_HOST_OT_ENABLE=y
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
# This config file optimizes the mbedTLS code size by disabling
|
||||
# unused features by ESP-Zigbee-SDK.
|
||||
|
||||
#
|
||||
# mbedTLS
|
||||
#
|
||||
|
||||
# Enable required
|
||||
CONFIG_MBEDTLS_AES_C=y
|
||||
CONFIG_MBEDTLS_CCM_C=y
|
||||
|
||||
# Disable unused
|
||||
CONFIG_MBEDTLS_GCM_C=n
|
||||
CONFIG_MBEDTLS_CMAC_C=n
|
||||
CONFIG_MBEDTLS_SHA1_C=n
|
||||
CONFIG_MBEDTLS_SHA3_C=n
|
||||
CONFIG_MBEDTLS_SHA512_C=n
|
||||
CONFIG_MBEDTLS_ECP_DP_SECP384R1_ENABLED=n
|
||||
CONFIG_MBEDTLS_ECP_DP_SECP521R1_ENABLED=n
|
||||
CONFIG_MBEDTLS_ECP_DP_SECP256K1_ENABLED=n
|
||||
CONFIG_MBEDTLS_ECP_DP_BP256R1_ENABLED=n
|
||||
CONFIG_MBEDTLS_ECP_DP_BP384R1_ENABLED=n
|
||||
CONFIG_MBEDTLS_ECP_DP_BP512R1_ENABLED=n
|
||||
CONFIG_MBEDTLS_KEY_EXCHANGE_RSA=n
|
||||
CONFIG_MBEDTLS_KEY_EXCHANGE_RSA_PSK=n
|
||||
CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_RSA=n
|
||||
|
||||
## v5.0
|
||||
CONFIG_MBEDTLS_ECP_DP_SECP192R1_ENABLED=n
|
||||
CONFIG_MBEDTLS_ECP_DP_SECP224R1_ENABLED=n
|
||||
CONFIG_MBEDTLS_ECP_DP_SECP192K1_ENABLED=n
|
||||
CONFIG_MBEDTLS_ECP_DP_SECP224K1_ENABLED=n
|
||||
CONFIG_MBEDTLS_KEY_EXCHANGE_DHE_RSA=n
|
||||
CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_RSA=n
|
||||
|
||||
## v6.0
|
||||
CONFIG_MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH=y
|
||||
CONFIG_MBEDTLS_CTR_DRBG_C=y
|
||||
CONFIG_MBEDTLS_HMAC_DRBG_C=n
|
||||
CONFIG_MBEDTLS_X509_RSASSA_PSS_SUPPORT=n
|
||||
CONFIG_MBEDTLS_CIPHER_MODE_CBC=n
|
||||
CONFIG_MBEDTLS_CIPHER_MODE_CFB=n
|
||||
CONFIG_MBEDTLS_CIPHER_MODE_CTR=n
|
||||
CONFIG_MBEDTLS_CIPHER_MODE_OFB=n
|
||||
CONFIG_MBEDTLS_CIPHER_MODE_XTS=n
|
||||
CONFIG_MBEDTLS_RSA_C=n
|
||||
CONFIG_MBEDTLS_MD5_C=n
|
||||
CONFIG_MBEDTLS_SHA384_C=n
|
||||
|
||||
# end of mbedTLS
|
||||
Reference in New Issue
Block a user