wip:
This commit is contained in:
1 parent
0daf4de19f
commit
036b856a93
2 files changed
+153
-44
No files matched your search
+70
-23
@@ -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
@@ -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();
|
||||||
|
|||||||
Reference in new issue
Block a user