Fixed static variable names according to the ESP-IDF C code formatting guide. Refs #1085 and fixes #1103
268 lines
9.0 KiB
C
268 lines
9.0 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
|
*
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "freertos/queue.h"
|
|
#include "esp_log.h"
|
|
#include "esp_check.h"
|
|
#include "relay_chn_notify.h"
|
|
|
|
static const char *TAG = "RELAY_CHN_NOTIFY";
|
|
|
|
// --- Config ---
|
|
#define RELAY_CHN_NOTIFY_QUEUE_LEN (16 + CONFIG_RELAY_CHN_COUNT * 4)
|
|
#define RELAY_CHN_NOTIFY_TASK_STACK 2048
|
|
#define RELAY_CHN_NOTIFY_TASK_PRIO (tskIDLE_PRIORITY + 5)
|
|
|
|
/// @brief Structure to hold a listener entry in the linked list.
|
|
typedef struct relay_chn_listener_entry_type {
|
|
relay_chn_state_listener_t listener; /*!< The listener function pointer */
|
|
ListItem_t list_item; /*!< FreeRTOS list item */
|
|
} relay_chn_listener_entry_t;
|
|
|
|
/// @brief Command types for the notification queue.
|
|
typedef enum {
|
|
RELAY_CHN_NOTIFY_CMD_BROADCAST, /*!< A relay channel state has changed. */
|
|
RELAY_CHN_NOTIFY_CMD_ADD_LISTENER, /*!< Request to add a new listener. */
|
|
RELAY_CHN_NOTIFY_CMD_REMOVE_LISTENER, /*!< Request to remove a listener. */
|
|
} relay_chn_notify_cmd_t;
|
|
|
|
/// @brief Payload for a state change event.
|
|
typedef struct {
|
|
uint8_t chn_id;
|
|
relay_chn_state_t old_state;
|
|
relay_chn_state_t new_state;
|
|
} relay_chn_notify_event_data_t;
|
|
|
|
/// @brief The command structure sent to the notification queue.
|
|
typedef struct {
|
|
relay_chn_notify_cmd_t cmd;
|
|
union {
|
|
relay_chn_notify_event_data_t event_data; /*!< Used for RELAY_CHN_NOTIFY_CMD_BROADCAST */
|
|
relay_chn_state_listener_t listener; /*!< Used for ADD/REMOVE listener commands */
|
|
} payload;
|
|
} relay_chn_notify_msg_t;
|
|
|
|
// The list that holds references to the registered listeners.
|
|
static List_t s_listeners;
|
|
|
|
static QueueHandle_t s_notify_queue = NULL;
|
|
static TaskHandle_t s_notify_task = NULL;
|
|
|
|
static void relay_chn_notify_task(void *arg);
|
|
|
|
|
|
esp_err_t relay_chn_notify_init(void)
|
|
{
|
|
if (s_notify_queue != NULL) {
|
|
return ESP_OK;
|
|
}
|
|
|
|
s_notify_queue = xQueueCreate(RELAY_CHN_NOTIFY_QUEUE_LEN, sizeof(relay_chn_notify_msg_t));
|
|
if (!s_notify_queue) {
|
|
ESP_LOGE(TAG, "Failed to create notify queue");
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
|
|
// Create the notify dispatcher task
|
|
BaseType_t ret = xTaskCreate(relay_chn_notify_task, "task_rlch_ntfy",
|
|
RELAY_CHN_NOTIFY_TASK_STACK, NULL,
|
|
RELAY_CHN_NOTIFY_TASK_PRIO, &s_notify_task);
|
|
if (ret != pdPASS) {
|
|
ESP_LOGE(TAG, "Failed to create notify task");
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
|
|
// Init the state listener list
|
|
vListInitialise(&s_listeners);
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
void relay_chn_notify_deinit(void)
|
|
{
|
|
if (s_notify_task != NULL) {
|
|
vTaskDelete(s_notify_task);
|
|
s_notify_task = NULL;
|
|
}
|
|
|
|
if (s_notify_queue != NULL) {
|
|
vQueueDelete(s_notify_queue);
|
|
s_notify_queue = NULL;
|
|
}
|
|
|
|
if (!listLIST_IS_EMPTY(&s_listeners)) {
|
|
// Free the listeners
|
|
while (listCURRENT_LIST_LENGTH(&s_listeners) > 0) {
|
|
ListItem_t *pxItem = listGET_HEAD_ENTRY(&s_listeners);
|
|
relay_chn_listener_entry_t *entry = listGET_LIST_ITEM_OWNER(pxItem);
|
|
uxListRemove(pxItem);
|
|
free(entry);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Find a listener entry in the list by its function pointer.
|
|
*
|
|
* This function replaces the old index-based search and is used to check
|
|
* for the existence of a listener before registration or for finding it
|
|
* during unregistration.
|
|
*
|
|
* @param listener The listener function pointer to find.
|
|
* @return Pointer to the listener entry if found, otherwise NULL.
|
|
*/
|
|
static relay_chn_listener_entry_t* find_listener_entry(relay_chn_state_listener_t listener)
|
|
{
|
|
if (listLIST_IS_EMPTY(&s_listeners)) {
|
|
ESP_LOGD(TAG, "No listeners registered");
|
|
return NULL;
|
|
}
|
|
|
|
// Iterate through the linked list of listeners
|
|
for (ListItem_t *pxListItem = listGET_HEAD_ENTRY(&s_listeners);
|
|
pxListItem != listGET_END_MARKER(&s_listeners);
|
|
pxListItem = listGET_NEXT(pxListItem)) {
|
|
|
|
relay_chn_listener_entry_t *entry = (relay_chn_listener_entry_t *) listGET_LIST_ITEM_OWNER(pxListItem);
|
|
if (entry->listener == listener) {
|
|
// Found the listener, return the entry
|
|
return entry;
|
|
}
|
|
}
|
|
|
|
// Listener was not found in the list
|
|
return NULL;
|
|
}
|
|
|
|
static void do_add_listener(relay_chn_state_listener_t listener)
|
|
{
|
|
// This is now only called from the dispatcher task, so no mutex needed.
|
|
if (find_listener_entry(listener) != NULL) {
|
|
ESP_LOGD(TAG, "Listener %p already registered", listener);
|
|
return;
|
|
}
|
|
relay_chn_listener_entry_t *entry = malloc(sizeof(relay_chn_listener_entry_t));
|
|
if (!entry) {
|
|
ESP_LOGE(TAG, "Failed to allocate memory for listener");
|
|
return;
|
|
}
|
|
entry->listener = listener;
|
|
vListInitialiseItem(&(entry->list_item));
|
|
listSET_LIST_ITEM_OWNER(&(entry->list_item), (void *)entry);
|
|
vListInsertEnd(&s_listeners, &(entry->list_item));
|
|
ESP_LOGD(TAG, "Registered listener %p", listener);
|
|
}
|
|
|
|
static void do_remove_listener(relay_chn_state_listener_t listener)
|
|
{
|
|
// This is now only called from the dispatcher task, so no mutex needed.
|
|
relay_chn_listener_entry_t *entry = find_listener_entry(listener);
|
|
if (entry != NULL) {
|
|
uxListRemove(&(entry->list_item));
|
|
free(entry);
|
|
ESP_LOGD(TAG, "Unregistered listener %p", listener);
|
|
} else {
|
|
ESP_LOGD(TAG, "Listener %p not found for unregistration.", listener);
|
|
}
|
|
|
|
if (listLIST_IS_EMPTY(&s_listeners)) {
|
|
// Flush all pending notifications in the queue
|
|
xQueueReset(s_notify_queue);
|
|
}
|
|
}
|
|
|
|
esp_err_t relay_chn_notify_add_listener(relay_chn_state_listener_t listener)
|
|
{
|
|
ESP_RETURN_ON_FALSE(listener, ESP_ERR_INVALID_ARG, TAG, "Listener cannot be NULL");
|
|
ESP_RETURN_ON_FALSE(s_notify_queue, ESP_ERR_INVALID_STATE, TAG, "Notify module not initialized");
|
|
|
|
relay_chn_notify_msg_t msg = { .cmd = RELAY_CHN_NOTIFY_CMD_ADD_LISTENER, .payload.listener = listener };
|
|
if (xQueueSend(s_notify_queue, &msg, 0) != pdTRUE) {
|
|
ESP_LOGE(TAG, "Notify queue is full, failed to queue add_listener");
|
|
return ESP_FAIL;
|
|
}
|
|
return ESP_OK;
|
|
}
|
|
|
|
void relay_chn_notify_remove_listener(relay_chn_state_listener_t listener)
|
|
{
|
|
if (listener == NULL) {
|
|
ESP_LOGD(TAG, "Cannot unregister a NULL listener.");
|
|
return;
|
|
}
|
|
if (!s_notify_queue) {
|
|
ESP_LOGE(TAG, "Notify module not initialized, cannot remove listener");
|
|
return;
|
|
}
|
|
|
|
relay_chn_notify_msg_t msg = { .cmd = RELAY_CHN_NOTIFY_CMD_REMOVE_LISTENER, .payload.listener = listener };
|
|
if (xQueueSendToFront(s_notify_queue, &msg, 0) != pdTRUE) {
|
|
ESP_LOGW(TAG, "Notify queue is full, failed to queue remove_listener");
|
|
}
|
|
}
|
|
|
|
esp_err_t relay_chn_notify_state_change(uint8_t chn_id, relay_chn_state_t old_state, relay_chn_state_t new_state)
|
|
{
|
|
if (!s_notify_queue) {
|
|
return ESP_ERR_INVALID_STATE;
|
|
}
|
|
|
|
relay_chn_notify_msg_t msg = {
|
|
.cmd = RELAY_CHN_NOTIFY_CMD_BROADCAST,
|
|
.payload.event_data.chn_id = chn_id,
|
|
.payload.event_data.old_state = old_state,
|
|
.payload.event_data.new_state = new_state,
|
|
};
|
|
|
|
// Try to send, do not wait if the queue is full
|
|
if (xQueueSend(s_notify_queue, &msg, 0) != pdTRUE) {
|
|
ESP_LOGW(TAG, "Notify queue is full, dropping event: %d -> %d for #%d", old_state, new_state, chn_id);
|
|
return ESP_FAIL;
|
|
}
|
|
return ESP_OK;
|
|
}
|
|
|
|
static void do_notify(relay_chn_notify_event_data_t *event_data)
|
|
{
|
|
// Iterate through the linked list of listeners and notify them.
|
|
// No mutex is needed as this is the only task accessing the list.
|
|
for (ListItem_t *pxListItem = listGET_HEAD_ENTRY(&s_listeners);
|
|
pxListItem != listGET_END_MARKER(&s_listeners);
|
|
pxListItem = listGET_NEXT(pxListItem)) {
|
|
relay_chn_listener_entry_t *entry = (relay_chn_listener_entry_t *) listGET_LIST_ITEM_OWNER(pxListItem);
|
|
if (entry && entry->listener) {
|
|
// Emit the state change to the listeners
|
|
entry->listener(event_data->chn_id, event_data->old_state, event_data->new_state);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---- Notify Task ----
|
|
static void relay_chn_notify_task(void *arg)
|
|
{
|
|
relay_chn_notify_msg_t msg;
|
|
for (;;) {
|
|
if (xQueueReceive(s_notify_queue, &msg, portMAX_DELAY) == pdTRUE) {
|
|
switch (msg.cmd) {
|
|
case RELAY_CHN_NOTIFY_CMD_BROADCAST: {
|
|
do_notify(&msg.payload.event_data);
|
|
break;
|
|
}
|
|
case RELAY_CHN_NOTIFY_CMD_ADD_LISTENER:
|
|
do_add_listener(msg.payload.listener);
|
|
break;
|
|
case RELAY_CHN_NOTIFY_CMD_REMOVE_LISTENER:
|
|
do_remove_listener(msg.payload.listener);
|
|
break;
|
|
default:
|
|
ESP_LOGE(TAG, "Unknown command type in notify queue: %d", msg.cmd);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} |