From 036b856a93695a21e5dff1d8983dda4fd7b22710 Mon Sep 17 00:00:00 2001 From: Alexey Zholtikov Date: Sun, 1 Nov 2026 07:59:36 +0300 Subject: [PATCH] wip: --- include/zh_nrf24_gateway.h | 93 +++++++++++++++++++++++++-------- zh_nrf24_gateway.c | 104 +++++++++++++++++++++++++++++-------- 2 files changed, 153 insertions(+), 44 deletions(-) diff --git a/include/zh_nrf24_gateway.h b/include/zh_nrf24_gateway.h index 277e2e0..389221c 100644 --- a/include/zh_nrf24_gateway.h +++ b/include/zh_nrf24_gateway.h @@ -9,9 +9,12 @@ * ACK packets. * * Events emitted: - * - ZH_NRF_GATEWAY_EVENT_SENSOR_DATA — sensor telemetry - * - ZH_NRF_GATEWAY_EVENT_SENSOR_PAIRED — a new {id, type} binding was - * created and the ACK was successfully delivered to the sensor + * - ZH_NRF_GATEWAY_EVENT_SENSOR_DATA — sensor telemetry + * - ZH_NRF_GATEWAY_EVENT_SENSOR_PAIRED — pairing completed (gateway + * assigned a new id and the ACK was delivered) + * - ZH_NRF_GATEWAY_EVENT_SENSOR_REGISTERED — data packet arrived from a + * {id, type} that was missing from the binding table; it was added + * on the fly (covers gateway replacement / NVS wipe without re-pairing) * * Binding table semantics: * - each binding is a pair {id, type}; @@ -43,32 +46,29 @@ ESP_EVENT_DECLARE_BASE(ZH_NRF_GATEWAY_EVENTS); typedef enum { - /** - * Sensor telemetry received. - * Event data: zh_nrf24_sensor_transmitted_data_t - */ + /** Sensor telemetry (data: zh_nrf24_sensor_transmitted_data_t) */ ZH_NRF_GATEWAY_EVENT_SENSOR_DATA, - /** - * A new {id, type} binding was created (pairing succeeded and the - * ACK was delivered to the sensor). - * Event data: zh_nrf_gateway_binding_t - */ + /** Pairing completed (data: zh_nrf_gateway_binding_t) */ ZH_NRF_GATEWAY_EVENT_SENSOR_PAIRED, + + /** Unknown {id, type} seen in a data packet, auto-added to bindings + * (data: zh_nrf_gateway_binding_t) */ + ZH_NRF_GATEWAY_EVENT_SENSOR_REGISTERED, } zh_nrf_gateway_event_t; typedef struct { - spi_host_device_t spi_host; - int pin_sck; - int pin_miso; - int pin_mosi; - int pin_csn; - int pin_ce; - int pin_irq; - uint8_t channel; - uint32_t address; - uint32_t spi_clock_hz; + spi_host_device_t spi_host; /*!< SPI host (SPI2_HOST / SPI3_HOST) */ + int pin_sck; /*!< SCK GPIO */ + int pin_miso; /*!< MISO GPIO */ + int pin_mosi; /*!< MOSI GPIO */ + int pin_csn; /*!< CSN GPIO (chip select, active low) */ + int pin_ce; /*!< CE GPIO (chip enable, active high) */ + int pin_irq; /*!< IRQ GPIO (open-drain, active low) */ + uint8_t channel; /*!< RF channel (0..125) */ + uint32_t address; /*!< 24-bit pipe address, transmitted as LE bytes */ + uint32_t spi_clock_hz; /*!< SPI clock, 0 → default 4 MHz */ } zh_nrf_gateway_config_t; /** A single {id, type} binding. */ @@ -78,14 +78,61 @@ typedef struct uint8_t type; /**< zh_nrf24_sensor_type_t */ } zh_nrf_gateway_binding_t; +/** + * @brief Initialize the gateway. + * + * Configures SPI, the IRQ GPIO with falling-edge interrupt, the nRF24L01+ + * transceiver (DPL enabled, RX mode) and prepares NVS and the default + * event loop if they are not already initialized. + * + * @param[in] config Gateway configuration (must not be NULL) + * + * @return ESP_OK on success, or an ESP-IDF error code + */ esp_err_t zh_nrf_gateway_init(const zh_nrf_gateway_config_t *config); + +/** + * @brief Start the receiver task. + * + * The task blocks on the IRQ semaphore (or polls the FIFO as a fallback + * once per second) and dispatches received packets. + * + * @return ESP_OK on success + */ esp_err_t zh_nrf_gateway_start(void); /* ===================== BINDING TABLE API ===================== */ +/** + * @brief Copy current bindings into @p buf. + * + * Thread-safe. Returns number of entries copied (≤ max_count and ≤ + * current table size). + */ size_t zh_nrf_gateway_bindings_get(zh_nrf_gateway_binding_t *buf, size_t max_count); + +/** @brief Current number of bindings in the table. */ size_t zh_nrf_gateway_bindings_count(void); -bool zh_nrf_gateway_binding_exists(uint8_t id, uint8_t type); + +/** + * @brief Check whether a {id, type} binding exists. + * + * @return true if the binding is present in the table + */ +bool zh_nrf_gateway_binding_exists(uint8_t id, uint8_t type); + +/** + * @brief Auto-register {id, type} if not present. + * + * Validates both fields, then adds the pair to the table and persists + * to NVS if it is not already there. The id is taken from the packet + * as-is (unlike pairing, which assigns a new id). + * + * @return 1 binding was newly added + * @return 0 binding already existed + * @return negative ESP error (invalid arg, NVS failure, etc.) + */ +int zh_nrf_gateway_binding_auto_register(uint8_t id, uint8_t type); /** * @brief Delete a {id, type} binding. diff --git a/zh_nrf24_gateway.c b/zh_nrf24_gateway.c index 5d9286b..038c1c1 100644 --- a/zh_nrf24_gateway.c +++ b/zh_nrf24_gateway.c @@ -202,7 +202,7 @@ static esp_err_t nrf_irq_init(void) } /* ============================================================ - * NVS BINDING TABLE + * NVS BINDING TABLE (internal) * ============================================================ */ static esp_err_t bindings_load(void) { @@ -251,22 +251,30 @@ static esp_err_t bindings_save(void) return err; } +/* Caller must hold bindings_mutex. */ +static bool bindings_exists_locked(uint8_t id, uint8_t type) +{ + for (uint16_t i = 0; i < s_ctx.bindings.count; i++) { + if (s_ctx.bindings.items[i].id == id && + s_ctx.bindings.items[i].type == type) { + return true; + } + } + return false; +} + +/* Caller must hold bindings_mutex. */ static uint8_t bindings_alloc_id(uint8_t type) { for (uint16_t id = BINDING_ID_MIN; id <= BINDING_ID_MAX; id++) { - bool used = false; - for (uint16_t i = 0; i < s_ctx.bindings.count; i++) { - if (s_ctx.bindings.items[i].id == id && - s_ctx.bindings.items[i].type == type) { - used = true; - break; - } + if (!bindings_exists_locked((uint8_t)id, type)) { + return (uint8_t)id; } - if (!used) return (uint8_t)id; } return 0; } +/* Caller must hold bindings_mutex. */ static esp_err_t bindings_add(uint8_t id, uint8_t type) { if (s_ctx.bindings.count >= ZH_NRF_GATEWAY_MAX_BINDINGS) { @@ -278,6 +286,7 @@ static esp_err_t bindings_add(uint8_t id, uint8_t type) return bindings_save(); } +/* Caller must hold bindings_mutex. */ static esp_err_t bindings_delete(uint8_t id, uint8_t type) { for (uint16_t i = 0; i < s_ctx.bindings.count; i++) { @@ -319,18 +328,38 @@ bool zh_nrf_gateway_binding_exists(uint8_t id, uint8_t type) { if (!s_ctx.initialized || !s_ctx.bindings_mutex) return false; xSemaphoreTake(s_ctx.bindings_mutex, portMAX_DELAY); - bool found = false; - for (uint16_t i = 0; i < s_ctx.bindings.count; i++) { - if (s_ctx.bindings.items[i].id == id && - s_ctx.bindings.items[i].type == type) { - found = true; - break; - } - } + bool found = bindings_exists_locked(id, type); xSemaphoreGive(s_ctx.bindings_mutex); return found; } +int zh_nrf_gateway_binding_auto_register(uint8_t id, uint8_t type) +{ + if (!s_ctx.initialized || !s_ctx.bindings_mutex) return -ESP_ERR_INVALID_STATE; + if (id < BINDING_ID_MIN || id > BINDING_ID_MAX) return -ESP_ERR_INVALID_ARG; + if (zh_get_nrf24_sensor_payload_size(type) == 0) return -ESP_ERR_INVALID_ARG; + + xSemaphoreTake(s_ctx.bindings_mutex, portMAX_DELAY); + + int result = 0; + if (!bindings_exists_locked(id, type)) { + esp_err_t err = bindings_add(id, type); + if (err == ESP_OK) { + result = 1; + } else { + if (s_ctx.bindings.count > 0 && + s_ctx.bindings.items[s_ctx.bindings.count - 1].id == id && + s_ctx.bindings.items[s_ctx.bindings.count - 1].type == type) { + s_ctx.bindings.count--; + } + result = -(int)err; + } + } + + xSemaphoreGive(s_ctx.bindings_mutex); + return result; +} + esp_err_t zh_nrf_gateway_binding_delete(uint8_t id, uint8_t type) { if (!s_ctx.initialized) return ESP_ERR_INVALID_STATE; @@ -526,7 +555,6 @@ static void handle_pairing_request(const uint8_t *data) return; } - /* --- ACK delivered → binding is final. Notify the application. --- */ ESP_LOGI(TAG, "Pairing complete: id=%u type=%u", new_id, req_type); zh_nrf_gateway_binding_t paired = { @@ -564,6 +592,37 @@ static void handle_sensor_data(const uint8_t *buf, uint8_t len) len, expected, d.type); } + /* ----- Auto-register unknown {id, type} ----- */ + if (d.id >= BINDING_ID_MIN && d.id <= BINDING_ID_MAX) { + int r = zh_nrf_gateway_binding_auto_register((uint8_t)d.id, + (uint8_t)d.type); + if (r == 1) { + ESP_LOGI(TAG, "Auto-registered sensor: id=%d type=%d (%s)", + d.id, d.type, + zh_get_nrf24_sensor_type_name((zh_nrf24_sensor_type_t)d.type)); + + zh_nrf_gateway_binding_t b = { + .id = (uint8_t)d.id, + .type = (uint8_t)d.type, + }; + esp_err_t perr = esp_event_post(ZH_NRF_GATEWAY_EVENTS, + ZH_NRF_GATEWAY_EVENT_SENSOR_REGISTERED, + &b, sizeof(b), + pdMS_TO_TICKS(100)); + if (perr != ESP_OK) { + ESP_LOGW(TAG, "esp_event_post(registered) failed: %s", + esp_err_to_name(perr)); + } + } else if (r < 0) { + ESP_LOGW(TAG, "Auto-register failed for id=%d type=%d: %s", + d.id, d.type, esp_err_to_name((esp_err_t)(-r))); + } + } else { + ESP_LOGW(TAG, "Sensor id %d out of range [%d..%d], skipping auto-register", + d.id, BINDING_ID_MIN, BINDING_ID_MAX); + } + + /* ----- Parse type-specific fields ----- */ switch (d.type) { case ZH_NRF24_SENSOR_TOUCH_SWITCH: break; @@ -598,11 +657,12 @@ static void handle_sensor_data(const uint8_t *buf, uint8_t len) break; } + /* ----- Post SENSOR_DATA ----- */ esp_err_t err = esp_event_post(ZH_NRF_GATEWAY_EVENTS, ZH_NRF_GATEWAY_EVENT_SENSOR_DATA, &d, sizeof(d), pdMS_TO_TICKS(100)); if (err != ESP_OK) { - ESP_LOGW(TAG, "esp_event_post failed: %s", esp_err_to_name(err)); + ESP_LOGW(TAG, "esp_event_post(data) failed: %s", esp_err_to_name(err)); } } @@ -712,10 +772,10 @@ esp_err_t zh_nrf_gateway_init(const zh_nrf_gateway_config_t *config) nrf_write_register(NRF_REG_CONFIG, 0x00); nrf_write_register(NRF_REG_EN_AA, 0x01); nrf_write_register(NRF_REG_EN_RXADDR, 0x01); - nrf_write_register(NRF_REG_SETUP_AW, 0x01); + nrf_write_register(NRF_REG_SETUP_AW, 0x01); /* 3-byte address */ nrf_write_register(NRF_REG_SETUP_RETR, 0xFF); nrf_write_register(NRF_REG_RF_CH, s_ctx.cfg.channel); - nrf_write_register(NRF_REG_RF_SETUP, 0x26); /* 250 kbps, 0 dBm */ + nrf_write_register(NRF_REG_RF_SETUP, 0x26); /* 250 kbps, 0 dBm */ uint8_t addr_bytes[3] = { (uint8_t)(s_ctx.cfg.address >> 0), @@ -728,10 +788,12 @@ esp_err_t zh_nrf_gateway_init(const zh_nrf_gateway_config_t *config) nrf_write_register(NRF_REG_STATUS, NRF_STATUS_RX_DR | NRF_STATUS_TX_DS | NRF_STATUS_MAX_RT); + /* Enable Dynamic Payload Length */ nrf_activate(); nrf_write_register(NRF_REG_FEATURE, NRF_FEATURE_EN_DPL); nrf_write_register(NRF_REG_DYNPD, NRF_DYNPD_DPL_P0); + /* Enter RX */ nrf_write_register(NRF_REG_CONFIG, NRF_CONFIG_RX_UNMASKED); vTaskDelay(pdMS_TO_TICKS(5)); nrf_ce_high();