Files
relay_chn/src/relay_chn_notify.c
ismail 86cc29a33b Fix static variable names
Fixed static variable names according to the ESP-IDF C code formatting guide.

Refs #1085 and fixes #1103
2025-09-04 18:20:51 +03:00

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;
}
}
}
}