Compare commits
2 Commits
db55c0b7e4
...
5e8e5a4cab
| Author | SHA1 | Date | |
|---|---|---|---|
|
5e8e5a4cab
|
|||
|
d2b38a5b4e
|
4
.gitignore
vendored
4
.gitignore
vendored
@@ -56,6 +56,10 @@ tools/test_apps/**/build_*_*/
|
||||
tools/test_apps/**/sdkconfig
|
||||
tools/test_apps/**/sdkconfig.old
|
||||
|
||||
# autogenerated config files
|
||||
sdkconfig
|
||||
test_apps/sdkconfig
|
||||
|
||||
TEST_LOGS/
|
||||
build_summary_*.xml
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@ set(priv_include_dirs "private_include")
|
||||
|
||||
set(srcs "src/relay_chn_core.c"
|
||||
"src/relay_chn_output.c"
|
||||
"src/relay_chn_run_info.c")
|
||||
"src/relay_chn_run_info.c"
|
||||
"src/relay_chn_notify.c")
|
||||
|
||||
if(CONFIG_RELAY_CHN_ENABLE_TILTING)
|
||||
list(APPEND srcs "src/relay_chn_tilt.c")
|
||||
|
||||
@@ -200,6 +200,9 @@ relay_chn_flip_direction_all();
|
||||
|
||||
### 3. Monitor channel state
|
||||
|
||||
> [!WARNING]
|
||||
> Listener callbacks are executed from the context of the notification dispatcher task. To ensure system responsiveness and prevent event loss, callbacks must be lightweight and non-blocking. Avoid any long-running operations or functions that may block, such as `vTaskDelay()` or semaphore takes with long timeouts, inside the callback.
|
||||
|
||||
For single mode:
|
||||
|
||||
```c
|
||||
|
||||
@@ -13,6 +13,36 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Register a channel state change listener.
|
||||
*
|
||||
* @param listener A function that implements relay_chn_state_listener_t interface.
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK: Success
|
||||
* - ESP_ERR_INVALID_ARG: Invalid argument
|
||||
* - ESP_ERR_NO_MEM: No enough memory
|
||||
* - ESP_FAIL: General failure
|
||||
*/
|
||||
extern esp_err_t relay_chn_notify_add_listener(relay_chn_state_listener_t listener);
|
||||
|
||||
/**
|
||||
* @brief Unregister a channel state change listener.
|
||||
*
|
||||
* @param listener A function that implements relay_chn_state_listener_t interface.
|
||||
*/
|
||||
extern void relay_chn_notify_remove_listener(relay_chn_state_listener_t listener);
|
||||
|
||||
static inline esp_err_t relay_chn_register_listener(relay_chn_state_listener_t listener)
|
||||
{
|
||||
return relay_chn_notify_add_listener(listener);
|
||||
}
|
||||
|
||||
static inline void relay_chn_unregister_listener(relay_chn_state_listener_t listener)
|
||||
{
|
||||
relay_chn_notify_remove_listener(listener);
|
||||
}
|
||||
|
||||
#if CONFIG_RELAY_CHN_COUNT > 1
|
||||
/**
|
||||
* @brief Get the current state of a relay channel.
|
||||
|
||||
51
private_include/relay_chn_notify.h
Normal file
51
private_include/relay_chn_notify.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
#include <stdint.h>
|
||||
#include "relay_chn_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Init the notify module.
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK: Success
|
||||
* - ESP_ERR_NO_MEM: Not enough memory to create notify queue
|
||||
*/
|
||||
esp_err_t relay_chn_notify_init(void);
|
||||
|
||||
/**
|
||||
* @brief Deinit the notify module.
|
||||
*
|
||||
* This function cleans up resources used by the notify module.
|
||||
*/
|
||||
void relay_chn_notify_deinit(void);
|
||||
|
||||
/**
|
||||
* @brief Notify all registered listeners about a state change.
|
||||
*
|
||||
* This function sends a state change event to an internal queue, which will then
|
||||
* be processed by a dedicated task to notify all registered listeners. This
|
||||
* function is typically called internally by the relay channel core logic.
|
||||
*
|
||||
* @param chn_id The ID of the relay channel whose state has changed.
|
||||
* @param old_state The previous state of the relay channel.
|
||||
* @param new_state The new state of the relay channel.
|
||||
*/
|
||||
esp_err_t relay_chn_notify_state_change(uint8_t chn_id,
|
||||
relay_chn_state_t old_state,
|
||||
relay_chn_state_t new_state);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -10,7 +10,7 @@ if [[ -z "$IDF_PATH" ]]; then
|
||||
fi
|
||||
|
||||
# ==== 2. Valid Modes and Defaults ====
|
||||
valid_test_tags=("core" "tilt" "listener" "all" "relay_chn" "nvs" "run_limit" "batch" "inertia" "direction" "auto" "sensitivity" "counter" "interrupt")
|
||||
valid_test_tags=("core" "tilt" "notify" "all" "relay_chn" "nvs" "run_limit" "batch" "inertia" "direction" "auto" "sensitivity" "counter" "interrupt")
|
||||
valid_test_profiles=("run_limit" "tilt" "nvs" "nvs_custom" "multi" "full_single" "full_multi")
|
||||
arg_tag="all" # Default to 'all' if no tag specified
|
||||
arg_profile="full_multi" # Default to 'full_multi' if no profile specified
|
||||
@@ -24,7 +24,7 @@ print_help() {
|
||||
echo "This script builds and runs tests for the relay_chn component using QEMU."
|
||||
echo ""
|
||||
echo "Arguments:"
|
||||
echo " -t, --tag [relay_chn|core|tilt|listener|nvs|run_limit|batch|inertia|direction|auto|sensitivity|counter|interrupt|all] Specify which test tag to run."
|
||||
echo " -t, --tag [relay_chn|core|tilt|notify|nvs|run_limit|batch|inertia|direction|auto|sensitivity|counter|interrupt|all] Specify which test tag to run."
|
||||
echo ""
|
||||
echo " If no tag is specified, it defaults to 'all'."
|
||||
echo ""
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "relay_chn_output.h"
|
||||
#include "relay_chn_run_info.h"
|
||||
#include "relay_chn_ctl.h"
|
||||
#include "relay_chn_notify.h"
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
#include "relay_chn_tilt.h"
|
||||
@@ -26,15 +27,6 @@
|
||||
static const char *TAG = "RELAY_CHN_CORE";
|
||||
|
||||
|
||||
// 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;
|
||||
|
||||
// The list that holds references to the registered listeners.
|
||||
static List_t relay_chn_listener_list;
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
/*
|
||||
* Run limit timer callback immediately dispatches a STOP command for the
|
||||
@@ -125,9 +117,9 @@ esp_err_t relay_chn_create(const uint8_t* gpio_map, uint8_t gpio_count)
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize tilt feature");
|
||||
#endif
|
||||
|
||||
// Init the state listener list
|
||||
vListInitialise(&relay_chn_listener_list);
|
||||
|
||||
// Initialize the notify feature
|
||||
ret = relay_chn_notify_init();
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize notify feature");
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -136,96 +128,15 @@ void relay_chn_destroy(void)
|
||||
#if CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
relay_chn_tilt_deinit();
|
||||
#endif
|
||||
relay_chn_notify_deinit();
|
||||
relay_chn_ctl_deinit();
|
||||
relay_chn_output_deinit();
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
relay_chn_nvs_deinit();
|
||||
#endif
|
||||
|
||||
// Free the listeners
|
||||
while (listCURRENT_LIST_LENGTH(&relay_chn_listener_list) > 0) {
|
||||
ListItem_t *pxItem = listGET_HEAD_ENTRY(&relay_chn_listener_list);
|
||||
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)
|
||||
{
|
||||
// Iterate through the linked list of listeners
|
||||
for (ListItem_t *pxListItem = listGET_HEAD_ENTRY(&relay_chn_listener_list);
|
||||
pxListItem != listGET_END_MARKER(&relay_chn_listener_list);
|
||||
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;
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_register_listener(relay_chn_state_listener_t listener)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(listener, ESP_ERR_INVALID_ARG, TAG, "Listener cannot be NULL");
|
||||
|
||||
// Check for duplicates
|
||||
if (find_listener_entry(listener) != NULL) {
|
||||
ESP_LOGD(TAG, "Listener %p already registered", listener);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Allocate memory for the new listener entry
|
||||
relay_chn_listener_entry_t *entry = malloc(sizeof(relay_chn_listener_entry_t));
|
||||
ESP_RETURN_ON_FALSE(entry, ESP_ERR_NO_MEM, TAG, "Failed to allocate memory for listener");
|
||||
|
||||
// Initialize and insert the new listener
|
||||
entry->listener = listener;
|
||||
vListInitialiseItem(&(entry->list_item));
|
||||
listSET_LIST_ITEM_OWNER(&(entry->list_item), (void *)entry);
|
||||
vListInsertEnd(&relay_chn_listener_list, &(entry->list_item));
|
||||
|
||||
ESP_LOGD(TAG, "Registered listener %p", listener);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void relay_chn_unregister_listener(relay_chn_state_listener_t listener)
|
||||
{
|
||||
if (listener == NULL)
|
||||
{
|
||||
ESP_LOGD(TAG, "Cannot unregister a NULL listener.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the listener entry in the list
|
||||
relay_chn_listener_entry_t *entry = find_listener_entry(listener);
|
||||
|
||||
if (entry != NULL) {
|
||||
// Remove the item from the list and free the allocated memory
|
||||
uxListRemove(&(entry->list_item));
|
||||
free(entry);
|
||||
ESP_LOGD(TAG, "Unregistered listener %p", listener);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Listener %p not found for unregistration.", listener);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
esp_err_t relay_chn_start_esp_timer_once(esp_timer_handle_t esp_timer, uint32_t time_ms)
|
||||
{
|
||||
esp_err_t ret = esp_timer_start_once(esp_timer, time_ms * 1000);
|
||||
@@ -251,16 +162,7 @@ void relay_chn_update_state(relay_chn_ctl_t *chn_ctl, relay_chn_state_t new_stat
|
||||
|
||||
chn_ctl->state = new_state;
|
||||
|
||||
// Iterate through the linked list of listeners and notify them.
|
||||
for (ListItem_t *pxListItem = listGET_HEAD_ENTRY(&relay_chn_listener_list);
|
||||
pxListItem != listGET_END_MARKER(&relay_chn_listener_list);
|
||||
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(chn_ctl->id, old_state, new_state);
|
||||
}
|
||||
}
|
||||
relay_chn_notify_state_change(chn_ctl->id, old_state, new_state);
|
||||
}
|
||||
|
||||
static void relay_chn_execute_idle(relay_chn_ctl_t *chn_ctl);
|
||||
|
||||
258
src/relay_chn_notify.c
Normal file
258
src/relay_chn_notify.c
Normal file
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
* 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 listeners;
|
||||
|
||||
static QueueHandle_t notify_msg_queue = NULL;
|
||||
static TaskHandle_t notify_task_handle = NULL;
|
||||
|
||||
static void relay_chn_notify_task(void *arg);
|
||||
|
||||
|
||||
esp_err_t relay_chn_notify_init(void)
|
||||
{
|
||||
if (notify_msg_queue != NULL) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
notify_msg_queue = xQueueCreate(RELAY_CHN_NOTIFY_QUEUE_LEN, sizeof(relay_chn_notify_msg_t));
|
||||
if (!notify_msg_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, ¬ify_task_handle);
|
||||
if (ret != pdPASS) {
|
||||
ESP_LOGE(TAG, "Failed to create notify task");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
// Init the state listener list
|
||||
vListInitialise(&listeners);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void relay_chn_notify_deinit(void)
|
||||
{
|
||||
if (notify_task_handle != NULL) {
|
||||
vTaskDelete(notify_task_handle);
|
||||
notify_task_handle = NULL;
|
||||
}
|
||||
|
||||
if (notify_msg_queue != NULL) {
|
||||
vQueueDelete(notify_msg_queue);
|
||||
notify_msg_queue = NULL;
|
||||
}
|
||||
|
||||
if (!listLIST_IS_EMPTY(&listeners)) {
|
||||
// Free the listeners
|
||||
while (listCURRENT_LIST_LENGTH(&listeners) > 0) {
|
||||
ListItem_t *pxItem = listGET_HEAD_ENTRY(&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)
|
||||
{
|
||||
// Iterate through the linked list of listeners
|
||||
for (ListItem_t *pxListItem = listGET_HEAD_ENTRY(&listeners);
|
||||
pxListItem != listGET_END_MARKER(&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(&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);
|
||||
}
|
||||
}
|
||||
|
||||
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(notify_msg_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(notify_msg_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 (!notify_msg_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 (xQueueSend(notify_msg_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 (!notify_msg_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(notify_msg_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(&listeners);
|
||||
pxListItem != listGET_END_MARKER(&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(notify_msg_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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
# === These files must be included in any case ===
|
||||
set(srcs "test_common.c"
|
||||
"test_relay_chn_notify_common.c"
|
||||
"test_app_main.c")
|
||||
|
||||
set(incdirs ".")
|
||||
@@ -7,10 +8,10 @@ set(incdirs ".")
|
||||
# === Selective compilation based on channel count ===
|
||||
if(CONFIG_RELAY_CHN_COUNT GREATER 1)
|
||||
list(APPEND srcs "test_relay_chn_core_multi.c"
|
||||
"test_relay_chn_listener_multi.c")
|
||||
"test_relay_chn_notify_multi.c")
|
||||
else()
|
||||
list(APPEND srcs "test_relay_chn_core_single.c"
|
||||
"test_relay_chn_listener_single.c")
|
||||
"test_relay_chn_notify_single.c")
|
||||
endif()
|
||||
|
||||
if(CONFIG_RELAY_CHN_ENABLE_TILTING)
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
#include "test_common.h"
|
||||
|
||||
|
||||
// --- Listener Test Globals ---
|
||||
typedef struct {
|
||||
uint8_t chn_id;
|
||||
relay_chn_state_t old_state;
|
||||
relay_chn_state_t new_state;
|
||||
int call_count;
|
||||
} listener_callback_info_t;
|
||||
|
||||
static listener_callback_info_t listener1_info;
|
||||
static listener_callback_info_t listener2_info;
|
||||
|
||||
// --- Listener Test Helper Functions ---
|
||||
|
||||
// Clear the memory from possible garbage values
|
||||
static void reset_listener_info(listener_callback_info_t* info) {
|
||||
memset(info, 0, sizeof(listener_callback_info_t));
|
||||
}
|
||||
|
||||
static void test_listener_1(uint8_t chn_id, relay_chn_state_t old_state, relay_chn_state_t new_state) {
|
||||
listener1_info.chn_id = chn_id;
|
||||
listener1_info.old_state = old_state;
|
||||
listener1_info.new_state = new_state;
|
||||
listener1_info.call_count++;
|
||||
}
|
||||
|
||||
static void test_listener_2(uint8_t chn_id, relay_chn_state_t old_state, relay_chn_state_t new_state) {
|
||||
listener2_info.chn_id = chn_id;
|
||||
listener2_info.old_state = old_state;
|
||||
listener2_info.new_state = new_state;
|
||||
listener2_info.call_count++;
|
||||
}
|
||||
|
||||
// ### Listener Functionality Tests
|
||||
|
||||
TEST_CASE("Listener is called on state change", "[relay_chn][listener]")
|
||||
{
|
||||
uint8_t ch = 0;
|
||||
reset_listener_info(&listener1_info);
|
||||
|
||||
// 1. Register the listener
|
||||
TEST_ESP_OK(relay_chn_register_listener(test_listener_1));
|
||||
|
||||
// 2. Trigger a state change
|
||||
relay_chn_run_forward(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow event to be processed
|
||||
|
||||
// 3. Verify the listener was called with correct parameters
|
||||
TEST_ASSERT_EQUAL(1, listener1_info.call_count);
|
||||
TEST_ASSERT_EQUAL(ch, listener1_info.chn_id);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, listener1_info.old_state);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, listener1_info.new_state);
|
||||
|
||||
// 4. Unregister to clean up
|
||||
relay_chn_unregister_listener(test_listener_1);
|
||||
}
|
||||
|
||||
TEST_CASE("Unregistered listener is not called", "[relay_chn][listener]")
|
||||
{
|
||||
uint8_t ch = 0;
|
||||
reset_listener_info(&listener1_info);
|
||||
|
||||
// 1. Register and then immediately unregister the listener
|
||||
TEST_ESP_OK(relay_chn_register_listener(test_listener_1));
|
||||
relay_chn_unregister_listener(test_listener_1);
|
||||
|
||||
// 2. Trigger a state change
|
||||
relay_chn_run_forward(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
|
||||
// 3. Verify the listener was NOT called
|
||||
TEST_ASSERT_EQUAL(0, listener1_info.call_count);
|
||||
}
|
||||
|
||||
TEST_CASE("Multiple listeners are called on state change", "[relay_chn][listener]")
|
||||
{
|
||||
uint8_t ch = 0;
|
||||
reset_listener_info(&listener1_info);
|
||||
reset_listener_info(&listener2_info);
|
||||
|
||||
// 1. Register two different listeners
|
||||
TEST_ESP_OK(relay_chn_register_listener(test_listener_1));
|
||||
TEST_ESP_OK(relay_chn_register_listener(test_listener_2));
|
||||
|
||||
// 2. Trigger a state change
|
||||
relay_chn_run_forward(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
|
||||
// 3. Verify listener 1 was called correctly
|
||||
TEST_ASSERT_EQUAL(1, listener1_info.call_count);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, listener1_info.old_state);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, listener1_info.new_state);
|
||||
|
||||
// 4. Verify listener 2 was also called correctly
|
||||
TEST_ASSERT_EQUAL(1, listener2_info.call_count);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, listener2_info.old_state);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, listener2_info.new_state);
|
||||
|
||||
// 5. Clean up
|
||||
relay_chn_unregister_listener(test_listener_1);
|
||||
relay_chn_unregister_listener(test_listener_2);
|
||||
}
|
||||
|
||||
TEST_CASE("Listener registration handles invalid arguments and duplicates", "[relay_chn][listener]")
|
||||
{
|
||||
reset_listener_info(&listener1_info);
|
||||
|
||||
// 1. Registering a NULL listener should fail
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_register_listener(NULL));
|
||||
|
||||
// 2. Unregistering a NULL listener should not crash
|
||||
relay_chn_unregister_listener(NULL);
|
||||
|
||||
// 3. Registering the same listener twice should be handled gracefully
|
||||
TEST_ESP_OK(relay_chn_register_listener(test_listener_1));
|
||||
TEST_ESP_OK(relay_chn_register_listener(test_listener_1)); // Second call should be a no-op
|
||||
|
||||
// 4. Trigger a state change and verify the listener is only called ONCE
|
||||
relay_chn_run_forward(0);
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(1, listener1_info.call_count);
|
||||
|
||||
// 5. Clean up
|
||||
relay_chn_unregister_listener(test_listener_1);
|
||||
}
|
||||
49
test_apps/main/test_relay_chn_notify_common.c
Normal file
49
test_apps/main/test_relay_chn_notify_common.c
Normal file
@@ -0,0 +1,49 @@
|
||||
#include "test_relay_chn_notify_common.h"
|
||||
|
||||
listener_callback_info_t listener1_info;
|
||||
listener_callback_info_t listener2_info;
|
||||
|
||||
// --- Globals for Advanced Tests ---
|
||||
SemaphoreHandle_t blocking_listener_sem = NULL;
|
||||
SemaphoreHandle_t log_check_sem = NULL;
|
||||
volatile int blocking_listener_call_count = 0;
|
||||
vprintf_like_t original_vprintf = NULL;
|
||||
|
||||
// --- Listener Test Helper Functions ---
|
||||
|
||||
// Clear the memory from possible garbage values
|
||||
void reset_listener_info(listener_callback_info_t* info) {
|
||||
memset(info, 0, sizeof(listener_callback_info_t));
|
||||
}
|
||||
|
||||
void test_listener_1(uint8_t chn_id, relay_chn_state_t old_state, relay_chn_state_t new_state) {
|
||||
listener1_info.chn_id = chn_id;
|
||||
listener1_info.old_state = old_state;
|
||||
listener1_info.new_state = new_state;
|
||||
listener1_info.call_count++;
|
||||
}
|
||||
|
||||
void test_listener_2(uint8_t chn_id, relay_chn_state_t old_state, relay_chn_state_t new_state) {
|
||||
listener2_info.chn_id = chn_id;
|
||||
listener2_info.old_state = old_state;
|
||||
listener2_info.new_state = new_state;
|
||||
listener2_info.call_count++;
|
||||
}
|
||||
|
||||
void blocking_listener(uint8_t chn_id, relay_chn_state_t old_state, relay_chn_state_t new_state) {
|
||||
blocking_listener_call_count++;
|
||||
// Block until the main test task unblocks us
|
||||
xSemaphoreTake(blocking_listener_sem, portMAX_DELAY);
|
||||
}
|
||||
|
||||
int log_check_vprintf(const char *format, va_list args) {
|
||||
// Buffer to hold the formatted log message
|
||||
char buffer[256];
|
||||
vsnprintf(buffer, sizeof(buffer), format, args);
|
||||
|
||||
if (strstr(buffer, "Notify queue is full")) {
|
||||
xSemaphoreGive(log_check_sem);
|
||||
}
|
||||
|
||||
return original_vprintf(format, args);
|
||||
}
|
||||
48
test_apps/main/test_relay_chn_notify_common.h
Normal file
48
test_apps/main/test_relay_chn_notify_common.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include "test_common.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include <stdarg.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// This is defined in the source file, we redefine it here for the test.
|
||||
// The test build must have the same CONFIG_RELAY_CHN_COUNT.
|
||||
#define TEST_RELAY_CHN_NOTIFY_QUEUE_LEN (16 + CONFIG_RELAY_CHN_COUNT * 4)
|
||||
|
||||
// --- Listener Test Globals ---
|
||||
typedef struct {
|
||||
uint8_t chn_id;
|
||||
relay_chn_state_t old_state;
|
||||
relay_chn_state_t new_state;
|
||||
int call_count;
|
||||
} listener_callback_info_t;
|
||||
|
||||
// --- Listener callback infos to be defined ---
|
||||
extern listener_callback_info_t listener1_info;
|
||||
extern listener_callback_info_t listener2_info;
|
||||
|
||||
// --- Globals for Advanced Tests ---
|
||||
extern SemaphoreHandle_t blocking_listener_sem;
|
||||
extern SemaphoreHandle_t log_check_sem;
|
||||
extern volatile int blocking_listener_call_count;
|
||||
extern vprintf_like_t original_vprintf;
|
||||
|
||||
|
||||
// --- Listener Test Helper Functions ---
|
||||
|
||||
// Clear the memory from possible garbage values
|
||||
void reset_listener_info(listener_callback_info_t* info);
|
||||
|
||||
// State listeners for notify module testing
|
||||
void test_listener_1(uint8_t chn_id, relay_chn_state_t old_state, relay_chn_state_t new_state);
|
||||
void test_listener_2(uint8_t chn_id, relay_chn_state_t old_state, relay_chn_state_t new_state);
|
||||
void blocking_listener(uint8_t chn_id, relay_chn_state_t old_state, relay_chn_state_t new_state);
|
||||
|
||||
int log_check_vprintf(const char *format, va_list args);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
166
test_apps/main/test_relay_chn_notify_multi.c
Normal file
166
test_apps/main/test_relay_chn_notify_multi.c
Normal file
@@ -0,0 +1,166 @@
|
||||
#include "test_relay_chn_notify_common.h"
|
||||
|
||||
// This is a private header, but we need it for direct notification calls and queue length.
|
||||
// It's included conditionally in the build via CMakeLists.txt when NVS is enabled.
|
||||
#include "relay_chn_notify.h"
|
||||
|
||||
// ### Listener Functionality Tests
|
||||
|
||||
TEST_CASE("Listener is called on state change for each channel", "[relay_chn][notify]")
|
||||
{
|
||||
// 1. Register the listener and reset info
|
||||
reset_listener_info(&listener1_info);
|
||||
TEST_ESP_OK(relay_chn_register_listener(test_listener_1));
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow registration to be processed
|
||||
|
||||
// Loop through each channel
|
||||
for (uint8_t ch = 0; ch < CONFIG_RELAY_CHN_COUNT; ch++) {
|
||||
// 2. Trigger a state change on the current channel.
|
||||
// tearDown() ensures each channel starts as IDLE.
|
||||
relay_chn_run_forward(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow event to be processed
|
||||
|
||||
// 3. Verify the listener was called with correct parameters for this channel.
|
||||
// The listener_info struct is overwritten each time, but we check it before the next iteration.
|
||||
TEST_ASSERT_EQUAL(ch, listener1_info.chn_id);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, listener1_info.old_state);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, listener1_info.new_state);
|
||||
}
|
||||
|
||||
// 4. Verify the total call count after the loop
|
||||
TEST_ASSERT_EQUAL(CONFIG_RELAY_CHN_COUNT, listener1_info.call_count);
|
||||
|
||||
// 5. Unregister to clean up
|
||||
relay_chn_unregister_listener(test_listener_1);
|
||||
}
|
||||
|
||||
TEST_CASE("Unregistered listener is not called for any channel", "[relay_chn][notify]")
|
||||
{
|
||||
reset_listener_info(&listener1_info);
|
||||
|
||||
// 1. Register and then immediately unregister the listener
|
||||
TEST_ESP_OK(relay_chn_register_listener(test_listener_1));
|
||||
relay_chn_unregister_listener(test_listener_1);
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow commands to process
|
||||
|
||||
// 2. Trigger a state change on all channels
|
||||
relay_chn_run_forward_all();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS * CONFIG_RELAY_CHN_COUNT)); // Allow all events to be processed
|
||||
|
||||
// 3. Verify the listener was NOT called
|
||||
TEST_ASSERT_EQUAL(0, listener1_info.call_count);
|
||||
}
|
||||
|
||||
TEST_CASE("Multiple listeners are called on state change for each channel", "[relay_chn][notify]")
|
||||
{
|
||||
// 1. Register listeners and reset info
|
||||
reset_listener_info(&listener1_info);
|
||||
reset_listener_info(&listener2_info);
|
||||
TEST_ESP_OK(relay_chn_register_listener(test_listener_1));
|
||||
TEST_ESP_OK(relay_chn_register_listener(test_listener_2));
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow registration commands to be processed
|
||||
|
||||
// Loop through each channel
|
||||
for (uint8_t ch = 0; ch < CONFIG_RELAY_CHN_COUNT; ch++) {
|
||||
// 2. Trigger a state change on the current channel
|
||||
relay_chn_run_forward(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
|
||||
// 3. Verify listener 1 was called correctly for this channel
|
||||
TEST_ASSERT_EQUAL(ch, listener1_info.chn_id);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, listener1_info.old_state);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, listener1_info.new_state);
|
||||
|
||||
// 4. Verify listener 2 was also called correctly for this channel
|
||||
TEST_ASSERT_EQUAL(ch, listener2_info.chn_id);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, listener2_info.old_state);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, listener2_info.new_state);
|
||||
}
|
||||
|
||||
// 5. Verify total call counts
|
||||
TEST_ASSERT_EQUAL(CONFIG_RELAY_CHN_COUNT, listener1_info.call_count);
|
||||
TEST_ASSERT_EQUAL(CONFIG_RELAY_CHN_COUNT, listener2_info.call_count);
|
||||
|
||||
// 6. Clean up
|
||||
relay_chn_unregister_listener(test_listener_1);
|
||||
relay_chn_unregister_listener(test_listener_2);
|
||||
}
|
||||
|
||||
TEST_CASE("Listener registration handles invalid arguments and duplicates", "[relay_chn][notify]")
|
||||
{
|
||||
reset_listener_info(&listener1_info);
|
||||
|
||||
// 1. Registering a NULL listener should fail
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_register_listener(NULL));
|
||||
|
||||
// 2. Unregistering a NULL listener should not crash
|
||||
relay_chn_unregister_listener(NULL);
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow commands to process
|
||||
|
||||
// 3. Registering the same listener twice should be handled gracefully
|
||||
TEST_ESP_OK(relay_chn_register_listener(test_listener_1));
|
||||
TEST_ESP_OK(relay_chn_register_listener(test_listener_1)); // Second call should be a no-op
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow registration commands to be processed
|
||||
|
||||
// 4. Trigger a state change on all channels and verify the listener is only called ONCE per channel
|
||||
relay_chn_run_forward_all();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS * CONFIG_RELAY_CHN_COUNT)); // Allow all events to be processed
|
||||
TEST_ASSERT_EQUAL(CONFIG_RELAY_CHN_COUNT, listener1_info.call_count);
|
||||
|
||||
// 5. Clean up
|
||||
relay_chn_unregister_listener(test_listener_1);
|
||||
}
|
||||
|
||||
TEST_CASE("Notify queue full scenario is handled gracefully", "[relay_chn][notify]")
|
||||
{
|
||||
// 1. Setup
|
||||
blocking_listener_sem = xSemaphoreCreateBinary();
|
||||
log_check_sem = xSemaphoreCreateBinary();
|
||||
blocking_listener_call_count = 0;
|
||||
|
||||
// Intercept logs to check for the "queue full" warning
|
||||
original_vprintf = esp_log_set_vprintf(log_check_vprintf);
|
||||
|
||||
// 2. Register a listener that will block, allowing the queue to fill up
|
||||
TEST_ESP_OK(relay_chn_register_listener(blocking_listener));
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow task to start
|
||||
|
||||
// 3. Fill the queue. The first event will be consumed immediately by the dispatcher,
|
||||
// which will then call the blocking_listener and block. The remaining (LEN - 1)
|
||||
// events will sit in the queue, leaving one empty slot.
|
||||
// Use different channel IDs to make the test more robust.
|
||||
for (int i = 0; i < TEST_RELAY_CHN_NOTIFY_QUEUE_LEN; i++) {
|
||||
uint8_t ch = i % CONFIG_RELAY_CHN_COUNT;
|
||||
TEST_ESP_OK(relay_chn_notify_state_change(ch, RELAY_CHN_STATE_IDLE, RELAY_CHN_STATE_FORWARD));
|
||||
}
|
||||
|
||||
// 4. Send one more event to fill the last slot in the queue. This should succeed.
|
||||
TEST_ESP_OK(relay_chn_notify_state_change(0, RELAY_CHN_STATE_IDLE, RELAY_CHN_STATE_FORWARD)); // Use any valid channel
|
||||
|
||||
// 5. Now the queue is full. Trigger one more event to cause an overflow.
|
||||
// This call should fail and log the warning.
|
||||
TEST_ASSERT_EQUAL(ESP_FAIL, relay_chn_notify_state_change(1 % CONFIG_RELAY_CHN_COUNT, RELAY_CHN_STATE_IDLE, RELAY_CHN_STATE_FORWARD));
|
||||
|
||||
// 6. Wait for the "queue full" log message to be captured by our vprintf hook
|
||||
TEST_ASSERT_TRUE_MESSAGE(xSemaphoreTake(log_check_sem, pdMS_TO_TICKS(1000)) == pdTRUE, "Did not receive 'queue full' log message");
|
||||
|
||||
// 7. Unblock the listener so it can process all queued items.
|
||||
// There was 1 initial event + QUEUE_LEN events that were successfully queued.
|
||||
for (int i = 0; i < TEST_RELAY_CHN_NOTIFY_QUEUE_LEN + 1; i++) {
|
||||
xSemaphoreGive(blocking_listener_sem);
|
||||
// Give the dispatcher task a moment to process one item from the queue
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
|
||||
// 8. Verify the listener was called exactly QUEUE_LEN + 1 times
|
||||
TEST_ASSERT_EQUAL_INT(TEST_RELAY_CHN_NOTIFY_QUEUE_LEN + 1, blocking_listener_call_count);
|
||||
|
||||
// 9. Cleanup
|
||||
esp_log_set_vprintf(original_vprintf);
|
||||
relay_chn_unregister_listener(blocking_listener);
|
||||
vSemaphoreDelete(blocking_listener_sem);
|
||||
vSemaphoreDelete(log_check_sem);
|
||||
blocking_listener_sem = NULL;
|
||||
log_check_sem = NULL;
|
||||
original_vprintf = NULL;
|
||||
}
|
||||
@@ -1,40 +1,12 @@
|
||||
#include "test_common.h"
|
||||
#include "test_relay_chn_notify_common.h"
|
||||
|
||||
|
||||
// --- Listener Test Globals ---
|
||||
typedef struct {
|
||||
relay_chn_state_t old_state;
|
||||
relay_chn_state_t new_state;
|
||||
int call_count;
|
||||
} listener_callback_info_t;
|
||||
|
||||
static listener_callback_info_t listener1_info;
|
||||
static listener_callback_info_t listener2_info;
|
||||
|
||||
// --- Listener Test Helper Functions ---
|
||||
|
||||
// Clear the memory from possible garbage values
|
||||
static void reset_listener_info(listener_callback_info_t* info) {
|
||||
memset(info, 0, sizeof(listener_callback_info_t));
|
||||
}
|
||||
|
||||
static void test_listener_1(uint8_t chn_id, relay_chn_state_t old_state, relay_chn_state_t new_state) {
|
||||
/* Just ignore the channel id */
|
||||
listener1_info.old_state = old_state;
|
||||
listener1_info.new_state = new_state;
|
||||
listener1_info.call_count++;
|
||||
}
|
||||
|
||||
static void test_listener_2(uint8_t chn_id, relay_chn_state_t old_state, relay_chn_state_t new_state) {
|
||||
/* Just ignore the channel id */
|
||||
listener2_info.old_state = old_state;
|
||||
listener2_info.new_state = new_state;
|
||||
listener2_info.call_count++;
|
||||
}
|
||||
// This is a private header, but we need it for direct notification calls and queue length.
|
||||
// It's included conditionally in the build via CMakeLists.txt when NVS is enabled.
|
||||
#include "relay_chn_notify.h"
|
||||
|
||||
// ### Listener Functionality Tests
|
||||
|
||||
TEST_CASE("Listener is called on state change", "[relay_chn][listener]")
|
||||
TEST_CASE("Listener is called on state change", "[relay_chn][notify]")
|
||||
{
|
||||
reset_listener_info(&listener1_info);
|
||||
|
||||
@@ -54,7 +26,7 @@ TEST_CASE("Listener is called on state change", "[relay_chn][listener]")
|
||||
relay_chn_unregister_listener(test_listener_1);
|
||||
}
|
||||
|
||||
TEST_CASE("Unregistered listener is not called", "[relay_chn][listener]")
|
||||
TEST_CASE("Unregistered listener is not called", "[relay_chn][notify]")
|
||||
{
|
||||
reset_listener_info(&listener1_info);
|
||||
|
||||
@@ -70,7 +42,7 @@ TEST_CASE("Unregistered listener is not called", "[relay_chn][listener]")
|
||||
TEST_ASSERT_EQUAL(0, listener1_info.call_count);
|
||||
}
|
||||
|
||||
TEST_CASE("Multiple listeners are called on state change", "[relay_chn][listener]")
|
||||
TEST_CASE("Multiple listeners are called on state change", "[relay_chn][notify]")
|
||||
{
|
||||
reset_listener_info(&listener1_info);
|
||||
reset_listener_info(&listener2_info);
|
||||
@@ -98,7 +70,7 @@ TEST_CASE("Multiple listeners are called on state change", "[relay_chn][listener
|
||||
relay_chn_unregister_listener(test_listener_2);
|
||||
}
|
||||
|
||||
TEST_CASE("Listener registration handles invalid arguments and duplicates", "[relay_chn][listener]")
|
||||
TEST_CASE("Listener registration handles invalid arguments and duplicates", "[relay_chn][notify]")
|
||||
{
|
||||
reset_listener_info(&listener1_info);
|
||||
|
||||
@@ -120,3 +92,55 @@ TEST_CASE("Listener registration handles invalid arguments and duplicates", "[re
|
||||
// 5. Clean up
|
||||
relay_chn_unregister_listener(test_listener_1);
|
||||
}
|
||||
|
||||
TEST_CASE("Notify queue full scenario is handled gracefully", "[relay_chn][notify]")
|
||||
{
|
||||
// 1. Setup
|
||||
blocking_listener_sem = xSemaphoreCreateBinary();
|
||||
log_check_sem = xSemaphoreCreateBinary();
|
||||
blocking_listener_call_count = 0;
|
||||
|
||||
// Intercept logs to check for the "queue full" warning
|
||||
original_vprintf = esp_log_set_vprintf(log_check_vprintf);
|
||||
|
||||
// 2. Register a listener that will block, allowing the queue to fill up
|
||||
TEST_ESP_OK(relay_chn_register_listener(blocking_listener));
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow task to start
|
||||
|
||||
// 3. Fill the queue. The first event will be consumed immediately by the dispatcher,
|
||||
// which will then call the blocking_listener and block. The remaining (LEN - 1)
|
||||
// events will sit in the queue, leaving one empty slot.
|
||||
for (int i = 0; i < TEST_RELAY_CHN_NOTIFY_QUEUE_LEN; i++) {
|
||||
TEST_ESP_OK(relay_chn_notify_state_change(0, RELAY_CHN_STATE_IDLE, RELAY_CHN_STATE_FORWARD));
|
||||
}
|
||||
|
||||
// 4. Send one more event to fill the last slot in the queue. This should succeed.
|
||||
TEST_ESP_OK(relay_chn_notify_state_change(0, RELAY_CHN_STATE_IDLE, RELAY_CHN_STATE_FORWARD));
|
||||
|
||||
// 5. Now the queue is full. Trigger one more event to cause an overflow.
|
||||
// This call should fail and log the warning.
|
||||
TEST_ASSERT_EQUAL(ESP_FAIL, relay_chn_notify_state_change(0, RELAY_CHN_STATE_IDLE, RELAY_CHN_STATE_FORWARD));
|
||||
|
||||
// 6. Wait for the "queue full" log message to be captured by our vprintf hook
|
||||
TEST_ASSERT_TRUE_MESSAGE(xSemaphoreTake(log_check_sem, pdMS_TO_TICKS(1000)) == pdTRUE, "Did not receive 'queue full' log message");
|
||||
|
||||
// 7. Unblock the listener so it can process all queued items.
|
||||
// There was 1 initial event + QUEUE_LEN events that were successfully queued.
|
||||
for (int i = 0; i < TEST_RELAY_CHN_NOTIFY_QUEUE_LEN + 1; i++) {
|
||||
xSemaphoreGive(blocking_listener_sem);
|
||||
// Give the dispatcher task a moment to process one item from the queue
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
|
||||
// 8. Verify the listener was called exactly QUEUE_LEN + 1 times
|
||||
TEST_ASSERT_EQUAL_INT(TEST_RELAY_CHN_NOTIFY_QUEUE_LEN + 1, blocking_listener_call_count);
|
||||
|
||||
// 9. Cleanup
|
||||
esp_log_set_vprintf(original_vprintf);
|
||||
relay_chn_unregister_listener(blocking_listener);
|
||||
vSemaphoreDelete(blocking_listener_sem);
|
||||
vSemaphoreDelete(log_check_sem);
|
||||
blocking_listener_sem = NULL;
|
||||
log_check_sem = NULL;
|
||||
original_vprintf = NULL;
|
||||
}
|
||||
Reference in New Issue
Block a user