This commit is contained in:
aZholtikov committed 2026-11-01 07:59:36 +03:00
1 parent 0daf4de19f
commit 036b856a93
2 files changed
+153 -44

No files matched your search

+70 -23
View File
@@ -9,9 +9,12 @@
* ACK packets. * ACK packets.
* *
* Events emitted: * Events emitted:
* - ZH_NRF_GATEWAY_EVENT_SENSOR_DATA — sensor telemetry * - ZH_NRF_GATEWAY_EVENT_SENSOR_DATA — sensor telemetry
* - ZH_NRF_GATEWAY_EVENT_SENSOR_PAIRED — a new {id, type} binding was * - ZH_NRF_GATEWAY_EVENT_SENSOR_PAIRED — pairing completed (gateway
* created and the ACK was successfully delivered to the sensor * 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: * Binding table semantics:
* - each binding is a pair {id, type}; * - each binding is a pair {id, type};
@@ -43,32 +46,29 @@ ESP_EVENT_DECLARE_BASE(ZH_NRF_GATEWAY_EVENTS);
typedef enum typedef enum
{ {
/** /** Sensor telemetry (data: zh_nrf24_sensor_transmitted_data_t) */
* Sensor telemetry received.
* Event data: zh_nrf24_sensor_transmitted_data_t
*/
ZH_NRF_GATEWAY_EVENT_SENSOR_DATA, ZH_NRF_GATEWAY_EVENT_SENSOR_DATA,
/** /** Pairing completed (data: zh_nrf_gateway_binding_t) */
* A new {id, type} binding was created (pairing succeeded and the
* ACK was delivered to the sensor).
* Event data: zh_nrf_gateway_binding_t
*/
ZH_NRF_GATEWAY_EVENT_SENSOR_PAIRED, 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; } zh_nrf_gateway_event_t;
typedef struct typedef struct
{ {
spi_host_device_t spi_host; spi_host_device_t spi_host; /*!< SPI host (SPI2_HOST / SPI3_HOST) */
int pin_sck; int pin_sck; /*!< SCK GPIO */
int pin_miso; int pin_miso; /*!< MISO GPIO */
int pin_mosi; int pin_mosi; /*!< MOSI GPIO */
int pin_csn; int pin_csn; /*!< CSN GPIO (chip select, active low) */
int pin_ce; int pin_ce; /*!< CE GPIO (chip enable, active high) */
int pin_irq; int pin_irq; /*!< IRQ GPIO (open-drain, active low) */
uint8_t channel; uint8_t channel; /*!< RF channel (0..125) */
uint32_t address; uint32_t address; /*!< 24-bit pipe address, transmitted as LE bytes */
uint32_t spi_clock_hz; uint32_t spi_clock_hz; /*!< SPI clock, 0 → default 4 MHz */
} zh_nrf_gateway_config_t; } zh_nrf_gateway_config_t;
/** A single {id, type} binding. */ /** A single {id, type} binding. */
@@ -78,14 +78,61 @@ typedef struct
uint8_t type; /**< zh_nrf24_sensor_type_t */ uint8_t type; /**< zh_nrf24_sensor_type_t */
} zh_nrf_gateway_binding_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); 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); esp_err_t zh_nrf_gateway_start(void);
/* ===================== BINDING TABLE API ===================== */ /* ===================== 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); 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); 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. * @brief Delete a {id, type} binding.
+83 -21
View File
@@ -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) static esp_err_t bindings_load(void)
{ {
@@ -251,22 +251,30 @@ static esp_err_t bindings_save(void)
return err; 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) static uint8_t bindings_alloc_id(uint8_t type)
{ {
for (uint16_t id = BINDING_ID_MIN; id <= BINDING_ID_MAX; id++) { for (uint16_t id = BINDING_ID_MIN; id <= BINDING_ID_MAX; id++) {
bool used = false; if (!bindings_exists_locked((uint8_t)id, type)) {
for (uint16_t i = 0; i < s_ctx.bindings.count; i++) { return (uint8_t)id;
if (s_ctx.bindings.items[i].id == id &&
s_ctx.bindings.items[i].type == type) {
used = true;
break;
}
} }
if (!used) return (uint8_t)id;
} }
return 0; return 0;
} }
/* Caller must hold bindings_mutex. */
static esp_err_t bindings_add(uint8_t id, uint8_t type) static esp_err_t bindings_add(uint8_t id, uint8_t type)
{ {
if (s_ctx.bindings.count >= ZH_NRF_GATEWAY_MAX_BINDINGS) { 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(); return bindings_save();
} }
/* Caller must hold bindings_mutex. */
static esp_err_t bindings_delete(uint8_t id, uint8_t type) static esp_err_t bindings_delete(uint8_t id, uint8_t type)
{ {
for (uint16_t i = 0; i < s_ctx.bindings.count; i++) { 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; if (!s_ctx.initialized || !s_ctx.bindings_mutex) return false;
xSemaphoreTake(s_ctx.bindings_mutex, portMAX_DELAY); xSemaphoreTake(s_ctx.bindings_mutex, portMAX_DELAY);
bool found = false; bool found = bindings_exists_locked(id, 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) {
found = true;
break;
}
}
xSemaphoreGive(s_ctx.bindings_mutex); xSemaphoreGive(s_ctx.bindings_mutex);
return found; 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) esp_err_t zh_nrf_gateway_binding_delete(uint8_t id, uint8_t type)
{ {
if (!s_ctx.initialized) return ESP_ERR_INVALID_STATE; if (!s_ctx.initialized) return ESP_ERR_INVALID_STATE;
@@ -526,7 +555,6 @@ static void handle_pairing_request(const uint8_t *data)
return; return;
} }
/* --- ACK delivered → binding is final. Notify the application. --- */
ESP_LOGI(TAG, "Pairing complete: id=%u type=%u", new_id, req_type); ESP_LOGI(TAG, "Pairing complete: id=%u type=%u", new_id, req_type);
zh_nrf_gateway_binding_t paired = { 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); 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) { switch (d.type) {
case ZH_NRF24_SENSOR_TOUCH_SWITCH: case ZH_NRF24_SENSOR_TOUCH_SWITCH:
break; break;
@@ -598,11 +657,12 @@ static void handle_sensor_data(const uint8_t *buf, uint8_t len)
break; break;
} }
/* ----- Post SENSOR_DATA ----- */
esp_err_t err = esp_event_post(ZH_NRF_GATEWAY_EVENTS, esp_err_t err = esp_event_post(ZH_NRF_GATEWAY_EVENTS,
ZH_NRF_GATEWAY_EVENT_SENSOR_DATA, ZH_NRF_GATEWAY_EVENT_SENSOR_DATA,
&d, sizeof(d), pdMS_TO_TICKS(100)); &d, sizeof(d), pdMS_TO_TICKS(100));
if (err != ESP_OK) { 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_CONFIG, 0x00);
nrf_write_register(NRF_REG_EN_AA, 0x01); nrf_write_register(NRF_REG_EN_AA, 0x01);
nrf_write_register(NRF_REG_EN_RXADDR, 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_SETUP_RETR, 0xFF);
nrf_write_register(NRF_REG_RF_CH, s_ctx.cfg.channel); 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 addr_bytes[3] = {
(uint8_t)(s_ctx.cfg.address >> 0), (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_write_register(NRF_REG_STATUS,
NRF_STATUS_RX_DR | NRF_STATUS_TX_DS | NRF_STATUS_MAX_RT); NRF_STATUS_RX_DR | NRF_STATUS_TX_DS | NRF_STATUS_MAX_RT);
/* Enable Dynamic Payload Length */
nrf_activate(); nrf_activate();
nrf_write_register(NRF_REG_FEATURE, NRF_FEATURE_EN_DPL); nrf_write_register(NRF_REG_FEATURE, NRF_FEATURE_EN_DPL);
nrf_write_register(NRF_REG_DYNPD, NRF_DYNPD_DPL_P0); nrf_write_register(NRF_REG_DYNPD, NRF_DYNPD_DPL_P0);
/* Enter RX */
nrf_write_register(NRF_REG_CONFIG, NRF_CONFIG_RX_UNMASKED); nrf_write_register(NRF_REG_CONFIG, NRF_CONFIG_RX_UNMASKED);
vTaskDelay(pdMS_TO_TICKS(5)); vTaskDelay(pdMS_TO_TICKS(5));
nrf_ce_high(); nrf_ce_high();