From 61f8ed440e9de79758cd9f58357faa9c19d924d1 Mon Sep 17 00:00:00 2001 From: ismail Date: Wed, 13 Aug 2025 17:53:27 +0300 Subject: [PATCH 01/69] Add single channel mode feature. The addition of a single-channel mode implied further modularisation of the component. This commit has broken the component down into the following modules to avoid a huge single source file and to make unit testing easier. The modules: - Separation of public and private code - *types and *defs - public relay_chn API - *adapter - *output - *run_info - *core - *ctl (control) - *tilt Closes #957. --- .vscode/settings.json | 21 +- include/relay_chn_adapter.h | 186 +++ include/relay_chn_defs.h | 24 + include/relay_chn_types.h | 58 + private_include/relay_chn_core.h | 114 ++ private_include/relay_chn_ctl.h | 56 + private_include/relay_chn_output.h | 113 ++ private_include/relay_chn_priv_types.h | 78 ++ private_include/relay_chn_run_info.h | 84 ++ private_include/relay_chn_tilt.h | 58 + src/relay_chn.c | 1470 ------------------------ src/relay_chn_core.c | 574 +++++++++ src/relay_chn_ctl_multi.c | 137 +++ src/relay_chn_ctl_single.c | 76 ++ src/relay_chn_output.c | 168 +++ src/relay_chn_run_info.c | 74 ++ src/relay_chn_tilt.c | 634 ++++++++++ 17 files changed, 2453 insertions(+), 1472 deletions(-) create mode 100644 include/relay_chn_adapter.h create mode 100644 include/relay_chn_defs.h create mode 100644 include/relay_chn_types.h create mode 100644 private_include/relay_chn_core.h create mode 100644 private_include/relay_chn_ctl.h create mode 100644 private_include/relay_chn_output.h create mode 100644 private_include/relay_chn_priv_types.h create mode 100644 private_include/relay_chn_run_info.h create mode 100644 private_include/relay_chn_tilt.h delete mode 100644 src/relay_chn.c create mode 100644 src/relay_chn_core.c create mode 100644 src/relay_chn_ctl_multi.c create mode 100644 src/relay_chn_ctl_single.c create mode 100644 src/relay_chn_output.c create mode 100644 src/relay_chn_run_info.c create mode 100644 src/relay_chn_tilt.c diff --git a/.vscode/settings.json b/.vscode/settings.json index 4720ba3..f19cc63 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,23 @@ { "files.associations": { - "relay_chn.h": "c" + "relay_chn.h": "c", + "stdlib.h": "c", + "cstdint": "c", + "relay_chn_run_info.h": "c", + "esp_err.h": "c", + "relay_chn_output.h": "c", + "relay_chn_core.h": "c", + "relay_chn_ctl.h": "c", + "relay_chn_tilt.h": "c", + "relay_chn_defs.h": "c", + "esp_check.h": "c", + "esp_event_base.h": "c", + "esp_event.h": "c", + "queue.h": "c", + "relay_chn_priv_types.h": "c", + "relay_chn_adapter.h": "c", + "relay_chn_types.h": "c" }, - "idf.port": "/dev/ttyUSB0" + "idf.port": "/dev/ttyUSB0", + "idf.pythonInstallPath": "/usr/bin/python" } \ No newline at end of file diff --git a/include/relay_chn_adapter.h b/include/relay_chn_adapter.h new file mode 100644 index 0000000..dd66907 --- /dev/null +++ b/include/relay_chn_adapter.h @@ -0,0 +1,186 @@ +/* + * SPDX-FileCopyrightText: 2025 Kozmotronik Tech + * + * SPDX-License-Identifier: MIT + * + * An adapter header to expose the appropriate API functions to the public API + * depending on the RELAY_CHN_COUNT value which determines single or multi mode. + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#if RELAY_CHN_COUNT > 1 +/** + * @brief Get the current state of a relay channel. + * + * @param[in] chn_id Channel ID to get state for. + * @return Current state of the specified channel, or RELAY_CHN_STATE_UNDEFINED if invalid. + */ +extern relay_chn_state_t relay_chn_ctl_get_state(uint8_t chn_id); + +/** + * @brief Get string representation of a relay channel's state. + * + * @param[in] chn_id Channel ID to get state string for. + * @return String representation of channel state, or "UNDEFINED" if invalid. + */ +extern char *relay_chn_ctl_get_state_str(uint8_t chn_id); + +/** + * @brief Run a relay channel in forward direction. + * + * @param[in] chn_id Channel ID to run forward, or RELAY_CHN_ID_ALL for all channels. + */ +extern void relay_chn_ctl_run_forward(uint8_t chn_id); + +/** + * @brief Run a relay channel in reverse direction. + * + * @param[in] chn_id Channel ID to run reverse, or RELAY_CHN_ID_ALL for all channels. + */ +extern void relay_chn_ctl_run_reverse(uint8_t chn_id); + +/** + * @brief Stop a relay channel. + * + * @param[in] chn_id Channel ID to stop, or RELAY_CHN_ID_ALL for all channels. + */ +extern void relay_chn_ctl_stop(uint8_t chn_id); + +/** + * @brief Flip the running direction of a relay channel. + * + * @param[in] chn_id Channel ID to flip direction for, or RELAY_CHN_ID_ALL for all channels. + */ +extern void relay_chn_ctl_flip_direction(uint8_t chn_id); + +/** + * @brief Get the current direction of a relay channel. + * + * @param[in] chn_id Channel ID to get direction for. + * @return Current direction of the specified channel, or RELAY_CHN_DIRECTION_DEFAULT if invalid. + */ +extern relay_chn_direction_t relay_chn_ctl_get_direction(uint8_t chn_id); + +static inline relay_chn_state_t relay_chn_get_state(uint8_t chn_id) +{ + return relay_chn_ctl_get_state(chn_id); +} + +static inline char *relay_chn_get_state_str(uint8_t chn_id) +{ + return relay_chn_ctl_get_state_str(chn_id); +} + +static inline void relay_chn_run_forward(uint8_t chn_id) +{ + relay_chn_ctl_run_forward(chn_id); +} + +static inline void relay_chn_run_reverse(uint8_t chn_id) +{ + relay_chn_ctl_run_reverse(chn_id); +} + +static inline void relay_chn_stop(uint8_t chn_id) +{ + relay_chn_ctl_stop(chn_id); +} + +static inline void relay_chn_flip_direction(uint8_t chn_id) +{ + relay_chn_ctl_flip_direction(chn_id); +} + +static inline relay_chn_direction_t relay_chn_get_direction(uint8_t chn_id) +{ + return relay_chn_ctl_get_direction(chn_id); +} + +#else + +/** + * @brief Get the current state of the relay channel. + * + * @return Current state of the channel. + */ +extern relay_chn_state_t relay_chn_ctl_get_state(void); + +/** + * @brief Get string representation of the relay channel's state. + * + * @return String representation of channel state. + */ +extern char *relay_chn_ctl_get_state_str(void); + +/** + * @brief Run the relay channel in forward direction. + */ +extern void relay_chn_ctl_run_forward(void); + +/** + * @brief Run the relay channel in reverse direction. + */ +extern void relay_chn_ctl_run_reverse(void); + +/** + * @brief Stop the relay channel. + */ +extern void relay_chn_ctl_stop(void); + +/** + * @brief Flip the running direction of the relay channel. + */ +extern void relay_chn_ctl_flip_direction(void); + +/** + * @brief Get the current direction of the relay channel. + * + * @return Current direction of the channel. + */ +extern relay_chn_direction_t relay_chn_ctl_get_direction(void); + +static inline relay_chn_state_t relay_chn_get_state(void) +{ + return relay_chn_ctl_get_state(); +} + +static inline char *relay_chn_get_state_str(void) +{ + return relay_chn_ctl_get_state_str(); +} + +static inline void relay_chn_run_forward(void) +{ + relay_chn_ctl_run_forward(); +} + +static inline void relay_chn_run_reverse(void) +{ + relay_chn_ctl_run_reverse(); +} + +static inline void relay_chn_stop(void) +{ + relay_chn_ctl_stop(); +} + +static inline void relay_chn_flip_direction(void) +{ + relay_chn_ctl_flip_direction(); +} + +static inline relay_chn_direction_t relay_chn_get_direction(void) +{ + return relay_chn_ctl_get_direction(); +} + +#endif // RELAY_CHN_COUNT > 1 + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/include/relay_chn_defs.h b/include/relay_chn_defs.h new file mode 100644 index 0000000..0000b01 --- /dev/null +++ b/include/relay_chn_defs.h @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2025 Kozmotronik Tech + * + * SPDX-License-Identifier: MIT + */ + + #pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/* Config defines for covenient writing */ +#define RELAY_CHN_OPPOSITE_INERTIA_MS CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS +#define RELAY_CHN_COUNT CONFIG_RELAY_CHN_COUNT +#define RELAY_CHN_ENABLE_TILTING CONFIG_RELAY_CHN_ENABLE_TILTING + +#if RELAY_CHN_COUNT > 1 +#define RELAY_CHN_ID_ALL RELAY_CHN_COUNT /*!< Special ID to address all channels */ +#endif + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/include/relay_chn_types.h b/include/relay_chn_types.h new file mode 100644 index 0000000..90db4ff --- /dev/null +++ b/include/relay_chn_types.h @@ -0,0 +1,58 @@ +/* + * SPDX-FileCopyrightText: 2025 Kozmotronik Tech + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include "relay_chn_defs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Enumeration for relay channel direction. + */ +typedef enum relay_chn_direction_enum { + RELAY_CHN_DIRECTION_DEFAULT, /*!< Default direction of the relay channel */ + RELAY_CHN_DIRECTION_FLIPPED /*!< Flipped direction of the relay channel */ +} relay_chn_direction_t; + + +/** + * @brief Enums that represent the state of a relay channel. + */ +typedef enum relay_chn_state_enum { + RELAY_CHN_STATE_UNDEFINED, /*!< The relay channel state is undefined */ + RELAY_CHN_STATE_IDLE, /*!< The relay channel is free to run or execute commands */ + RELAY_CHN_STATE_STOPPED, /*!< The relay channel is stopped and not running */ + RELAY_CHN_STATE_FORWARD, /*!< The relay channel is running in the forward direction */ + RELAY_CHN_STATE_REVERSE, /*!< The relay channel is running in the reverse direction */ + RELAY_CHN_STATE_FORWARD_PENDING, /*!< The relay channel is pending to run in the forward direction */ + RELAY_CHN_STATE_REVERSE_PENDING, /*!< The relay channel is pending to run in the reverse direction */ +#if CONFIG_RELAY_CHN_ENABLE_TILTING == 1 + RELAY_CHN_STATE_TILT_FORWARD, /*!< The relay channel is tilting for forward */ + RELAY_CHN_STATE_TILT_REVERSE, /*!< The relay channel is tilting for reverse */ +#endif +} relay_chn_state_t; + +/** + * @brief Relay channel state change listener. + * + * An optional interface to listen to the channel state change events. + * The listeners SHOULD be implemented as light functions and SHOULD NOT contain + * any blocking calls. Otherwise the relay_chn module would not function properly + * since it is designed as event driven. + * + * @param chn_id The ID of the channel whose state has changed. + * @param old_state The old state of the channel. + * @param new_state The new state of the channel. + */ +typedef void (*relay_chn_state_listener_t)(uint8_t chn_id, relay_chn_state_t old_state, relay_chn_state_t new_state); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/private_include/relay_chn_core.h b/private_include/relay_chn_core.h new file mode 100644 index 0000000..d132e99 --- /dev/null +++ b/private_include/relay_chn_core.h @@ -0,0 +1,114 @@ +/* + * SPDX-FileCopyrightText: 2025 Kozmotronik Tech + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include "esp_err.h" +#include "esp_log.h" +#include "esp_event_base.h" +#include "esp_event.h" +#include "esp_timer.h" +#include "relay_chn_defs.h" +#include "relay_chn_types.h" +#include "relay_chn_priv_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/// Event base used by *_core, *_ctl, and *_tilt modules. +ESP_EVENT_DECLARE_BASE(RELAY_CHN_CMD_EVENT); + +/** + * @brief Initializes the relay channel timer. + * + * This function creates a timer for the relay channel to handle direction change inertia. + * Required by *_ctl_* module. + * + * @param chn_ctl Pointer to the relay channel control structure. + * @return esp_err_t ESP_OK on success, or an error code on failure. + */ +esp_err_t relay_chn_init_timer(relay_chn_ctl_t *chn_ctl); + +/** + * @brief Issues a command to the relay channel. + * + * Evaluates the current state of the relay channel and issues the command accordingly. + * Required by *_core, *_ctl_* and *_tilt modules. + * + * @param chn_ctl Pointer to the relay channel control structure. + * @param cmd The command to issue. + */ +void relay_chn_issue_cmd(relay_chn_ctl_t* chn_ctl, relay_chn_cmd_t cmd); + +/** + * @brief Dispatches a relay channel command to the event loop. + * + * @param chn_ctl Pointer to the relay channel control structure. + * @param cmd The command to dispatch. + */ +void relay_chn_dispatch_cmd(relay_chn_ctl_t *chn_ctl, relay_chn_cmd_t cmd); + +/** + * @brief Returns the string representation of a relay channel command. + * + * @param cmd The relay channel command. + * @return char* The string representation of the command. + */ +char *relay_chn_cmd_str(relay_chn_cmd_t cmd); + +/** + * @brief Starts the ESP timer once with the specified time in milliseconds. + * + * Starts the ESP timer to run once after the specified time. + * If the timer is already running, it stops it first and then starts it again. + * Required by *_ctl_* and *_tilt modules. + * + * @param esp_timer The ESP timer handle. + * @param time_ms The time in milliseconds to wait before the timer expires. + * @return esp_err_t ESP_OK on success, or an error code on failure. + */ +esp_err_t relay_chn_start_esp_timer_once(esp_timer_handle_t esp_timer, uint32_t time_ms); + +/** + * @brief Updates the state of the relay channel and notifies listeners. + * + * This function updates the state of the relay channel and notifies all registered listeners + * about the state change. + * Required by *_ctl_* and *_tilt modules. + * + * @param chn_ctl Pointer to the relay channel control structure. + * @param new_state The new state to set for the relay channel. + */ +void relay_chn_update_state(relay_chn_ctl_t *chn_ctl, relay_chn_state_t new_state); + +/** + * @brief Return the text presentation of an state. + * + * @param state A state with type of relay_chn_state_t. + * @return char* The text presentation of the state. "UNKNOWN" if the state is not known. + */ +char *relay_chn_state_str(relay_chn_state_t state); + +#if RELAY_CHN_COUNT > 1 +/** + * @brief Check if the provided channel ID is valid. + * + * @param chn_id Channel ID to check. + * @return true Channel ID is valid. + * @return false Channel ID is invalid. + */ +bool relay_chn_is_channel_id_valid(uint8_t chn_id); +#endif // RELAY_CHN_COUNT > 1 + +#if RELAY_CHN_ENABLE_TILTING == 1 +/// Relay channel event loop handle declaration for *_tilt module. +extern esp_event_loop_handle_t relay_chn_event_loop; +#endif // RELAY_CHN_ENABLE_TILTING + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/private_include/relay_chn_ctl.h b/private_include/relay_chn_ctl.h new file mode 100644 index 0000000..8f89a11 --- /dev/null +++ b/private_include/relay_chn_ctl.h @@ -0,0 +1,56 @@ +/* + * SPDX-FileCopyrightText: 2025 Kozmotronik Tech developer@kozmotronik.com.tr + * + * SPDX-License-Identifier: MIT + * + * Expose the *_ctl functions required by *_core.c file. + */ + +#pragma once + +#include "relay_chn_priv_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Initialize the relay channel control. + * + * @param output Pointer to the output object(s). + * @param run_info Pointer to the runtime information object(s). + * + * @return esp_err_t Returns ESP_OK on success, or an error code on failure. + */ +esp_err_t relay_chn_ctl_init(relay_chn_output_t *output, relay_chn_run_info_t *run_info); + +/** + * @brief Deinitialize the relay channel control. + * + * This function cleans up resources used by the relay channel control. + */ +void relay_chn_ctl_deinit(void); + +#if RELAY_CHN_COUNT > 1 +/** + * @brief Get the control structure for a specific relay channel. + * + * @param chn_id The ID of the relay channel to retrieve. + * + * @return relay_chn_ctl_t* Pointer to the control structure for the specified channel, or NULL if not found. + */ +relay_chn_ctl_t *relay_chn_ctl_get(uint8_t chn_id); + +/** + * @brief Get the control structures for all relay channels. + * + * @return relay_chn_ctl_t* Pointer to the array of control structures for all channels. + */ +relay_chn_ctl_t *relay_chn_ctl_get_all(void); +#else +relay_chn_ctl_t *relay_chn_ctl_get(void); +#endif // RELAY_CHN_COUNT > 1 + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/private_include/relay_chn_output.h b/private_include/relay_chn_output.h new file mode 100644 index 0000000..27a8de9 --- /dev/null +++ b/private_include/relay_chn_output.h @@ -0,0 +1,113 @@ +/* + * SPDX-FileCopyrightText: 2025 Kozmotronik Tech + * + * SPDX-License-Identifier: MIT + * + * Abstraction layer for controlling relay channel outputs. This is the layer + * that interacts with the GPIO pins to control the relay channels. + */ + +#pragma once + +#include +#include "esp_err.h" +#include "relay_chn_priv_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Initialize relay channel outputs. + * + * Maps relay channels to GPIO pins and prepares them for operation. + * + * @param[in] gpio_map Array of GPIO pin numbers for each relay channel. + * @param[in] gpio_count Number of GPIO pins (relay channels). + * + * @return ESP_OK on success, error code otherwise. + */ +esp_err_t relay_chn_output_init(const uint8_t* gpio_map, uint8_t gpio_count); + +/** + * @brief Deinitialize relay channel outputs. + * + * Releases resources and resets GPIO pins used for relay channels. + */ +void relay_chn_output_deinit(void); + +#if RELAY_CHN_COUNT > 1 +/** + * @brief Get the relay channel output object for a specific channel. + * + * @param[in] chn_id Channel ID. + * + * @return Pointer to relay channel output object, or NULL if invalid. + */ +relay_chn_output_t *relay_chn_output_get(uint8_t chn_id); + +/** + * @brief Get all relay channel output objects. + * + * @return Pointer to array of relay channel output objects. + */ +relay_chn_output_t *relay_chn_output_get_all(void); +#else +/** + * @brief Get the relay channel output object. + * + * @return Pointer to relay channel output object. + */ +relay_chn_output_t *relay_chn_output_get(void); +#endif // RELAY_CHN_COUNT > 1 + +/** + * @brief Stop the relay channel output. + * + * Sets the relay channel to the stop state. + * + * @param[in] output Pointer to relay channel output object. + * + * @return ESP_OK on success, error code otherwise. + */ +esp_err_t relay_chn_output_stop(relay_chn_output_t *output); + +/** + * @brief Set relay channel output to forward direction. + * + * @param[in] output Pointer to relay channel output object. + * + * @return ESP_OK on success, error code otherwise. + */ +esp_err_t relay_chn_output_forward(relay_chn_output_t *output); + +/** + * @brief Set relay channel output to reverse direction. + * + * @param[in] output Pointer to relay channel output object. + * + * @return ESP_OK on success, error code otherwise. + */ +esp_err_t relay_chn_output_reverse(relay_chn_output_t *output); + +/** + * @brief Flip the direction of the relay channel output. + * + * Changes the direction from forward to reverse or vice versa. + * + * @param[in] output Pointer to relay channel output object. + */ +void relay_chn_output_flip(relay_chn_output_t *output); + +/** + * @brief Get the current direction of the relay channel output. + * + * @param[in] output Pointer to relay channel output object. + * + * @return Current direction of the relay channel. + */ +relay_chn_direction_t relay_chn_output_get_direction(relay_chn_output_t *output); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/private_include/relay_chn_priv_types.h b/private_include/relay_chn_priv_types.h new file mode 100644 index 0000000..0967d86 --- /dev/null +++ b/private_include/relay_chn_priv_types.h @@ -0,0 +1,78 @@ +/* + * SPDX-FileCopyrightText: 2025 Kozmotronik Tech + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include "driver/gpio.h" +#include "esp_timer.h" +#include "relay_chn_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Enumeration for relay channel commands. + */ +typedef enum { + RELAY_CHN_CMD_NONE, /*!< No command */ + RELAY_CHN_CMD_STOP, /*!< Stop the relay channel */ + RELAY_CHN_CMD_FORWARD, /*!< Run the relay channel in the forward direction */ + RELAY_CHN_CMD_REVERSE, /*!< Run the relay channel in the reverse direction */ + RELAY_CHN_CMD_FLIP, /*!< Flip the direction of the relay channel */ + RELAY_CHN_CMD_IDLE /*!< Free the relay channel */ +} relay_chn_cmd_t; + + +/** + * @brief Structure to hold the output configuration of a relay channel. + */ +typedef struct { + gpio_num_t forward_pin; /*!< GPIO pin number for the forward direction */ + gpio_num_t reverse_pin; /*!< GPIO pin number for the reverse direction */ + relay_chn_direction_t direction; /*!< The current direction of the relay channel */ +} relay_chn_output_t; + +/** + * @brief Structure to hold runtime information for a relay channel. + */ +typedef struct { + relay_chn_cmd_t last_run_cmd; /*!< The last run command issued on the relay channel; forward or reverse */ + uint32_t last_run_cmd_time_ms; /*!< The time in milliseconds when the last run command was issued */ +} relay_chn_run_info_t; + +#if RELAY_CHN_ENABLE_TILTING == 1 +/// @brief Tilt commands. +typedef enum { + RELAY_CHN_TILT_CMD_NONE, /*!< No command */ + RELAY_CHN_TILT_CMD_STOP, /*!< Tilt command stop */ + RELAY_CHN_TILT_CMD_FORWARD, /*!< Tilt command for forward */ + RELAY_CHN_TILT_CMD_REVERSE /*!< Tilt command for reverse */ +} relay_chn_tilt_cmd_t; + +/// Forward declaration for relay_chn_tilt_ctl +typedef struct relay_chn_tilt_ctl relay_chn_tilt_ctl_t; +#endif + +/** + * @brief Structure to hold the state and configuration of a relay channel. + */ +typedef struct { + uint8_t id; /*!< The ID of the relay channel */ + relay_chn_state_t state; /*!< The current state of the relay channel */ + relay_chn_run_info_t *run_info; /*!< Runtime information of the relay channel */ + relay_chn_output_t *output; /*!< Output configuration of the relay channel */ + relay_chn_cmd_t pending_cmd; /*!< The command that is pending to be issued */ + esp_timer_handle_t inertia_timer; /*!< Timer to handle the opposite direction inertia time */ +#if RELAY_CHN_ENABLE_TILTING == 1 + relay_chn_tilt_ctl_t *tilt_ctl; /*!< Pointer to the tilt control structure if tilting is enabled */ +#endif +} relay_chn_ctl_t; + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/private_include/relay_chn_run_info.h b/private_include/relay_chn_run_info.h new file mode 100644 index 0000000..0b14296 --- /dev/null +++ b/private_include/relay_chn_run_info.h @@ -0,0 +1,84 @@ +/* + * SPDX-FileCopyrightText: 2025 Kozmotronik Tech + * + * SPDX-License-Identifier: MIT + * + * This is for managing the run information of relay channels. + */ + +#pragma once + +#include "relay_chn_priv_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Initialize relay channel run information. + * + * Initializes the run information for all relay channels with default values. + */ +void relay_chn_run_info_init(void); + +#if RELAY_CHN_COUNT > 1 +/** + * @brief Get run information object for a specific relay channel. + * + * @param[in] chn_id Channel ID to get run information for. + * @return Pointer to run information structure, or NULL if channel ID is invalid. + */ +relay_chn_run_info_t *relay_chn_run_info_get(uint8_t chn_id); + +/** + * @brief Get run information objects for all relay channels. + * + * @return Pointer to array of run information structures. + */ +relay_chn_run_info_t *relay_chn_run_info_get_all(void); +#else +/** + * @brief Get run information object for the single relay channel. + * + * @return Pointer to run information structure. + */ +relay_chn_run_info_t *relay_chn_run_info_get(void); +#endif // RELAY_CHN_COUNT > 1 + +/** + * @brief Get the last run command for a relay channel. + * + * @param[in] run_info Pointer to run information structure. + * + * @return Last command that was executed, or RELAY_CHN_CMD_NONE if invalid. + */ +relay_chn_cmd_t relay_chn_run_info_get_last_run_cmd(relay_chn_run_info_t *run_info); + +/** + * @brief Set the last run command for a relay channel. + * + * @param[in] run_info Pointer to run information structure. + * @param[in] cmd Command to set as last run command. + */ +void relay_chn_run_info_set_last_run_cmd(relay_chn_run_info_t *run_info, relay_chn_cmd_t cmd); + +/** + * @brief Get the timestamp of the last run command. + * + * @param[in] run_info Pointer to run information structure. + * + * @return Timestamp in milliseconds of last command, or 0 if invalid. + */ +uint32_t relay_chn_run_info_get_last_run_cmd_time_ms(relay_chn_run_info_t *run_info); + +/** + * @brief Set the timestamp for the last run command. + * + * @param[in] run_info Pointer to run information structure. + * @param[in] time_ms Timestamp in milliseconds to set. + */ +void relay_chn_run_info_set_last_run_cmd_time_ms(relay_chn_run_info_t *run_info, uint32_t time_ms); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/private_include/relay_chn_tilt.h b/private_include/relay_chn_tilt.h new file mode 100644 index 0000000..ab1b5c9 --- /dev/null +++ b/private_include/relay_chn_tilt.h @@ -0,0 +1,58 @@ +/* + * SPDX-FileCopyrightText: 2025 Kozmotronik Tech + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include "relay_chn_priv_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Initialize relay channel tilt controls. + * + * Sets up tilt functionality for relay channels including timers and event handlers. + * Must be called before using any other tilt functions. + * + * @param[in] chn_ctls Array of relay channel control structures. + * + * @return ESP_OK on success, error code otherwise. + */ +esp_err_t relay_chn_tilt_init(relay_chn_ctl_t *chn_ctls); + +/** + * @brief Deinitialize relay channel tilt controls. + * + * Cleans up tilt resources including timers and event handlers. + * Should be called when tilt functionality is no longer needed. + */ +void relay_chn_tilt_deinit(void); + +/** + * @brief Dispatch a tilt command to a relay channel. + * + * Queues a tilt command for execution on the specified channel. + * + * @param[in] tilt_ctl Pointer to tilt control structure. + * @param[in] cmd Tilt command to execute. + * + * @return ESP_OK on success, error code otherwise. + */ +esp_err_t relay_chn_tilt_dispatch_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_tilt_cmd_t cmd); + +/** + * @brief Reset tilt counters for a relay channel. + * + * Resets both forward and reverse tilt counters to zero. + * + * @param[in] tilt_ctl Pointer to tilt control structure. + */ +void relay_chn_tilt_reset_count(relay_chn_tilt_ctl_t *tilt_ctl); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/src/relay_chn.c b/src/relay_chn.c deleted file mode 100644 index 069fe96..0000000 --- a/src/relay_chn.c +++ /dev/null @@ -1,1470 +0,0 @@ -/** - * @file relay_chn.c - * - * @author - * Ismail Sahillioglu - * - * @date 2025.02.08 - * - * @ingroup relay_chn - * - * @brief This file contains the implementation of the relay channel component. - * @{ - */ - -#include -#include -#include "esp_err.h" -#include "esp_check.h" -#include "esp_log.h" -#include "esp_task.h" -#include "driver/gpio.h" -#include "esp_timer.h" -#include "esp_event_base.h" -#include "esp_event.h" -#include "relay_chn.h" -#include "freertos/idf_additions.h" -#include "sdkconfig.h" - - -#define RELAY_CHN_OPPOSITE_INERTIA_MS CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS -#define RELAY_CHN_COUNT CONFIG_RELAY_CHN_COUNT -#define RELAY_CHN_ENABLE_TILTING CONFIG_RELAY_CHN_ENABLE_TILTING - -static const char *TAG = "relay_chn"; - -ESP_EVENT_DEFINE_BASE(RELAY_CHN_CMD_EVENT); - - -/** - * @brief Enumeration for relay channel commands. - */ -enum relay_chn_cmd_enum { - RELAY_CHN_CMD_NONE, ///< No command. - RELAY_CHN_CMD_STOP, ///< Stop the relay channel. - RELAY_CHN_CMD_FORWARD, ///< Run the relay channel in the forward direction. - RELAY_CHN_CMD_REVERSE, ///< Run the relay channel in the reverse direction. - RELAY_CHN_CMD_FLIP, ///< Flip the direction of the relay channel. - RELAY_CHN_CMD_FREE ///< Free the relay channel. -}; - -/// @brief Alias for the enum type relay_chn_cmd_enum. -typedef enum relay_chn_cmd_enum relay_chn_cmd_t; - -/** - * @brief Structure to hold runtime information for a relay channel. - */ -typedef struct relay_chn_run_info_type { - relay_chn_cmd_t last_run_cmd; ///< The last run command issued on the relay channel; forward or reverse. - uint32_t last_run_cmd_time_ms; ///< The time in milliseconds when the last run command was issued. -} relay_chn_run_info_t; - -/** - * @brief Structure to hold the output configuration of a relay channel. - */ -typedef struct relay_chn_output_type { - gpio_num_t forward_pin; ///< GPIO pin number for the forward direction. - gpio_num_t reverse_pin; ///< GPIO pin number for the reverse direction. - relay_chn_direction_t direction; ///< The current direction of the relay channel. -} relay_chn_output_t; - -typedef struct relay_chn_type relay_chn_t; // Forward declaration - -/** - * @brief Function pointer type for relay channel command execution functions. - * @param relay_chn Pointer to the relay channel to execute the command on. - */ -typedef void(*relay_chn_cmd_fn_t)(relay_chn_t*); - -#if RELAY_CHN_ENABLE_TILTING == 0 - -/** - * @brief Structure to hold the state and configuration of a relay channel. - */ -typedef struct relay_chn_type { - uint8_t id; ///< The ID of the relay channel. - relay_chn_state_t state; ///< The current state of the relay channel. - relay_chn_run_info_t run_info; ///< Runtime information of the relay channel. - relay_chn_output_t output; ///< Output configuration of the relay channel. - relay_chn_cmd_t pending_cmd; ///< The command that is pending to be issued - esp_timer_handle_t inertia_timer; ///< Timer to handle the opposite direction inertia time. -} relay_chn_t; - -#else - -/** - * @name Tilt Pattern Timing Definitions - * @{ - * The min and max timing definitions as well as the default timing definitions. - * These definitions are used to define and adjust the tilt sensitivity. - */ - -#define RELAY_CHN_TILT_RUN_MIN_MS 50 -#define RELAY_CHN_TILT_RUN_MAX_MS 10 -#define RELAY_CHN_TILT_PAUSE_MIN_MS 450 -#define RELAY_CHN_TILT_PAUSE_MAX_MS 90 - -#define RELAY_CHN_TILT_DEFAULT_RUN_MS 15 -#define RELAY_CHN_TILT_DEFAULT_PAUSE_MS 150 - -#define RELAY_CHN_TILT_DEFAULT_SENSITIVITY \ - ( (RELAY_CHN_TILT_DEFAULT_RUN_MS - RELAY_CHN_TILT_RUN_MIN_MS) \ - * 100 / (RELAY_CHN_TILT_RUN_MAX_MS - RELAY_CHN_TILT_RUN_MIN_MS) ) -/// @} - - -ESP_EVENT_DEFINE_BASE(RELAY_CHN_TILT_CMD_EVENT_BASE); - -/// @brief Tilt commands. -enum relay_chn_tilt_cmd_enum { - RELAY_CHN_TILT_CMD_NONE, ///< No command. - RELAY_CHN_TILT_CMD_STOP, ///< Tilt command stop. - RELAY_CHN_TILT_CMD_FORWARD, ///< Tilt command for forward. - RELAY_CHN_TILT_CMD_REVERSE ///< Tilt command for reverse. -}; - -/// @brief Alias for the enum type relay_chn_tilt_cmd_enum. -typedef enum relay_chn_tilt_cmd_enum relay_chn_tilt_cmd_t; - -/// @brief Tilt steps. -enum relay_chn_tilt_step_enum { - RELAY_CHN_TILT_STEP_NONE, ///< No step. - RELAY_CHN_TILT_STEP_PENDING, ///< Pending step. - RELAY_CHN_TILT_STEP_MOVE, ///< Move step. Tilt is driving either for forward or reverse. - RELAY_CHN_TILT_STEP_PAUSE ///< Pause step. Tilt is paused. -}; - -/// @brief Alias for the enum relay_chn_tilt_step_enum. -typedef enum relay_chn_tilt_step_enum relay_chn_tilt_step_t; - -/// @brief Tilt timing structure to manage tilt pattern timing. -typedef struct relay_chn_tilt_timing_struct { - uint8_t sensitivity; ///< Tilt sensitivity in percentage (%). - uint32_t move_time_ms; ///< Move time in milliseconds. - uint32_t pause_time_ms; ///< Pause time in milliseconds. -} relay_chn_tilt_timing_t; - -/// @brief Tilt counter structure to manage tilt count. -typedef struct relay_chn_tilt_counter_struct { - uint32_t tilt_forward_count; ///< Tilt forward count. - uint32_t tilt_reverse_count; ///< Tilt reverse count. -} relay_chn_tilt_counter_t; - -/// @brief Tilt control structure to manage tilt operations. -typedef struct relay_chn_tilt_control_struct { - relay_chn_tilt_cmd_t cmd; ///< The tilt command in process. - relay_chn_tilt_step_t step; ///< Current tilt step. - relay_chn_tilt_timing_t tilt_timing; ///< Tilt timing structure. - relay_chn_tilt_counter_t tilt_counter; ///< Tilt counter structure. - esp_timer_handle_t tilt_timer; ///< Tilt timer handle. -} relay_chn_tilt_control_t; - -/** - * @brief Structure to hold the state and configuration of a relay channel. - */ -typedef struct relay_chn_type { - uint8_t id; ///< The ID of the relay channel. - relay_chn_state_t state; ///< The current state of the relay channel. - relay_chn_run_info_t run_info; ///< Runtime information of the relay channel. - relay_chn_output_t output; ///< Output configuration of the relay channel. - relay_chn_cmd_t pending_cmd; ///< The command that is pending to be issued - esp_timer_handle_t inertia_timer; ///< Timer to handle the opposite direction inertia time. - relay_chn_tilt_control_t tilt_control; ///< Tilt control block. -} relay_chn_t; - -static esp_err_t relay_chn_init_tilt_control(relay_chn_t *relay_chn); -static esp_err_t relay_chn_tilt_init(void); -static void relay_chn_tilt_count_reset(relay_chn_t *relay_chn); -static esp_err_t relay_chn_dispatch_tilt_cmd(relay_chn_t *relay_chn, relay_chn_tilt_cmd_t cmd); - -#endif // RELAY_CHN_ENABLE_TILTING - - -/** - * @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 The list that holds references to the registered listeners. - * - * Uses a FreeRTOS list for safe and dynamic management of listeners. - */ -static List_t relay_chn_listener_list; - -static relay_chn_t relay_channels[RELAY_CHN_COUNT]; -static esp_event_loop_handle_t relay_chn_event_loop; - - -// Private function declarations -// Event handler for the relay channel command event -static void relay_chn_event_handler(void* handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data); - -/** - * @brief Check if the provided channel ID is valid. - * - * @param chn_id Channel ID to check. - * @return true Channel ID is valid. - * @return false Channel ID is invalid. - */ -static bool relay_chn_is_channel_id_valid(uint8_t chn_id); - -/** - * @brief Dispatches a relay channel command to the event loop. - * - * @param relay_chn The relay channel. - * @param cmd The command to dispatch. - */ -static void relay_chn_dispatch_cmd(relay_chn_t *relay_chn, relay_chn_cmd_t cmd); - -/** - * @brief Returns the string representation of a relay channel command. - * - * @param cmd The relay channel command. - * @return char* The string representation of the command. - */ -static char *relay_chn_cmd_str(relay_chn_cmd_t cmd); - -/** - * @brief Timer callback function for relay channel direction change inertia. - * - * This function is called when the opposite direction inertia timer expires. It checks if the channel - * has a pending command and dispatches it if there is one. - * - * @param arg The channel ID of the relay channel. - */ -static void relay_chn_timer_cb(void* arg) -{ - uint8_t chn_id = *(uint8_t*) arg; - if (!relay_chn_is_channel_id_valid(chn_id)) { - ESP_LOGE(TAG, "relay_chn_timer_cb: Invalid relay channel ID!"); - return; - } - relay_chn_t* relay_chn = &relay_channels[chn_id]; - // Does channel have a pending command? - if (relay_chn->pending_cmd != RELAY_CHN_CMD_NONE) { - relay_chn_dispatch_cmd(relay_chn, relay_chn->pending_cmd); - relay_chn->pending_cmd = RELAY_CHN_CMD_NONE; - } - else { - ESP_LOGE(TAG, "relay_chn_timer_cb: No pending cmd for relay channel %d!", chn_id); - } -} - -static esp_err_t relay_chn_init_timer(relay_chn_t *relay_chn) -{ - char timer_name[32]; - snprintf(timer_name, sizeof(timer_name), "relay_chn_%d_timer", relay_chn->id); - esp_timer_create_args_t timer_args = { - .callback = relay_chn_timer_cb, - .arg = &relay_chn->id, - .name = timer_name - }; - return esp_timer_create(&timer_args, &relay_chn->inertia_timer); -} - -/** - * @brief Check if the provided GPIO pin number is valid for the current device. - * - * @param gpio The GPIO pin number to check. - * @return true GPIO pin number is valid. - * @return false GPIO pin number is invalid. - */ -static bool relay_chn_is_gpio_valid(gpio_num_t gpio) -{ - return gpio >= 0 && gpio < GPIO_PIN_COUNT; -} - -static esp_err_t relay_chn_create_event_loop() -{ - esp_event_loop_args_t loop_args = { - .queue_size = RELAY_CHN_COUNT * 8, - .task_name = "relay_chn_event_loop", - .task_priority = ESP_TASKD_EVENT_PRIO - 1, - .task_stack_size = 2048, - .task_core_id = tskNO_AFFINITY - }; - esp_err_t ret = esp_event_loop_create(&loop_args, &relay_chn_event_loop); - ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create event loop for relay channel"); - ret = esp_event_handler_register_with(relay_chn_event_loop, - RELAY_CHN_CMD_EVENT, - ESP_EVENT_ANY_ID, - relay_chn_event_handler, NULL); - return ret; -} - -esp_err_t relay_chn_create(const gpio_num_t* gpio_map, uint8_t gpio_count) -{ - ESP_RETURN_ON_FALSE(gpio_map, ESP_ERR_INVALID_ARG, TAG, "gpio_map cannot be NULL"); - - // Check if the device's GPIOs are enough for the number of channels - if (RELAY_CHN_COUNT > (GPIO_PIN_COUNT / 2)) { - ESP_LOGE(TAG, "Not enough GPIOs for the number of channels!"); - ESP_LOGE(TAG, "Max available num of channels: %d, requested channels: %d", GPIO_PIN_COUNT / 2, RELAY_CHN_COUNT); - return ESP_ERR_INVALID_ARG; - } - - // Check if the provided GPIOs correspond to the number of channels - if (gpio_count != RELAY_CHN_COUNT * 2) { - ESP_LOGE(TAG, "Invalid number of GPIOs provided: %d", gpio_count); - ESP_LOGE(TAG, "Expected number of GPIOs: %d", RELAY_CHN_COUNT * 2); - return ESP_ERR_INVALID_ARG; - } - - esp_err_t ret; - for (int i = 0; i < RELAY_CHN_COUNT; i++) { - int gpio_index = i << 1; // gpio_index = i * 2 - gpio_num_t forward_pin = gpio_map[gpio_index]; - gpio_num_t reverse_pin = gpio_map[gpio_index + 1]; - // Check if the GPIOs are valid - if (!relay_chn_is_gpio_valid(forward_pin)) { - ESP_LOGE(TAG, "Invalid GPIO pin number: %d", forward_pin); - return ESP_ERR_INVALID_ARG; - } - if (!relay_chn_is_gpio_valid(reverse_pin)) { - ESP_LOGE(TAG, "Invalid GPIO pin number: %d", reverse_pin); - return ESP_ERR_INVALID_ARG; - } - // Check if the GPIOs are valid - - // Initialize the GPIOs - ret = gpio_reset_pin(forward_pin); - ESP_RETURN_ON_ERROR(ret, TAG, "Failed to reset GPIO forward pin for channel %d", i); - ret = gpio_set_direction(forward_pin, GPIO_MODE_OUTPUT); - ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set GPIO direction for forward pin for channel %d", i); - - ret = gpio_reset_pin(reverse_pin); - ESP_RETURN_ON_ERROR(ret, TAG, "Failed to reset GPIO reverse pin for channel %d", i); - ret = gpio_set_direction(reverse_pin, GPIO_MODE_OUTPUT); - ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set GPIO direction for reverse pin for channel %d", i); - // Initialize the GPIOs - - // Initialize the relay channel - relay_chn_t* relay_chn = &relay_channels[i]; - relay_chn->id = i; - relay_chn->output.forward_pin = forward_pin; - relay_chn->output.reverse_pin = reverse_pin; - relay_chn->output.direction = RELAY_CHN_DIRECTION_DEFAULT; - relay_chn->state = RELAY_CHN_STATE_FREE; - relay_chn->pending_cmd = RELAY_CHN_CMD_NONE; - relay_chn->run_info.last_run_cmd = RELAY_CHN_CMD_NONE; - ret = relay_chn_init_timer(relay_chn); // Create direction change inertia timer - ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create relay channel timer for channel %d", i); -#if RELAY_CHN_ENABLE_TILTING == 1 - ret = relay_chn_init_tilt_control(relay_chn); - ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize tilt control for channel %d", i); -#endif - } - - // Create relay channel command event loop - ret = relay_chn_create_event_loop(); - ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create relay channel event loop"); - -#if RELAY_CHN_ENABLE_TILTING == 1 - // Must call after the event loop is initialized - ret = relay_chn_tilt_init(); // Initialize tilt feature - ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize tilt feature"); -#endif - - // Init the state listener list - vListInitialise(&relay_chn_listener_list); - - return ret; -} - -void relay_chn_destroy(void) -{ - // Destroy the event loop - esp_event_loop_delete(relay_chn_event_loop); - relay_chn_event_loop = NULL; - - // 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); - } - - // Destroy the timers and reset GPIOs - for (int i = 0; i < RELAY_CHN_COUNT; i++) { - relay_chn_t* relay_chn = &relay_channels[i]; - if (relay_chn->inertia_timer != NULL) { - esp_timer_delete(relay_chn->inertia_timer); - relay_chn->inertia_timer = NULL; - } - -#if RELAY_CHN_ENABLE_TILTING == 1 - if (relay_chn->tilt_control.tilt_timer != NULL) { - esp_timer_delete(relay_chn->tilt_control.tilt_timer); - relay_chn->tilt_control.tilt_timer = NULL; - } -#endif // RELAY_CHN_ENABLE_TILTING - - gpio_reset_pin(relay_chn->output.forward_pin); - gpio_reset_pin(relay_chn->output.reverse_pin); - } -} - -/** - * @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); - } -} - -/** - * @brief Check channel ID validity - * - * @param chn_id Channel ID to check - * @return true If channel is valid - * @return false If channel is invalid - */ -static bool relay_chn_is_channel_id_valid(uint8_t chn_id) -{ - bool valid = (chn_id < RELAY_CHN_COUNT) || chn_id == RELAY_CHN_ID_ALL; - if (!valid) { - ESP_LOGE(TAG, "Invalid channel ID: %d", chn_id); - } - return valid; -} - - -// Dispatch relay channel command to its event loop -static void relay_chn_dispatch_cmd(relay_chn_t *relay_chn, relay_chn_cmd_t cmd) { - if (cmd == RELAY_CHN_CMD_NONE) { - return; - } - esp_event_post_to(relay_chn_event_loop, - RELAY_CHN_CMD_EVENT, - cmd, - &relay_chn->id, - sizeof(relay_chn->id), portMAX_DELAY); - -#if RELAY_CHN_ENABLE_TILTING == 1 - // Reset the tilt counter when the command is either FORWARD or REVERSE - if (cmd == RELAY_CHN_CMD_FORWARD || cmd == RELAY_CHN_CMD_REVERSE) { - relay_chn_tilt_count_reset(relay_chn); - } -#endif -} - -static 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); - if (ret == ESP_ERR_INVALID_STATE) { - // This timer is already running, stop the timer first - ret = esp_timer_stop(esp_timer); - if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) { - return ret; - } - ret = esp_timer_start_once(esp_timer, time_ms * 1000); - } - return ret; -} - -static void relay_chn_update_state(relay_chn_t *relay_chn, relay_chn_state_t new_state) -{ - relay_chn_state_t old_state = relay_chn->state; - - // Only update and notify if the state has actually changed. - if (old_state == new_state) { - return; - } - - relay_chn->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(relay_chn->id, old_state, new_state); - } - } -} - -/** - * @brief The command issuer function. - * - * This function is the deciding logic for issuing a command to a relay channel. It evaluates - * the current state of the channel before issuing the command. Then it decides whether to run - * the command immediately or wait for the opposite inertia time. - * - * The STOP command is an exception, it is always run immediately since it is safe in any case. - * - * Another special consideration is the FLIP command. If the channel is running, the FLIP command - * is issued after the channel is stopped. If the channel is stopped, the FLIP command is issued - * immediately. - * - * @param relay_chn The relay channel to issue the command to. - * @param cmd The command to issue. - */ -static void relay_chn_issue_cmd(relay_chn_t* relay_chn, relay_chn_cmd_t cmd) -{ - if (cmd == RELAY_CHN_CMD_NONE) { - return; - } - - if (cmd == RELAY_CHN_CMD_STOP) { - if (relay_chn->state == RELAY_CHN_STATE_STOPPED) { - return; // Do nothing if already stopped - } - // If the command is STOP, issue it immediately - relay_chn_dispatch_cmd(relay_chn, cmd); - return; - } - - // Evaluate the channel's next move depending on its status - switch (relay_chn->state) - { - case RELAY_CHN_STATE_FREE: - // If the channel is free, run the command immediately - relay_chn_dispatch_cmd(relay_chn, cmd); - break; - - case RELAY_CHN_STATE_FORWARD_PENDING: - case RELAY_CHN_STATE_REVERSE_PENDING: - // The channel is already waiting for the opposite inertia time, - // so do nothing unless the command is STOP - if (cmd == RELAY_CHN_CMD_STOP) { - relay_chn_dispatch_cmd(relay_chn, cmd); - } - break; - - case RELAY_CHN_STATE_STOPPED: - if (relay_chn->run_info.last_run_cmd == cmd || relay_chn->run_info.last_run_cmd == RELAY_CHN_CMD_NONE) { - // Since the state is STOPPED, the inertia timer should be running and must be invalidated - // with the pending FREE command - esp_timer_stop(relay_chn->inertia_timer); - relay_chn->pending_cmd = RELAY_CHN_CMD_NONE; - - // If this is the first run or the last run command is the same as the current command, - // run the command immediately - relay_chn_dispatch_cmd(relay_chn, cmd); - } - else { - // If the last run command is different from the current command, calculate the time passed - // since the last run command stopped and decide whether to run the command immediately or wait - uint32_t inertia_time_passed_ms = (uint32_t) (esp_timer_get_time() / 1000) - relay_chn->run_info.last_run_cmd_time_ms; - uint32_t inertia_time_ms = RELAY_CHN_OPPOSITE_INERTIA_MS - inertia_time_passed_ms; - if (inertia_time_ms > 0) { - relay_chn->pending_cmd = cmd; - relay_chn_state_t new_state = cmd == RELAY_CHN_CMD_FORWARD - ? RELAY_CHN_STATE_FORWARD_PENDING : RELAY_CHN_STATE_REVERSE_PENDING; - relay_chn_update_state(relay_chn, new_state); - // If the time passed is less than the opposite inertia time, wait for the remaining time - relay_chn_start_esp_timer_once(relay_chn->inertia_timer, inertia_time_ms); - } - else { - // If the time passed is more than the opposite inertia time, run the command immediately - relay_chn_dispatch_cmd(relay_chn, cmd); - } - } - break; - - case RELAY_CHN_STATE_FORWARD: - case RELAY_CHN_STATE_REVERSE: - if (cmd == RELAY_CHN_CMD_FLIP) { - // If the command is FLIP, stop the running channel first, then issue the FLIP command - relay_chn_dispatch_cmd(relay_chn, RELAY_CHN_CMD_STOP); - relay_chn_dispatch_cmd(relay_chn, cmd); - return; - } - - if (relay_chn->run_info.last_run_cmd == cmd) { - // If the last run command is the same as the current command, do nothing - return; - } - - // Stop the channel first before the schedule - relay_chn_dispatch_cmd(relay_chn, RELAY_CHN_CMD_STOP); - - // If the last run command is different from the current command, wait for the opposite inertia time - relay_chn->pending_cmd = cmd; - relay_chn_state_t new_state = cmd == RELAY_CHN_CMD_FORWARD - ? RELAY_CHN_STATE_FORWARD_PENDING : RELAY_CHN_STATE_REVERSE_PENDING; - relay_chn_update_state(relay_chn, new_state); - relay_chn_start_esp_timer_once(relay_chn->inertia_timer, RELAY_CHN_OPPOSITE_INERTIA_MS); - break; - -#if RELAY_CHN_ENABLE_TILTING == 1 - case RELAY_CHN_STATE_TILT_FORWARD: - // Terminate tilting first - relay_chn_dispatch_tilt_cmd(relay_chn, RELAY_CHN_TILT_CMD_STOP); - if (cmd == RELAY_CHN_CMD_FORWARD) { - // Schedule for running forward - relay_chn->pending_cmd = cmd; - relay_chn_update_state(relay_chn, RELAY_CHN_STATE_FORWARD_PENDING); - relay_chn_start_esp_timer_once(relay_chn->inertia_timer, RELAY_CHN_OPPOSITE_INERTIA_MS); - } else if (cmd == RELAY_CHN_CMD_REVERSE) { - // Run directly since it is the same direction - relay_chn_dispatch_cmd(relay_chn, cmd); - relay_chn_update_state(relay_chn, RELAY_CHN_STATE_REVERSE); - } - break; - case RELAY_CHN_STATE_TILT_REVERSE: - // Terminate tilting first - relay_chn_dispatch_tilt_cmd(relay_chn, RELAY_CHN_TILT_CMD_STOP); - if (cmd == RELAY_CHN_CMD_FORWARD) { - // Run directly since it is the same direction - relay_chn_dispatch_cmd(relay_chn, cmd); - relay_chn_update_state(relay_chn, RELAY_CHN_STATE_FORWARD); - } else if (cmd == RELAY_CHN_CMD_REVERSE) { - // Schedule for running reverse - relay_chn->pending_cmd = cmd; - relay_chn_update_state(relay_chn, RELAY_CHN_STATE_REVERSE_PENDING); - relay_chn_start_esp_timer_once(relay_chn->inertia_timer, RELAY_CHN_OPPOSITE_INERTIA_MS); - } - break; -#endif - - default: ESP_LOGD(TAG, "relay_chn_evaluate: Unknown relay channel state!"); - } -} - -/* relay_chn APIs */ -relay_chn_state_t relay_chn_get_state(uint8_t chn_id) -{ - if (!relay_chn_is_channel_id_valid(chn_id) || chn_id == RELAY_CHN_ID_ALL) { - return RELAY_CHN_STATE_UNDEFINED; - } - return relay_channels[chn_id].state; -} - -char *relay_chn_get_state_str(uint8_t chn_id) -{ - if (!relay_chn_is_channel_id_valid(chn_id) || chn_id == RELAY_CHN_ID_ALL) { - return relay_chn_state_str(RELAY_CHN_STATE_UNDEFINED); - } - return relay_chn_state_str(relay_channels[chn_id].state); -} - -static void relay_chn_issue_cmd_on_all_channels(relay_chn_cmd_t cmd) -{ - for (int i = 0; i < RELAY_CHN_COUNT; i++) { - relay_chn_issue_cmd(&relay_channels[i], cmd); - } -} - -void relay_chn_run_forward(uint8_t chn_id) -{ - if (!relay_chn_is_channel_id_valid(chn_id)) return; - - if (chn_id == RELAY_CHN_ID_ALL) { - relay_chn_issue_cmd_on_all_channels(RELAY_CHN_CMD_FORWARD); - return; - } - - relay_chn_t* relay_chn = &relay_channels[chn_id]; - relay_chn_issue_cmd(relay_chn, RELAY_CHN_CMD_FORWARD); -} - -void relay_chn_run_reverse(uint8_t chn_id) -{ - if (!relay_chn_is_channel_id_valid(chn_id)) return; - - if (chn_id == RELAY_CHN_ID_ALL) { - relay_chn_issue_cmd_on_all_channels(RELAY_CHN_CMD_REVERSE); - return; - } - - relay_chn_t* relay_chn = &relay_channels[chn_id]; - relay_chn_issue_cmd(relay_chn, RELAY_CHN_CMD_REVERSE); -} - -void relay_chn_stop(uint8_t chn_id) -{ - if (!relay_chn_is_channel_id_valid(chn_id)) return; - - if (chn_id == RELAY_CHN_ID_ALL) { - relay_chn_issue_cmd_on_all_channels(RELAY_CHN_CMD_STOP); - return; - } - - relay_chn_t* relay_chn = &relay_channels[chn_id]; - relay_chn_issue_cmd(relay_chn, RELAY_CHN_CMD_STOP); -} - -void relay_chn_flip_direction(uint8_t chn_id) -{ - if (!relay_chn_is_channel_id_valid(chn_id)) return; - - if (chn_id == RELAY_CHN_ID_ALL) { - relay_chn_issue_cmd_on_all_channels(RELAY_CHN_CMD_FLIP); - return; - } - - relay_chn_t* relay_chn = &relay_channels[chn_id]; - relay_chn_issue_cmd(relay_chn, RELAY_CHN_CMD_FLIP); -} - -relay_chn_direction_t relay_chn_get_direction(uint8_t chn_id) -{ - if (!relay_chn_is_channel_id_valid(chn_id)) { - return RELAY_CHN_DIRECTION_DEFAULT; - } - return relay_channels[chn_id].output.direction; -} -/* relay_chn APIs */ - -static esp_err_t relay_chn_output_stop(relay_chn_t *relay_chn) -{ - esp_err_t ret; - ret = gpio_set_level(relay_chn->output.forward_pin, 0); - ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set forward pin to LOW for relay channel #%d", relay_chn->id); - ret = gpio_set_level(relay_chn->output.reverse_pin, 0); - return ret; -} - -static esp_err_t relay_chn_output_forward(relay_chn_t *relay_chn) -{ - esp_err_t ret; - ret = gpio_set_level(relay_chn->output.forward_pin, 1); - ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set forward pin to HIGH for relay channel #%d", relay_chn->id); - ret = gpio_set_level(relay_chn->output.reverse_pin, 0); - return ret; -} - -static esp_err_t relay_chn_output_reverse(relay_chn_t *relay_chn) -{ - esp_err_t ret; - ret = gpio_set_level(relay_chn->output.forward_pin, 0); - ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set forward pin to LOW for relay channel #%d", relay_chn->id); - ret = gpio_set_level(relay_chn->output.reverse_pin, 1); - return ret; -} - -static void relay_chn_execute_stop(relay_chn_t *relay_chn) -{ - if (relay_chn_output_stop(relay_chn) != ESP_OK) { - ESP_LOGE(TAG, "relay_chn_execute_stop: Failed to output stop for relay channel #%d!", relay_chn->id); - } - relay_chn_state_t previous_state = relay_chn->state; - relay_chn_update_state(relay_chn, RELAY_CHN_STATE_STOPPED); - - // If there is any pending command, cancel it since the STOP command is issued right after it - relay_chn->pending_cmd = RELAY_CHN_CMD_NONE; - // Invalidate the channel's timer if it is active - esp_timer_stop(relay_chn->inertia_timer); - - // Save the last run time only if the previous state was either STATE FORWARD - // or STATE_REVERSE. Then schedule a free command. - if (previous_state == RELAY_CHN_STATE_FORWARD || previous_state == RELAY_CHN_STATE_REVERSE) { - // Record the command's last run time - relay_chn->run_info.last_run_cmd_time_ms = esp_timer_get_time() / 1000; - // Schedule a free command for the channel - relay_chn->pending_cmd = RELAY_CHN_CMD_FREE; - relay_chn_start_esp_timer_once(relay_chn->inertia_timer, RELAY_CHN_OPPOSITE_INERTIA_MS); - } else { - // If the channel was not running one of the run or fwd, issue a free command immediately - relay_chn_dispatch_cmd(relay_chn, RELAY_CHN_CMD_FREE); - } -} - -static void relay_chn_execute_forward(relay_chn_t *relay_chn) -{ - if (relay_chn_output_forward(relay_chn) != ESP_OK) { - ESP_LOGE(TAG, "relay_chn_execute_forward: Failed to output forward for relay channel #%d!", relay_chn->id); - return; - } - relay_chn->run_info.last_run_cmd = RELAY_CHN_CMD_FORWARD; - relay_chn_update_state(relay_chn, RELAY_CHN_STATE_FORWARD); -} - -static void relay_chn_execute_reverse(relay_chn_t *relay_chn) -{ - if (relay_chn_output_reverse(relay_chn) != ESP_OK) { - ESP_LOGE(TAG, "relay_chn_execute_reverse: Failed to output reverse for relay channel #%d!", relay_chn->id); - return; - } - relay_chn->run_info.last_run_cmd = RELAY_CHN_CMD_REVERSE; - relay_chn_update_state(relay_chn, RELAY_CHN_STATE_REVERSE); -} - -static void relay_chn_execute_flip(relay_chn_t *relay_chn) -{ - // Flip the output GPIO pins - gpio_num_t temp = relay_chn->output.forward_pin; - relay_chn->output.forward_pin = relay_chn->output.reverse_pin; - relay_chn->output.reverse_pin = temp; - // Flip the direction - relay_chn->output.direction = (relay_chn->output.direction == RELAY_CHN_DIRECTION_DEFAULT) - ? RELAY_CHN_DIRECTION_FLIPPED - : RELAY_CHN_DIRECTION_DEFAULT; - // Set an inertia on the channel to prevent any immediate movement - relay_chn->pending_cmd = RELAY_CHN_CMD_FREE; - relay_chn_start_esp_timer_once(relay_chn->inertia_timer, RELAY_CHN_OPPOSITE_INERTIA_MS); -} - -void relay_chn_execute_free(relay_chn_t *relay_chn) -{ - relay_chn->pending_cmd = RELAY_CHN_CMD_NONE; - // Invalidate the channel's timer if it is active - esp_timer_stop(relay_chn->inertia_timer); - relay_chn_update_state(relay_chn, RELAY_CHN_STATE_FREE); -} - -static void relay_chn_event_handler(void* handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data) -{ - uint8_t chn_id = *(uint8_t*) event_data; - if (!relay_chn_is_channel_id_valid(chn_id)) { - return; - } - relay_chn_t* relay_chn = &relay_channels[chn_id]; - ESP_LOGD(TAG, "relay_chn_event_handler: Channel %d, Command: %s", relay_chn->id, relay_chn_cmd_str(event_id)); - switch (event_id) { - case RELAY_CHN_CMD_STOP: - relay_chn_execute_stop(relay_chn); - break; - case RELAY_CHN_CMD_FORWARD: - relay_chn_execute_forward(relay_chn); - break; - case RELAY_CHN_CMD_REVERSE: - relay_chn_execute_reverse(relay_chn); - break; - case RELAY_CHN_CMD_FLIP: - relay_chn_execute_flip(relay_chn); - break; - case RELAY_CHN_CMD_FREE: - relay_chn_execute_free(relay_chn); - break; - default: - ESP_LOGD(TAG, "Unknown relay channel command!"); - } -} - -static char *relay_chn_cmd_str(relay_chn_cmd_t cmd) -{ - switch (cmd) { - case RELAY_CHN_CMD_STOP: - return "STOP"; - case RELAY_CHN_CMD_FORWARD: - return "FORWARD"; - case RELAY_CHN_CMD_REVERSE: - return "REVERSE"; - case RELAY_CHN_CMD_FLIP: - return "FLIP"; - case RELAY_CHN_CMD_FREE: - return "FREE"; - default: - return "UNKNOWN"; - } -} - -char *relay_chn_state_str(relay_chn_state_t state) -{ - switch (state) { - case RELAY_CHN_STATE_FREE: - return "FREE"; - case RELAY_CHN_STATE_STOPPED: - return "STOPPED"; - case RELAY_CHN_STATE_FORWARD: - return "FORWARD"; - case RELAY_CHN_STATE_REVERSE: - return "REVERSE"; - case RELAY_CHN_STATE_FORWARD_PENDING: - return "FORWARD_PENDING"; - case RELAY_CHN_STATE_REVERSE_PENDING: - return "REVERSE_PENDING"; -#if RELAY_CHN_ENABLE_TILTING == 1 - case RELAY_CHN_STATE_TILT_FORWARD: - return "TILT_FORWARD"; - case RELAY_CHN_STATE_TILT_REVERSE: - return "TILT_REVERSE"; -#endif - default: - return "UNKNOWN"; - } -} - -#if RELAY_CHN_ENABLE_TILTING == 1 - -/** - * @brief Dispatch a tilt command to the relay channel event loop. - * - * @param relay_chn The relay channel to send the command to. - * @param cmd The tilt command. - * @return - * - ESP_OK on success. - * - ESP_ERR_INVALID_ARG if the command is none. - * - Other error codes on failure. - */ -static esp_err_t relay_chn_dispatch_tilt_cmd(relay_chn_t *relay_chn, relay_chn_tilt_cmd_t cmd) -{ - if (cmd == RELAY_CHN_TILT_CMD_NONE) return ESP_ERR_INVALID_ARG; - return esp_event_post_to(relay_chn_event_loop, - RELAY_CHN_TILT_CMD_EVENT_BASE, - cmd, - &relay_chn->id, - sizeof(relay_chn->id), portMAX_DELAY); -} - -/** - * @brief Get the required timing before tilting depending on the last run. - * - * @param relay_chn the relay channel. - * @param cmd The tilt command. - * @return The time that is required in ms. - */ -static uint32_t relay_chn_get_required_timing_before_tilting(relay_chn_t *relay_chn, relay_chn_tilt_cmd_t cmd) -{ - if (cmd == RELAY_CHN_TILT_CMD_FORWARD && relay_chn->run_info.last_run_cmd == RELAY_CHN_CMD_REVERSE) - return 0; - else if (cmd == RELAY_CHN_TILT_CMD_REVERSE && relay_chn->run_info.last_run_cmd == RELAY_CHN_CMD_FORWARD) - return 0; - - uint32_t inertia_time_passed_ms = (uint32_t) (esp_timer_get_time() / 1000) - relay_chn->run_info.last_run_cmd_time_ms; - return RELAY_CHN_OPPOSITE_INERTIA_MS - inertia_time_passed_ms; -} - -/** - * @brief Issue a tilt command to a specific relay channel. - * - * @param chn_id The channel ID. - * @param cmd The tilt command. - */ -static void relay_chn_issue_tilt_cmd(uint8_t chn_id, relay_chn_tilt_cmd_t cmd) -{ - relay_chn_t* relay_chn = &relay_channels[chn_id]; - - if (relay_chn->run_info.last_run_cmd == RELAY_CHN_CMD_NONE) { - // Do not tilt if the channel hasn't been run before - ESP_LOGD(TAG, "relay_chn_issue_tilt_cmd: Tilt will not be executed since the channel hasn't been run yet"); - return; - } - - if (relay_chn->tilt_control.cmd == cmd) { - ESP_LOGD(TAG, "relay_chn_issue_tilt_cmd: There is already a tilt command in progress!"); - return; - } - - // Set the command that will be processed - relay_chn->tilt_control.cmd = cmd; - switch (relay_chn->state) { - case RELAY_CHN_STATE_FREE: - // Relay channel is free, tilt can be issued immediately - relay_chn_dispatch_tilt_cmd(relay_chn, cmd); - break; - - case RELAY_CHN_STATE_FORWARD_PENDING: - case RELAY_CHN_STATE_REVERSE_PENDING: - // Issue a stop command first so that the timer and pending cmd get cleared - relay_chn_dispatch_cmd(relay_chn, RELAY_CHN_CMD_STOP); - // break not put intentionally - case RELAY_CHN_STATE_STOPPED: { - // Check if channel needs timing before tilting - uint32_t req_timing_ms = relay_chn_get_required_timing_before_tilting(relay_chn, cmd); - if (req_timing_ms == 0) { - relay_chn_dispatch_tilt_cmd(relay_chn, cmd); - } else { - // Channel needs timing before running tilting action, schedule it - relay_chn->tilt_control.step = RELAY_CHN_TILT_STEP_PENDING; - relay_chn_start_esp_timer_once(relay_chn->tilt_control.tilt_timer, req_timing_ms); - } - break; - } - - case RELAY_CHN_STATE_FORWARD: - if (cmd == RELAY_CHN_TILT_CMD_FORWARD) { - // Stop the running channel first - relay_chn_dispatch_cmd(relay_chn, RELAY_CHN_CMD_STOP); - // Schedule for tilting - relay_chn->tilt_control.step = RELAY_CHN_TILT_STEP_PENDING; - relay_chn_start_esp_timer_once(relay_chn->tilt_control.tilt_timer, RELAY_CHN_OPPOSITE_INERTIA_MS); - } else if (cmd == RELAY_CHN_TILT_CMD_REVERSE) { - // Stop the running channel first - relay_chn_dispatch_cmd(relay_chn, RELAY_CHN_CMD_STOP); - // If the tilt cmd is TILT_REVERSE then dispatch it immediately - relay_chn_dispatch_tilt_cmd(relay_chn, cmd); - } - break; - - case RELAY_CHN_STATE_REVERSE: - if (cmd == RELAY_CHN_TILT_CMD_REVERSE) { - // Stop the running channel first - relay_chn_dispatch_cmd(relay_chn, RELAY_CHN_CMD_STOP); - // Schedule for tilting - relay_chn->tilt_control.step = RELAY_CHN_TILT_STEP_PENDING; - relay_chn_start_esp_timer_once(relay_chn->tilt_control.tilt_timer, RELAY_CHN_OPPOSITE_INERTIA_MS); - } else if (cmd == RELAY_CHN_TILT_CMD_FORWARD) { - // Stop the running channel first - relay_chn_dispatch_cmd(relay_chn, RELAY_CHN_CMD_STOP); - // If the tilt cmd is TILT_FORWARD then dispatch it immediately - relay_chn_dispatch_tilt_cmd(relay_chn, cmd); - } - break; - - default: - ESP_LOGD(TAG, "relay_chn_issue_tilt_cmd: Unexpected relay channel state: %s!", relay_chn_state_str(relay_chn->state)); - } -} - -static void relay_chn_issue_tilt_cmd_on_all_channels(relay_chn_tilt_cmd_t cmd) -{ - for (int i = 0; i < RELAY_CHN_COUNT; i++) { - relay_chn_issue_tilt_cmd(i, cmd); - } -} - -static void relay_chn_issue_tilt_auto(uint8_t chn_id) -{ - relay_chn_t* relay_chn = &relay_channels[chn_id]; - if (relay_chn->run_info.last_run_cmd == RELAY_CHN_CMD_FORWARD || relay_chn->state == RELAY_CHN_STATE_FORWARD) { - relay_chn_issue_tilt_cmd(chn_id, RELAY_CHN_TILT_CMD_FORWARD); - } - else if (relay_chn->run_info.last_run_cmd == RELAY_CHN_CMD_REVERSE || relay_chn->state == RELAY_CHN_STATE_REVERSE) { - relay_chn_issue_tilt_cmd(chn_id, RELAY_CHN_TILT_CMD_REVERSE); - } -} - -void relay_chn_tilt_auto(uint8_t chn_id) -{ - if (!relay_chn_is_channel_id_valid(chn_id)) { - return; - } - - // Execute for all channels - if (chn_id == RELAY_CHN_ID_ALL) { - for (int i = 0; i < RELAY_CHN_COUNT; i++) { - relay_chn_issue_tilt_auto(i); - } - return; - } - // Execute for a single channel - else relay_chn_issue_tilt_auto(chn_id); -} - -void relay_chn_tilt_forward(uint8_t chn_id) -{ - if (!relay_chn_is_channel_id_valid(chn_id)) { - return; - } - - if (chn_id == RELAY_CHN_ID_ALL) relay_chn_issue_tilt_cmd_on_all_channels(RELAY_CHN_TILT_CMD_FORWARD); - else relay_chn_issue_tilt_cmd(chn_id, RELAY_CHN_TILT_CMD_FORWARD); -} - -void relay_chn_tilt_reverse(uint8_t chn_id) -{ - if (!relay_chn_is_channel_id_valid(chn_id)) { - return; - } - - if (chn_id == RELAY_CHN_ID_ALL) relay_chn_issue_tilt_cmd_on_all_channels(RELAY_CHN_TILT_CMD_REVERSE); - else relay_chn_issue_tilt_cmd(chn_id, RELAY_CHN_TILT_CMD_REVERSE); -} - -static void _relay_chn_tilt_stop(uint8_t chn_id) -{ - relay_chn_t* relay_chn = &relay_channels[chn_id]; - if (relay_chn->tilt_control.cmd != RELAY_CHN_TILT_CMD_NONE) { - esp_event_post_to(relay_chn_event_loop, - RELAY_CHN_TILT_CMD_EVENT_BASE, - RELAY_CHN_TILT_CMD_STOP, - &relay_chn->id, - sizeof(relay_chn->id), portMAX_DELAY); - } -} - -void relay_chn_tilt_stop(uint8_t chn_id) -{ - if (!relay_chn_is_channel_id_valid(chn_id)) { - return; - } - - if (chn_id == RELAY_CHN_ID_ALL) { - for (int i = 0; i < RELAY_CHN_COUNT; i++) { - _relay_chn_tilt_stop(i); - } - } - else { - _relay_chn_tilt_stop(chn_id); - } -} - -static void relay_chn_set_tilt_timing_values(relay_chn_tilt_timing_t *tilt_timing, - uint8_t sensitivity, - uint32_t run_time_ms, - uint32_t pause_time_ms) -{ - tilt_timing->sensitivity = sensitivity; - tilt_timing->move_time_ms = run_time_ms; - tilt_timing->pause_time_ms = pause_time_ms; -} - -static void _relay_chn_tilt_sensitivity_set(relay_chn_t *relay_chn, uint8_t sensitivity) -{ - if (sensitivity >= 100) { - relay_chn_set_tilt_timing_values(&relay_chn->tilt_control.tilt_timing, - 100, - RELAY_CHN_TILT_RUN_MAX_MS, - RELAY_CHN_TILT_PAUSE_MAX_MS); - } - else if (sensitivity == 0) { - relay_chn_set_tilt_timing_values(&relay_chn->tilt_control.tilt_timing, - 0, - RELAY_CHN_TILT_RUN_MIN_MS, - RELAY_CHN_TILT_PAUSE_MIN_MS); - } - else { - // Compute the new timing values from the sensitivity percent value by using linear interpolation - uint32_t tilt_run_time_ms = 0, tilt_pause_time_ms = 0; - tilt_run_time_ms = RELAY_CHN_TILT_RUN_MIN_MS + (sensitivity * (RELAY_CHN_TILT_RUN_MAX_MS - RELAY_CHN_TILT_RUN_MIN_MS) / 100); - tilt_pause_time_ms = RELAY_CHN_TILT_PAUSE_MIN_MS + (sensitivity * (RELAY_CHN_TILT_PAUSE_MAX_MS - RELAY_CHN_TILT_PAUSE_MIN_MS) / 100); - relay_chn_set_tilt_timing_values(&relay_chn->tilt_control.tilt_timing, - sensitivity, - tilt_run_time_ms, - tilt_pause_time_ms); - } -} - -void relay_chn_tilt_sensitivity_set(uint8_t chn_id, uint8_t sensitivity) -{ - if (!relay_chn_is_channel_id_valid(chn_id)) { - return; - } - - if (chn_id == RELAY_CHN_ID_ALL) { - for (int i = 0; i < RELAY_CHN_COUNT; i++) { - _relay_chn_tilt_sensitivity_set(&relay_channels[i], sensitivity); - } - } - else { - _relay_chn_tilt_sensitivity_set(&relay_channels[chn_id], sensitivity); - } -} - -esp_err_t relay_chn_tilt_sensitivity_get(uint8_t chn_id, uint8_t *sensitivity, size_t length) -{ - if (!relay_chn_is_channel_id_valid(chn_id)) { - return ESP_ERR_INVALID_ARG; - } - if (sensitivity == NULL) { - ESP_LOGD(TAG, "relay_chn_tilt_sensitivity_get: sensitivity is NULL"); - return ESP_ERR_INVALID_ARG; - } - if (chn_id == RELAY_CHN_ID_ALL) { - if (length < RELAY_CHN_COUNT) { - ESP_LOGD(TAG, "relay_chn_tilt_sensitivity_get: length is too short to store all sensitivity values"); - return ESP_ERR_INVALID_ARG; - } - - for (int i = 0; i < RELAY_CHN_COUNT; i++) { - sensitivity[i] = relay_channels[i].tilt_control.tilt_timing.sensitivity; - } - return ESP_OK; - } - *sensitivity = relay_channels[chn_id].tilt_control.tilt_timing.sensitivity; - return ESP_OK; -} - -static void relay_chn_tilt_count_reset(relay_chn_t *relay_chn) -{ - relay_chn->tilt_control.tilt_counter.tilt_forward_count = 0; - relay_chn->tilt_control.tilt_counter.tilt_reverse_count = 0; -} - -/** - * @brief Update tilt count automatically and return the current value. - * - * This helper function updates the relevant tilt count depending on the - * last run info and helps the tilt module in deciding whether the requested - * tilt should execute or not. - * This is useful to control reverse tilting particularly. For example: - * - If the channel's last run was FORWARD and a TILT_FORWARD is requested, - * then the tilt counter will count up on the - * relay_chn_tilt_counter_struct::tilt_forward_count and the function will - * return the actual count. - * - If the channel's last run was FORWARD and a TILT_REVERSE is requested, - * then the relay_chn_tilt_counter_struct::tilt_forward_count will be checked - * against zero first, and then it will count down and return the actual count - * if it is greater than 0, else the function will return 0. - * - If the tilt command is irrelevant then the function will return 0. - * - If the last run is irrelevant then the function will return 0. - * - * @param relay_chn The relay channel handle. - * @return uint32_t The actual value of the relevant counter. - * @return 0 if: - * - related counter is already 0. - * - tilt command is irrelevant. - * - last run info is irrelevant. - */ -static uint32_t relay_chn_tilt_count_update(relay_chn_t *relay_chn) -{ - if (relay_chn->run_info.last_run_cmd == RELAY_CHN_CMD_FORWARD) { - if (relay_chn->tilt_control.cmd == RELAY_CHN_TILT_CMD_FORWARD) { - return ++relay_chn->tilt_control.tilt_counter.tilt_forward_count; - } - else if (relay_chn->tilt_control.cmd == RELAY_CHN_TILT_CMD_REVERSE) { - if (relay_chn->tilt_control.tilt_counter.tilt_forward_count > 0) { - --relay_chn->tilt_control.tilt_counter.tilt_forward_count; - // Still should do one more move, return non-zero value - return 1; - } - else - return 0; - } - else { - relay_chn_tilt_count_reset(relay_chn); - return 0; - } - } - else if (relay_chn->run_info.last_run_cmd == RELAY_CHN_CMD_REVERSE) { - if (relay_chn->tilt_control.cmd == RELAY_CHN_TILT_CMD_REVERSE) { - return ++relay_chn->tilt_control.tilt_counter.tilt_reverse_count; - } - else if (relay_chn->tilt_control.cmd == RELAY_CHN_TILT_CMD_FORWARD) { - if (relay_chn->tilt_control.tilt_counter.tilt_reverse_count > 0) { - --relay_chn->tilt_control.tilt_counter.tilt_reverse_count; - // Still should do one more move, return non-zero value - return 1; - } - else - return 0; - } - else { - relay_chn_tilt_count_reset(relay_chn); - return 0; - } - } - return 0; -} - -static void relay_chn_tilt_execute_tilt_stop(relay_chn_t *relay_chn) -{ - // Stop the channel's timer if active - esp_timer_stop(relay_chn->tilt_control.tilt_timer); - // Invalidate tilt cmd and step - relay_chn->tilt_control.cmd = RELAY_CHN_TILT_CMD_NONE; - relay_chn->tilt_control.step = RELAY_CHN_TILT_STEP_NONE; - // Stop the channel - if (relay_chn_output_stop(relay_chn) != ESP_OK) { - ESP_LOGE(TAG, "relay_chn_tilt_execute_tilt_stop: Failed to output stop for relay channel #%d!", relay_chn->id); - } - relay_chn_dispatch_cmd(relay_chn, RELAY_CHN_CMD_STOP); -} - -static void relay_chn_tilt_execute_tilt_forward(relay_chn_t *relay_chn) -{ - if (relay_chn_output_reverse(relay_chn) != ESP_OK) { - ESP_LOGE(TAG, "relay_chn_tilt_execute_tilt_forward: Failed to output reverse for relay channel #%d!", relay_chn->id); - // Stop tilting because of the error - relay_chn_dispatch_tilt_cmd(relay_chn, RELAY_CHN_TILT_CMD_STOP); - return; - } - // Set the move time timer - relay_chn_start_esp_timer_once(relay_chn->tilt_control.tilt_timer, - relay_chn->tilt_control.tilt_timing.move_time_ms); - // Set to pause step - relay_chn->tilt_control.step = RELAY_CHN_TILT_STEP_PAUSE; -} - -static void relay_chn_tilt_execute_tilt_reverse(relay_chn_t *relay_chn) -{ - if (relay_chn_output_forward(relay_chn) != ESP_OK) { - ESP_LOGE(TAG, "relay_chn_tilt_execute_tilt_reverse: Failed to output forward for relay channel #%d!", relay_chn->id); - // Stop tilting because of the error - relay_chn_dispatch_tilt_cmd(relay_chn, RELAY_CHN_TILT_CMD_STOP); - return; - } - // Set the move time timer - relay_chn_start_esp_timer_once(relay_chn->tilt_control.tilt_timer, - relay_chn->tilt_control.tilt_timing.move_time_ms); - // Set to pause step - relay_chn->tilt_control.step = RELAY_CHN_TILT_STEP_PAUSE; -} - -static void relay_chn_tilt_execute_tilt_pause(relay_chn_t *relay_chn) -{ - // Pause the channel - if (relay_chn_output_stop(relay_chn) != ESP_OK) { - ESP_LOGE(TAG, "relay_chn_tilt_execute_tilt_stop: Failed to output stop for relay channel #%d!", relay_chn->id); - // Stop tilting because of the error - relay_chn_dispatch_tilt_cmd(relay_chn, RELAY_CHN_TILT_CMD_STOP); - return; - } - - // Update the tilt counter before the next move and expect the return value to be greater than 0 - if (relay_chn_tilt_count_update(relay_chn) == 0) { - ESP_LOGD(TAG, "relay_chn_tilt_execute_tilt_stop: Relay channel cannot tilt anymore"); - // Stop tilting since the tilting limit has been reached - relay_chn_dispatch_tilt_cmd(relay_chn, RELAY_CHN_TILT_CMD_STOP); - return; - } - - // Set the pause time timer - relay_chn_start_esp_timer_once(relay_chn->tilt_control.tilt_timer, - relay_chn->tilt_control.tilt_timing.pause_time_ms); - // Set to move step - relay_chn->tilt_control.step = RELAY_CHN_TILT_STEP_MOVE; -} - -static void relay_chn_tilt_event_handler(void *handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data) -{ - uint8_t chn_id = *(uint8_t*) event_data; - if (!relay_chn_is_channel_id_valid(chn_id)) { - return; - } - relay_chn_t* relay_chn = &relay_channels[chn_id]; - ESP_LOGD(TAG, "relay_chn_event_handler: Channel %d, Command: %s", relay_chn->id, relay_chn_cmd_str(event_id)); - switch(event_id) { - case RELAY_CHN_TILT_CMD_STOP: - relay_chn_tilt_execute_tilt_stop(relay_chn); - break; - case RELAY_CHN_TILT_CMD_FORWARD: - relay_chn_tilt_execute_tilt_forward(relay_chn); - // Update channel state - relay_chn_update_state(relay_chn, RELAY_CHN_STATE_TILT_FORWARD); - break; - case RELAY_CHN_TILT_CMD_REVERSE: - relay_chn_tilt_execute_tilt_reverse(relay_chn); - // Update channel state - relay_chn_update_state(relay_chn, RELAY_CHN_STATE_TILT_REVERSE); - break; - default: - ESP_LOGW(TAG, "Unexpected relay channel tilt command: %ld!", event_id); - } -} - -// Timer callback for the relay_chn_tilt_control_t::tilt_timer -static void relay_chn_tilt_timer_cb(void *arg) -{ - uint8_t chn_id = *(uint8_t*) arg; - if (!relay_chn_is_channel_id_valid(chn_id)) { - ESP_LOGE(TAG, "relay_chn_tilt_timer_cb: Invalid relay channel ID!"); - return; - } - relay_chn_t* relay_chn = &relay_channels[chn_id]; - - switch (relay_chn->tilt_control.step) - { - case RELAY_CHN_TILT_STEP_MOVE: - if (relay_chn->tilt_control.cmd == RELAY_CHN_TILT_CMD_FORWARD) { - relay_chn_tilt_execute_tilt_forward(relay_chn); - } - else if (relay_chn->tilt_control.cmd == RELAY_CHN_TILT_CMD_REVERSE) { - relay_chn_tilt_execute_tilt_reverse(relay_chn); - } - break; - - case RELAY_CHN_TILT_STEP_PAUSE: - relay_chn_tilt_execute_tilt_pause(relay_chn); - break; - - case RELAY_CHN_TILT_STEP_PENDING: - // Just dispatch the pending tilt command - relay_chn_dispatch_tilt_cmd(relay_chn, relay_chn->tilt_control.cmd); - break; - - default: - break; - } -} - -static esp_err_t relay_chn_init_tilt_control(relay_chn_t *relay_chn) -{ - relay_chn_tilt_control_t *tilt_control = &relay_chn->tilt_control; - tilt_control->cmd = RELAY_CHN_TILT_CMD_NONE; - tilt_control->step = RELAY_CHN_TILT_STEP_NONE; - tilt_control->tilt_timing.sensitivity = RELAY_CHN_TILT_DEFAULT_SENSITIVITY; - tilt_control->tilt_timing.move_time_ms = RELAY_CHN_TILT_DEFAULT_RUN_MS; - tilt_control->tilt_timing.pause_time_ms = RELAY_CHN_TILT_DEFAULT_PAUSE_MS; - relay_chn_tilt_count_reset(relay_chn); - - // Create tilt timer for the channel - char timer_name[32]; - snprintf(timer_name, sizeof(timer_name), "relay_chn_%2d_tilt_timer", relay_chn->id); - esp_timer_create_args_t timer_args = { - .callback = relay_chn_tilt_timer_cb, - .arg = &relay_chn->id, - .name = timer_name - }; - return esp_timer_create(&timer_args, &relay_chn->tilt_control.tilt_timer); -} - -// Should call once from relay_chn_init -static esp_err_t relay_chn_tilt_init(void) -{ - esp_err_t ret; - ret = esp_event_handler_register_with(relay_chn_event_loop, - RELAY_CHN_TILT_CMD_EVENT_BASE, - ESP_EVENT_ANY_ID, - relay_chn_tilt_event_handler, NULL); - return ret; -} - -#endif // RELAY_CHN_ENABLE_TILTING - -/// @} \ No newline at end of file diff --git a/src/relay_chn_core.c b/src/relay_chn_core.c new file mode 100644 index 0000000..85e30ee --- /dev/null +++ b/src/relay_chn_core.c @@ -0,0 +1,574 @@ +/* + * SPDX-FileCopyrightText: 2025 Kozmotronik Tech + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include "esp_check.h" +#include "esp_task.h" +#include "relay_chn_output.h" +#include "relay_chn_run_info.h" +#include "relay_chn_ctl.h" +#if RELAY_CHN_ENABLE_TILTING == 1 +#include "relay_chn_tilt.h" +#endif +#include "relay_chn_core.h" + + +static const char *TAG = "RELAY_CHN_CORE"; + +ESP_EVENT_DEFINE_BASE(RELAY_CHN_CMD_EVENT); + + +// 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; + +// Define the event loop for global access both for this module and tilt module. +esp_event_loop_handle_t relay_chn_event_loop = NULL; + + +// Private function declarations +// Event handler for the relay channel command event +static void relay_chn_event_handler(void* handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data); + + +// Timer callback function for relay channel direction change inertia. +static void relay_chn_timer_cb(void* arg) +{ + relay_chn_ctl_t* chn_ctl = (relay_chn_ctl_t*) arg; + // Does channel have a pending command? + if (chn_ctl->pending_cmd != RELAY_CHN_CMD_NONE) { + relay_chn_dispatch_cmd(chn_ctl, chn_ctl->pending_cmd); + chn_ctl->pending_cmd = RELAY_CHN_CMD_NONE; + } + else { + ESP_LOGE(TAG, "relay_chn_timer_cb: No pending cmd for relay channel %d!", chn_ctl->id); + } +} + +esp_err_t relay_chn_init_timer(relay_chn_ctl_t *chn_ctl) +{ + char timer_name[32]; + snprintf(timer_name, sizeof(timer_name), "relay_chn_%d_timer", chn_ctl->id); + esp_timer_create_args_t timer_args = { + .callback = relay_chn_timer_cb, + .arg = chn_ctl, + .name = timer_name + }; + return esp_timer_create(&timer_args, &chn_ctl->inertia_timer); +} + +static esp_err_t relay_chn_create_event_loop() +{ + esp_event_loop_args_t loop_args = { + .queue_size = RELAY_CHN_COUNT * 8, + .task_name = "relay_chn_event_loop", + .task_priority = ESP_TASKD_EVENT_PRIO - 1, + .task_stack_size = 2048, + .task_core_id = tskNO_AFFINITY + }; + esp_err_t ret = esp_event_loop_create(&loop_args, &relay_chn_event_loop); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create event loop for relay channel"); + ret = esp_event_handler_register_with(relay_chn_event_loop, + RELAY_CHN_CMD_EVENT, + ESP_EVENT_ANY_ID, + relay_chn_event_handler, NULL); + return ret; +} + +esp_err_t relay_chn_create(const uint8_t* gpio_map, uint8_t gpio_count) +{ + ESP_RETURN_ON_FALSE(gpio_map != NULL, ESP_ERR_INVALID_ARG, TAG, "gpio_map cannot be NULL"); + + esp_err_t ret; + // Initialize the output + ret = relay_chn_output_init(gpio_map, gpio_count); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize relay channel outputs"); + + // Initialize the run info + relay_chn_run_info_init(); + +#if RELAY_CHN_COUNT > 1 + relay_chn_output_t *outputs = relay_chn_output_get_all(); + relay_chn_run_info_t *run_infos = relay_chn_run_info_get_all(); +#else + relay_chn_output_t *outputs = relay_chn_output_get(); + relay_chn_run_info_t *run_infos = relay_chn_run_info_get(); +#endif + + // Initialize the relay channel controls + ret = relay_chn_ctl_init(outputs, run_infos); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize relay channel control"); + + // Create relay channel command event loop + ret = relay_chn_create_event_loop(); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create relay channel event loop"); + +#if RELAY_CHN_ENABLE_TILTING == 1 + // Initialize the tilt feature +#if RELAY_CHN_COUNT > 1 + relay_chn_ctl_t *chn_ctls = relay_chn_ctl_get_all(); +#else + relay_chn_ctl_t *chn_ctls = relay_chn_ctl_get(); +#endif // RELAY_CHN_COUNT > 1 + ret = relay_chn_tilt_init(chn_ctls); // Initialize tilt feature + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize tilt feature"); +#endif + + // Init the state listener list + vListInitialise(&relay_chn_listener_list); + + return ret; +} + +void relay_chn_destroy(void) +{ +#if RELAY_CHN_ENABLE_TILTING == 1 + relay_chn_tilt_deinit(); +#endif + relay_chn_ctl_deinit(); + relay_chn_output_deinit(); + + // Destroy the event loop + esp_event_loop_delete(relay_chn_event_loop); + relay_chn_event_loop = NULL; + + // 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); + } +} + + +// Dispatch relay channel command to its event loop +void relay_chn_dispatch_cmd(relay_chn_ctl_t *chn_ctl, relay_chn_cmd_t cmd) { + if (cmd == RELAY_CHN_CMD_NONE) { + return; + } + + // Since the event_loop library creates a deep copy of the event data, + // and we need to pass the pointer of the relevant channel, here we need + // to pass the pointer to the pointer of the channel (&chn_ctl) so that + // the pointer value is preserved in the event data. + esp_event_post_to(relay_chn_event_loop, + RELAY_CHN_CMD_EVENT, + cmd, + &chn_ctl, + sizeof(chn_ctl), + portMAX_DELAY); + +#if RELAY_CHN_ENABLE_TILTING == 1 + // Reset the tilt counter when the command is either FORWARD or REVERSE + if (cmd == RELAY_CHN_CMD_FORWARD || cmd == RELAY_CHN_CMD_REVERSE) { + relay_chn_tilt_reset_count(chn_ctl->tilt_ctl); + } +#endif +} + +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); + if (ret == ESP_ERR_INVALID_STATE) { + // This timer is already running, stop the timer first + ret = esp_timer_stop(esp_timer); + if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) { + return ret; + } + ret = esp_timer_start_once(esp_timer, time_ms * 1000); + } + return ret; +} + +void relay_chn_update_state(relay_chn_ctl_t *chn_ctl, relay_chn_state_t new_state) +{ + relay_chn_state_t old_state = chn_ctl->state; + + // Only update and notify if the state has actually changed. + if (old_state == new_state) { + return; + } + + 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); + } + } +} + +/** + * @brief The command issuer function. + * + * This function is the deciding logic for issuing a command to a relay channel. It evaluates + * the current state of the channel before issuing the command. Then it decides whether to run + * the command immediately or wait for the opposite inertia time. + * + * The STOP command is an exception, it is always run immediately since it is safe in any case. + * + * Another special consideration is the FLIP command. If the channel is running, the FLIP command + * is issued after the channel is stopped. If the channel is stopped, the FLIP command is issued + * immediately. + * + * @param chn_ctl The relay channel to issue the command to. + * @param cmd The command to issue. + */ +void relay_chn_issue_cmd(relay_chn_ctl_t* chn_ctl, relay_chn_cmd_t cmd) +{ + if (cmd == RELAY_CHN_CMD_NONE) { + return; + } + + if (cmd == RELAY_CHN_CMD_STOP) { + if (chn_ctl->state == RELAY_CHN_STATE_STOPPED) { + return; // Do nothing if already stopped + } + // If the command is STOP, issue it immediately + relay_chn_dispatch_cmd(chn_ctl, cmd); + return; + } + + relay_chn_cmd_t last_run_cmd = relay_chn_run_info_get_last_run_cmd(chn_ctl->run_info); + // Evaluate the channel's next move depending on its status + switch (chn_ctl->state) + { + case RELAY_CHN_STATE_IDLE: + // If the channel is idle, run the command immediately + relay_chn_dispatch_cmd(chn_ctl, cmd); + break; + + case RELAY_CHN_STATE_FORWARD_PENDING: + case RELAY_CHN_STATE_REVERSE_PENDING: + // The channel is already waiting for the opposite inertia time, + // so do nothing unless the command is STOP + if (cmd == RELAY_CHN_CMD_STOP) { + relay_chn_dispatch_cmd(chn_ctl, cmd); + } + break; + + case RELAY_CHN_STATE_STOPPED: + if (last_run_cmd == cmd || last_run_cmd == RELAY_CHN_CMD_NONE) { + // Since the state is STOPPED, the inertia timer should be running and must be invalidated + // with the pending FREE command + esp_timer_stop(chn_ctl->inertia_timer); + chn_ctl->pending_cmd = RELAY_CHN_CMD_NONE; + + // If this is the first run or the last run command is the same as the current command, + // run the command immediately + relay_chn_dispatch_cmd(chn_ctl, cmd); + } + else { + // If the last run command is different from the current command, calculate the time passed + // since the last run command stopped and decide whether to run the command immediately or wait + uint32_t last_run_cmd_time_ms = relay_chn_run_info_get_last_run_cmd_time_ms(chn_ctl->run_info); + uint32_t inertia_time_passed_ms = (uint32_t) (esp_timer_get_time() / 1000) - last_run_cmd_time_ms; + uint32_t inertia_time_ms = RELAY_CHN_OPPOSITE_INERTIA_MS - inertia_time_passed_ms; + if (inertia_time_ms > 0) { + chn_ctl->pending_cmd = cmd; + relay_chn_state_t new_state = cmd == RELAY_CHN_CMD_FORWARD + ? RELAY_CHN_STATE_FORWARD_PENDING : RELAY_CHN_STATE_REVERSE_PENDING; + relay_chn_update_state(chn_ctl, new_state); + // If the time passed is less than the opposite inertia time, wait for the remaining time + relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, inertia_time_ms); + } + else { + // If the time passed is more than the opposite inertia time, run the command immediately + relay_chn_dispatch_cmd(chn_ctl, cmd); + } + } + break; + + case RELAY_CHN_STATE_FORWARD: + case RELAY_CHN_STATE_REVERSE: + if (cmd == RELAY_CHN_CMD_FLIP) { + // If the command is FLIP, stop the running channel first, then issue the FLIP command + relay_chn_dispatch_cmd(chn_ctl, RELAY_CHN_CMD_STOP); + relay_chn_dispatch_cmd(chn_ctl, cmd); + return; + } + + if (last_run_cmd == cmd) { + // If the last run command is the same as the current command, do nothing + return; + } + + // Stop the channel first before the schedule + relay_chn_dispatch_cmd(chn_ctl, RELAY_CHN_CMD_STOP); + + // If the last run command is different from the current command, wait for the opposite inertia time + chn_ctl->pending_cmd = cmd; + relay_chn_state_t new_state = cmd == RELAY_CHN_CMD_FORWARD + ? RELAY_CHN_STATE_FORWARD_PENDING : RELAY_CHN_STATE_REVERSE_PENDING; + relay_chn_update_state(chn_ctl, new_state); + relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, RELAY_CHN_OPPOSITE_INERTIA_MS); + break; + +#if RELAY_CHN_ENABLE_TILTING == 1 + case RELAY_CHN_STATE_TILT_FORWARD: + // Terminate tilting first + relay_chn_tilt_dispatch_cmd(chn_ctl->tilt_ctl, RELAY_CHN_TILT_CMD_STOP); + if (cmd == RELAY_CHN_CMD_FORWARD) { + // Schedule for running forward + chn_ctl->pending_cmd = cmd; + relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_FORWARD_PENDING); + relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, RELAY_CHN_OPPOSITE_INERTIA_MS); + } else if (cmd == RELAY_CHN_CMD_REVERSE) { + // Run directly since it is the same direction + relay_chn_dispatch_cmd(chn_ctl, cmd); + relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_REVERSE); + } + break; + case RELAY_CHN_STATE_TILT_REVERSE: + // Terminate tilting first + relay_chn_tilt_dispatch_cmd(chn_ctl->tilt_ctl, RELAY_CHN_TILT_CMD_STOP); + if (cmd == RELAY_CHN_CMD_FORWARD) { + // Run directly since it is the same direction + relay_chn_dispatch_cmd(chn_ctl, cmd); + relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_FORWARD); + } else if (cmd == RELAY_CHN_CMD_REVERSE) { + // Schedule for running reverse + chn_ctl->pending_cmd = cmd; + relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_REVERSE_PENDING); + relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, RELAY_CHN_OPPOSITE_INERTIA_MS); + } + break; +#endif + + default: ESP_LOGD(TAG, "relay_chn_evaluate: Unknown relay channel state!"); + } +} + +#if RELAY_CHN_COUNT > 1 +bool relay_chn_is_channel_id_valid(uint8_t chn_id) +{ + bool valid = (chn_id < RELAY_CHN_COUNT) || chn_id == RELAY_CHN_ID_ALL; + if (!valid) { + ESP_LOGE(TAG, "Invalid channel ID: %d", chn_id); + } + return valid; +} +#endif // RELAY_CHN_COUNT > 1 + + +static void relay_chn_execute_stop(relay_chn_ctl_t *chn_ctl) +{ + if (relay_chn_output_stop(chn_ctl->output) != ESP_OK) { + ESP_LOGE(TAG, "relay_chn_execute_stop: Failed to output stop for relay channel #%d!", chn_ctl->id); + } + relay_chn_state_t previous_state = chn_ctl->state; + relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_STOPPED); + + // If there is any pending command, cancel it since the STOP command is issued right after it + chn_ctl->pending_cmd = RELAY_CHN_CMD_NONE; + // Invalidate the channel's timer if it is active + esp_timer_stop(chn_ctl->inertia_timer); + + // Save the last run time only if the previous state was either STATE FORWARD + // or STATE_REVERSE. Then schedule a free command. + if (previous_state == RELAY_CHN_STATE_FORWARD || previous_state == RELAY_CHN_STATE_REVERSE) { + // Record the command's last run time + relay_chn_run_info_set_last_run_cmd_time_ms(chn_ctl->run_info, (uint32_t)(esp_timer_get_time() / 1000)); + // Schedule a free command for the channel + chn_ctl->pending_cmd = RELAY_CHN_CMD_IDLE; + relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, RELAY_CHN_OPPOSITE_INERTIA_MS); + } else { + // If the channel was not running one of the run or fwd, issue a free command immediately + relay_chn_dispatch_cmd(chn_ctl, RELAY_CHN_CMD_IDLE); + } +} + +static void relay_chn_execute_forward(relay_chn_ctl_t *chn_ctl) +{ + if (relay_chn_output_forward(chn_ctl->output) != ESP_OK) { + ESP_LOGE(TAG, "relay_chn_execute_forward: Failed to output forward for relay channel #%d!", chn_ctl->id); + return; + } + relay_chn_run_info_set_last_run_cmd(chn_ctl->run_info, RELAY_CHN_CMD_FORWARD); + relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_FORWARD); +} + +static void relay_chn_execute_reverse(relay_chn_ctl_t *chn_ctl) +{ + if (relay_chn_output_reverse(chn_ctl->output) != ESP_OK) { + ESP_LOGE(TAG, "relay_chn_execute_reverse: Failed to output reverse for relay channel #%d!", chn_ctl->id); + return; + } + relay_chn_run_info_set_last_run_cmd(chn_ctl->run_info, RELAY_CHN_CMD_REVERSE); + relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_REVERSE); +} + +static void relay_chn_execute_flip(relay_chn_ctl_t *chn_ctl) +{ + relay_chn_output_flip(chn_ctl->output); + // Set an inertia on the channel to prevent any immediate movement + chn_ctl->pending_cmd = RELAY_CHN_CMD_IDLE; + relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, RELAY_CHN_OPPOSITE_INERTIA_MS); +} + +void relay_chn_execute_idle(relay_chn_ctl_t *chn_ctl) +{ + chn_ctl->pending_cmd = RELAY_CHN_CMD_NONE; + // Invalidate the channel's timer if it is active + esp_timer_stop(chn_ctl->inertia_timer); + relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_IDLE); +} + +static void relay_chn_event_handler(void* handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data) +{ + relay_chn_ctl_t* chn_ctl = *(relay_chn_ctl_t**) event_data; + ESP_RETURN_VOID_ON_FALSE(chn_ctl != NULL, TAG, "event_data is NULL"); + ESP_LOGD(TAG, "relay_chn_event_handler: Command: %s", relay_chn_cmd_str(event_id)); + + switch (event_id) { + case RELAY_CHN_CMD_STOP: + relay_chn_execute_stop(chn_ctl); + break; + case RELAY_CHN_CMD_FORWARD: + relay_chn_execute_forward(chn_ctl); + break; + case RELAY_CHN_CMD_REVERSE: + relay_chn_execute_reverse(chn_ctl); + break; + case RELAY_CHN_CMD_FLIP: + relay_chn_execute_flip(chn_ctl); + break; + case RELAY_CHN_CMD_IDLE: + relay_chn_execute_idle(chn_ctl); + break; + default: + ESP_LOGD(TAG, "Unknown relay channel command!"); + } +} + +char *relay_chn_cmd_str(relay_chn_cmd_t cmd) +{ + switch (cmd) { + case RELAY_CHN_CMD_STOP: + return "STOP"; + case RELAY_CHN_CMD_FORWARD: + return "FORWARD"; + case RELAY_CHN_CMD_REVERSE: + return "REVERSE"; + case RELAY_CHN_CMD_FLIP: + return "FLIP"; + case RELAY_CHN_CMD_IDLE: + return "IDLE"; + default: + return "UNKNOWN"; + } +} + +char *relay_chn_state_str(relay_chn_state_t state) +{ + switch (state) { + case RELAY_CHN_STATE_IDLE: + return "IDLE"; + case RELAY_CHN_STATE_STOPPED: + return "STOPPED"; + case RELAY_CHN_STATE_FORWARD: + return "FORWARD"; + case RELAY_CHN_STATE_REVERSE: + return "REVERSE"; + case RELAY_CHN_STATE_FORWARD_PENDING: + return "FORWARD_PENDING"; + case RELAY_CHN_STATE_REVERSE_PENDING: + return "REVERSE_PENDING"; +#if RELAY_CHN_ENABLE_TILTING == 1 + case RELAY_CHN_STATE_TILT_FORWARD: + return "TILT_FORWARD"; + case RELAY_CHN_STATE_TILT_REVERSE: + return "TILT_REVERSE"; +#endif + default: + return "UNKNOWN"; + } +} \ No newline at end of file diff --git a/src/relay_chn_ctl_multi.c b/src/relay_chn_ctl_multi.c new file mode 100644 index 0000000..13c97e8 --- /dev/null +++ b/src/relay_chn_ctl_multi.c @@ -0,0 +1,137 @@ +/* + * SPDX-FileCopyrightText: 2025 Kozmotronik Tech + * + * SPDX-License-Identifier: MIT + */ + +#include "esp_check.h" +#include "relay_chn_priv_types.h" +#include "relay_chn_core.h" +#include "relay_chn_ctl.h" +#include "relay_chn_output.h" + +static const char *TAG = "RELAY_CHN_CTL"; + +static relay_chn_ctl_t chn_ctls[RELAY_CHN_COUNT]; + + +esp_err_t relay_chn_ctl_init(relay_chn_output_t *outputs, relay_chn_run_info_t *run_infos) +{ + // Initialize all relay channels + esp_err_t ret; + for (int i = 0; i < RELAY_CHN_COUNT; i++) { + relay_chn_ctl_t* chn_ctl = &chn_ctls[i]; + relay_chn_output_t* output = &outputs[i]; + relay_chn_run_info_t* run_info = &run_infos[i]; + + chn_ctl->id = i; + chn_ctl->state = RELAY_CHN_STATE_IDLE; + chn_ctl->pending_cmd = RELAY_CHN_CMD_NONE; + + chn_ctl->output = output; + chn_ctl->run_info = run_info; + ret = relay_chn_init_timer(chn_ctl); // Create direction change inertia timer + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create relay channel timer for channel %d", i); + } + return ESP_OK; +} + +void relay_chn_ctl_deinit() +{ + for (int i = 0; i < RELAY_CHN_COUNT; i++) { + relay_chn_ctl_t* chn_ctl = &chn_ctls[i]; + if (chn_ctl->inertia_timer != NULL) { + esp_timer_delete(chn_ctl->inertia_timer); + chn_ctl->inertia_timer = NULL; + } + } +} + +relay_chn_state_t relay_chn_ctl_get_state(uint8_t chn_id) +{ + if (!relay_chn_is_channel_id_valid(chn_id) || chn_id == RELAY_CHN_ID_ALL) { + return RELAY_CHN_STATE_UNDEFINED; + } + return chn_ctls[chn_id].state; +} + +char *relay_chn_ctl_get_state_str(uint8_t chn_id) +{ + if (!relay_chn_is_channel_id_valid(chn_id) || chn_id == RELAY_CHN_ID_ALL) { + return relay_chn_state_str(RELAY_CHN_STATE_UNDEFINED); + } + return relay_chn_state_str(chn_ctls[chn_id].state); +} + +static void relay_chn_ctl_issue_cmd_on_all_channels(relay_chn_cmd_t cmd) +{ + for (int i = 0; i < RELAY_CHN_COUNT; i++) { + relay_chn_issue_cmd(&chn_ctls[i], cmd); + } +} + +void relay_chn_ctl_run_forward(uint8_t chn_id) +{ + if (!relay_chn_is_channel_id_valid(chn_id)) return; + + if (chn_id == RELAY_CHN_ID_ALL) { + relay_chn_ctl_issue_cmd_on_all_channels(RELAY_CHN_CMD_FORWARD); + return; + } + relay_chn_issue_cmd(&chn_ctls[chn_id], RELAY_CHN_CMD_FORWARD); +} + +void relay_chn_ctl_run_reverse(uint8_t chn_id) +{ + if (!relay_chn_is_channel_id_valid(chn_id)) return; + + if (chn_id == RELAY_CHN_ID_ALL) { + relay_chn_ctl_issue_cmd_on_all_channels(RELAY_CHN_CMD_REVERSE); + return; + } + relay_chn_issue_cmd(&chn_ctls[chn_id], RELAY_CHN_CMD_REVERSE); +} + +void relay_chn_ctl_stop(uint8_t chn_id) +{ + if (!relay_chn_is_channel_id_valid(chn_id)) return; + + if (chn_id == RELAY_CHN_ID_ALL) { + relay_chn_ctl_issue_cmd_on_all_channels(RELAY_CHN_CMD_STOP); + return; + } + relay_chn_issue_cmd(&chn_ctls[chn_id], RELAY_CHN_CMD_STOP); +} + +void relay_chn_ctl_flip_direction(uint8_t chn_id) +{ + if (!relay_chn_is_channel_id_valid(chn_id)) return; + + if (chn_id == RELAY_CHN_ID_ALL) { + relay_chn_ctl_issue_cmd_on_all_channels(RELAY_CHN_CMD_FLIP); + return; + } + relay_chn_issue_cmd(&chn_ctls[chn_id], RELAY_CHN_CMD_FLIP); +} + +relay_chn_direction_t relay_chn_ctl_get_direction(uint8_t chn_id) +{ + if (!relay_chn_is_channel_id_valid(chn_id)) { + return RELAY_CHN_DIRECTION_DEFAULT; + } + relay_chn_ctl_t *chn_ctl = &chn_ctls[chn_id]; + return relay_chn_output_get_direction(chn_ctl->output); +} + +relay_chn_ctl_t *relay_chn_ctl_get(uint8_t chn_id) +{ + if (!relay_chn_is_channel_id_valid(chn_id)) { + return NULL; + } + return &chn_ctls[chn_id]; +} + +relay_chn_ctl_t *relay_chn_ctl_get_all(void) +{ + return chn_ctls; +} \ No newline at end of file diff --git a/src/relay_chn_ctl_single.c b/src/relay_chn_ctl_single.c new file mode 100644 index 0000000..0435b33 --- /dev/null +++ b/src/relay_chn_ctl_single.c @@ -0,0 +1,76 @@ +/* + * SPDX-FileCopyrightText: 2025 Kozmotronik Tech + * + * SPDX-License-Identifier: MIT + */ + +#include "relay_chn_priv_types.h" +#include "relay_chn_core.h" +#include "relay_chn_ctl.h" +#include "relay_chn_output.h" + + +static relay_chn_ctl_t chn_ctl; + + +esp_err_t relay_chn_ctl_init(relay_chn_output_t *output, relay_chn_run_info_t *run_info) +{ + // Initialize the relay channel + chn_ctl.id = 0; // Single channel, so ID is 0 + chn_ctl.state = RELAY_CHN_STATE_IDLE; + chn_ctl.pending_cmd = RELAY_CHN_CMD_NONE; + chn_ctl.output = output; + chn_ctl.run_info = run_info; + return relay_chn_init_timer(&chn_ctl); // Create direction change inertia timer +} + + +void relay_chn_ctl_deinit() +{ + if (chn_ctl.inertia_timer != NULL) { + esp_timer_delete(chn_ctl.inertia_timer); + chn_ctl.inertia_timer = NULL; + } +} + +/* relay_chn APIs */ +relay_chn_state_t relay_chn_ctl_get_state() +{ + return chn_ctl.state; +} + +char *relay_chn_ctl_get_state_str() +{ + return relay_chn_state_str(chn_ctl.state); +} + +void relay_chn_ctl_run_forward() +{ + relay_chn_issue_cmd(&chn_ctl, RELAY_CHN_CMD_FORWARD); +} + +void relay_chn_ctl_run_reverse() +{ + relay_chn_issue_cmd(&chn_ctl, RELAY_CHN_CMD_REVERSE); +} + +void relay_chn_ctl_stop() +{ + relay_chn_issue_cmd(&chn_ctl, RELAY_CHN_CMD_STOP); +} + +void relay_chn_ctl_flip_direction() +{ + relay_chn_issue_cmd(&chn_ctl, RELAY_CHN_CMD_FLIP); +} + +relay_chn_direction_t relay_chn_ctl_get_direction() +{ + return relay_chn_output_get_direction(chn_ctl.output); +} +/* relay_chn APIs */ + +relay_chn_ctl_t *relay_chn_ctl_get() +{ + return &chn_ctl; +} \ No newline at end of file diff --git a/src/relay_chn_output.c b/src/relay_chn_output.c new file mode 100644 index 0000000..4f819d1 --- /dev/null +++ b/src/relay_chn_output.c @@ -0,0 +1,168 @@ +/* + * SPDX-FileCopyrightText: 2025 Kozmotronik Tech + * + * SPDX-License-Identifier: MIT + */ + +#include "esp_check.h" +#include "esp_log.h" +#include "relay_chn_defs.h" +#include "relay_chn_output.h" +#include "relay_chn_core.h" + + +static const char *TAG = "RELAY_CHN_OUTPUT"; + +#if RELAY_CHN_COUNT > 1 +static relay_chn_output_t outputs[RELAY_CHN_COUNT]; +#else +static relay_chn_output_t output; +#endif + + +static esp_err_t relay_chn_output_check_gpio_capabilities(uint8_t gpio_count) +{ + // Check if the device's GPIOs are enough for the number of channels + if (RELAY_CHN_COUNT > (GPIO_PIN_COUNT / 2)) { + ESP_LOGE(TAG, "Not enough GPIOs for the number of channels!"); + ESP_LOGE(TAG, "Max available num of channels: %d, requested channels: %d", GPIO_PIN_COUNT / 2, RELAY_CHN_COUNT); + return ESP_ERR_INVALID_ARG; + } + + // Check if the provided GPIOs correspond to the number of channels + if (gpio_count != RELAY_CHN_COUNT * 2) { + ESP_LOGE(TAG, "Invalid number of GPIOs provided: %d", gpio_count); + ESP_LOGE(TAG, "Expected number of GPIOs: %d", RELAY_CHN_COUNT * 2); + return ESP_ERR_INVALID_ARG; + } + return ESP_OK; +} + +static esp_err_t relay_chn_output_ctl_init(relay_chn_output_t *output, gpio_num_t forward_pin, gpio_num_t reverse_pin) +{ + ESP_RETURN_ON_FALSE(GPIO_IS_VALID_OUTPUT_GPIO(forward_pin), ESP_ERR_INVALID_ARG, TAG, + "Invalid GPIO pin number for forward_pin: %d", forward_pin); + ESP_RETURN_ON_FALSE(GPIO_IS_VALID_OUTPUT_GPIO(reverse_pin), ESP_ERR_INVALID_ARG, TAG, + "Invalid GPIO pin number for reverse_pin: %d", reverse_pin); + + // Check if the GPIOs are valid + esp_err_t ret; + // Initialize the GPIOs + ret = gpio_reset_pin(forward_pin); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to reset GPIO forward pin: %d", forward_pin); + ret = gpio_set_direction(forward_pin, GPIO_MODE_OUTPUT); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set GPIO direction for forward pin: %d", forward_pin); + + ret = gpio_reset_pin(reverse_pin); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to reset GPIO reverse pin: %d", reverse_pin); + ret = gpio_set_direction(reverse_pin, GPIO_MODE_OUTPUT); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set GPIO direction for reverse pin: %d", reverse_pin); + // Initialize the GPIOs + + // Initialize the relay channel output + output->forward_pin = forward_pin; + output->reverse_pin = reverse_pin; + output->direction = RELAY_CHN_DIRECTION_DEFAULT; + return ESP_OK; +} + +esp_err_t relay_chn_output_init(const uint8_t* gpio_map, uint8_t gpio_count) +{ + esp_err_t ret; + ret = relay_chn_output_check_gpio_capabilities(gpio_count); + ESP_RETURN_ON_ERROR(ret, TAG, "Device does not support the provided GPIOs"); + +#if RELAY_CHN_COUNT > 1 + for (int i = 0; i < RELAY_CHN_COUNT; i++) { + relay_chn_output_t* output = &outputs[i]; + int gpio_index = i << 1; // gpio_index = i * 2 + gpio_num_t forward_pin = (gpio_num_t) gpio_map[gpio_index]; + gpio_num_t reverse_pin = (gpio_num_t) gpio_map[gpio_index + 1]; + + ret = relay_chn_output_ctl_init(output, forward_pin, reverse_pin); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize relay channel %d", i); + } +#else + ret = relay_chn_output_ctl_init(&output, gpio_map[0], gpio_map[1]); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize relay channel"); +#endif + return ESP_OK; +} + +static void relay_chn_output_ctl_deinit(relay_chn_output_t *output) +{ + gpio_reset_pin(output->forward_pin); + gpio_reset_pin(output->reverse_pin); +} + +void relay_chn_output_deinit() +{ +#if RELAY_CHN_COUNT > 1 + for (int i = 0; i < RELAY_CHN_COUNT; i++) { + relay_chn_output_ctl_deinit(&outputs[i]); + } +#else + relay_chn_output_ctl_deinit(&output); +#endif // RELAY_CHN_COUNT > 1 +} + +#if RELAY_CHN_COUNT > 1 +relay_chn_output_t *relay_chn_output_get(uint8_t chn_id) +{ + if (!relay_chn_is_channel_id_valid(chn_id)) { + return NULL; + } + return &outputs[chn_id]; +} + +relay_chn_output_t *relay_chn_output_get_all(void) +{ + return outputs; +} +#else +relay_chn_output_t *relay_chn_output_get(void) +{ + return &output; +} +#endif // RELAY_CHN_COUNT > 1 + +esp_err_t relay_chn_output_stop(relay_chn_output_t *output) +{ + esp_err_t ret; + ret = gpio_set_level(output->forward_pin, 0); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set forward pin to LOW"); + return gpio_set_level(output->reverse_pin, 0); +} + +esp_err_t relay_chn_output_forward(relay_chn_output_t *output) +{ + esp_err_t ret; + ret = gpio_set_level(output->forward_pin, 1); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set forward pin to HIGH"); + return gpio_set_level(output->reverse_pin, 0); +} + +esp_err_t relay_chn_output_reverse(relay_chn_output_t *output) +{ + esp_err_t ret; + ret = gpio_set_level(output->forward_pin, 0); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set forward pin to LOW"); + return gpio_set_level(output->reverse_pin, 1); +} + +void relay_chn_output_flip(relay_chn_output_t *output) +{ + // Flip the output GPIO pins + gpio_num_t temp = output->forward_pin; + output->forward_pin = output->reverse_pin; + output->reverse_pin = temp; + // Flip the direction + output->direction = (output->direction == RELAY_CHN_DIRECTION_DEFAULT) + ? RELAY_CHN_DIRECTION_FLIPPED + : RELAY_CHN_DIRECTION_DEFAULT; +} + +relay_chn_direction_t relay_chn_output_get_direction(relay_chn_output_t *output) +{ + return output->direction; +} \ No newline at end of file diff --git a/src/relay_chn_run_info.c b/src/relay_chn_run_info.c new file mode 100644 index 0000000..e021023 --- /dev/null +++ b/src/relay_chn_run_info.c @@ -0,0 +1,74 @@ +/* + * SPDX-FileCopyrightText: 2025 Kozmotronik Tech + * + * SPDX-License-Identifier: MIT + */ + +#include "relay_chn_core.h" +#include "relay_chn_run_info.h" + + +#if RELAY_CHN_COUNT > 1 +static relay_chn_run_info_t run_infos[RELAY_CHN_COUNT]; +#else +static relay_chn_run_info_t run_info; +#endif + +void relay_chn_run_info_init() +{ +#if RELAY_CHN_COUNT > 1 + for (int i = 0; i < RELAY_CHN_COUNT; i++) { + run_infos[i].last_run_cmd = RELAY_CHN_CMD_NONE; + run_infos[i].last_run_cmd_time_ms = 0; + } +#else + run_info.last_run_cmd = RELAY_CHN_CMD_NONE; + run_info.last_run_cmd_time_ms = 0; +#endif +} + +#if RELAY_CHN_COUNT > 1 +relay_chn_run_info_t *relay_chn_run_info_get(uint8_t chn_id) +{ + if (!relay_chn_is_channel_id_valid(chn_id)) { + return NULL; + } + return &run_infos[chn_id]; +} + +relay_chn_run_info_t *relay_chn_run_info_get_all() +{ + return run_infos; +} +#else +relay_chn_run_info_t *relay_chn_run_info_get() +{ + return &run_info; +} +#endif // RELAY_CHN_COUNT > 1 + +relay_chn_cmd_t relay_chn_run_info_get_last_run_cmd(relay_chn_run_info_t *run_info) +{ + return run_info == NULL ? RELAY_CHN_CMD_NONE : run_info->last_run_cmd; +} + +void relay_chn_run_info_set_last_run_cmd(relay_chn_run_info_t *run_info, relay_chn_cmd_t cmd) +{ + if (!run_info) { + return; + } + run_info->last_run_cmd = cmd; +} + +uint32_t relay_chn_run_info_get_last_run_cmd_time_ms(relay_chn_run_info_t *run_info) +{ + return run_info == NULL ? 0 : run_info->last_run_cmd_time_ms; +} + +void relay_chn_run_info_set_last_run_cmd_time_ms(relay_chn_run_info_t *run_info, uint32_t time_ms) +{ + if (!run_info) { + return; + } + run_info->last_run_cmd_time_ms = time_ms; +} \ No newline at end of file diff --git a/src/relay_chn_tilt.c b/src/relay_chn_tilt.c new file mode 100644 index 0000000..de7610f --- /dev/null +++ b/src/relay_chn_tilt.c @@ -0,0 +1,634 @@ +/* + * SPDX-FileCopyrightText: 2025 Kozmotronik Tech + * + * SPDX-License-Identifier: MIT + */ + +#include "esp_check.h" +#include "relay_chn_core.h" +#include "relay_chn_output.h" +#include "relay_chn_run_info.h" +#include "relay_chn_tilt.h" + + +static const char *TAG = "RELAY_CHN_TILT"; + +/**@{*/ +/* + * Tilt Pattern Timing Definitions + * + * The min and max timing definitions as well as the default timing definitions. + * These definitions are used to define and adjust the tilt sensitivity. + */ +#define RELAY_CHN_TILT_RUN_MIN_MS 50 +#define RELAY_CHN_TILT_RUN_MAX_MS 10 +#define RELAY_CHN_TILT_PAUSE_MIN_MS 450 +#define RELAY_CHN_TILT_PAUSE_MAX_MS 90 + +#define RELAY_CHN_TILT_DEFAULT_RUN_MS 15 +#define RELAY_CHN_TILT_DEFAULT_PAUSE_MS 150 + +#define RELAY_CHN_TILT_DEFAULT_SENSITIVITY \ + ( (RELAY_CHN_TILT_DEFAULT_RUN_MS - RELAY_CHN_TILT_RUN_MIN_MS) \ + * 100 / (RELAY_CHN_TILT_RUN_MAX_MS - RELAY_CHN_TILT_RUN_MIN_MS) ) +/**@}*/ + +ESP_EVENT_DEFINE_BASE(RELAY_CHN_TILT_CMD_EVENT_BASE); + + +/// @brief Tilt steps. +typedef enum { + RELAY_CHN_TILT_STEP_NONE, /*!< No step */ + RELAY_CHN_TILT_STEP_PENDING, /*!< Pending step */ + RELAY_CHN_TILT_STEP_MOVE, /*!< Move step. Tilt is driving either for forward or reverse */ + RELAY_CHN_TILT_STEP_PAUSE /*!< Pause step. Tilt is paused */ +} relay_chn_tilt_step_t; + +/// @brief Tilt timing structure to manage tilt pattern timing. +typedef struct { + uint8_t sensitivity; /*!< Tilt sensitivity in percentage (%) */ + uint32_t move_time_ms; /*!< Move time in milliseconds */ + uint32_t pause_time_ms; /*!< Pause time in milliseconds */ +} relay_chn_tilt_timing_t; + +/// @brief Tilt counter structure to manage tilt count. +typedef struct { + uint32_t tilt_forward_count; /*!< Tilt forward count */ + uint32_t tilt_reverse_count; /*!< Tilt reverse count */ +} relay_chn_tilt_counter_t; + +/// @brief Tilt control structure to manage tilt operations. +typedef struct relay_chn_tilt_ctl { + relay_chn_ctl_t *chn_ctl; /*!< The relay channel control structure */ + relay_chn_tilt_cmd_t cmd; /*!< The tilt command in process */ + relay_chn_tilt_step_t step; /*!< Current tilt step */ + relay_chn_tilt_timing_t tilt_timing; /*!< Tilt timing structure */ + relay_chn_tilt_counter_t tilt_counter; /*!< Tilt counter structure */ + esp_timer_handle_t tilt_timer; /*!< Tilt timer handle */ +} relay_chn_tilt_ctl_t; + + +#if RELAY_CHN_COUNT > 1 +static relay_chn_tilt_ctl_t tilt_ctls[RELAY_CHN_COUNT]; +#else +static relay_chn_tilt_ctl_t tilt_ctl; +#endif + + +esp_err_t relay_chn_tilt_dispatch_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_tilt_cmd_t cmd) +{ + if (cmd == RELAY_CHN_TILT_CMD_NONE) return ESP_ERR_INVALID_ARG; + + // Since the event_loop library creates a deep copy of the event data, + // and we need to pass the pointer of the relevant tilt control, here we need + // to pass the pointer to the pointer of the tilt_control (&tilt_ctl) so that + // the pointer value is preserved in the event data. + return esp_event_post_to(relay_chn_event_loop, + RELAY_CHN_TILT_CMD_EVENT_BASE, + cmd, + &tilt_ctl, + sizeof(tilt_ctl), portMAX_DELAY); +} + +// Returns the required timing before tilting depending on the last run. +static uint32_t relay_chn_tilt_get_required_timing_before_tilting(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_tilt_cmd_t cmd) +{ + relay_chn_cmd_t last_run_cmd = relay_chn_run_info_get_last_run_cmd(tilt_ctl->chn_ctl->run_info); + if (cmd == RELAY_CHN_TILT_CMD_FORWARD && last_run_cmd == RELAY_CHN_CMD_REVERSE) + return 0; + else if (cmd == RELAY_CHN_TILT_CMD_REVERSE && last_run_cmd == RELAY_CHN_CMD_FORWARD) + return 0; + + uint32_t last_run_cmd_time_ms = relay_chn_run_info_get_last_run_cmd_time_ms(tilt_ctl->chn_ctl->run_info); + uint32_t inertia_time_passed_ms = (uint32_t) (esp_timer_get_time() / 1000) - last_run_cmd_time_ms; + return RELAY_CHN_OPPOSITE_INERTIA_MS - inertia_time_passed_ms; +} + +// Issue a tilt command to a specific relay channel. +static void relay_chn_tilt_issue_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_tilt_cmd_t cmd) +{ + if (relay_chn_run_info_get_last_run_cmd(tilt_ctl->chn_ctl->run_info) == RELAY_CHN_CMD_NONE) { + // Do not tilt if the channel hasn't been run before + ESP_LOGD(TAG, "relay_chn_tilt_issue_cmd: Tilt will not be executed since the channel hasn't been run yet"); + return; + } + + if (tilt_ctl->cmd == cmd) { + ESP_LOGD(TAG, "relay_chn_tilt_issue_cmd: There is already a tilt command in progress!"); + return; + } + + // Set the command that will be processed + tilt_ctl->cmd = cmd; + switch (tilt_ctl->chn_ctl->state) { + case RELAY_CHN_STATE_IDLE: + // Relay channel is free, tilt can be issued immediately + relay_chn_tilt_dispatch_cmd(tilt_ctl, cmd); + break; + + case RELAY_CHN_STATE_FORWARD_PENDING: + case RELAY_CHN_STATE_REVERSE_PENDING: + // Issue a stop command first so that the timer and pending cmd get cleared + relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP); + // break not put intentionally + case RELAY_CHN_STATE_STOPPED: { + // Check if channel needs timing before tilting + uint32_t req_timing_ms = relay_chn_tilt_get_required_timing_before_tilting(tilt_ctl, cmd); + if (req_timing_ms == 0) { + relay_chn_tilt_dispatch_cmd(tilt_ctl, cmd); + } else { + // Channel needs timing before running tilting action, schedule it + tilt_ctl->step = RELAY_CHN_TILT_STEP_PENDING; + relay_chn_start_esp_timer_once(tilt_ctl->tilt_timer, req_timing_ms); + } + break; + } + + case RELAY_CHN_STATE_FORWARD: + if (cmd == RELAY_CHN_TILT_CMD_FORWARD) { + // Stop the running channel first + relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP); + // Schedule for tilting + tilt_ctl->step = RELAY_CHN_TILT_STEP_PENDING; + relay_chn_start_esp_timer_once(tilt_ctl->tilt_timer, RELAY_CHN_OPPOSITE_INERTIA_MS); + } else if (cmd == RELAY_CHN_TILT_CMD_REVERSE) { + // Stop the running channel first + relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP); + // If the tilt cmd is TILT_REVERSE then dispatch it immediately + relay_chn_tilt_dispatch_cmd(tilt_ctl, cmd); + } + break; + + case RELAY_CHN_STATE_REVERSE: + if (cmd == RELAY_CHN_TILT_CMD_REVERSE) { + // Stop the running channel first + relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP); + // Schedule for tilting + tilt_ctl->step = RELAY_CHN_TILT_STEP_PENDING; + relay_chn_start_esp_timer_once(tilt_ctl->tilt_timer, RELAY_CHN_OPPOSITE_INERTIA_MS); + } else if (cmd == RELAY_CHN_TILT_CMD_FORWARD) { + // Stop the running channel first + relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP); + // If the tilt cmd is TILT_FORWARD then dispatch it immediately + relay_chn_tilt_dispatch_cmd(tilt_ctl, cmd); + } + break; + + default: + ESP_LOGD(TAG, "relay_chn_tilt_issue_cmd: Unexpected relay channel state: %s!", relay_chn_state_str(tilt_ctl->chn_ctl->state)); + } +} + +static void relay_chn_tilt_issue_auto(relay_chn_tilt_ctl_t *tilt_ctl) +{ + relay_chn_cmd_t last_run_cmd = relay_chn_run_info_get_last_run_cmd(tilt_ctl->chn_ctl->run_info); + if (last_run_cmd == RELAY_CHN_CMD_FORWARD || tilt_ctl->chn_ctl->state == RELAY_CHN_STATE_FORWARD) { + relay_chn_tilt_issue_cmd(tilt_ctl, RELAY_CHN_TILT_CMD_FORWARD); + } + else if (last_run_cmd == RELAY_CHN_CMD_REVERSE || tilt_ctl->chn_ctl->state == RELAY_CHN_STATE_REVERSE) { + relay_chn_tilt_issue_cmd(tilt_ctl, RELAY_CHN_TILT_CMD_REVERSE); + } +} + +#if RELAY_CHN_COUNT > 1 +void relay_chn_tilt_auto(uint8_t chn_id) +{ + if (!relay_chn_is_channel_id_valid(chn_id)) { + return; + } + + // Execute for all channels + if (chn_id == RELAY_CHN_ID_ALL) { + for (int i = 0; i < RELAY_CHN_COUNT; i++) { + relay_chn_tilt_issue_auto(&tilt_ctls[i]); + } + } + // Execute for a single channel + else { + relay_chn_tilt_ctl_t* tilt_ctl = &tilt_ctls[chn_id]; + relay_chn_tilt_issue_auto(tilt_ctl); + } +} + +static void relay_chn_tilt_issue_cmd_on_all_channels(relay_chn_tilt_cmd_t cmd) +{ + for (int i = 0; i < RELAY_CHN_COUNT; i++) { + relay_chn_tilt_ctl_t* tilt_ctl = &tilt_ctls[i]; + relay_chn_tilt_issue_cmd(tilt_ctl, cmd); + } +} + +void relay_chn_tilt_forward(uint8_t chn_id) +{ + if (!relay_chn_is_channel_id_valid(chn_id)) { + return; + } + + if (chn_id == RELAY_CHN_ID_ALL) + relay_chn_tilt_issue_cmd_on_all_channels(RELAY_CHN_TILT_CMD_FORWARD); + else { + relay_chn_tilt_ctl_t* tilt_ctl = &tilt_ctls[chn_id]; + relay_chn_tilt_issue_cmd(tilt_ctl, RELAY_CHN_TILT_CMD_FORWARD); + } +} + +void relay_chn_tilt_reverse(uint8_t chn_id) +{ + if (!relay_chn_is_channel_id_valid(chn_id)) { + return; + } + + if (chn_id == RELAY_CHN_ID_ALL) + relay_chn_tilt_issue_cmd_on_all_channels(RELAY_CHN_TILT_CMD_REVERSE); + else { + relay_chn_tilt_ctl_t* tilt_ctl = &tilt_ctls[chn_id]; + relay_chn_tilt_issue_cmd(tilt_ctl, RELAY_CHN_TILT_CMD_REVERSE); + } +} + +void relay_chn_tilt_stop(uint8_t chn_id) +{ + if (!relay_chn_is_channel_id_valid(chn_id)) { + return; + } + + if (chn_id == RELAY_CHN_ID_ALL) { + for (int i = 0; i < RELAY_CHN_COUNT; i++) { + relay_chn_tilt_dispatch_cmd(&tilt_ctls[i], RELAY_CHN_TILT_CMD_STOP); + } + } + else { + relay_chn_tilt_dispatch_cmd(&tilt_ctls[chn_id], RELAY_CHN_TILT_CMD_STOP); + } +} + +#else // RELAY_CHN_COUNT > 1 + +void relay_chn_tilt_auto() +{ + relay_chn_tilt_issue_auto(&tilt_ctl); +} + +void relay_chn_tilt_forward() +{ + relay_chn_tilt_issue_cmd(&tilt_ctl, RELAY_CHN_TILT_CMD_FORWARD); +} + +void relay_chn_tilt_reverse() +{ + relay_chn_tilt_issue_cmd(&tilt_ctl, RELAY_CHN_TILT_CMD_REVERSE); +} + +void relay_chn_tilt_stop() +{ + relay_chn_tilt_dispatch_cmd(&tilt_ctl, RELAY_CHN_TILT_CMD_STOP); +} +#endif // RELAY_CHN_COUNT > 1 + +static void relay_chn_tilt_set_timing_values(relay_chn_tilt_timing_t *tilt_timing, + uint8_t sensitivity, + uint32_t run_time_ms, + uint32_t pause_time_ms) +{ + tilt_timing->sensitivity = sensitivity; + tilt_timing->move_time_ms = run_time_ms; + tilt_timing->pause_time_ms = pause_time_ms; +} + +static void _relay_chn_tilt_sensitivity_set(relay_chn_tilt_ctl_t *tilt_ctl, uint8_t sensitivity) +{ + if (sensitivity >= 100) { + relay_chn_tilt_set_timing_values(&tilt_ctl->tilt_timing, + 100, + RELAY_CHN_TILT_RUN_MAX_MS, + RELAY_CHN_TILT_PAUSE_MAX_MS); + } + else if (sensitivity == 0) { + relay_chn_tilt_set_timing_values(&tilt_ctl->tilt_timing, + 0, + RELAY_CHN_TILT_RUN_MIN_MS, + RELAY_CHN_TILT_PAUSE_MIN_MS); + } + else { + // Compute the new timing values from the sensitivity percent value by using linear interpolation + uint32_t tilt_run_time_ms = 0, tilt_pause_time_ms = 0; + tilt_run_time_ms = RELAY_CHN_TILT_RUN_MIN_MS + (sensitivity * (RELAY_CHN_TILT_RUN_MAX_MS - RELAY_CHN_TILT_RUN_MIN_MS) / 100); + tilt_pause_time_ms = RELAY_CHN_TILT_PAUSE_MIN_MS + (sensitivity * (RELAY_CHN_TILT_PAUSE_MAX_MS - RELAY_CHN_TILT_PAUSE_MIN_MS) / 100); + + relay_chn_tilt_set_timing_values(&tilt_ctl->tilt_timing, + sensitivity, + tilt_run_time_ms, + tilt_pause_time_ms); + } +} + +#if RELAY_CHN_COUNT > 1 +void relay_chn_tilt_set_sensitivity(uint8_t chn_id, uint8_t sensitivity) +{ + if (!relay_chn_is_channel_id_valid(chn_id)) { + return; + } + + if (chn_id == RELAY_CHN_ID_ALL) { + for (int i = 0; i < RELAY_CHN_COUNT; i++) { + _relay_chn_tilt_sensitivity_set(&tilt_ctls[i], sensitivity); + } + } + else { + _relay_chn_tilt_sensitivity_set(&tilt_ctls[chn_id], sensitivity); + } +} + +esp_err_t relay_chn_tilt_get_sensitivity(uint8_t chn_id, uint8_t *sensitivity, size_t length) +{ + if (!relay_chn_is_channel_id_valid(chn_id)) { + return ESP_ERR_INVALID_ARG; + } + if (sensitivity == NULL) { + ESP_LOGD(TAG, "relay_chn_tilt_get_sensitivity: sensitivity is NULL"); + return ESP_ERR_INVALID_ARG; + } + if (chn_id == RELAY_CHN_ID_ALL) { + if (length < RELAY_CHN_COUNT) { + ESP_LOGD(TAG, "relay_chn_tilt_get_sensitivity: length is too short to store all sensitivity values"); + return ESP_ERR_INVALID_ARG; + } + + for (int i = 0; i < RELAY_CHN_COUNT; i++) { + sensitivity[i] = tilt_ctls[i].tilt_timing.sensitivity; + } + return ESP_OK; + } + *sensitivity = tilt_ctls[chn_id].tilt_timing.sensitivity; + return ESP_OK; +} + +#else + +void relay_chn_tilt_set_sensitivity(uint8_t sensitivity) +{ + _relay_chn_tilt_sensitivity_set(&tilt_ctl, sensitivity); +} + +uint8_t relay_chn_tilt_get_sensitivity() +{ + return tilt_ctl.tilt_timing.sensitivity; +} +#endif // RELAY_CHN_COUNT > 1 + +void relay_chn_tilt_reset_count(relay_chn_tilt_ctl_t *tilt_ctl) +{ + tilt_ctl->tilt_counter.tilt_forward_count = 0; + tilt_ctl->tilt_counter.tilt_reverse_count = 0; +} + +/** + * @brief Update tilt count automatically and return the current value. + * + * This helper function updates the relevant tilt count depending on the + * last run info and helps the tilt module in deciding whether the requested + * tilt should execute or not. + * This is useful to control reverse tilting particularly. For example: + * - If the channel's last run was FORWARD and a TILT_FORWARD is requested, + * then the tilt counter will count up on the + * relay_chn_tilt_counter_type::tilt_forward_count and the function will + * return the actual count. + * - If the channel's last run was FORWARD and a TILT_REVERSE is requested, + * then the relay_chn_tilt_counter_type::tilt_forward_count will be checked + * against zero first, and then it will count down and return the actual count + * if it is greater than 0, else the function will return 0. + * - If the tilt command is irrelevant then the function will return 0. + * - If the last run is irrelevant then the function will return 0. + * + * @param tilt_ctl The relay channel handle. + * @return uint32_t The actual value of the relevant counter. + * @return 0 if: + * - related counter is already 0. + * - tilt command is irrelevant. + * - last run info is irrelevant. + */ +static uint32_t relay_chn_tilt_count_update(relay_chn_tilt_ctl_t *tilt_ctl) +{ + relay_chn_cmd_t last_run_cmd = relay_chn_run_info_get_last_run_cmd(tilt_ctl->chn_ctl->run_info); + if (last_run_cmd == RELAY_CHN_CMD_FORWARD) { + if (tilt_ctl->cmd == RELAY_CHN_TILT_CMD_FORWARD) { + return ++tilt_ctl->tilt_counter.tilt_forward_count; + } + else if (tilt_ctl->cmd == RELAY_CHN_TILT_CMD_REVERSE) { + if (tilt_ctl->tilt_counter.tilt_forward_count > 0) { + --tilt_ctl->tilt_counter.tilt_forward_count; + // Still should do one more move, return non-zero value + return 1; + } + else + return 0; + } + else { + relay_chn_tilt_reset_count(tilt_ctl); + return 0; + } + } + else if (last_run_cmd == RELAY_CHN_CMD_REVERSE) { + if (tilt_ctl->cmd == RELAY_CHN_TILT_CMD_REVERSE) { + return ++tilt_ctl->tilt_counter.tilt_reverse_count; + } + else if (tilt_ctl->cmd == RELAY_CHN_TILT_CMD_FORWARD) { + if (tilt_ctl->tilt_counter.tilt_reverse_count > 0) { + --tilt_ctl->tilt_counter.tilt_reverse_count; + // Still should do one more move, return non-zero value + return 1; + } + else + return 0; + } + else { + relay_chn_tilt_reset_count(tilt_ctl); + return 0; + } + } + return 0; +} + +static void relay_chn_tilt_execute_stop(relay_chn_tilt_ctl_t *tilt_ctl) +{ + // Stop the channel's timer if active + esp_timer_stop(tilt_ctl->tilt_timer); + // Invalidate tilt cmd and step + tilt_ctl->cmd = RELAY_CHN_TILT_CMD_NONE; + tilt_ctl->step = RELAY_CHN_TILT_STEP_NONE; + // Stop the channel + if (relay_chn_output_stop(tilt_ctl->chn_ctl->output) != ESP_OK) { + ESP_LOGE(TAG, "relay_chn_tilt_execute_stop: Failed to output stop for relay channel #%d!", tilt_ctl->chn_ctl->id); + } + relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP); +} + +static void relay_chn_tilt_execute_forward(relay_chn_tilt_ctl_t *tilt_ctl) +{ + if (relay_chn_output_reverse(tilt_ctl->chn_ctl->output) != ESP_OK) { + ESP_LOGE(TAG, "relay_chn_tilt_execute_forward: Failed to output reverse for relay channel #%d!", tilt_ctl->chn_ctl->id); + // Stop tilting because of the error + relay_chn_tilt_dispatch_cmd(tilt_ctl, RELAY_CHN_TILT_CMD_STOP); + return; + } + // Set the move time timer + relay_chn_start_esp_timer_once(tilt_ctl->tilt_timer, tilt_ctl->tilt_timing.move_time_ms); + // Set to pause step + tilt_ctl->step = RELAY_CHN_TILT_STEP_PAUSE; +} + +static void relay_chn_tilt_execute_reverse(relay_chn_tilt_ctl_t *tilt_ctl) +{ + if (relay_chn_output_forward(tilt_ctl->chn_ctl->output) != ESP_OK) { + ESP_LOGE(TAG, "relay_chn_tilt_execute_reverse: Failed to output forward for relay channel #%d!", tilt_ctl->chn_ctl->id); + // Stop tilting because of the error + relay_chn_tilt_dispatch_cmd(tilt_ctl, RELAY_CHN_TILT_CMD_STOP); + return; + } + // Set the move time timer + relay_chn_start_esp_timer_once(tilt_ctl->tilt_timer, tilt_ctl->tilt_timing.move_time_ms); + // Set to pause step + tilt_ctl->step = RELAY_CHN_TILT_STEP_PAUSE; +} + +static void relay_chn_tilt_execute_pause(relay_chn_tilt_ctl_t *tilt_ctl) +{ + // Pause the channel + if (relay_chn_output_stop(tilt_ctl->chn_ctl->output) != ESP_OK) { + ESP_LOGE(TAG, "relay_chn_tilt_execute_pause: Failed to output stop for relay channel #%d!", tilt_ctl->chn_ctl->id); + // Stop tilting because of the error + relay_chn_tilt_dispatch_cmd(tilt_ctl, RELAY_CHN_TILT_CMD_STOP); + return; + } + + // Update the tilt counter before the next move and expect the return value to be greater than 0 + if (relay_chn_tilt_count_update(tilt_ctl) == 0) { + ESP_LOGD(TAG, "relay_chn_tilt_execute_pause: Relay channel cannot tilt anymore"); + // Stop tilting since the tilting limit has been reached + relay_chn_tilt_dispatch_cmd(tilt_ctl, RELAY_CHN_TILT_CMD_STOP); + return; + } + + // Set the pause time timer + relay_chn_start_esp_timer_once(tilt_ctl->tilt_timer, tilt_ctl->tilt_timing.pause_time_ms); + // Set to move step + tilt_ctl->step = RELAY_CHN_TILT_STEP_MOVE; +} + +static void relay_chn_tilt_event_handler(void *handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data) +{ + relay_chn_tilt_ctl_t* tilt_ctl = *(relay_chn_tilt_ctl_t**) event_data; + ESP_RETURN_VOID_ON_FALSE(tilt_ctl != NULL, TAG, "event_data is NULL"); + ESP_LOGD(TAG, "relay_chn_event_handler: Command: %s", relay_chn_cmd_str(event_id)); + + switch(event_id) { + case RELAY_CHN_TILT_CMD_STOP: + relay_chn_tilt_execute_stop(tilt_ctl); + break; + case RELAY_CHN_TILT_CMD_FORWARD: + relay_chn_tilt_execute_forward(tilt_ctl); + // Update channel state + relay_chn_update_state(tilt_ctl->chn_ctl, RELAY_CHN_STATE_TILT_FORWARD); + break; + case RELAY_CHN_TILT_CMD_REVERSE: + relay_chn_tilt_execute_reverse(tilt_ctl); + // Update channel state + relay_chn_update_state(tilt_ctl->chn_ctl, RELAY_CHN_STATE_TILT_REVERSE); + break; + default: + ESP_LOGW(TAG, "Unexpected relay channel tilt command: %ld!", event_id); + } +} + +// Timer callback for the relay_chn_tilt_control_t::tilt_timer +static void relay_chn_tilt_timer_cb(void *arg) +{ + relay_chn_tilt_ctl_t* tilt_ctl = (relay_chn_tilt_ctl_t*) arg; + ESP_RETURN_VOID_ON_FALSE(tilt_ctl != NULL, TAG, "relay_chn_tilt_timer_cb: event_data is NULL"); + + switch (tilt_ctl->step) + { + case RELAY_CHN_TILT_STEP_MOVE: + if (tilt_ctl->cmd == RELAY_CHN_TILT_CMD_FORWARD) { + relay_chn_tilt_execute_forward(tilt_ctl); + } + else if (tilt_ctl->cmd == RELAY_CHN_TILT_CMD_REVERSE) { + relay_chn_tilt_execute_reverse(tilt_ctl); + } + break; + + case RELAY_CHN_TILT_STEP_PAUSE: + relay_chn_tilt_execute_pause(tilt_ctl); + break; + + case RELAY_CHN_TILT_STEP_PENDING: + // Just dispatch the pending tilt command + relay_chn_tilt_dispatch_cmd(tilt_ctl, tilt_ctl->cmd); + break; + + default: + break; + } +} + +esp_err_t relay_chn_tilt_ctl_init(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_ctl_t *chn_ctl) +{ + tilt_ctl->cmd = RELAY_CHN_TILT_CMD_NONE; + tilt_ctl->step = RELAY_CHN_TILT_STEP_NONE; + tilt_ctl->tilt_timing.sensitivity = RELAY_CHN_TILT_DEFAULT_SENSITIVITY; + tilt_ctl->tilt_timing.move_time_ms = RELAY_CHN_TILT_DEFAULT_RUN_MS; + tilt_ctl->tilt_timing.pause_time_ms = RELAY_CHN_TILT_DEFAULT_PAUSE_MS; + relay_chn_tilt_reset_count(tilt_ctl); + + tilt_ctl->chn_ctl = chn_ctl; + tilt_ctl->chn_ctl->tilt_ctl = tilt_ctl; // + + // Create tilt timer for the channel + char timer_name[32]; + snprintf(timer_name, sizeof(timer_name), "relay_chn_%2d_tilt_timer", chn_ctl->id); + esp_timer_create_args_t timer_args = { + .callback = relay_chn_tilt_timer_cb, + .arg = tilt_ctl, + .name = timer_name + }; + return esp_timer_create(&timer_args, &tilt_ctl->tilt_timer); +} + +esp_err_t relay_chn_tilt_init(relay_chn_ctl_t *chn_ctls) +{ +#if RELAY_CHN_COUNT > 1 + for (int i = 0; i < RELAY_CHN_COUNT; i++) { + relay_chn_tilt_ctl_init(&tilt_ctls[i], &chn_ctls[i]); + } +#else + relay_chn_tilt_ctl_init(&tilt_ctl, chn_ctls); +#endif // RELAY_CHN_COUNT > 1 + + return esp_event_handler_register_with(relay_chn_event_loop, + RELAY_CHN_TILT_CMD_EVENT_BASE, + ESP_EVENT_ANY_ID, + relay_chn_tilt_event_handler, NULL); +} + +void relay_chn_tilt_ctl_deinit(relay_chn_tilt_ctl_t *tilt_ctl) +{ + if (tilt_ctl->tilt_timer != NULL) { + esp_timer_delete(tilt_ctl->tilt_timer); + tilt_ctl->tilt_timer = NULL; + } +} + +void relay_chn_tilt_deinit() +{ +#if RELAY_CHN_COUNT > 1 + for (int i = 0; i < RELAY_CHN_COUNT; i++) { + relay_chn_tilt_ctl_deinit(&tilt_ctls[i]); + } +#else + relay_chn_tilt_ctl_deinit(&tilt_ctl); +#endif // RELAY_CHN_COUNT > 1 + esp_event_handler_unregister_with(relay_chn_event_loop, + RELAY_CHN_TILT_CMD_EVENT_BASE, + ESP_EVENT_ANY_ID, + relay_chn_tilt_event_handler); +} \ No newline at end of file From 9f1134763eae35fb392d51ba625d1c045dc2b75a Mon Sep 17 00:00:00 2001 From: ismail Date: Wed, 13 Aug 2025 17:55:07 +0300 Subject: [PATCH 02/69] Refactor and update the relay_chn component. Refactor relay channel component to support single and multi-channel modes; update CMake configuration and enhance API documentation. --- CMakeLists.txt | 25 ++++- README.md | 138 +++++++++++++++++++++++---- include/relay_chn.h | 228 ++++++++++++++++++++++++++------------------ 3 files changed, 278 insertions(+), 113 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b301f47..e5b0c68 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,21 @@ -idf_component_register(SRCS "src/relay_chn.c" - INCLUDE_DIRS include - REQUIRES driver - PRIV_REQUIRES esp_timer esp_event) +set(include_dirs "include") +set(priv_include_dirs "private_include") + +set(srcs "src/relay_chn_core.c" + "src/relay_chn_output.c" + "src/relay_chn_run_info.c") + +if(CONFIG_RELAY_CHN_ENABLE_TILTING) + list(APPEND srcs "src/relay_chn_tilt.c") +endif() + +if(CONFIG_RELAY_CHN_COUNT GREATER 1) + list(APPEND srcs "src/relay_chn_ctl_multi.c") +else() + list(APPEND srcs "src/relay_chn_ctl_single.c") +endif() + +idf_component_register(SRCS ${srcs} + INCLUDE_DIRS ${include_dirs} + PRIV_INCLUDE_DIRS ${priv_include_dirs} + REQUIRES driver esp_timer esp_event) diff --git a/README.md b/README.md index aba6d7b..0597737 100644 --- a/README.md +++ b/README.md @@ -43,11 +43,28 @@ dependencies: ## Usage +The `relay_chn` component can be used in two different modes, which are determined by the 'CONFIG_RELAY_CHN_COUNT' configuration: + +- Single channel mode (`CONFIG_RELAY_CHN_COUNT == 1`) +- Multi channel mode (`CONFIG_RELAY_CHN_COUNT > 1`) + +Depending on the mode, the component will be built selectively, so the signatures of some available API functions may vary, either including or excluding a channel ID parameter: + +```c +relay_chn_run_forward(); // No channel ID parameter for single channel mode +// or +relay_chn_run_forward(2); // Channel ID parameters will be needed in multi channel mode +``` + +See the examples for further reference + ### 1. Initialize relay channels ```c // Define GPIO pins for relay channels -const gpio_num_t gpio_map[] = {GPIO_NUM_4, GPIO_NUM_5}; // One channel example +const uint8_t gpio_map[] = {4, 5}; // One channel example +/*------------------------------------------------------------------------*/ +const uint8_t gpio_map[] = {4, 5, 9, 10, 18, 19}; // Or a 3 channel example const uint8_t gpio_count = sizeof(gpio_map) / sizeof(gpio_map[0]); // Create and initialize relay channels @@ -59,53 +76,138 @@ if (ret != ESP_OK) { ### 2. Control relay channels +For single mode: + ```c -// Run channel 0 forward +// Run the channel forward +relay_chn_run_forward(); + +// Run the channel reverse +relay_chn_run_reverse(); + +// Stop the channel +relay_chn_stop(); + +// Flip the direction of the channel +relay_chn_flip_direction(); +``` + +For multi mode + +```c +// Run channel #0 forward relay_chn_run_forward(0); +// Run all channels forward +relay_chn_run_forward(RELAY_CHN_ID_ALL); -// Run channel 0 reverse -relay_chn_run_reverse(0); +// Run channel #1 reverse +relay_chn_run_reverse(1); +// Run all channels reverse +relay_chn_run_reverse(RELAY_CHN_ID_ALL); -// Stop channel 0 -relay_chn_stop(0); +// Stop channel #1 +relay_chn_stop(1); +// Stop all channels +relay_chn_stop(RELAY_CHN_ID_ALL); -// Flip direction of channel 0 +// Flip direction of channel #0 relay_chn_flip_direction(0); +// Flip direction of all channels +relay_chn_flip_direction(RELAY_CHN_ID_ALL); ``` ### 3. Monitor channel state +For single mode: + ```c // Get channel state -relay_chn_state_t state = relay_chn_get_state(0); -char *state_str = relay_chn_get_state_str(0); +relay_chn_state_t state = relay_chn_get_state(); +// Get the string representation of the state of the channel +char *state_str = relay_chn_get_state_str(); // Get channel direction +relay_chn_direction_t direction = relay_chn_get_direction(); + +// Listen to relay channel state changes +static void relay_chn_listener(uint8_t chn_id, relay_chn_state_t old_state, relay_chn_state_t new_state) { + /* The channel id can be ignored in single mode */ + /* Handle state changes */ +} +// Register the listener callback +relay_chn_register_listener(relay_chn_listener); +// Unregister the listener when it is not needed anymore +relay_chn_unregister_listener(relay_chn_listener); +``` + +For multi mode: + +```c +// Get channel #0 state +relay_chn_state_t state = relay_chn_get_state(0); +// Get the string representation of the state of the channel #0 +char *state_str = relay_chn_get_state_str(0); + +// Get channel #0 direction relay_chn_direction_t direction = relay_chn_get_direction(0); + +/* The listener is same for multi mode */ ``` ### 4. Tilting Interface (if enabled) +For single mode: + ```c // Assuming CONFIG_RELAY_CHN_ENABLE_TILTING is enabled -// Start tilting automatically (channel 0) +// Start tilting automatically +relay_chn_tilt_auto(); + +// Tilt forward +relay_chn_tilt_forward(); + +// Tilt reverse +relay_chn_tilt_reverse(); + +// Stop tilting +relay_chn_tilt_stop(); + +// Set tilting sensitivity (sensitivity as percentage) +relay_chn_tilt_sensitivity_set(90); + +// Get tilting sensitivity (sensitivty as percentage) +uint8_t sensitivity = relay_chn_tilt_get_sensitivity(); +``` + +For multi mode: + +```c +// Assuming CONFIG_RELAY_CHN_ENABLE_TILTING is enabled + +// Start tilting automatically on channel #0 relay_chn_tilt_auto(0); +relay_chn_tilt_auto(RELAY_CHN_ID_ALL); // on all channels -// Tilt forward (channel 0) -relay_chn_tilt_forward(0); +// Tilt forward on channel #1 +relay_chn_tilt_forward(1); +relay_chn_tilt_forward(RELAY_CHN_ID_ALL); -// Tilt reverse (channel 0) -relay_chn_tilt_reverse(0); +// Tilt reverse on channel #2 +relay_chn_tilt_reverse(2); +relay_chn_tilt_reverse(RELAY_CHN_ID_ALL); -// Stop tilting (channel 0) +// Stop tilting on channel #0 relay_chn_tilt_stop(0); +relay_chn_tilt_stop(RELAY_CHN_ID_ALL); -// Set tilting sensitivity (channel 0, sensitivity as percentage) +// Set tilting sensitivity (sensitivity as percentage) for channel #0 relay_chn_tilt_sensitivity_set(0, 90); +relay_chn_tilt_sensitivity_set(RELAY_CHN_ID_ALL, 90); -// Get tilting sensitivity (channel 0, sensitivty as percentage) -uint8_t sensitivity = relay_chn_tilt_sensitivity_get(0); +// Get tilting sensitivity (sensitivty as percentage) +uint8_t sensitivity; +relay_chn_tilt_get_sensitivity(0, &sensitivity, sizeof(sensitivity)); ``` ## License diff --git a/include/relay_chn.h b/include/relay_chn.h index 5d7f8a0..9912860 100644 --- a/include/relay_chn.h +++ b/include/relay_chn.h @@ -1,16 +1,8 @@ -#ifndef RELAY_CHN_H -#define RELAY_CHN_H -/** - * @file relay_chn.h - * - * @author - * Ismail Sahillioglu +/* + * SPDX-FileCopyrightText: 2025 Kozmotronik Tech + * + * SPDX-License-Identifier: MIT * - * @date 2025.02.08 - * - * @defgroup relay_chn Relay Channel Controller - * @ingroup components - * @{ * One relay channel consists of 2 output relays, hence 2 GPIO pins are required for each relay channel. * This module provides an API to control the relay channels, specifically to drive bipolar motors. * It also provides APIs to control the direction of the relay channel, bipolar motors in mind. @@ -22,66 +14,17 @@ * reliability and prevent conflict operations. Also, the esp timer is used to manage the direction change inertia. */ +#pragma once + #include "esp_err.h" -#include "driver/gpio.h" -#include +#include "relay_chn_defs.h" +#include "relay_chn_types.h" +#include "relay_chn_adapter.h" #ifdef __cplusplus extern "C" { #endif -#define RELAY_CHN_ID_ALL CONFIG_RELAY_CHN_COUNT ///< Special ID to address all channels - -/** - * @brief Enumeration for relay channel direction. - */ -enum relay_chn_direction_enum { - RELAY_CHN_DIRECTION_DEFAULT, ///< Default direction of the relay channel. - RELAY_CHN_DIRECTION_FLIPPED ///< Flipped direction of the relay channel. -}; - -/** - * @brief Alias for the enum type relay_chn_direction_enum. - */ -typedef enum relay_chn_direction_enum relay_chn_direction_t; - -/** - * @brief Enums that represent the state of a relay channel. - */ -enum relay_chn_state_enum { - RELAY_CHN_STATE_UNDEFINED, ///< The relay channel state is undefined. - RELAY_CHN_STATE_FREE, ///< The relay channel is free to run or execute commands. - RELAY_CHN_STATE_STOPPED, ///< The relay channel is stopped and not running. - RELAY_CHN_STATE_FORWARD, ///< The relay channel is running in the forward direction. - RELAY_CHN_STATE_REVERSE, ///< The relay channel is running in the reverse direction. - RELAY_CHN_STATE_FORWARD_PENDING, ///< The relay channel is pending to run in the forward direction. - RELAY_CHN_STATE_REVERSE_PENDING, ///< The relay channel is pending to run in the reverse direction. -#if CONFIG_RELAY_CHN_ENABLE_TILTING == 1 - RELAY_CHN_STATE_TILT_FORWARD, ///< The relay channel is tilting for forward. - RELAY_CHN_STATE_TILT_REVERSE, ///< The relay channel is tilting for reverse. -#endif -}; - -/** - * @brief Alias for the enum type relay_chn_state_enum. - */ -typedef enum relay_chn_state_enum relay_chn_state_t; - -/** - * @brief Relay channel state change listener. - * - * An optional interface to listen to the channel state change events. - * The listeners SHOULD be implemented as light functions and SHOULD NOT contain - * any blocking calls. Otherwise the relay_chn module would not function properly - * since it is designed as event driven. - * - * @param chn_id The ID of the channel whose state has changed. - * @param old_state The old state of the channel. - * @param new_state The new state of the channel. - */ -typedef void (*relay_chn_state_listener_t)(uint8_t chn_id, relay_chn_state_t old_state, relay_chn_state_t new_state); - - /** * @brief Create and initialize relay channels. * @@ -95,7 +38,7 @@ typedef void (*relay_chn_state_listener_t)(uint8_t chn_id, relay_chn_state_t old * - ESP_ERR_INVALID_ARG: Invalid argument * - ESP_FAIL: General failure */ -esp_err_t relay_chn_create(const gpio_num_t* gpio_map, uint8_t gpio_count); +esp_err_t relay_chn_create(const uint8_t* gpio_map, uint8_t gpio_count); /** * @brief Destroy the relay channels and free resources. @@ -124,6 +67,7 @@ esp_err_t relay_chn_register_listener(relay_chn_state_listener_t listener); */ void relay_chn_unregister_listener(relay_chn_state_listener_t listener); +#if RELAY_CHN_COUNT > 1 /** * @brief Get the state of the specified relay channel. * @@ -150,14 +94,6 @@ relay_chn_state_t relay_chn_get_state(uint8_t chn_id); */ char *relay_chn_get_state_str(uint8_t chn_id); -/** - * @brief Return the text presentation of an state. - * - * @param state A state with type of relay_chn_state_t. - * @return char* The text presentation of the state. "UNKNOWN" if the state is not known. - */ -char *relay_chn_state_str(relay_chn_state_t state); - /** * @brief Runs the relay channel in the forward direction. * @@ -191,8 +127,7 @@ void relay_chn_stop(uint8_t chn_id); * @brief Flips the direction of the specified relay channel. * * This function toggles the direction of the relay channel identified by the - * given channel ID. It is typically used to change the state of the relay - * from its current direction to the opposite direction. + * given channel ID. * * @param chn_id The ID of the relay channel to flip. This should be a valid * channel ID within the range of available relay channels. @@ -217,9 +152,9 @@ relay_chn_direction_t relay_chn_get_direction(uint8_t chn_id); /** * @brief Enables automatic tilting for the specified relay channel. * - * This function enables automatic tilting mode for the given relay channel. The channel will automatically - * switch between forward and reverse tilting based on some internal sensing mechanism (not detailed here). - * Requires appropriate hardware support and configuration. + * This function enables automatic tilting mode for the given relay channel. + * The channel will automatically switch between forward and reverse tilting + * based on the last movement of the channel * * @param chn_id The ID of the relay channel to enable automatic tilting. */ @@ -228,8 +163,7 @@ void relay_chn_tilt_auto(uint8_t chn_id); /** * @brief Tilts the specified relay channel forward. * - * This function initiates a forward tilting action for the specified relay channel. This is a manual tilting - * operation, unlike `relay_chn_tilt_auto()`. + * This function initiates a forward tilting action for the specified relay channel. * * @param chn_id The ID of the relay channel to tilt forward. */ @@ -238,8 +172,7 @@ void relay_chn_tilt_forward(uint8_t chn_id); /** * @brief Tilts the specified relay channel reverse. * - * This function initiates a reverse tilting action for the specified relay channel. This is a manual tilting - * operation, unlike `relay_chn_tilt_auto()`. + * This function initiates a reverse tilting action for the specified relay channel. * * @param chn_id The ID of the relay channel to tilt reverse. */ @@ -263,7 +196,7 @@ void relay_chn_tilt_stop(uint8_t chn_id); * @param chn_id The ID of the relay channel to set the sensitivity for. * @param sensitivity The sensitivity in percentage: 0 - 100%. */ -void relay_chn_tilt_sensitivity_set(uint8_t chn_id, uint8_t sensitivity); +void relay_chn_tilt_set_sensitivity(uint8_t chn_id, uint8_t sensitivity); /** * @brief Gets the tilting sensitivity for the specified relay channel. @@ -278,14 +211,127 @@ void relay_chn_tilt_sensitivity_set(uint8_t chn_id, uint8_t sensitivity); * - ESP_OK: Success * - ESP_ERR_INVALID_ARG: Invalid argument */ -esp_err_t relay_chn_tilt_sensitivity_get(uint8_t chn_id, uint8_t *sensitivity, size_t length); +esp_err_t relay_chn_tilt_get_sensitivity(uint8_t chn_id, uint8_t *sensitivity, size_t length); #endif // CONFIG_RELAY_CHN_ENABLE_TILTING +#else // RELAY_CHN_COUNT > 1 + +/** + * @brief Get the state of the relay channel. + * + * This function retrieves the current state of the relay channel. + * + * @return The current state of the relay channel. + */ +relay_chn_state_t relay_chn_get_state(void); + +/** + * @brief Get the state string of the relay channel. + * + * This function returns a string representation of the state of the relay channel. + * + * @return A pointer to a string representing the state of the relay + * channel. The returned string is managed internally and should not be + * modified or freed by the caller. + */ +char *relay_chn_get_state_str(void); + +/** + * @brief Runs the relay channel in the forward direction. + * + * This function activates the relay channel to run in the forward direction. + */ +void relay_chn_run_forward(void); + +/** + * @brief Runs the relay channel in reverse. + * + * This function activates the relay channel to run in reverse. + */ +void relay_chn_run_reverse(void); + +/** + * @brief Stops the relay channel. + * + * This function stops the operation of the relay channel. + */ +void relay_chn_stop(void); + +/** + * @brief Flips the direction of the relay channel. + * + * This function toggles the direction of the relay channel. + */ +void relay_chn_flip_direction(void); + +/** + * @brief Get the direction of the relay channel. + * + * This function retrieves the direction configuration of a relay channel. + * + * @return The direction of the relay channel as a value of type + * relay_chn_direction_t. + */ +relay_chn_direction_t relay_chn_get_direction(void); + + +#if CONFIG_RELAY_CHN_ENABLE_TILTING == 1 + +/** + * @brief Enables automatic tilting for the relay channel. + * + * This function enables automatic tilting mode for the given relay channel. + * The channel will automatically switch between forward and reverse tilting + * based on the last movement of the channel + */ +void relay_chn_tilt_auto(void); + +/** + * @brief Tilts the relay channel forward. + * + * This function initiates a forward tilting action for the relay channel. + */ +void relay_chn_tilt_forward(void); + +/** + * @brief Tilts the relay channel reverse. + * + * This function initiates a reverse tilting action for the relay channel. + */ +void relay_chn_tilt_reverse(void); + +/** + * @brief Stops the tilting action on the relay channel. + * + * This function stops any ongoing tilting action (automatic or manual) on the relay channel. + */ +void relay_chn_tilt_stop(void); + +/** + * @brief Sets the tilting sensitivity for the relay channel. + * + * This function sets the sensitivity for the automatic tilting mechanism. A higher sensitivity value + * typically means the channel will react more readily to tilting events. + * + * @param sensitivity The sensitivity in percentage: 0 - 100%. + */ +void relay_chn_tilt_set_sensitivity(uint8_t sensitivity); + +/** + * @brief Gets the tilting sensitivity for the relay channel. + * + * This function retrieves the currently set sensitivity for the relay channel's automatic + * tilting mechanism. + * + * @return Sensitivity value in percentage: 0 - 100%. + */ +uint8_t relay_chn_tilt_get_sensitivity(void); + +#endif // CONFIG_RELAY_CHN_ENABLE_TILTING + +#endif // RELAY_CHN_COUNT > 1 + #ifdef __cplusplus } -#endif - -/// @} - -#endif // RELAY_CHN_H \ No newline at end of file +#endif \ No newline at end of file From f8d6e74f2350202a986d469f5262c2180f98786f Mon Sep 17 00:00:00 2001 From: ismail Date: Wed, 13 Aug 2025 17:57:01 +0300 Subject: [PATCH 03/69] Refactor and update configuration for single channel mode - Created `test_relay_chn_tilt_multi.c` and `test_relay_chn_tilt_single.c` to implement comprehensive tests for the tilt functionality of relay channels, covering various scenarios including transitions between states and sensitivity settings. - Introduced a new partition table in `partitionTable.csv` for proper memory management. - Updated `sdkconfig` to set the relay channel count to 1 for single channel testing and adjusted related configurations. - Added default configuration file `sdkconfig.defaults.single` for streamlined testing setup. --- test_apps/main/CMakeLists.txt | 21 +- test_apps/main/test_common.c | 40 ++- test_apps/main/test_common.h | 10 +- ...chn_core.c => test_relay_chn_core_multi.c} | 34 +-- test_apps/main/test_relay_chn_core_single.c | 204 +++++++++++++ ...ener.c => test_relay_chn_listener_multi.c} | 6 +- .../main/test_relay_chn_listener_single.c | 130 +++++++++ ...chn_tilt.c => test_relay_chn_tilt_multi.c} | 34 +-- test_apps/main/test_relay_chn_tilt_single.c | 275 ++++++++++++++++++ test_apps/partition_table/partitionTable.csv | 5 + test_apps/sdkconfig | 2 +- test_apps/sdkconfig.defaults.single | 8 + test_apps/sdkconfig.old | 42 +-- 13 files changed, 721 insertions(+), 90 deletions(-) rename test_apps/main/{test_relay_chn_core.c => test_relay_chn_core_multi.c} (94%) create mode 100644 test_apps/main/test_relay_chn_core_single.c rename test_apps/main/{test_relay_chn_listener.c => test_relay_chn_listener_multi.c} (95%) create mode 100644 test_apps/main/test_relay_chn_listener_single.c rename test_apps/main/{test_relay_chn_tilt.c => test_relay_chn_tilt_multi.c} (93%) create mode 100644 test_apps/main/test_relay_chn_tilt_single.c create mode 100644 test_apps/partition_table/partitionTable.csv create mode 100644 test_apps/sdkconfig.defaults.single diff --git a/test_apps/main/CMakeLists.txt b/test_apps/main/CMakeLists.txt index 2fec2dc..5ff1cfd 100644 --- a/test_apps/main/CMakeLists.txt +++ b/test_apps/main/CMakeLists.txt @@ -1,14 +1,23 @@ # === These files must be included in any case === set(srcs "test_common.c" - "test_app_main.c" - "test_relay_chn_core.c" - "test_relay_chn_listener.c") + "test_app_main.c") -if(CONFIG_RELAY_CHN_ENABLE_TILTING) - list(APPEND srcs "test_relay_chn_tilt.c") +# === 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") +else() + list(APPEND srcs "test_relay_chn_core_single.c" + "test_relay_chn_listener_single.c") endif() -message(STATUS "srcs=${srcs}") +if(CONFIG_RELAY_CHN_ENABLE_TILTING) + if(CONFIG_RELAY_CHN_COUNT GREATER 1) + list(APPEND srcs "test_relay_chn_tilt_multi.c") + else() + list(APPEND srcs "test_relay_chn_tilt_single.c") + endif() +endif() # In order for the cases defined by `TEST_CASE` to be linked into the final elf, # the component can be registered as WHOLE_ARCHIVE diff --git a/test_apps/main/test_common.c b/test_apps/main/test_common.c index c952b68..be7169c 100644 --- a/test_apps/main/test_common.c +++ b/test_apps/main/test_common.c @@ -2,16 +2,38 @@ const char *TEST_TAG = "RELAY_CHN_TEST"; -// GPIO eşlemesi (örn: GPIO_NUM_4 vs GPIO_NUM_5) -const gpio_num_t gpio_map[] = { - GPIO_NUM_4, GPIO_NUM_5, GPIO_NUM_18, GPIO_NUM_19 -}; - -const uint8_t gpio_count = sizeof(gpio_map) / sizeof(gpio_map[0]); -const uint8_t relay_chn_count = gpio_count / 2; - -// Konfigürasyon tabanlı inertia süresi +const uint8_t relay_chn_count = CONFIG_RELAY_CHN_COUNT; const uint32_t opposite_inertia_ms = CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS; const uint32_t test_delay_margin_ms = 50; // ms toleransı bool g_is_component_initialized = false; + +// Test-wide GPIO map +#if CONFIG_RELAY_CHN_COUNT > 1 +const uint8_t gpio_map[] = { + 0, 1, + 2, 3 +#if CONFIG_RELAY_CHN_COUNT > 2 + , 4, 5 +#if CONFIG_RELAY_CHN_COUNT > 3 + , 6, 7 +#if CONFIG_RELAY_CHN_COUNT > 4 + , 8, 9 +#if CONFIG_RELAY_CHN_COUNT > 5 + , 10, 11 +#if CONFIG_RELAY_CHN_COUNT > 6 + , 12, 13 +#if CONFIG_RELAY_CHN_COUNT > 7 + , 14, 15 +#endif +#endif +#endif +#endif +#endif +#endif +}; +#else +const uint8_t gpio_map[] = {4, 5}; +#endif + +const uint8_t gpio_count = sizeof(gpio_map) / sizeof(gpio_map[0]); \ No newline at end of file diff --git a/test_apps/main/test_common.h b/test_apps/main/test_common.h index 50d3f63..6c9c9eb 100644 --- a/test_apps/main/test_common.h +++ b/test_apps/main/test_common.h @@ -3,23 +3,21 @@ #include // For memset #include "unity.h" #include "relay_chn.h" -#include "driver/gpio.h" #include "esp_log.h" -#include "sdkconfig.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" // Test log tag extern const char *TEST_TAG; -// GPIO konfigürasyonları -extern const gpio_num_t gpio_map[]; +// GPIO configurations +extern const uint8_t gpio_map[]; extern const uint8_t gpio_count; extern const uint8_t relay_chn_count; -// Config parametreleri +// Config variables for tests extern const uint32_t opposite_inertia_ms; extern const uint32_t test_delay_margin_ms; -// Init durumu +// Init state extern bool g_is_component_initialized; diff --git a/test_apps/main/test_relay_chn_core.c b/test_apps/main/test_relay_chn_core_multi.c similarity index 94% rename from test_apps/main/test_relay_chn_core.c rename to test_apps/main/test_relay_chn_core_multi.c index 0f29c3e..8551f7c 100644 --- a/test_apps/main/test_relay_chn_core.c +++ b/test_apps/main/test_relay_chn_core_multi.c @@ -13,20 +13,20 @@ TEST_CASE("relay_chn_create handles invalid arguments", "[relay_chn][core]") TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(gpio_map, 1)); TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(gpio_map, 0)); - // 3. Test with invalid GPIO numbers (GPIO_NUM_MAX is an invalid GPIO for output) - gpio_num_t invalid_gpio_map[] = {GPIO_NUM_4, GPIO_NUM_MAX, GPIO_NUM_18, GPIO_NUM_19}; + // 3. Test with invalid GPIO numbers (127 is an invalid GPIO for output) + uint8_t invalid_gpio_map[] = {4, 127, 18, 19}; TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(invalid_gpio_map, gpio_count)); } // --- Basic Functionality Tests --- -// TEST_CASE: Test that relay channels initialize correctly to RELAY_CHN_STATE_FREE +// TEST_CASE: Test that relay channels initialize correctly to RELAY_CHN_STATE_IDLE TEST_CASE("Relay channels initialize correctly to FREE state", "[relay_chn][core]") { TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); g_is_component_initialized = true; for (uint8_t i = 0; i < relay_chn_count; i++) { - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(i)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i)); } } @@ -39,7 +39,7 @@ TEST_CASE("Run forward does nothing if channel id is invalid", "[relay_chn][core relay_chn_run_forward(invalid_id); // relay_chn_run_forward returns void // Short delay for state to update vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(i)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i)); } } @@ -67,7 +67,7 @@ TEST_CASE("Run reverse does nothing if channel id is invalid", "[relay_chn][core // Call run_reverse with an invalid ID relay_chn_run_reverse(invalid_id); vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(i)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i)); } } @@ -127,13 +127,13 @@ TEST_CASE("stop with ID_ALL stops all running channels", "[relay_chn][core][id_a // 3. Verify all channels have transitioned to the FREE state for (uint8_t i = 0; i < relay_chn_count; i++) { - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(i)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i)); } } -// TEST_CASE: Test that relays stop and transition to RELAY_CHN_STATE_FREE +// TEST_CASE: Test that relays stop and transition to RELAY_CHN_STATE_IDLE // This test also verifies the transition to FREE state after a STOP command. TEST_CASE("Relay channels stop and update to FREE state", "[relay_chn][core]") { TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); @@ -151,9 +151,9 @@ TEST_CASE("Relay channels stop and update to FREE state", "[relay_chn][core]") { vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(i)); - // Then, wait for the inertia period for it to transition to RELAY_CHN_STATE_FREE + // Then, wait for the inertia period for it to transition to RELAY_CHN_STATE_IDLE vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(i)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i)); } } @@ -203,7 +203,7 @@ TEST_CASE("Multiple channels can operate independently", "[relay_chn][core]") { relay_chn_run_forward(0); // relay_chn_run_forward returns void vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(0)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(1)); // Other channel should not be affected + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(1)); // Other channel should not be affected // Start Channel 1 in reverse direction relay_chn_run_reverse(1); // relay_chn_run_reverse returns void @@ -214,14 +214,14 @@ TEST_CASE("Multiple channels can operate independently", "[relay_chn][core]") { // Stop Channel 0 and wait for it to become FREE relay_chn_stop(0); // relay_chn_stop returns void vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(0)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(0)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(1)); // Other channel should continue running // Stop Channel 1 and wait for it to become FREE relay_chn_stop(1); // relay_chn_stop returns void vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(0)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(1)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(0)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(1)); } else { ESP_LOGW("TEST", "Skipping 'Multiple channels can operate independently' test: Not enough channels available."); } @@ -301,7 +301,7 @@ TEST_CASE("Running in same direction does not incur inertia", "[relay_chn][core] } // TEST_CASE: Test transition from FREE state to running (no inertia expected) -// Scenario: RELAY_CHN_STATE_FREE -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD +// Scenario: RELAY_CHN_STATE_IDLE -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD TEST_CASE("FREE to Running transition without inertia", "[relay_chn][core][inertia]") { uint8_t ch = 0; @@ -309,7 +309,7 @@ TEST_CASE("FREE to Running transition without inertia", "[relay_chn][core][inert g_is_component_initialized = true; // setUp() should have already brought the channel to FREE state - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(ch)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(ch)); // Start in forward direction relay_chn_run_forward(ch); // relay_chn_run_forward returns void @@ -388,7 +388,7 @@ TEST_CASE("Flipping a running channel stops it and flips direction", "[relay_chn // 4. Wait for the flip inertia to pass, after which it should be FREE and FLIPPED vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(ch)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(ch)); TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction(ch)); } diff --git a/test_apps/main/test_relay_chn_core_single.c b/test_apps/main/test_relay_chn_core_single.c new file mode 100644 index 0000000..734f4bf --- /dev/null +++ b/test_apps/main/test_relay_chn_core_single.c @@ -0,0 +1,204 @@ +#include "test_common.h" + + +// --- Initialization Tests --- + +TEST_CASE("relay_chn_create handles invalid arguments", "[relay_chn][core]") +{ + // 1. Test with NULL gpio_map + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(NULL, gpio_count)); + + // 2. Test with incorrect gpio_count (must be RELAY_CHN_COUNT * 2) + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(gpio_map, gpio_count - 1)); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(gpio_map, 1)); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(gpio_map, 0)); + + // 3. Test with invalid GPIO numbers (GPIO_NUM_MAX is an invalid GPIO for output) + uint8_t invalid_gpio_map[] = {4, 127}; + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(invalid_gpio_map, gpio_count)); +} + +// --- Basic Functionality Tests --- + +// TEST_CASE: Test that relay channels initialize correctly to RELAY_CHN_STATE_IDLE +TEST_CASE("Relay channels initialize correctly to IDLE state", "[relay_chn][core]") { + TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); + g_is_component_initialized = true; + + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); +} + +// TEST_CASE: Test that relays run in the forward direction and update their state +TEST_CASE("Relay channels run forward and update state", "[relay_chn][core]") { + TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); + g_is_component_initialized = true; + + relay_chn_run_forward(); + // Short delay for state to update + vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); +} + +// TEST_CASE: Test that relays run in the reverse direction and update their state +TEST_CASE("Relay channels run reverse and update state", "[relay_chn][core]") { + TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); + g_is_component_initialized = true; + + relay_chn_run_reverse(); // relay_chn_run_reverse returns void + vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state()); +} + + +// TEST_CASE: Test that relays stop and transition to RELAY_CHN_STATE_IDLE +// This test also verifies the transition to IDLE state after a STOP command. +TEST_CASE("Relay channels stop and update to IDLE state", "[relay_chn][core]") { + TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); + g_is_component_initialized = true; + + // First, run forward to test stopping and transitioning to IDLE state + relay_chn_run_forward(); + vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); + + // Now, issue the stop command + relay_chn_stop(); // relay_chn_stop returns void + // Immediately after stop, state should be STOPPED + vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state()); + + // Then, wait for the inertia period for it to transition to RELAY_CHN_STATE_IDLE + vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); +} + + +// ### Inertia and State Transition Tests + +// This section specifically targets the inertia periods and complex state transitions as per the component's logic. + +// TEST_CASE: Test transition from forward to reverse with inertia and state checks +// Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_REVERSE +TEST_CASE("Forward to Reverse transition with opposite inertia", "[relay_chn][core][inertia]") { + TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); + g_is_component_initialized = true; + + // 1. Start in forward direction + relay_chn_run_forward(); + vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Short delay for state stabilization + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); + + // 2. Issue reverse command + relay_chn_run_reverse(); // relay_chn_run_reverse returns void + // Immediately after the command, the motor should be stopped + vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE_PENDING, relay_chn_get_state()); + + // Wait for the inertia period (after which the reverse command will be dispatched) + vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state()); // Should now be in reverse state +} + +// TEST_CASE: Test transition from reverse to forward with inertia and state checks +// Scenario: RELAY_CHN_STATE_REVERSE -> (relay_chn_run_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_FORWARD +TEST_CASE("Reverse to Forward transition with opposite inertia", "[relay_chn][core][inertia]") { + TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); + g_is_component_initialized = true; + + // 1. Start in reverse direction + relay_chn_run_reverse(); // relay_chn_run_reverse returns void + vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state()); + + // 2. Issue forward command + relay_chn_run_forward(); + vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD_PENDING, relay_chn_get_state()); + + // Wait for inertia + vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); +} + +// TEST_CASE: Test issuing the same run command while already running (no inertia expected) +// Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD +TEST_CASE("Running in same direction does not incur inertia", "[relay_chn][core][inertia]") { + TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); + g_is_component_initialized = true; + + // 1. Start in forward direction + relay_chn_run_forward(); + vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); + + // 2. Issue the same forward command again + relay_chn_run_forward(); + // As per the code, is_direction_opposite_to_current_motion should return false, so no inertia. + // Just a short delay to check state remains the same. + vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); +} + +// TEST_CASE: Test transition from IDLE state to running (no inertia expected) +// Scenario: RELAY_CHN_STATE_IDLE -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD +TEST_CASE("IDLE to Running transition without inertia", "[relay_chn][core][inertia]") { + TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); + g_is_component_initialized = true; + + // setUp() should have already brought the channel to IDLE state + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); + + // Start in forward direction + relay_chn_run_forward(); + // No inertia is expected when starting from IDLE state. + vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); +} + +// ### Direction Flipping Tests + +TEST_CASE("Single channel direction can be flipped", "[relay_chn][core][direction]") +{ + TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); + g_is_component_initialized = true; + + // 1. Initial direction should be default + TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction()); + + // 2. Flip the direction + relay_chn_flip_direction(); + vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); // Wait for flip inertia + + // 3. Verify direction is flipped + TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction()); + + // 4. Flip back + relay_chn_flip_direction(); + vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); // Wait for flip inertia + + // 5. Verify direction is back to default + TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction()); +} + +TEST_CASE("Flipping a running channel stops it and flips direction", "[relay_chn][core][direction]") +{ + TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); + g_is_component_initialized = true; + + // 1. Start channel running and verify state + relay_chn_run_forward(); + vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); + + // 2. Flip the direction while running + relay_chn_flip_direction(); + vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Give time for events to process + + // 3. The channel should stop as part of the flip process + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state()); + + // 4. Wait for the flip inertia to pass, after which it should be IDLE and FLIPPED + vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); + TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction()); +} \ No newline at end of file diff --git a/test_apps/main/test_relay_chn_listener.c b/test_apps/main/test_relay_chn_listener_multi.c similarity index 95% rename from test_apps/main/test_relay_chn_listener.c rename to test_apps/main/test_relay_chn_listener_multi.c index 9415068..1d368d5 100644 --- a/test_apps/main/test_relay_chn_listener.c +++ b/test_apps/main/test_relay_chn_listener_multi.c @@ -52,7 +52,7 @@ TEST_CASE("Listener is called on state change", "[relay_chn][listener]") { // 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_FREE, listener1_info.old_state); + 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 @@ -96,12 +96,12 @@ TEST_CASE("Multiple listeners are called on state change", "[relay_chn][listener // 3. Verify listener 1 was called correctly TEST_ASSERT_EQUAL(1, listener1_info.call_count); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, listener1_info.old_state); + 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_FREE, listener2_info.old_state); + 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 diff --git a/test_apps/main/test_relay_chn_listener_single.c b/test_apps/main/test_relay_chn_listener_single.c new file mode 100644 index 0000000..4346967 --- /dev/null +++ b/test_apps/main/test_relay_chn_listener_single.c @@ -0,0 +1,130 @@ +#include "test_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++; +} + +// ### Listener Functionality Tests + +TEST_CASE("Listener is called on state change", "[relay_chn][listener]") { + TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); + g_is_component_initialized = true; + + 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(); + 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(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]") { + TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); + g_is_component_initialized = true; + + 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(); + 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]") { + TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); + g_is_component_initialized = true; + + 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(); + 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]") { + TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); + g_is_component_initialized = true; + + 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(); + 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); +} diff --git a/test_apps/main/test_relay_chn_tilt.c b/test_apps/main/test_relay_chn_tilt_multi.c similarity index 93% rename from test_apps/main/test_relay_chn_tilt.c rename to test_apps/main/test_relay_chn_tilt_multi.c index 47b7f32..916afc3 100644 --- a/test_apps/main/test_relay_chn_tilt.c +++ b/test_apps/main/test_relay_chn_tilt_multi.c @@ -23,7 +23,7 @@ void prepare_channel_for_tilt(uint8_t chn_id, int initial_cmd) { vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Allow command to process relay_chn_stop(chn_id); // Stop it to set last_run_cmd but return to FREE for next test vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(chn_id)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(chn_id)); } // TEST_CASE: Test transition from running forward to tilt forward @@ -79,7 +79,7 @@ TEST_CASE("Run Reverse to Tilt Reverse transition with inertia", "[relay_chn][ti } // TEST_CASE: Test transition from FREE state to tilt forward (now with preparation) -// Scenario: RELAY_CHN_STATE_FREE -> (prepare) -> RELAY_CHN_STATE_FREE -> (relay_chn_tilt_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_FORWARD +// Scenario: RELAY_CHN_STATE_IDLE -> (prepare) -> RELAY_CHN_STATE_IDLE -> (relay_chn_tilt_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_FORWARD TEST_CASE("FREE to Tilt Forward transition with inertia (prepared)", "[relay_chn][tilt][inertia]") { uint8_t ch = 0; @@ -88,7 +88,7 @@ TEST_CASE("FREE to Tilt Forward transition with inertia (prepared)", "[relay_chn // Prepare channel by running forward first to set last_run_cmd prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(ch)); // Ensure we are back to FREE + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(ch)); // Ensure we are back to FREE // Issue tilt forward command relay_chn_tilt_forward(ch); @@ -98,7 +98,7 @@ TEST_CASE("FREE to Tilt Forward transition with inertia (prepared)", "[relay_chn } // TEST_CASE: Test transition from FREE state to tilt reverse (now with preparation) -// Scenario: RELAY_CHN_STATE_FREE -> (prepare) -> RELAY_CHN_STATE_FREE -> (relay_chn_tilt_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_REVERSE +// Scenario: RELAY_CHN_STATE_IDLE -> (prepare) -> RELAY_CHN_STATE_IDLE -> (relay_chn_tilt_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_REVERSE TEST_CASE("FREE to Tilt Reverse transition with inertia (prepared)", "[relay_chn][tilt][inertia]") { uint8_t ch = 0; @@ -107,7 +107,7 @@ TEST_CASE("FREE to Tilt Reverse transition with inertia (prepared)", "[relay_chn // Prepare channel by running reverse first to set last_run_cmd prepare_channel_for_tilt(ch, RELAY_CHN_CMD_REVERSE); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(ch)); // Ensure we are back to FREE + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(ch)); // Ensure we are back to FREE // Issue tilt reverse command relay_chn_tilt_reverse(ch); @@ -179,7 +179,7 @@ TEST_CASE("Tilt Forward to Run Reverse transition without inertia", "[relay_chn] } // TEST_CASE: Test stopping from a tilt state (no inertia for stop command itself) -// Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_stop) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_FREE +// Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_stop) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_IDLE TEST_CASE("Tilt to Stop transition without immediate inertia for stop", "[relay_chn][tilt][inertia]") { uint8_t ch = 0; @@ -196,7 +196,7 @@ TEST_CASE("Tilt to Stop transition without immediate inertia for stop", "[relay_ relay_chn_stop(ch); // Stop command should apply immediately, setting state to FREE since last state was tilt. vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(ch)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(ch)); } // ### Tilt Broadcast Command (RELAY_CHN_ID_ALL) Tests @@ -259,7 +259,7 @@ TEST_CASE("tilt_stop with ID_ALL stops all tilting channels", "[relay_chn][tilt] // 3. Verify all channels are free for (uint8_t i = 0; i < relay_chn_count; i++) { - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(i)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i)); } } @@ -306,28 +306,28 @@ TEST_CASE("relay_chn_tilt_auto chooses correct direction", "[relay_chn][tilt][au } // Test sensitivity set/get -TEST_CASE("relay_chn_tilt_sensitivity_set and get", "[relay_chn][tilt][sensitivity]") { +TEST_CASE("relay_chn_tilt_set_sensitivity and get", "[relay_chn][tilt][sensitivity]") { uint8_t ch = 0; uint8_t val = 0; TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); g_is_component_initialized = true; - relay_chn_tilt_sensitivity_set(ch, 0); - TEST_ESP_OK(relay_chn_tilt_sensitivity_get(ch, &val, 1)); + relay_chn_tilt_set_sensitivity(ch, 0); + TEST_ESP_OK(relay_chn_tilt_get_sensitivity(ch, &val, 1)); TEST_ASSERT_EQUAL_UINT8(0, val); - relay_chn_tilt_sensitivity_set(ch, 50); - TEST_ESP_OK(relay_chn_tilt_sensitivity_get(ch, &val, 1)); + relay_chn_tilt_set_sensitivity(ch, 50); + TEST_ESP_OK(relay_chn_tilt_get_sensitivity(ch, &val, 1)); TEST_ASSERT_EQUAL_UINT8(50, val); - relay_chn_tilt_sensitivity_set(ch, 100); - TEST_ESP_OK(relay_chn_tilt_sensitivity_get(ch, &val, 1)); + relay_chn_tilt_set_sensitivity(ch, 100); + TEST_ESP_OK(relay_chn_tilt_get_sensitivity(ch, &val, 1)); TEST_ASSERT_EQUAL_UINT8(100, val); // Set all channels - relay_chn_tilt_sensitivity_set(RELAY_CHN_ID_ALL, 42); + relay_chn_tilt_set_sensitivity(RELAY_CHN_ID_ALL, 42); uint8_t vals[CONFIG_RELAY_CHN_COUNT] = {0}; - TEST_ESP_OK(relay_chn_tilt_sensitivity_get(RELAY_CHN_ID_ALL, vals, relay_chn_count)); + TEST_ESP_OK(relay_chn_tilt_get_sensitivity(RELAY_CHN_ID_ALL, vals, relay_chn_count)); for (int i = 0; i < relay_chn_count; ++i) { TEST_ASSERT_EQUAL_UINT8(42, vals[i]); } diff --git a/test_apps/main/test_relay_chn_tilt_single.c b/test_apps/main/test_relay_chn_tilt_single.c new file mode 100644 index 0000000..7a0b8b0 --- /dev/null +++ b/test_apps/main/test_relay_chn_tilt_single.c @@ -0,0 +1,275 @@ +#include "test_common.h" + + +// ### Tilt Functionality Tests (Conditional) + +// This section will only be compiled if **`CONFIG_RELAY_CHN_ENABLE_TILTING`** is defined as **`1`** in `sdkconfig`. + +#ifndef CONFIG_RELAY_CHN_ENABLE_TILTING +#error "This test requires CONFIG_RELAY_CHN_ENABLE_TILTING" +#endif + +#define RELAY_CHN_CMD_FORWARD 1 +#define RELAY_CHN_CMD_REVERSE 2 + +// Helper function to prepare channel for tilt tests +void prepare_channel_for_tilt(int initial_cmd) { + // Ensure the channel has had a 'last_run_cmd' + if (initial_cmd == RELAY_CHN_CMD_FORWARD) { + relay_chn_run_forward(); + } else { // Assuming initial_cmd is RELAY_CHN_CMD_REVERSE + relay_chn_run_reverse(); + } + vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Allow command to process + relay_chn_stop(); // Stop it to set last_run_cmd but return to FREE for next test + vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); +} + +// TEST_CASE: Test transition from running forward to tilt forward +// Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_tilt_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_FORWARD +TEST_CASE("Run Forward to Tilt Forward transition with inertia", "[relay_chn][tilt][inertia]") { + TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); + g_is_component_initialized = true; + + // Prepare channel by running forward first to set last_run_cmd + prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); + + // 1. Start in forward direction + relay_chn_run_forward(); + vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); + + // 2. Issue tilt forward command + relay_chn_tilt_forward(); + // After tilt command, it should immediately stop and then trigger inertia. + vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state()); + + // Wait for the inertia period (after which the tilt command will be dispatched) + vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state()); +} + +// TEST_CASE: Test transition from running reverse to tilt reverse +// Scenario: RELAY_CHN_STATE_REVERSE -> (relay_chn_tilt_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_REVERSE +TEST_CASE("Run Reverse to Tilt Reverse transition with inertia", "[relay_chn][tilt][inertia]") { + TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); + g_is_component_initialized = true; + + // Prepare channel by running reverse first to set last_run_cmd + prepare_channel_for_tilt(RELAY_CHN_CMD_REVERSE); + + // 1. Start in reverse direction + relay_chn_run_reverse(); + vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state()); + + // 2. Issue tilt reverse command + relay_chn_tilt_reverse(); + vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state()); + + vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state()); +} + +// TEST_CASE: Test transition from FREE state to tilt forward (now with preparation) +// Scenario: RELAY_CHN_STATE_IDLE -> (prepare) -> RELAY_CHN_STATE_IDLE -> (relay_chn_tilt_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_FORWARD +TEST_CASE("FREE to Tilt Forward transition with inertia (prepared)", "[relay_chn][tilt][inertia]") { + TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); + g_is_component_initialized = true; + + // Prepare channel by running forward first to set last_run_cmd + prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); // Ensure we are back to FREE + + // Issue tilt forward command + relay_chn_tilt_forward(); + // From FREE state, tilt command should still incur the inertia due to the internal timer logic + vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state()); +} + +// TEST_CASE: Test transition from FREE state to tilt reverse (now with preparation) +// Scenario: RELAY_CHN_STATE_IDLE -> (prepare) -> RELAY_CHN_STATE_IDLE -> (relay_chn_tilt_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_REVERSE +TEST_CASE("FREE to Tilt Reverse transition with inertia (prepared)", "[relay_chn][tilt][inertia]") { + TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); + g_is_component_initialized = true; + + // Prepare channel by running reverse first to set last_run_cmd + prepare_channel_for_tilt(RELAY_CHN_CMD_REVERSE); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); // Ensure we are back to FREE + + // Issue tilt reverse command + relay_chn_tilt_reverse(); + vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state()); +} + +// TEST_CASE: Test transition from tilt forward to run forward (inertia expected for run) +// Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD +TEST_CASE("Tilt Forward to Run Forward transition with inertia", "[relay_chn][tilt][inertia]") { + TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); + g_is_component_initialized = true; + + // Prepare channel by running forward first to set last_run_cmd, then tilt + prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); + relay_chn_tilt_forward(); // Go to tilt state + vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state()); + + // 2. Issue run forward command + relay_chn_run_forward(); + // From Tilt to Run in the same logical name but in the opposite direction, inertia is expected. + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD_PENDING, relay_chn_get_state()); + vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); +} + +// TEST_CASE: Test transition from tilt reverse to run reverse (no inertia expected for run) +// Scenario: RELAY_CHN_STATE_TILT_REVERSE -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_REVERSE +TEST_CASE("Tilt Reverse to Run Reverse transition with inertia", "[relay_chn][tilt][inertia]") { + TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); + g_is_component_initialized = true; + + // Prepare channel by running reverse first to set last_run_cmd, then tilt + prepare_channel_for_tilt(RELAY_CHN_CMD_REVERSE); + relay_chn_tilt_reverse(); // Go to tilt state + vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state()); + + // 2. Issue run reverse command + relay_chn_run_reverse(); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE_PENDING, relay_chn_get_state()); + vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state()); +} + +// TEST_CASE: Test transition from tilt forward to run reverse (without inertia) +// Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_REVERSE +TEST_CASE("Tilt Forward to Run Reverse transition without inertia", "[relay_chn][tilt][inertia]") { + TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); + g_is_component_initialized = true; + + // Prepare channel by running forward first to set last_run_cmd, then tilt + prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); + relay_chn_tilt_forward(); // Go to tilt state + vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state()); + + // 2. Issue run reverse command (opposite direction) + relay_chn_run_reverse(); + vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state()); +} + +// TEST_CASE: Test stopping from a tilt state (no inertia for stop command itself) +// Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_stop) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_IDLE +TEST_CASE("Tilt to Stop transition without immediate inertia for stop", "[relay_chn][tilt][inertia]") { + TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); + g_is_component_initialized = true; + + // Prepare channel by running forward first to set last_run_cmd, then tilt + prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); + relay_chn_tilt_forward(); // Go to tilt state + vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state()); + + // 2. Issue stop command + relay_chn_stop(); + // Stop command should apply immediately, setting state to FREE since last state was tilt. + vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); +} + +// Test relay_chn_tilt_auto() chooses correct tilt direction +TEST_CASE("relay_chn_tilt_auto chooses correct direction", "[relay_chn][tilt][auto]") { + TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); + g_is_component_initialized = true; + + // Prepare FORWARD + prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); + relay_chn_tilt_auto(); + vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state()); + relay_chn_tilt_stop(); + vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + + // Prepare REVERSE + prepare_channel_for_tilt(RELAY_CHN_CMD_REVERSE); + relay_chn_tilt_auto(); + vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state()); +} + +// Test sensitivity set/get +TEST_CASE("relay_chn_tilt_set_sensitivity and get", "[relay_chn][tilt][sensitivity]") { + TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); + g_is_component_initialized = true; + + relay_chn_tilt_set_sensitivity(0); + TEST_ASSERT_EQUAL_UINT8(0, relay_chn_tilt_get_sensitivity()); + + relay_chn_tilt_set_sensitivity(50); + TEST_ASSERT_EQUAL_UINT8(50, relay_chn_tilt_get_sensitivity()); + + relay_chn_tilt_set_sensitivity(100); + TEST_ASSERT_EQUAL_UINT8(100, relay_chn_tilt_get_sensitivity()); + + relay_chn_tilt_set_sensitivity(42); + TEST_ASSERT_EQUAL_UINT8(42, relay_chn_tilt_get_sensitivity()); +} + +// Test tilt counter logic: forward x3, reverse x3, extra reverse fails +TEST_CASE("tilt counter logic: forward and reverse consumption", "[relay_chn][tilt][counter]") { + TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); + g_is_component_initialized = true; + + prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); + + // Tilt forward 3 times + for (int i = 0; i < 3; ++i) { + relay_chn_tilt_forward(); + vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state()); + relay_chn_tilt_stop(); + vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + } + + // Now tilt reverse 3 times (should succeed) + for (int i = 0; i < 3; ++i) { + relay_chn_tilt_reverse(); + vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + if (i < 3) { + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state()); + relay_chn_tilt_stop(); + vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + } + } + + // Extra reverse tilt should fail (counter exhausted) + relay_chn_tilt_reverse(); + vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + // Should not enter TILT_REVERSE, should remain FREE or STOPPED + relay_chn_state_t state = relay_chn_get_state(); + TEST_ASSERT(state != RELAY_CHN_STATE_TILT_REVERSE); +} + +// Test run command during TILT state +TEST_CASE("run command during TILT state transitions correctly", "[relay_chn][tilt][run-during-tilt]") { + TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); + g_is_component_initialized = true; + + prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); + relay_chn_tilt_forward(); + vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state()); + + // Issue run reverse while in TILT_FORWARD + relay_chn_run_reverse(); + vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + // Should transition to REVERSE or REVERSE_PENDING depending on inertia logic + relay_chn_state_t state = relay_chn_get_state(); + TEST_ASSERT(state == RELAY_CHN_STATE_REVERSE || state == RELAY_CHN_STATE_REVERSE_PENDING); +} \ No newline at end of file diff --git a/test_apps/partition_table/partitionTable.csv b/test_apps/partition_table/partitionTable.csv new file mode 100644 index 0000000..4f87a1f --- /dev/null +++ b/test_apps/partition_table/partitionTable.csv @@ -0,0 +1,5 @@ +# ESP-IDF Partition Table +# Name, Type, SubType, Offset, Size, Flags +nvs,data,nvs,0xa000,24K, +phy_init,data,phy,0x10000,4K, +factory,app,factory,0x20000,1M, diff --git a/test_apps/sdkconfig b/test_apps/sdkconfig index e62945e..a20e982 100644 --- a/test_apps/sdkconfig +++ b/test_apps/sdkconfig @@ -1261,7 +1261,7 @@ CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=y # Relay Channel Driver Configuration # CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 -CONFIG_RELAY_CHN_COUNT=2 +CONFIG_RELAY_CHN_COUNT=1 CONFIG_RELAY_CHN_ENABLE_TILTING=y # end of Relay Channel Driver Configuration # end of Component config diff --git a/test_apps/sdkconfig.defaults.single b/test_apps/sdkconfig.defaults.single new file mode 100644 index 0000000..13ad935 --- /dev/null +++ b/test_apps/sdkconfig.defaults.single @@ -0,0 +1,8 @@ +# Disable task WDT for tests +CONFIG_ESP_TASK_WDT_INIT=n + +# Relay Channel Driver Default Configuration for Testing +# Keep this as short as possible for tests +CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 +CONFIG_RELAY_CHN_COUNT=1 +CONFIG_RELAY_CHN_ENABLE_TILTING=y \ No newline at end of file diff --git a/test_apps/sdkconfig.old b/test_apps/sdkconfig.old index e62945e..74f5cce 100644 --- a/test_apps/sdkconfig.old +++ b/test_apps/sdkconfig.old @@ -1,6 +1,6 @@ # # Automatically generated file. DO NOT EDIT. -# Espressif IoT Development Framework (ESP-IDF) 5.4.2 Project Configuration +# Espressif IoT Development Framework (ESP-IDF) 5.4.0 Project Configuration # CONFIG_SOC_BROWNOUT_RESET_SUPPORTED="Not determined" CONFIG_SOC_TWAI_BRP_DIV_SUPPORTED="Not determined" @@ -98,7 +98,6 @@ CONFIG_SOC_I2C_FIFO_LEN=32 CONFIG_SOC_I2C_CMD_REG_NUM=16 CONFIG_SOC_I2C_SUPPORT_SLAVE=y CONFIG_SOC_I2C_SUPPORT_APB=y -CONFIG_SOC_I2C_SUPPORT_10BIT_ADDR=y CONFIG_SOC_I2C_STOP_INDEPENDENT=y CONFIG_SOC_I2S_NUM=2 CONFIG_SOC_I2S_HW_VERSION_1=y @@ -176,8 +175,6 @@ CONFIG_SOC_TIMER_GROUP_TIMERS_PER_GROUP=2 CONFIG_SOC_TIMER_GROUP_COUNTER_BIT_WIDTH=64 CONFIG_SOC_TIMER_GROUP_TOTAL_TIMERS=4 CONFIG_SOC_TIMER_GROUP_SUPPORT_APB=y -CONFIG_SOC_LP_TIMER_BIT_WIDTH_LO=32 -CONFIG_SOC_LP_TIMER_BIT_WIDTH_HI=16 CONFIG_SOC_TOUCH_SENSOR_VERSION=1 CONFIG_SOC_TOUCH_SENSOR_NUM=10 CONFIG_SOC_TOUCH_SAMPLE_CFG_NUM=1 @@ -247,7 +244,7 @@ CONFIG_IDF_TOOLCHAIN_GCC=y CONFIG_IDF_TARGET_ARCH_XTENSA=y CONFIG_IDF_TARGET_ARCH="xtensa" CONFIG_IDF_TARGET="esp32" -CONFIG_IDF_INIT_VERSION="5.4.2" +CONFIG_IDF_INIT_VERSION="$IDF_INIT_VERSION" CONFIG_IDF_TARGET_ESP32=y CONFIG_IDF_FIRMWARE_CHIP_ID=0x0000 @@ -288,10 +285,10 @@ CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y # CONFIG_BOOTLOADER_LOG_LEVEL_NONE is not set # CONFIG_BOOTLOADER_LOG_LEVEL_ERROR is not set # CONFIG_BOOTLOADER_LOG_LEVEL_WARN is not set -CONFIG_BOOTLOADER_LOG_LEVEL_INFO=y -# CONFIG_BOOTLOADER_LOG_LEVEL_DEBUG is not set +# CONFIG_BOOTLOADER_LOG_LEVEL_INFO is not set +CONFIG_BOOTLOADER_LOG_LEVEL_DEBUG=y # CONFIG_BOOTLOADER_LOG_LEVEL_VERBOSE is not set -CONFIG_BOOTLOADER_LOG_LEVEL=3 +CONFIG_BOOTLOADER_LOG_LEVEL=4 # # Format @@ -402,7 +399,7 @@ CONFIG_PARTITION_TABLE_SINGLE_APP=y # CONFIG_PARTITION_TABLE_CUSTOM is not set CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions_singleapp.csv" -CONFIG_PARTITION_TABLE_OFFSET=0x8000 +CONFIG_PARTITION_TABLE_OFFSET=0x9000 CONFIG_PARTITION_TABLE_MD5=y # end of Partition Table @@ -465,7 +462,6 @@ CONFIG_TWAI_ERRATA_FIX_LISTEN_ONLY_DOM=y # CONFIG_ADC_DISABLE_DAC=y # CONFIG_ADC_SUPPRESS_DEPRECATE_WARN is not set -# CONFIG_ADC_SKIP_LEGACY_CONFLICT_CHECK is not set # # Legacy ADC Calibration Configuration @@ -481,55 +477,42 @@ CONFIG_ADC_CAL_LUT_ENABLE=y # Legacy DAC Driver Configurations # # CONFIG_DAC_SUPPRESS_DEPRECATE_WARN is not set -# CONFIG_DAC_SKIP_LEGACY_CONFLICT_CHECK is not set # end of Legacy DAC Driver Configurations # # Legacy MCPWM Driver Configurations # # CONFIG_MCPWM_SUPPRESS_DEPRECATE_WARN is not set -# CONFIG_MCPWM_SKIP_LEGACY_CONFLICT_CHECK is not set # end of Legacy MCPWM Driver Configurations # # Legacy Timer Group Driver Configurations # # CONFIG_GPTIMER_SUPPRESS_DEPRECATE_WARN is not set -# CONFIG_GPTIMER_SKIP_LEGACY_CONFLICT_CHECK is not set # end of Legacy Timer Group Driver Configurations # # Legacy RMT Driver Configurations # # CONFIG_RMT_SUPPRESS_DEPRECATE_WARN is not set -# CONFIG_RMT_SKIP_LEGACY_CONFLICT_CHECK is not set # end of Legacy RMT Driver Configurations # # Legacy I2S Driver Configurations # # CONFIG_I2S_SUPPRESS_DEPRECATE_WARN is not set -# CONFIG_I2S_SKIP_LEGACY_CONFLICT_CHECK is not set # end of Legacy I2S Driver Configurations -# -# Legacy I2C Driver Configurations -# -# CONFIG_I2C_SKIP_LEGACY_CONFLICT_CHECK is not set -# end of Legacy I2C Driver Configurations - # # Legacy PCNT Driver Configurations # # CONFIG_PCNT_SUPPRESS_DEPRECATE_WARN is not set -# CONFIG_PCNT_SKIP_LEGACY_CONFLICT_CHECK is not set # end of Legacy PCNT Driver Configurations # # Legacy SDM Driver Configurations # # CONFIG_SDM_SUPPRESS_DEPRECATE_WARN is not set -# CONFIG_SDM_SKIP_LEGACY_CONFLICT_CHECK is not set # end of Legacy SDM Driver Configurations # end of Driver Configurations @@ -572,7 +555,6 @@ CONFIG_DAC_DMA_AUTO_16BIT_ALIGN=y CONFIG_GPTIMER_ISR_HANDLER_IN_IRAM=y # CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM is not set # CONFIG_GPTIMER_ISR_IRAM_SAFE is not set -CONFIG_GPTIMER_OBJ_CACHE_SAFE=y # CONFIG_GPTIMER_ENABLE_DEBUG_LOG is not set # end of ESP-Driver:GPTimer Configurations @@ -724,7 +706,7 @@ CONFIG_RTC_CLK_CAL_CYCLES=1024 # # Peripheral Control # -# CONFIG_PERIPH_CTRL_FUNC_IN_IRAM is not set +CONFIG_PERIPH_CTRL_FUNC_IN_IRAM=y # end of Peripheral Control # @@ -954,6 +936,7 @@ CONFIG_HAL_ASSERTION_EQUALS_SYSTEM=y CONFIG_HAL_DEFAULT_ASSERTION_LEVEL=2 CONFIG_HAL_SPI_MASTER_FUNC_IN_IRAM=y CONFIG_HAL_SPI_SLAVE_FUNC_IN_IRAM=y +# CONFIG_HAL_ECDSA_GEN_SIG_CM is not set # end of Hardware Abstraction Layer (HAL) and Low Level (LL) # @@ -1062,7 +1045,6 @@ CONFIG_MBEDTLS_HAVE_TIME=y # CONFIG_MBEDTLS_PLATFORM_TIME_ALT is not set # CONFIG_MBEDTLS_HAVE_TIME_DATE is not set CONFIG_MBEDTLS_ECDSA_DETERMINISTIC=y -CONFIG_MBEDTLS_SHA1_C=y CONFIG_MBEDTLS_SHA512_C=y # CONFIG_MBEDTLS_SHA3_C is not set CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT=y @@ -1143,7 +1125,6 @@ CONFIG_MBEDTLS_ECP_NIST_OPTIM=y # CONFIG_MBEDTLS_HKDF_C is not set # CONFIG_MBEDTLS_THREADING_C is not set CONFIG_MBEDTLS_ERROR_STRINGS=y -# CONFIG_MBEDTLS_ALLOW_WEAK_CERTIFICATE_VERIFICATION is not set # end of mbedTLS # @@ -1203,7 +1184,6 @@ CONFIG_SPI_FLASH_BROWNOUT_RESET=y # CONFIG_SPI_FLASH_SUSPEND_TSUS_VAL_US=50 # CONFIG_SPI_FLASH_FORCE_ENABLE_XMC_C_SUSPEND is not set -# CONFIG_SPI_FLASH_FORCE_ENABLE_C6_H2_SUSPEND is not set # end of Optional and Experimental Features (READ DOCS FIRST) # end of Main Flash configuration @@ -1277,10 +1257,10 @@ CONFIG_RELAY_CHN_ENABLE_TILTING=y # CONFIG_LOG_BOOTLOADER_LEVEL_NONE is not set # CONFIG_LOG_BOOTLOADER_LEVEL_ERROR is not set # CONFIG_LOG_BOOTLOADER_LEVEL_WARN is not set -CONFIG_LOG_BOOTLOADER_LEVEL_INFO=y -# CONFIG_LOG_BOOTLOADER_LEVEL_DEBUG is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_INFO is not set +CONFIG_LOG_BOOTLOADER_LEVEL_DEBUG=y # CONFIG_LOG_BOOTLOADER_LEVEL_VERBOSE is not set -CONFIG_LOG_BOOTLOADER_LEVEL=3 +CONFIG_LOG_BOOTLOADER_LEVEL=4 # CONFIG_APP_ROLLBACK_ENABLE is not set # CONFIG_FLASH_ENCRYPTION_ENABLED is not set # CONFIG_FLASHMODE_QIO is not set From f04632dc77b429302ff28e1f9cbbedc802c4a097 Mon Sep 17 00:00:00 2001 From: ismail Date: Thu, 14 Aug 2025 16:38:00 +0300 Subject: [PATCH 04/69] Unignore .vscode folder. Unignore .vscode folder and add necessary configuration files for a synced development environment. --- .gitignore | 2 +- .vscode/c_cpp_properties.json | 23 +++++++++++++++++++++++ .vscode/launch.json | 15 +++++++++++++++ .vscode/settings.json | 22 +--------------------- test_apps/.vscode/settings.json | 3 +++ 5 files changed, 43 insertions(+), 22 deletions(-) create mode 100644 .vscode/c_cpp_properties.json create mode 100644 .vscode/launch.json create mode 100644 test_apps/.vscode/settings.json diff --git a/.gitignore b/.gitignore index ca0bf62..a5217f8 100644 --- a/.gitignore +++ b/.gitignore @@ -68,7 +68,7 @@ coverage_report/ test_multi_heap_host # VS Code Settings -.vscode/ +# .vscode/ # VIM files *.swp diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..7182d4a --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,23 @@ +{ + "configurations": [ + { + "name": "ESP-IDF", + "compilerPath": "${config:idf.toolsPath}/tools/riscv32-esp-elf/esp-14.2.0_20241119/riscv32-esp-elf/bin/riscv32-esp-elf-gcc", + "compileCommands": "${config:idf.buildPath}/compile_commands.json", + "includePath": [ + "${config:idf.espIdfPath}/components/**", + "${config:idf.espIdfPathWin}/components/**", + "${workspaceFolder}/**" + ], + "browse": { + "path": [ + "${config:idf.espIdfPath}/components", + "${config:idf.espIdfPathWin}/components", + "${workspaceFolder}" + ], + "limitSymbolsToIncludedHeaders": true + } + } + ], + "version": 4 +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..2511a38 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "gdbtarget", + "request": "attach", + "name": "Eclipse CDT GDB Adapter" + }, + { + "type": "espidf", + "name": "Launch", + "request": "launch" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index f19cc63..b53318f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,23 +1,3 @@ { - "files.associations": { - "relay_chn.h": "c", - "stdlib.h": "c", - "cstdint": "c", - "relay_chn_run_info.h": "c", - "esp_err.h": "c", - "relay_chn_output.h": "c", - "relay_chn_core.h": "c", - "relay_chn_ctl.h": "c", - "relay_chn_tilt.h": "c", - "relay_chn_defs.h": "c", - "esp_check.h": "c", - "esp_event_base.h": "c", - "esp_event.h": "c", - "queue.h": "c", - "relay_chn_priv_types.h": "c", - "relay_chn_adapter.h": "c", - "relay_chn_types.h": "c" - }, - "idf.port": "/dev/ttyUSB0", - "idf.pythonInstallPath": "/usr/bin/python" + "C_Cpp.intelliSenseEngine": "default" } \ No newline at end of file diff --git a/test_apps/.vscode/settings.json b/test_apps/.vscode/settings.json new file mode 100644 index 0000000..0bb915a --- /dev/null +++ b/test_apps/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "C_Cpp.intelliSenseEngine": "default" +} From b19f0c553b846c7f1b78f267e716ce9ea7a069c2 Mon Sep 17 00:00:00 2001 From: ismail Date: Tue, 19 Aug 2025 17:33:45 +0300 Subject: [PATCH 05/69] Add NVS support for relay channel config persistence - Introduced NVS configuration options in Kconfig. - Implemented NVS initialization and deinitialization in relay_chn_core. - Added functions for storing and retrieving relay channel direction and tilt sensitivity in NVS. - Updated relay_chn_tilt and relay_chn_output to utilize NVS for state management. - Created relay_chn_nvs.c and relay_chn_nvs.h for NVS-related functionalities. Closes #1074. --- CMakeLists.txt | 7 +- Kconfig | 35 ++++++++ include/relay_chn_defs.h | 9 ++ private_include/relay_chn_nvs.h | 106 ++++++++++++++++++++++ src/relay_chn_core.c | 15 ++++ src/relay_chn_nvs.c | 125 ++++++++++++++++++++++++++ src/relay_chn_output.c | 63 +++++++++++-- src/relay_chn_tilt.c | 153 ++++++++++++++++++++++++++++---- 8 files changed, 490 insertions(+), 23 deletions(-) create mode 100644 private_include/relay_chn_nvs.h create mode 100644 src/relay_chn_nvs.c diff --git a/CMakeLists.txt b/CMakeLists.txt index e5b0c68..e7cda68 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,12 @@ else() list(APPEND srcs "src/relay_chn_ctl_single.c") endif() +if(CONFIG_RELAY_CHN_NVS) + list(APPEND srcs "src/relay_chn_nvs.c") +endif() + + idf_component_register(SRCS ${srcs} INCLUDE_DIRS ${include_dirs} PRIV_INCLUDE_DIRS ${priv_include_dirs} - REQUIRES driver esp_timer esp_event) + REQUIRES driver esp_timer esp_event nvs_flash) diff --git a/Kconfig b/Kconfig index c897c2a..638e846 100644 --- a/Kconfig +++ b/Kconfig @@ -26,4 +26,39 @@ menu "Relay Channel Driver Configuration" at a time. Tilting is specifically designed for controlling some types of curtains that need to be adjusted to let enter specific amount of day light. + + config RELAY_CHN_ENABLE_NVS + bool "Enable persistent NVS storage for relay channel" + default n + help + If enabled, relay channel configuration will be stored in NVS. + +endmenu + +menu "Relay Channel NVS Storage Configuration" + depends on RELAY_CHN_ENABLE_NVS + + config RELAY_CHN_NVS_NAMESPACE + string "NVS namespace for relay channel storage" + default "relay_chn" + help + The NVS namespace used for storing relay channel configuration. + This should be unique to avoid conflicts with other components. + + config RELAY_CHN_NVS_CUSTOM_PARTITION + bool "Use custom NVS partition for relay channel storage" + default n + help + If enabled, a custom NVS partition will be used for storing + relay channel configuration. If disabled, the default NVS + partition will be used. + + config RELAY_CHN_NVS_CUSTOM_PARTITION_NAME + string "Custom NVS partition name" + depends on RELAY_CHN_NVS_CUSTOM_PARTITION + default "app_data" + help + The name of the custom NVS partition used for storing relay channel + configuration. Make sure the name is exactly the same as label defined + in the relevant partition table. endmenu \ No newline at end of file diff --git a/include/relay_chn_defs.h b/include/relay_chn_defs.h index 0000b01..855905e 100644 --- a/include/relay_chn_defs.h +++ b/include/relay_chn_defs.h @@ -14,6 +14,15 @@ extern "C" { #define RELAY_CHN_OPPOSITE_INERTIA_MS CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS #define RELAY_CHN_COUNT CONFIG_RELAY_CHN_COUNT #define RELAY_CHN_ENABLE_TILTING CONFIG_RELAY_CHN_ENABLE_TILTING +#define RELAY_CHN_ENABLE_NVS CONFIG_RELAY_CHN_ENABLE_NVS + +#if RELAY_CHN_ENABLE_NVS == 1 +#define RELAY_CHN_NVS_NAMESPACE CONFIG_RELAY_CHN_NVS_NAMESPACE +#define RELAY_CHN_NVS_CUSTOM_PARTITION CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION +#if RELAY_CHN_NVS_CUSTOM_PARTITION == 1 +#define RELAY_CHN_NVS_CUSTOM_PARTITION_NAME CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME +#endif +#endif #if RELAY_CHN_COUNT > 1 #define RELAY_CHN_ID_ALL RELAY_CHN_COUNT /*!< Special ID to address all channels */ diff --git a/private_include/relay_chn_nvs.h b/private_include/relay_chn_nvs.h new file mode 100644 index 0000000..6145494 --- /dev/null +++ b/private_include/relay_chn_nvs.h @@ -0,0 +1,106 @@ +/* + * SPDX-FileCopyrightText: 2025 Kozmotronik Tech + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include "esp_err.h" +#include "nvs_flash.h" +#include "nvs.h" +#include "relay_chn_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Initialize NVS storage for relay channels. + * + * @attention Before calling this function, make sure the NVS flash is initialised + * using either the nvs_flash_init() function for the default NVS partition or the + * nvs_flash_init_partition() function for a custom partition. + * + * @return ESP_OK on success, error code otherwise. + */ +esp_err_t relay_chn_nvs_init(void); + +/** + * @brief Store relay channel direction in NVS. + * + * @param[in] ch Channel number. + * @param[in] direction Direction to store. + * @return ESP_OK on success, error code otherwise. + */ +esp_err_t relay_chn_nvs_set_direction(uint8_t ch, relay_chn_direction_t direction); + +/** + * @brief Retrieve relay channel direction from NVS. + * + * @param[in] ch Channel number. + * @param[out] direction Pointer to store retrieved direction. + * @return ESP_OK on success, error code otherwise. + */ +esp_err_t relay_chn_nvs_get_direction(uint8_t ch, relay_chn_direction_t *direction); + +#ifdef RELAY_CHN_ENABLE_TILTING +/** + * @brief Store tilt sensitivity in NVS. + * + * @param[in] ch Channel number. + * @param[in] sensitivity Sensitivity value to store. + * @return ESP_OK on success, error code otherwise. + */ +esp_err_t relay_chn_nvs_set_tilt_sensitivity(uint8_t ch, uint8_t sensitivity); + +/** + * @brief Retrieve tilt sensitivity from NVS. + * + * @param[in] ch Channel number. + * @param[out] sensitivity Pointer to store retrieved sensitivity. + * @return ESP_OK on success, error code otherwise. + */ +esp_err_t relay_chn_nvs_get_tilt_sensitivity(uint8_t ch, uint8_t *sensitivity); + +/** + * @brief Store tilt counters in NVS. + * + * @param[in] ch Channel number. + * @param[in] forward_count Forward tilt counter value. + * @param[in] reverse_count Reverse tilt counter value. + * @return ESP_OK on success, error code otherwise. + */ +esp_err_t relay_chn_nvs_set_tilt_count(uint8_t ch, uint32_t forward_count, uint32_t reverse_count); + +/** + * @brief Retrieve tilt counters from NVS. + * + * @param[in] ch Channel number. + * @param[out] forward_count Pointer to store forward tilt counter. + * @param[out] reverse_count Pointer to store reverse tilt counter. + * @return ESP_OK on success, error code otherwise. + */ +esp_err_t relay_chn_nvs_get_tilt_count(uint8_t ch, uint32_t *forward_count, uint32_t *reverse_count); +#endif // RELAY_CHN_ENABLE_TILTING + +/** + * @brief Erase all keys in the NVS namespace. + * + * This function will erase all key-value pairs in the NVS namespace used by relay channels. + * + * @return ESP_OK on success, error code otherwise. + */ +esp_err_t relay_chn_nvs_erase_all(void); + +/** + * @brief Deinitialize NVS storage for relay channels. + * + * @return ESP_OK on success, error code otherwise. + */ +esp_err_t relay_chn_nvs_deinit(void); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/src/relay_chn_core.c b/src/relay_chn_core.c index 85e30ee..0f2210d 100644 --- a/src/relay_chn_core.c +++ b/src/relay_chn_core.c @@ -11,9 +11,15 @@ #include "relay_chn_output.h" #include "relay_chn_run_info.h" #include "relay_chn_ctl.h" + #if RELAY_CHN_ENABLE_TILTING == 1 #include "relay_chn_tilt.h" #endif + +#if RELAY_CHN_ENABLE_NVS == 1 +#include "relay_chn_nvs.h" +#endif + #include "relay_chn_core.h" @@ -89,6 +95,11 @@ esp_err_t relay_chn_create(const uint8_t* gpio_map, uint8_t gpio_count) ESP_RETURN_ON_FALSE(gpio_map != NULL, ESP_ERR_INVALID_ARG, TAG, "gpio_map cannot be NULL"); esp_err_t ret; +#if RELAY_CHN_ENABLE_NVS == 1 + ret = relay_chn_nvs_init(); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize NVS for relay channel"); +#endif + // Initialize the output ret = relay_chn_output_init(gpio_map, gpio_count); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize relay channel outputs"); @@ -137,6 +148,10 @@ void relay_chn_destroy(void) relay_chn_ctl_deinit(); relay_chn_output_deinit(); +#if RELAY_CHN_ENABLE_NVS == 1 + relay_chn_nvs_deinit(); +#endif + // Destroy the event loop esp_event_loop_delete(relay_chn_event_loop); relay_chn_event_loop = NULL; diff --git a/src/relay_chn_nvs.c b/src/relay_chn_nvs.c new file mode 100644 index 0000000..0f136f0 --- /dev/null +++ b/src/relay_chn_nvs.c @@ -0,0 +1,125 @@ +/* + * SPDX-FileCopyrightText: 2025 Kozmotronik Tech + * + * SPDX-License-Identifier: MIT + */ + +#include "esp_check.h" +#include "relay_chn_nvs.h" + +#define RELAY_CHN_KEY_DIR "dir" +#ifdef RELAY_CHN_ENABLE_TILTING +#define RELAY_CHN_KEY_SENS(ch) "sens_%d" +#define RELAY_CHN_KEY_TFWD(ch) "tfwd_%d" +#define RELAY_CHN_KEY_TREV(ch) "trev_%d" +#endif + +static const char *TAG = "RELAY_CHN_STORAGE_NVS"; + +static nvs_handle_t relay_chn_nvs; + +esp_err_t relay_chn_nvs_init() +{ + esp_err_t ret; +#if RELAY_CHN_NVS_CUSTOM_PARTITION == 1 + ret = nvs_open_from_partition(RELAY_CHN_NVS_CUSTOM_PARTITION_NAME, + RELAY_CHN_NVS_NAMESPACE, + NVS_READWRITE, + &relay_chn_nvs); + + ESP_RETURN_ON_ERROR(ret, + TAG, + "Failed to open NVS namespace '%s' from partition '%s' with error %s", + RELAY_CHN_NVS_NAMESPACE, + RELAY_CHN_NVS_CUSTOM_PARTITION_NAME, + esp_err_to_name(ret)); +#else + ret = nvs_open(RELAY_CHN_NVS_NAMESPACE, NVS_READWRITE, &relay_chn_nvs); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to open NVS namespace '%s'", RELAY_CHN_NVS_NAMESPACE); +#endif // RELAY_CHN_NVS_CUSTOM_PARTITION + return ESP_OK; +} + +esp_err_t relay_chn_nvs_set_direction(uint8_t ch, relay_chn_direction_t direction) +{ + uint8_t direction_val; + esp_err_t ret = nvs_get_u8(relay_chn_nvs, RELAY_CHN_KEY_DIR, &direction_val); + if (ret == ESP_ERR_NVS_NOT_FOUND) { + // The key does not exist yet, set it to zero which is the default direction + direction_val = RELAY_CHN_DIRECTION_DEFAULT; + } else if (ret != ESP_OK) { + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to get direction from NVS with error: %s", esp_err_to_name(ret)); + } + direction_val &= ~(1 << ch); // Clear the bit for the channel + direction_val |= (((uint8_t) direction) << ch); // Set the new direction bit + ret = nvs_set_u8(relay_chn_nvs, RELAY_CHN_KEY_DIR, direction_val); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set direction for channel %d", ch); + return nvs_commit(relay_chn_nvs); +} + +esp_err_t relay_chn_nvs_get_direction(uint8_t ch, relay_chn_direction_t *direction) +{ + ESP_RETURN_ON_FALSE(direction != NULL, ESP_ERR_INVALID_ARG, TAG, "Direction pointer is NULL"); + + uint8_t direction_val; + esp_err_t ret = nvs_get_u8(relay_chn_nvs, RELAY_CHN_KEY_DIR, &direction_val); + if (ret != ESP_OK) { + return ret; // Return error if the key does not exist + } + *direction = (relay_chn_direction_t)((direction_val >> ch) & 0x01); + return ESP_OK; +} + +#ifdef RELAY_CHN_ENABLE_TILTING +esp_err_t relay_chn_nvs_set_tilt_sensitivity(uint8_t ch, uint8_t sensitivity) +{ + esp_err_t ret = nvs_set_u8(relay_chn_nvs, RELAY_CHN_KEY_SENS(ch), sensitivity); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set tilt sensitivity for channel %d", ch); + return nvs_commit(relay_chn_nvs); +} + +esp_err_t relay_chn_nvs_get_tilt_sensitivity(uint8_t ch, uint8_t *sensitivity) +{ + ESP_RETURN_ON_FALSE(sensitivity != NULL, ESP_ERR_INVALID_ARG, TAG, "Sensitivity pointer is NULL"); + + return nvs_get_u8(relay_chn_nvs, RELAY_CHN_KEY_SENS(ch), sensitivity); +} + +esp_err_t relay_chn_nvs_set_tilt_count(uint8_t ch, uint32_t forward_count, uint32_t reverse_count) +{ + esp_err_t ret; + ret = nvs_set_u32(relay_chn_nvs, RELAY_CHN_KEY_TFWD(ch), forward_count); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to save forward_count tilt counter"); + ret = nvs_set_u32(relay_chn_nvs, RELAY_CHN_KEY_TREV(ch), reverse_count); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to save reverse_count tilt counter"); + return nvs_commit(relay_chn_nvs); +} + +esp_err_t relay_chn_nvs_get_tilt_count(uint8_t ch, uint32_t *forward_count, uint32_t *reverse_count) +{ + ESP_RETURN_ON_FALSE(forward_count != NULL && reverse_count != NULL, + ESP_ERR_INVALID_ARG, TAG, "Counter pointers are NULL"); + + esp_err_t ret = nvs_get_u32(relay_chn_nvs, RELAY_CHN_KEY_TFWD(ch), forward_count); + if (ret != ESP_OK) { + return ret; // Return error if the key does not exist + } + return nvs_get_u32(relay_chn_nvs, RELAY_CHN_KEY_TREV(ch), reverse_count); +} +#endif // RELAY_CHN_ENABLE_TILTING + +esp_err_t relay_chn_nvs_erase_all() +{ + // Erase all key-value pairs in the relay_chn NVS namespace + esp_err_t ret = nvs_erase_all(relay_chn_nvs); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to erase all keys in NVS namespace '%s'", RELAY_CHN_NVS_NAMESPACE); + + // Commit the changes + return nvs_commit(relay_chn_nvs); +} + +esp_err_t relay_chn_nvs_deinit() +{ + nvs_close(relay_chn_nvs); + return ESP_OK; +} diff --git a/src/relay_chn_output.c b/src/relay_chn_output.c index 4f819d1..44c1465 100644 --- a/src/relay_chn_output.c +++ b/src/relay_chn_output.c @@ -10,6 +10,10 @@ #include "relay_chn_output.h" #include "relay_chn_core.h" +#if RELAY_CHN_ENABLE_NVS == 1 +#include "relay_chn_nvs.h" +#endif + static const char *TAG = "RELAY_CHN_OUTPUT"; @@ -38,7 +42,10 @@ static esp_err_t relay_chn_output_check_gpio_capabilities(uint8_t gpio_count) return ESP_OK; } -static esp_err_t relay_chn_output_ctl_init(relay_chn_output_t *output, gpio_num_t forward_pin, gpio_num_t reverse_pin) +static esp_err_t relay_chn_output_ctl_init(relay_chn_output_t *output, + gpio_num_t forward_pin, + gpio_num_t reverse_pin, + relay_chn_direction_t direction) { ESP_RETURN_ON_FALSE(GPIO_IS_VALID_OUTPUT_GPIO(forward_pin), ESP_ERR_INVALID_ARG, TAG, "Invalid GPIO pin number for forward_pin: %d", forward_pin); @@ -60,12 +67,26 @@ static esp_err_t relay_chn_output_ctl_init(relay_chn_output_t *output, gpio_num_ // Initialize the GPIOs // Initialize the relay channel output - output->forward_pin = forward_pin; - output->reverse_pin = reverse_pin; - output->direction = RELAY_CHN_DIRECTION_DEFAULT; + output->forward_pin = direction == RELAY_CHN_DIRECTION_DEFAULT ? forward_pin : reverse_pin; + output->reverse_pin = direction == RELAY_CHN_DIRECTION_DEFAULT ? reverse_pin : forward_pin; + output->direction = direction; return ESP_OK; } +#if RELAY_CHN_ENABLE_NVS == 1 +static esp_err_t relay_chn_output_load_direction(uint8_t ch, relay_chn_direction_t *direction) +{ + esp_err_t ret = relay_chn_nvs_get_direction(ch, direction); + if (ret == ESP_ERR_NVS_NOT_FOUND) { + // If the key does not exist, use the default direction + *direction = RELAY_CHN_DIRECTION_DEFAULT; + } else if (ret != ESP_OK) { + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to get direction from storage for channel %d: %s", ch, esp_err_to_name(ret)); + } + return ESP_OK; +} +#endif + esp_err_t relay_chn_output_init(const uint8_t* gpio_map, uint8_t gpio_count) { esp_err_t ret; @@ -78,12 +99,24 @@ esp_err_t relay_chn_output_init(const uint8_t* gpio_map, uint8_t gpio_count) int gpio_index = i << 1; // gpio_index = i * 2 gpio_num_t forward_pin = (gpio_num_t) gpio_map[gpio_index]; gpio_num_t reverse_pin = (gpio_num_t) gpio_map[gpio_index + 1]; - - ret = relay_chn_output_ctl_init(output, forward_pin, reverse_pin); + + relay_chn_direction_t direction = RELAY_CHN_DIRECTION_DEFAULT; +#if RELAY_CHN_ENABLE_NVS == 1 + // If NVS storage is enabled, retrieve the direction from storage + ret = relay_chn_output_load_direction(i, &direction); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load direction from storage for channel %d", i); +#endif + ret = relay_chn_output_ctl_init(output, forward_pin, reverse_pin, direction); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize relay channel %d", i); } #else - ret = relay_chn_output_ctl_init(&output, gpio_map[0], gpio_map[1]); + relay_chn_direction_t direction = RELAY_CHN_DIRECTION_DEFAULT; +#if RELAY_CHN_ENABLE_NVS == 1 + // If NVS storage is enabled, retrieve the direction from storage + ret = relay_chn_output_load_direction(0, &direction); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load direction from storage for channel %d", 0); +#endif + ret = relay_chn_output_ctl_init(&output, gpio_map[0], gpio_map[1], direction); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize relay channel"); #endif return ESP_OK; @@ -160,6 +193,22 @@ void relay_chn_output_flip(relay_chn_output_t *output) output->direction = (output->direction == RELAY_CHN_DIRECTION_DEFAULT) ? RELAY_CHN_DIRECTION_FLIPPED : RELAY_CHN_DIRECTION_DEFAULT; + +#if RELAY_CHN_ENABLE_NVS == 1 + uint8_t ch = 0; +#if RELAY_CHN_COUNT > 1 + for (uint8_t i = 0; i < RELAY_CHN_COUNT; i++) { + if (output == &outputs[i]) { + ch = i; + break; + } + } +#endif + esp_err_t ret = relay_chn_nvs_set_direction(ch, output->direction); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to save flipped direction for channel %d: %s", ch, esp_err_to_name(ret)); + } +#endif } relay_chn_direction_t relay_chn_output_get_direction(relay_chn_output_t *output) diff --git a/src/relay_chn_tilt.c b/src/relay_chn_tilt.c index de7610f..c89c6e9 100644 --- a/src/relay_chn_tilt.c +++ b/src/relay_chn_tilt.c @@ -10,6 +10,12 @@ #include "relay_chn_run_info.h" #include "relay_chn_tilt.h" +#if RELAY_CHN_ENABLE_NVS == 1 +#include "relay_chn_nvs.h" + +#define RELAY_CHN_TILT_FLUSH_DEBOUNCE_MS 3000 +#endif + static const char *TAG = "RELAY_CHN_TILT"; @@ -65,6 +71,9 @@ typedef struct relay_chn_tilt_ctl { relay_chn_tilt_timing_t tilt_timing; /*!< Tilt timing structure */ relay_chn_tilt_counter_t tilt_counter; /*!< Tilt counter structure */ esp_timer_handle_t tilt_timer; /*!< Tilt timer handle */ +#if RELAY_CHN_ENABLE_NVS == 1 + esp_timer_handle_t flush_timer; /*!< Flush timer to avoid frequent write of tilt counters */ +#endif } relay_chn_tilt_ctl_t; @@ -295,7 +304,7 @@ static void relay_chn_tilt_set_timing_values(relay_chn_tilt_timing_t *tilt_timin tilt_timing->pause_time_ms = pause_time_ms; } -static void _relay_chn_tilt_sensitivity_set(relay_chn_tilt_ctl_t *tilt_ctl, uint8_t sensitivity) +static void relay_chn_tilt_compute_set_sensitivity(relay_chn_tilt_ctl_t *tilt_ctl, uint8_t sensitivity) { if (sensitivity >= 100) { relay_chn_tilt_set_timing_values(&tilt_ctl->tilt_timing, @@ -309,6 +318,12 @@ static void _relay_chn_tilt_sensitivity_set(relay_chn_tilt_ctl_t *tilt_ctl, uint RELAY_CHN_TILT_RUN_MIN_MS, RELAY_CHN_TILT_PAUSE_MIN_MS); } + else if (sensitivity == RELAY_CHN_TILT_DEFAULT_SENSITIVITY) { + relay_chn_tilt_set_timing_values(&tilt_ctl->tilt_timing, + sensitivity, + RELAY_CHN_TILT_DEFAULT_RUN_MS, + RELAY_CHN_TILT_DEFAULT_PAUSE_MS); + } else { // Compute the new timing values from the sensitivity percent value by using linear interpolation uint32_t tilt_run_time_ms = 0, tilt_pause_time_ms = 0; @@ -331,12 +346,16 @@ void relay_chn_tilt_set_sensitivity(uint8_t chn_id, uint8_t sensitivity) if (chn_id == RELAY_CHN_ID_ALL) { for (int i = 0; i < RELAY_CHN_COUNT; i++) { - _relay_chn_tilt_sensitivity_set(&tilt_ctls[i], sensitivity); + relay_chn_tilt_compute_set_sensitivity(&tilt_ctls[i], sensitivity); } } else { - _relay_chn_tilt_sensitivity_set(&tilt_ctls[chn_id], sensitivity); + relay_chn_tilt_compute_set_sensitivity(&tilt_ctls[chn_id], sensitivity); } + +#if RELAY_CHN_ENABLE_NVS == 1 + relay_chn_nvs_set_tilt_sensitivity(chn_id, sensitivity); +#endif // RELAY_CHN_ENABLE_NVS } esp_err_t relay_chn_tilt_get_sensitivity(uint8_t chn_id, uint8_t *sensitivity, size_t length) @@ -367,7 +386,11 @@ esp_err_t relay_chn_tilt_get_sensitivity(uint8_t chn_id, uint8_t *sensitivity, s void relay_chn_tilt_set_sensitivity(uint8_t sensitivity) { - _relay_chn_tilt_sensitivity_set(&tilt_ctl, sensitivity); + relay_chn_tilt_compute_set_sensitivity(&tilt_ctl, sensitivity); + +#if RELAY_CHN_ENABLE_NVS == 1 + relay_chn_nvs_set_tilt_sensitivity(0, sensitivity); +#endif // RELAY_CHN_ENABLE_NVS } uint8_t relay_chn_tilt_get_sensitivity() @@ -380,6 +403,10 @@ void relay_chn_tilt_reset_count(relay_chn_tilt_ctl_t *tilt_ctl) { tilt_ctl->tilt_counter.tilt_forward_count = 0; tilt_ctl->tilt_counter.tilt_reverse_count = 0; + +#if RELAY_CHN_ENABLE_NVS == 1 + esp_timer_stop(tilt_ctl->flush_timer); +#endif } /** @@ -449,6 +476,31 @@ static uint32_t relay_chn_tilt_count_update(relay_chn_tilt_ctl_t *tilt_ctl) return 0; } +#if RELAY_CHN_ENABLE_NVS == 1 +static esp_err_t relay_chn_tilt_save_tilt_counter(relay_chn_tilt_ctl_t *tilt_ctl) +{ + // Save the tilt count to NVS storage + esp_err_t ret = relay_chn_nvs_set_tilt_count(tilt_ctl->chn_ctl->id, + tilt_ctl->tilt_counter.tilt_forward_count, + tilt_ctl->tilt_counter.tilt_reverse_count); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "relay_chn_tilt_execute_stop: Failed to save tilt count for channel #%d: %s", tilt_ctl->chn_ctl->id, esp_err_to_name(ret)); + } + return ESP_OK; +} + +static void relay_chn_tilt_flush_timer_cb(void *arg) +{ + relay_chn_tilt_ctl_t* tilt_ctl = (relay_chn_tilt_ctl_t*) arg; + ESP_RETURN_VOID_ON_FALSE(tilt_ctl != NULL, TAG, "relay_chn_tilt_flush_timer_cb: timer arg is NULL"); + // Save the tilt count to storage + esp_err_t ret = relay_chn_tilt_save_tilt_counter(tilt_ctl); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "relay_chn_tilt_execute_stop: Failed to save tilt count for channel #%d: %s", tilt_ctl->chn_ctl->id, esp_err_to_name(ret)); + } +} +#endif + static void relay_chn_tilt_execute_stop(relay_chn_tilt_ctl_t *tilt_ctl) { // Stop the channel's timer if active @@ -461,6 +513,11 @@ static void relay_chn_tilt_execute_stop(relay_chn_tilt_ctl_t *tilt_ctl) ESP_LOGE(TAG, "relay_chn_tilt_execute_stop: Failed to output stop for relay channel #%d!", tilt_ctl->chn_ctl->id); } relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP); + +#if RELAY_CHN_ENABLE_NVS == 1 + // Start the flush debounce timer + relay_chn_start_esp_timer_once(tilt_ctl->flush_timer, RELAY_CHN_TILT_FLUSH_DEBOUNCE_MS); +#endif } static void relay_chn_tilt_execute_forward(relay_chn_tilt_ctl_t *tilt_ctl) @@ -544,7 +601,7 @@ static void relay_chn_tilt_event_handler(void *handler_arg, esp_event_base_t eve static void relay_chn_tilt_timer_cb(void *arg) { relay_chn_tilt_ctl_t* tilt_ctl = (relay_chn_tilt_ctl_t*) arg; - ESP_RETURN_VOID_ON_FALSE(tilt_ctl != NULL, TAG, "relay_chn_tilt_timer_cb: event_data is NULL"); + ESP_RETURN_VOID_ON_FALSE(tilt_ctl != NULL, TAG, "relay_chn_tilt_timer_cb: timer arg is NULL"); switch (tilt_ctl->step) { @@ -571,17 +628,44 @@ static void relay_chn_tilt_timer_cb(void *arg) } } -esp_err_t relay_chn_tilt_ctl_init(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_ctl_t *chn_ctl) +#if RELAY_CHN_ENABLE_NVS == 1 +static esp_err_t relay_chn_tilt_load_sensitivity(uint8_t ch, uint8_t *sensitivity) +{ + esp_err_t ret = relay_chn_nvs_get_tilt_sensitivity(ch, sensitivity); + if (ret == ESP_ERR_NVS_NOT_FOUND) { + *sensitivity = RELAY_CHN_TILT_DEFAULT_SENSITIVITY; + return ESP_OK; + } + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load tilt sensitivity for channel %d", ch); + return ESP_OK; +} + +static esp_err_t relay_chn_tilt_load_tilt_counter(uint8_t ch, relay_chn_tilt_counter_t *tilt_counter) +{ + esp_err_t ret = relay_chn_nvs_get_tilt_count(ch, &tilt_counter->tilt_forward_count, &tilt_counter->tilt_reverse_count); + if (ret == ESP_ERR_NVS_NOT_FOUND) { + ESP_LOGD(TAG, "relay_chn_tilt_load_tilt_counter: No tilt counters found in NVS for channel %d, initializing to zero", ch); + tilt_counter->tilt_forward_count = 0; + tilt_counter->tilt_reverse_count = 0; + return ESP_OK; + } + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load tilt counters for channel %d", ch); + return ESP_OK; +} +#endif // RELAY_CHN_ENABLE_NVS + +static esp_err_t relay_chn_tilt_ctl_init(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_ctl_t *chn_ctl, + relay_chn_tilt_counter_t *tilt_counter, uint8_t sensitivity) { tilt_ctl->cmd = RELAY_CHN_TILT_CMD_NONE; tilt_ctl->step = RELAY_CHN_TILT_STEP_NONE; - tilt_ctl->tilt_timing.sensitivity = RELAY_CHN_TILT_DEFAULT_SENSITIVITY; - tilt_ctl->tilt_timing.move_time_ms = RELAY_CHN_TILT_DEFAULT_RUN_MS; - tilt_ctl->tilt_timing.pause_time_ms = RELAY_CHN_TILT_DEFAULT_PAUSE_MS; - relay_chn_tilt_reset_count(tilt_ctl); + relay_chn_tilt_compute_set_sensitivity(tilt_ctl, sensitivity); + // Init tilt counters + tilt_ctl->tilt_counter.tilt_forward_count = tilt_counter->tilt_forward_count; + tilt_ctl->tilt_counter.tilt_reverse_count = tilt_counter->tilt_reverse_count; tilt_ctl->chn_ctl = chn_ctl; - tilt_ctl->chn_ctl->tilt_ctl = tilt_ctl; // + tilt_ctl->chn_ctl->tilt_ctl = tilt_ctl; // Create tilt timer for the channel char timer_name[32]; @@ -591,17 +675,50 @@ esp_err_t relay_chn_tilt_ctl_init(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_ctl_ .arg = tilt_ctl, .name = timer_name }; - return esp_timer_create(&timer_args, &tilt_ctl->tilt_timer); + esp_err_t ret = esp_timer_create(&timer_args, &tilt_ctl->tilt_timer); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create tilt timer for channel %d", chn_ctl->id); + +#if RELAY_CHN_ENABLE_NVS == 1 + // Create flush timer for the tilt counters + snprintf(timer_name, sizeof(timer_name), "relay_chn_%2d_tilt_flush_timer", chn_ctl->id); + timer_args.callback = relay_chn_tilt_flush_timer_cb; + timer_args.name = timer_name; + ret = esp_timer_create(&timer_args, &tilt_ctl->flush_timer); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create tilt flush timer for channel %d", chn_ctl->id); +#endif + return ESP_OK; } esp_err_t relay_chn_tilt_init(relay_chn_ctl_t *chn_ctls) { + uint8_t sensitivity; + relay_chn_tilt_counter_t tilt_counter; + #if RELAY_CHN_COUNT > 1 for (int i = 0; i < RELAY_CHN_COUNT; i++) { - relay_chn_tilt_ctl_init(&tilt_ctls[i], &chn_ctls[i]); - } +#if RELAY_CHN_ENABLE_NVS == 1 + esp_err_t ret = relay_chn_tilt_load_sensitivity(i, &sensitivity); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load tilt sensitivity for channel %d", i); + ret = relay_chn_tilt_load_tilt_counter(i, &tilt_counter); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load tilt counters for channel %d", i); #else - relay_chn_tilt_ctl_init(&tilt_ctl, chn_ctls); + sensitivity = RELAY_CHN_TILT_DEFAULT_SENSITIVITY; + tilt_counter.tilt_forward_count = 0; + tilt_counter.tilt_reverse_count = 0; +#endif // RELAY_CHN_ENABLE_NVS == 1 +relay_chn_tilt_ctl_init(&tilt_ctls[i], &chn_ctls[i], &tilt_counter, sensitivity); +} +#else + sensitivity = RELAY_CHN_TILT_DEFAULT_SENSITIVITY; + tilt_counter.tilt_forward_count = 0; + tilt_counter.tilt_reverse_count = 0; +#if RELAY_CHN_ENABLE_NVS == 1 + esp_err_t ret = relay_chn_tilt_load_sensitivity(0, &sensitivity); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load tilt sensitivity for channel %d", 0); + ret = relay_chn_tilt_load_tilt_counter(0, &tilt_counter); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load tilt counters for channel %d", 0); +#endif // RELAY_CHN_ENABLE_NVS == 1 + relay_chn_tilt_ctl_init(&tilt_ctl, chn_ctls, &tilt_counter, sensitivity); #endif // RELAY_CHN_COUNT > 1 return esp_event_handler_register_with(relay_chn_event_loop, @@ -616,6 +733,12 @@ void relay_chn_tilt_ctl_deinit(relay_chn_tilt_ctl_t *tilt_ctl) esp_timer_delete(tilt_ctl->tilt_timer); tilt_ctl->tilt_timer = NULL; } +#if RELAY_CHN_ENABLE_NVS == 1 + if (tilt_ctl->flush_timer != NULL) { + esp_timer_delete(tilt_ctl->flush_timer); + tilt_ctl->flush_timer = NULL; + } +#endif // RELAY_CHN_ENABLE_NVS == 1 } void relay_chn_tilt_deinit() From 61edf11b7505bb463be9138d1f21570947c8d06a Mon Sep 17 00:00:00 2001 From: ismail Date: Tue, 19 Aug 2025 17:36:23 +0300 Subject: [PATCH 06/69] Refactor to remove redundant initialization and add NVS storage tests - Removed unnecessary calls to relay_chn_create and g_is_component_initialized in multiple test cases across test_relay_chn_core_single.c, test_relay_chn_listener_multi.c, and test_relay_chn_listener_single.c. - Introduced new test files for NVS functionality: test_relay_chn_nvs_multi.c and test_relay_chn_nvs_single.c, covering initialization, direction setting, invalid parameters, and erase operations. - Updated partition table configuration to support NVS storage, including the addition of a new partition file part_nvs.csv. - Adjusted sdkconfig files to enable NVS support and configure custom partition settings for relay channels. --- test_apps/main/CMakeLists.txt | 17 ++- test_apps/main/test_app_main.c | 66 ++++++++- test_apps/main/test_common.c | 21 ++- test_apps/main/test_common.h | 3 + test_apps/main/test_relay_chn_core_multi.c | 56 ------- test_apps/main/test_relay_chn_core_single.c | 30 ---- .../main/test_relay_chn_listener_multi.c | 12 -- .../main/test_relay_chn_listener_single.c | 12 -- test_apps/main/test_relay_chn_nvs_multi.c | 140 ++++++++++++++++++ test_apps/main/test_relay_chn_nvs_single.c | 126 ++++++++++++++++ test_apps/main/test_relay_chn_tilt_multi.c | 52 +------ test_apps/main/test_relay_chn_tilt_single.c | 40 +---- test_apps/partition_table/partitionTable.csv | 5 - test_apps/partitions/part_nvs.csv | 6 + test_apps/sdkconfig | 24 ++- test_apps/sdkconfig.defaults | 3 +- test_apps/sdkconfig.defaults.custom_nvs | 15 ++ test_apps/sdkconfig.defaults.single | 3 +- .../sdkconfig.defaults.single.custom_nvs | 15 ++ 19 files changed, 428 insertions(+), 218 deletions(-) create mode 100644 test_apps/main/test_relay_chn_nvs_multi.c create mode 100644 test_apps/main/test_relay_chn_nvs_single.c delete mode 100644 test_apps/partition_table/partitionTable.csv create mode 100644 test_apps/partitions/part_nvs.csv create mode 100644 test_apps/sdkconfig.defaults.custom_nvs create mode 100644 test_apps/sdkconfig.defaults.single.custom_nvs diff --git a/test_apps/main/CMakeLists.txt b/test_apps/main/CMakeLists.txt index 5ff1cfd..f148b59 100644 --- a/test_apps/main/CMakeLists.txt +++ b/test_apps/main/CMakeLists.txt @@ -2,6 +2,8 @@ set(srcs "test_common.c" "test_app_main.c") +set(incdirs ".") + # === Selective compilation based on channel count === if(CONFIG_RELAY_CHN_COUNT GREATER 1) list(APPEND srcs "test_relay_chn_core_multi.c" @@ -14,16 +16,27 @@ endif() if(CONFIG_RELAY_CHN_ENABLE_TILTING) if(CONFIG_RELAY_CHN_COUNT GREATER 1) list(APPEND srcs "test_relay_chn_tilt_multi.c") - else() + else() list(APPEND srcs "test_relay_chn_tilt_single.c") endif() endif() +if(CONFIG_RELAY_CHN_ENABLE_NVS) + list(APPEND incdirs "../../private_include") + list(APPEND srcs "../../src/relay_chn_nvs.c") + if(CONFIG_RELAY_CHN_COUNT GREATER 1) + list(APPEND srcs "test_relay_chn_nvs_multi.c") + else() + list(APPEND srcs "test_relay_chn_nvs_single.c") + endif() +endif() + + # In order for the cases defined by `TEST_CASE` to be linked into the final elf, # the component can be registered as WHOLE_ARCHIVE idf_component_register( SRCS ${srcs} - INCLUDE_DIRS "." + INCLUDE_DIRS ${incdirs} REQUIRES unity relay_chn WHOLE_ARCHIVE ) diff --git a/test_apps/main/test_app_main.c b/test_apps/main/test_app_main.c index c72d68f..edabae1 100644 --- a/test_apps/main/test_app_main.c +++ b/test_apps/main/test_app_main.c @@ -1,11 +1,16 @@ +#include #include "esp_log.h" #include "esp_system.h" -#include "test_common.h" #include "unity.h" #include "unity_internals.h" #include "unity_test_runner.h" -#include +#include "test_common.h" + +#if RELAY_CHN_ENABLE_NVS == 1 +#include "nvs_flash.h" +#include "relay_chn_nvs.h" +#endif #ifndef RELAY_CHN_UNITY_TEST_GROUP_TAG @@ -15,20 +20,60 @@ void setUp() { - g_is_component_initialized = false; + } void tearDown() { - // Clean up after each test - if (g_is_component_initialized) { - relay_chn_destroy(); - g_is_component_initialized = false; + reset_channels_to_idle_state(); +} + +static void test_nvs_flash_init(void) +{ + esp_err_t ret; +#if RELAY_CHN_NVS_CUSTOM_PARTITION == 1 + ret = nvs_flash_init_partition(RELAY_CHN_NVS_CUSTOM_PARTITION_NAME); + ESP_LOGI(TEST_TAG, "test_nvs_flash_init: NVS flash init partition return: %s", esp_err_to_name(ret)); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + // NVS partition is truncated and needs to be erased + ret = nvs_flash_erase_partition(RELAY_CHN_NVS_CUSTOM_PARTITION_NAME); + if (ret == ESP_OK) { + ret = nvs_flash_init_partition(RELAY_CHN_NVS_CUSTOM_PARTITION_NAME); + } } +#else + ret = nvs_flash_init(); + ESP_LOGI(TEST_TAG, "test_nvs_flash_init: NVS flash init return: %s", esp_err_to_name(ret)); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + // NVS partition is truncated and needs to be erased + ret = nvs_flash_erase(); + if (ret == ESP_OK) { + ret = nvs_flash_init(); + } + } +#endif + TEST_ESP_OK(ret); +} + +static void test_nvs_flash_deinit(void) +{ + esp_err_t ret; +#if RELAY_CHN_NVS_CUSTOM_PARTITION == 1 + ret = nvs_flash_deinit_partition(RELAY_CHN_NVS_CUSTOM_PARTITION_NAME); +#else + ret = nvs_flash_deinit(); +#endif + TEST_ESP_OK(ret); } void app_main(void) { + // Init NVS once for all tests + test_nvs_flash_init(); + + // Create relay_chn once for all tests + TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); + UNITY_BEGIN(); // Log general test information @@ -43,6 +88,13 @@ void app_main(void) } UNITY_END(); + + // Destroy relay_chn + relay_chn_destroy(); + + // Deinit NVS + test_nvs_flash_deinit(); + ESP_LOGI(TEST_TAG, "All tests complete."); esp_restart(); // Restart to invoke qemu exit diff --git a/test_apps/main/test_common.c b/test_apps/main/test_common.c index be7169c..3db95de 100644 --- a/test_apps/main/test_common.c +++ b/test_apps/main/test_common.c @@ -4,9 +4,7 @@ const char *TEST_TAG = "RELAY_CHN_TEST"; const uint8_t relay_chn_count = CONFIG_RELAY_CHN_COUNT; const uint32_t opposite_inertia_ms = CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS; -const uint32_t test_delay_margin_ms = 50; // ms toleransı - -bool g_is_component_initialized = false; +const uint32_t test_delay_margin_ms = 50; // ms tolerance // Test-wide GPIO map #if CONFIG_RELAY_CHN_COUNT > 1 @@ -36,4 +34,19 @@ const uint8_t gpio_map[] = { const uint8_t gpio_map[] = {4, 5}; #endif -const uint8_t gpio_count = sizeof(gpio_map) / sizeof(gpio_map[0]); \ No newline at end of file +const uint8_t gpio_count = sizeof(gpio_map) / sizeof(gpio_map[0]); + +void reset_channels_to_idle_state() +{ +#if CONFIG_RELAY_CHN_COUNT > 1 + relay_chn_stop(RELAY_CHN_ID_ALL); + vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i)); + } +#else + relay_chn_stop(); + vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); +#endif +} \ No newline at end of file diff --git a/test_apps/main/test_common.h b/test_apps/main/test_common.h index 6c9c9eb..7a17e80 100644 --- a/test_apps/main/test_common.h +++ b/test_apps/main/test_common.h @@ -21,3 +21,6 @@ extern const uint32_t test_delay_margin_ms; // Init state extern bool g_is_component_initialized; + +// Reset channels to Idle state +void reset_channels_to_idle_state(void); \ No newline at end of file diff --git a/test_apps/main/test_relay_chn_core_multi.c b/test_apps/main/test_relay_chn_core_multi.c index 8551f7c..8b35c5c 100644 --- a/test_apps/main/test_relay_chn_core_multi.c +++ b/test_apps/main/test_relay_chn_core_multi.c @@ -22,9 +22,6 @@ TEST_CASE("relay_chn_create handles invalid arguments", "[relay_chn][core]") // TEST_CASE: Test that relay channels initialize correctly to RELAY_CHN_STATE_IDLE TEST_CASE("Relay channels initialize correctly to FREE state", "[relay_chn][core]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - for (uint8_t i = 0; i < relay_chn_count; i++) { TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i)); } @@ -32,8 +29,6 @@ TEST_CASE("Relay channels initialize correctly to FREE state", "[relay_chn][core // TEST_CASE: Test that relays do nothing when an invlid channel id given TEST_CASE("Run forward does nothing if channel id is invalid", "[relay_chn][core]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; for (uint8_t i = 0; i < relay_chn_count; i++) { int invalid_id = relay_chn_count * 2 + i; relay_chn_run_forward(invalid_id); // relay_chn_run_forward returns void @@ -45,9 +40,6 @@ TEST_CASE("Run forward does nothing if channel id is invalid", "[relay_chn][core // TEST_CASE: Test that relays run in the forward direction and update their state TEST_CASE("Relay channels run forward and update state", "[relay_chn][core]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - for (uint8_t i = 0; i < relay_chn_count; i++) { relay_chn_run_forward(i); // relay_chn_run_forward returns void // Short delay for state to update @@ -58,9 +50,6 @@ TEST_CASE("Relay channels run forward and update state", "[relay_chn][core]") { // TEST_CASE: Test that relays do nothing when an invlid channel id given TEST_CASE("Run reverse does nothing if channel id is invalid", "[relay_chn][core]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // Verify that no valid channels were affected for (uint8_t i = 0; i < relay_chn_count; i++) { int invalid_id = relay_chn_count * 2 + i; @@ -73,9 +62,6 @@ TEST_CASE("Run reverse does nothing if channel id is invalid", "[relay_chn][core // TEST_CASE: Test that relays run in the reverse direction and update their state TEST_CASE("Relay channels run reverse and update state", "[relay_chn][core]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - for (uint8_t i = 0; i < relay_chn_count; i++) { relay_chn_run_reverse(i); // relay_chn_run_reverse returns void vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); @@ -88,9 +74,6 @@ TEST_CASE("Relay channels run reverse and update state", "[relay_chn][core]") { TEST_CASE("run_forward with ID_ALL sets all channels to FORWARD", "[relay_chn][core][id_all]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - relay_chn_run_forward(RELAY_CHN_ID_ALL); vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); @@ -101,9 +84,6 @@ TEST_CASE("run_forward with ID_ALL sets all channels to FORWARD", "[relay_chn][c TEST_CASE("run_reverse with ID_ALL sets all channels to REVERSE", "[relay_chn][core][id_all]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - relay_chn_run_reverse(RELAY_CHN_ID_ALL); vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); @@ -114,9 +94,6 @@ TEST_CASE("run_reverse with ID_ALL sets all channels to REVERSE", "[relay_chn][c TEST_CASE("stop with ID_ALL stops all running channels", "[relay_chn][core][id_all]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // 1. Start all channels forward to ensure they are in a known running state relay_chn_run_forward(RELAY_CHN_ID_ALL); vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); @@ -136,9 +113,6 @@ TEST_CASE("stop with ID_ALL stops all running channels", "[relay_chn][core][id_a // TEST_CASE: Test that relays stop and transition to RELAY_CHN_STATE_IDLE // This test also verifies the transition to FREE state after a STOP command. TEST_CASE("Relay channels stop and update to FREE state", "[relay_chn][core]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - for (uint8_t i = 0; i < relay_chn_count; i++) { // First, run forward to test stopping and transitioning to FREE state relay_chn_run_forward(i); // relay_chn_run_forward returns void @@ -159,9 +133,6 @@ TEST_CASE("Relay channels stop and update to FREE state", "[relay_chn][core]") { // TEST_CASE: Get state should return UNDEFINED when id is not valid TEST_CASE("Get state returns UNDEFINED when id is invalid", "[relay_chn][core]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - for (uint8_t i = 0; i < relay_chn_count; i++) { int invalid_id = relay_chn_count * 2 + i; TEST_ASSERT_EQUAL(RELAY_CHN_STATE_UNDEFINED, relay_chn_get_state(invalid_id)); @@ -177,9 +148,6 @@ TEST_CASE("Get state returns UNDEFINED when id is invalid", "[relay_chn][core]") // TEST_CASE: Get state string should return "UNKNOWN" when id is not valid TEST_CASE("Get state string returns UNKNOWN when id is invalid", "[relay_chn][core]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - for (uint8_t i = 0; i < relay_chn_count; i++) { int invalid_id = relay_chn_count * 2 + i; TEST_ASSERT_EQUAL_STRING("UNKNOWN", relay_chn_get_state_str(invalid_id)); @@ -195,9 +163,6 @@ TEST_CASE("Get state string returns UNKNOWN when id is invalid", "[relay_chn][co // TEST_CASE: Test independent operation of multiple relay channels TEST_CASE("Multiple channels can operate independently", "[relay_chn][core]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - if (relay_chn_count >= 2) { // Start Channel 0 in forward direction relay_chn_run_forward(0); // relay_chn_run_forward returns void @@ -237,9 +202,6 @@ TEST_CASE("Multiple channels can operate independently", "[relay_chn][core]") { TEST_CASE("Forward to Reverse transition with opposite inertia", "[relay_chn][core][inertia]") { uint8_t ch = 0; // Channel to test - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // 1. Start in forward direction relay_chn_run_forward(ch); // relay_chn_run_forward returns void vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Short delay for state stabilization @@ -261,9 +223,6 @@ TEST_CASE("Forward to Reverse transition with opposite inertia", "[relay_chn][co TEST_CASE("Reverse to Forward transition with opposite inertia", "[relay_chn][core][inertia]") { uint8_t ch = 0; - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // 1. Start in reverse direction relay_chn_run_reverse(ch); // relay_chn_run_reverse returns void vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); @@ -284,9 +243,6 @@ TEST_CASE("Reverse to Forward transition with opposite inertia", "[relay_chn][co TEST_CASE("Running in same direction does not incur inertia", "[relay_chn][core][inertia]") { uint8_t ch = 0; - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // 1. Start in forward direction relay_chn_run_forward(ch); // relay_chn_run_forward returns void vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); @@ -305,9 +261,6 @@ TEST_CASE("Running in same direction does not incur inertia", "[relay_chn][core] TEST_CASE("FREE to Running transition without inertia", "[relay_chn][core][inertia]") { uint8_t ch = 0; - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // setUp() should have already brought the channel to FREE state TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(ch)); @@ -322,8 +275,6 @@ TEST_CASE("FREE to Running transition without inertia", "[relay_chn][core][inert TEST_CASE("Single channel direction can be flipped", "[relay_chn][core][direction]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; const uint8_t ch = 0; // 1. Initial direction should be default @@ -346,9 +297,6 @@ TEST_CASE("Single channel direction can be flipped", "[relay_chn][core][directio TEST_CASE("All channels direction can be flipped simultaneously", "[relay_chn][core][direction][id_all]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // 1. Flip all channels relay_chn_flip_direction(RELAY_CHN_ID_ALL); vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); @@ -370,8 +318,6 @@ TEST_CASE("All channels direction can be flipped simultaneously", "[relay_chn][c TEST_CASE("Flipping a running channel stops it and flips direction", "[relay_chn][core][direction]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; const uint8_t ch = 0; // 1. Start channel running and verify state @@ -394,8 +340,6 @@ TEST_CASE("Flipping a running channel stops it and flips direction", "[relay_chn TEST_CASE("Direction flip handles invalid channel ID gracefully", "[relay_chn][core][direction]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; const uint8_t invalid_ch = relay_chn_count + 5; relay_chn_flip_direction(invalid_ch); // Call with an invalid ID diff --git a/test_apps/main/test_relay_chn_core_single.c b/test_apps/main/test_relay_chn_core_single.c index 734f4bf..3c3f4a8 100644 --- a/test_apps/main/test_relay_chn_core_single.c +++ b/test_apps/main/test_relay_chn_core_single.c @@ -22,17 +22,11 @@ TEST_CASE("relay_chn_create handles invalid arguments", "[relay_chn][core]") // TEST_CASE: Test that relay channels initialize correctly to RELAY_CHN_STATE_IDLE TEST_CASE("Relay channels initialize correctly to IDLE state", "[relay_chn][core]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); } // TEST_CASE: Test that relays run in the forward direction and update their state TEST_CASE("Relay channels run forward and update state", "[relay_chn][core]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - relay_chn_run_forward(); // Short delay for state to update vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); @@ -41,9 +35,6 @@ TEST_CASE("Relay channels run forward and update state", "[relay_chn][core]") { // TEST_CASE: Test that relays run in the reverse direction and update their state TEST_CASE("Relay channels run reverse and update state", "[relay_chn][core]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - relay_chn_run_reverse(); // relay_chn_run_reverse returns void vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state()); @@ -53,9 +44,6 @@ TEST_CASE("Relay channels run reverse and update state", "[relay_chn][core]") { // TEST_CASE: Test that relays stop and transition to RELAY_CHN_STATE_IDLE // This test also verifies the transition to IDLE state after a STOP command. TEST_CASE("Relay channels stop and update to IDLE state", "[relay_chn][core]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // First, run forward to test stopping and transitioning to IDLE state relay_chn_run_forward(); vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); @@ -80,9 +68,6 @@ TEST_CASE("Relay channels stop and update to IDLE state", "[relay_chn][core]") { // TEST_CASE: Test transition from forward to reverse with inertia and state checks // Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_REVERSE TEST_CASE("Forward to Reverse transition with opposite inertia", "[relay_chn][core][inertia]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // 1. Start in forward direction relay_chn_run_forward(); vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Short delay for state stabilization @@ -102,9 +87,6 @@ TEST_CASE("Forward to Reverse transition with opposite inertia", "[relay_chn][co // TEST_CASE: Test transition from reverse to forward with inertia and state checks // Scenario: RELAY_CHN_STATE_REVERSE -> (relay_chn_run_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_FORWARD TEST_CASE("Reverse to Forward transition with opposite inertia", "[relay_chn][core][inertia]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // 1. Start in reverse direction relay_chn_run_reverse(); // relay_chn_run_reverse returns void vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); @@ -123,9 +105,6 @@ TEST_CASE("Reverse to Forward transition with opposite inertia", "[relay_chn][co // TEST_CASE: Test issuing the same run command while already running (no inertia expected) // Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD TEST_CASE("Running in same direction does not incur inertia", "[relay_chn][core][inertia]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // 1. Start in forward direction relay_chn_run_forward(); vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); @@ -142,9 +121,6 @@ TEST_CASE("Running in same direction does not incur inertia", "[relay_chn][core] // TEST_CASE: Test transition from IDLE state to running (no inertia expected) // Scenario: RELAY_CHN_STATE_IDLE -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD TEST_CASE("IDLE to Running transition without inertia", "[relay_chn][core][inertia]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // setUp() should have already brought the channel to IDLE state TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); @@ -159,9 +135,6 @@ TEST_CASE("IDLE to Running transition without inertia", "[relay_chn][core][inert TEST_CASE("Single channel direction can be flipped", "[relay_chn][core][direction]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // 1. Initial direction should be default TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction()); @@ -182,9 +155,6 @@ TEST_CASE("Single channel direction can be flipped", "[relay_chn][core][directio TEST_CASE("Flipping a running channel stops it and flips direction", "[relay_chn][core][direction]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // 1. Start channel running and verify state relay_chn_run_forward(); vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); diff --git a/test_apps/main/test_relay_chn_listener_multi.c b/test_apps/main/test_relay_chn_listener_multi.c index 1d368d5..1f8d979 100644 --- a/test_apps/main/test_relay_chn_listener_multi.c +++ b/test_apps/main/test_relay_chn_listener_multi.c @@ -37,9 +37,6 @@ static void test_listener_2(uint8_t chn_id, relay_chn_state_t old_state, relay_c TEST_CASE("Listener is called on state change", "[relay_chn][listener]") { uint8_t ch = 0; - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - reset_listener_info(&listener1_info); // 1. Register the listener @@ -61,9 +58,6 @@ TEST_CASE("Listener is called on state change", "[relay_chn][listener]") { TEST_CASE("Unregistered listener is not called", "[relay_chn][listener]") { uint8_t ch = 0; - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - reset_listener_info(&listener1_info); // 1. Register and then immediately unregister the listener @@ -80,9 +74,6 @@ TEST_CASE("Unregistered listener is not called", "[relay_chn][listener]") { TEST_CASE("Multiple listeners are called on state change", "[relay_chn][listener]") { uint8_t ch = 0; - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - reset_listener_info(&listener1_info); reset_listener_info(&listener2_info); @@ -110,9 +101,6 @@ TEST_CASE("Multiple listeners are called on state change", "[relay_chn][listener } TEST_CASE("Listener registration handles invalid arguments and duplicates", "[relay_chn][listener]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - reset_listener_info(&listener1_info); // 1. Registering a NULL listener should fail diff --git a/test_apps/main/test_relay_chn_listener_single.c b/test_apps/main/test_relay_chn_listener_single.c index 4346967..9d0970d 100644 --- a/test_apps/main/test_relay_chn_listener_single.c +++ b/test_apps/main/test_relay_chn_listener_single.c @@ -35,9 +35,6 @@ static void test_listener_2(uint8_t chn_id, relay_chn_state_t old_state, relay_c // ### Listener Functionality Tests TEST_CASE("Listener is called on state change", "[relay_chn][listener]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - reset_listener_info(&listener1_info); // 1. Register the listener @@ -57,9 +54,6 @@ TEST_CASE("Listener is called on state change", "[relay_chn][listener]") { } TEST_CASE("Unregistered listener is not called", "[relay_chn][listener]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - reset_listener_info(&listener1_info); // 1. Register and then immediately unregister the listener @@ -75,9 +69,6 @@ TEST_CASE("Unregistered listener is not called", "[relay_chn][listener]") { } TEST_CASE("Multiple listeners are called on state change", "[relay_chn][listener]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - reset_listener_info(&listener1_info); reset_listener_info(&listener2_info); @@ -105,9 +96,6 @@ TEST_CASE("Multiple listeners are called on state change", "[relay_chn][listener } TEST_CASE("Listener registration handles invalid arguments and duplicates", "[relay_chn][listener]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - reset_listener_info(&listener1_info); // 1. Registering a NULL listener should fail diff --git a/test_apps/main/test_relay_chn_nvs_multi.c b/test_apps/main/test_relay_chn_nvs_multi.c new file mode 100644 index 0000000..c17721f --- /dev/null +++ b/test_apps/main/test_relay_chn_nvs_multi.c @@ -0,0 +1,140 @@ +/* + * SPDX-FileCopyrightText: 2025 Kozmotronik Tech + * + * SPDX-License-Identifier: MIT + */ + +#include +#include "unity.h" +#include "esp_system.h" +#include "nvs_flash.h" +#include "relay_chn_nvs.h" + +TEST_CASE("Test relay storage init/deinit", "[relay_chn][nvs]") +{ + TEST_ESP_OK(relay_chn_nvs_init()); + TEST_ESP_OK(relay_chn_nvs_deinit()); +} + +TEST_CASE("Test direction setting and getting", "[relay_chn][nvs]") +{ + TEST_ESP_OK(relay_chn_nvs_init()); + + // Test all channels + relay_chn_direction_t dir; + relay_chn_direction_t test_directions[] = { + RELAY_CHN_DIRECTION_DEFAULT, + RELAY_CHN_DIRECTION_FLIPPED + }; + + for (int channel = 0; channel < 2; channel++) { + TEST_ESP_OK(relay_chn_nvs_set_direction(channel, test_directions[channel])); + TEST_ESP_OK(relay_chn_nvs_get_direction(channel, &dir)); + TEST_ASSERT_EQUAL(test_directions[channel], dir); + } + + TEST_ESP_OK(relay_chn_nvs_deinit()); +} + +TEST_CASE("Test invalid parameters", "[relay_chn][nvs]") +{ + TEST_ESP_OK(relay_chn_nvs_init()); + + // Test NULL pointer for all channels + for (int channel = 0; channel < RELAY_CHN_COUNT; channel++) { + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_direction(channel, NULL)); + } + + TEST_ESP_OK(relay_chn_nvs_deinit()); +} + +TEST_CASE("Test relay_chn_nvs_erase_all", "[relay_chn][nvs]") +{ + TEST_ESP_OK(relay_chn_nvs_init()); + + // Store some test data first + relay_chn_direction_t direction = RELAY_CHN_DIRECTION_FLIPPED; + for (int channel = 0; channel < RELAY_CHN_COUNT; channel++) { + TEST_ESP_OK(relay_chn_nvs_set_direction(0, direction)); + } + +#ifdef RELAY_CHN_ENABLE_TILTING + uint8_t sensitivity = 50; + for (int channel = 0; channel < RELAY_CHN_COUNT; channel++) { + TEST_ESP_OK(relay_chn_nvs_set_tilt_sensitivity(0, sensitivity)); + TEST_ESP_OK(relay_chn_nvs_set_tilt_count(0, 100, 200)); + } +#endif + + // Test erase all + TEST_ESP_OK(relay_chn_nvs_erase_all()); + + // Verify data was erased by trying to read it back + relay_chn_direction_t read_direction; + TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_direction(0, &read_direction)); + +#ifdef RELAY_CHN_ENABLE_TILTING + uint8_t read_sensitivity; + TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_tilt_sensitivity(0, &read_sensitivity)); + + uint32_t fwd_count, rev_count; + TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_tilt_count(0, &fwd_count, &rev_count)); +#endif + + TEST_ESP_OK(relay_chn_nvs_deinit()); +} + +#ifdef RELAY_CHN_ENABLE_TILTING +TEST_CASE("Test sensitivity setting and getting", "[relay_chn][nvs][tilt]") +{ + TEST_ESP_OK(relay_chn_nvs_init()); + + const uint8_t test_sensitivity = 75; + uint8_t sensitivity; + + // Test all channels + for (int channel = 0; channel < RELAY_CHN_COUNT; channel++) { + TEST_ESP_OK(relay_chn_nvs_set_tilt_sensitivity(channel, test_sensitivity)); + TEST_ESP_OK(relay_chn_nvs_get_tilt_sensitivity(channel, &sensitivity)); + TEST_ASSERT_EQUAL(test_sensitivity, sensitivity); + } + + TEST_ESP_OK(relay_chn_nvs_deinit()); +} + +TEST_CASE("Test tilt counter operations", "[relay_chn][nvs][tilt]") +{ + TEST_ESP_OK(relay_chn_nvs_init()); + + const uint32_t fwd_count = 100; + const uint32_t rev_count = 200; + uint32_t fwd_read, rev_read; + + // Test all channels + for (int channel = 0; channel < RELAY_CHN_COUNT; channel++) { + // Test setting counters + TEST_ESP_OK(relay_chn_nvs_set_tilt_count(channel, fwd_count, rev_count)); + TEST_ESP_OK(relay_chn_nvs_get_tilt_count(channel, &fwd_read, &rev_read)); + TEST_ASSERT_EQUAL(fwd_count, fwd_read); + TEST_ASSERT_EQUAL(rev_count, rev_read); + } + + TEST_ESP_OK(relay_chn_nvs_deinit()); +} + +TEST_CASE("Test tilting invalid parameters", "[relay_chn][nvs][tilt]") +{ + TEST_ESP_OK(relay_chn_nvs_init()); + + uint32_t fwd_count, rev_count; + + // Test NULL pointers for all channels + for (int channel = 0; channel < RELAY_CHN_COUNT; channel++) { + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_tilt_sensitivity(channel, NULL)); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_tilt_count(channel, NULL, &rev_count)); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_tilt_count(channel, &fwd_count, NULL)); + } + + TEST_ESP_OK(relay_chn_nvs_deinit()); +} +#endif // RELAY_CHN_ENABLE_TILTING \ No newline at end of file diff --git a/test_apps/main/test_relay_chn_nvs_single.c b/test_apps/main/test_relay_chn_nvs_single.c new file mode 100644 index 0000000..3a21638 --- /dev/null +++ b/test_apps/main/test_relay_chn_nvs_single.c @@ -0,0 +1,126 @@ +/* + * SPDX-FileCopyrightText: 2025 Kozmotronik Tech + * + * SPDX-License-Identifier: MIT + */ + +#include +#include "unity.h" +#include "esp_system.h" +#include "nvs_flash.h" +#include "relay_chn_nvs.h" + + +TEST_CASE("Test relay storage init/deinit", "[relay_chn][nvs]") +{ + TEST_ESP_OK(relay_chn_nvs_init()); + TEST_ESP_OK(relay_chn_nvs_deinit()); +} + +TEST_CASE("Test direction setting and getting", "[relay_chn][nvs]") +{ + TEST_ESP_OK(relay_chn_nvs_init()); + + // Test channel 0 + TEST_ESP_OK(relay_chn_nvs_set_direction(0, RELAY_CHN_DIRECTION_DEFAULT)); + relay_chn_direction_t dir; + TEST_ESP_OK(relay_chn_nvs_get_direction(0, &dir)); + TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, dir); + + // Test channel 1 + TEST_ESP_OK(relay_chn_nvs_set_direction(0, RELAY_CHN_DIRECTION_FLIPPED)); + TEST_ESP_OK(relay_chn_nvs_get_direction(0, &dir)); + TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, dir); + + TEST_ESP_OK(relay_chn_nvs_deinit()); +} + +TEST_CASE("Test invalid parameters", "[relay_chn][nvs]") +{ + TEST_ESP_OK(relay_chn_nvs_init()); + + // Test NULL pointer + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_direction(0, NULL)); + + TEST_ESP_OK(relay_chn_nvs_deinit()); +} + +TEST_CASE("Test relay_chn_nvs_erase_all", "[relay_chn][nvs]") +{ + TEST_ESP_OK(relay_chn_nvs_init()); + + // Store some test data first + relay_chn_direction_t direction = RELAY_CHN_DIRECTION_FLIPPED; + TEST_ESP_OK(relay_chn_nvs_set_direction(0, direction)); + +#ifdef RELAY_CHN_ENABLE_TILTING + uint8_t sensitivity = 50; + TEST_ESP_OK(relay_chn_nvs_set_tilt_sensitivity(0, sensitivity)); + TEST_ESP_OK(relay_chn_nvs_set_tilt_count(0, 100, 200)); +#endif + + // Test erase all + TEST_ESP_OK(relay_chn_nvs_erase_all()); + + // Verify data was erased by trying to read it back + relay_chn_direction_t read_direction; + TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_direction(0, &read_direction)); + +#ifdef RELAY_CHN_ENABLE_TILTING + uint8_t read_sensitivity; + TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_tilt_sensitivity(0, &read_sensitivity)); + + uint32_t fwd_count, rev_count; + TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_tilt_count(0, &fwd_count, &rev_count)); +#endif + + TEST_ESP_OK(relay_chn_nvs_deinit()); +} + +#ifdef RELAY_CHN_ENABLE_TILTING +TEST_CASE("Test sensitivity setting and getting", "[relay_chn][nvs][tilt]") +{ + TEST_ESP_OK(relay_chn_nvs_init()); + + const uint8_t test_sensitivity = 75; + TEST_ESP_OK(relay_chn_nvs_set_tilt_sensitivity(0, test_sensitivity)); + + uint8_t sensitivity; + TEST_ESP_OK(relay_chn_nvs_get_tilt_sensitivity(0, &sensitivity)); + TEST_ASSERT_EQUAL(test_sensitivity, sensitivity); + + TEST_ESP_OK(relay_chn_nvs_deinit()); +} + +TEST_CASE("Test tilt counter operations", "[relay_chn][nvs][tilt]") +{ + TEST_ESP_OK(relay_chn_nvs_init()); + + const uint32_t fwd_count = 100; + const uint32_t rev_count = 200; + + // Test setting counters + TEST_ESP_OK(relay_chn_nvs_set_tilt_count(0, fwd_count, rev_count)); + + uint32_t fwd_read, rev_read; + TEST_ESP_OK(relay_chn_nvs_get_tilt_count(0, &fwd_read, &rev_read)); + TEST_ASSERT_EQUAL(fwd_count, fwd_read); + TEST_ASSERT_EQUAL(rev_count, rev_read); + + TEST_ESP_OK(relay_chn_nvs_deinit()); +} + +TEST_CASE("Test tilting invalid parameters", "[relay_chn][nvs][tilt]") +{ + TEST_ESP_OK(relay_chn_nvs_init()); + + uint32_t fwd_count, rev_count; + + // Test NULL pointers + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_tilt_sensitivity(0, NULL)); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_tilt_count(0, NULL, &rev_count)); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_tilt_count(0, &fwd_count, NULL)); + + TEST_ESP_OK(relay_chn_nvs_deinit()); +} +#endif // RELAY_CHN_ENABLE_TILTING \ No newline at end of file diff --git a/test_apps/main/test_relay_chn_tilt_multi.c b/test_apps/main/test_relay_chn_tilt_multi.c index 916afc3..2f17e47 100644 --- a/test_apps/main/test_relay_chn_tilt_multi.c +++ b/test_apps/main/test_relay_chn_tilt_multi.c @@ -14,6 +14,10 @@ // Helper function to prepare channel for tilt tests void prepare_channel_for_tilt(uint8_t chn_id, int initial_cmd) { + // Ensure the channel reset tilt control + relay_chn_tilt_stop(chn_id); + vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + // Ensure the channel has had a 'last_run_cmd' if (initial_cmd == RELAY_CHN_CMD_FORWARD) { relay_chn_run_forward(chn_id); @@ -31,9 +35,6 @@ void prepare_channel_for_tilt(uint8_t chn_id, int initial_cmd) { TEST_CASE("Run Forward to Tilt Forward transition with inertia", "[relay_chn][tilt][inertia]") { uint8_t ch = 0; - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // Prepare channel by running forward first to set last_run_cmd prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD); @@ -58,9 +59,6 @@ TEST_CASE("Run Forward to Tilt Forward transition with inertia", "[relay_chn][ti TEST_CASE("Run Reverse to Tilt Reverse transition with inertia", "[relay_chn][tilt][inertia]") { uint8_t ch = 0; - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // Prepare channel by running reverse first to set last_run_cmd prepare_channel_for_tilt(ch, RELAY_CHN_CMD_REVERSE); @@ -83,9 +81,6 @@ TEST_CASE("Run Reverse to Tilt Reverse transition with inertia", "[relay_chn][ti TEST_CASE("FREE to Tilt Forward transition with inertia (prepared)", "[relay_chn][tilt][inertia]") { uint8_t ch = 0; - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // Prepare channel by running forward first to set last_run_cmd prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(ch)); // Ensure we are back to FREE @@ -102,9 +97,6 @@ TEST_CASE("FREE to Tilt Forward transition with inertia (prepared)", "[relay_chn TEST_CASE("FREE to Tilt Reverse transition with inertia (prepared)", "[relay_chn][tilt][inertia]") { uint8_t ch = 0; - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // Prepare channel by running reverse first to set last_run_cmd prepare_channel_for_tilt(ch, RELAY_CHN_CMD_REVERSE); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(ch)); // Ensure we are back to FREE @@ -120,9 +112,6 @@ TEST_CASE("FREE to Tilt Reverse transition with inertia (prepared)", "[relay_chn TEST_CASE("Tilt Forward to Run Forward transition with inertia", "[relay_chn][tilt][inertia]") { uint8_t ch = 0; - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // Prepare channel by running forward first to set last_run_cmd, then tilt prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD); relay_chn_tilt_forward(ch); // Go to tilt state @@ -142,9 +131,6 @@ TEST_CASE("Tilt Forward to Run Forward transition with inertia", "[relay_chn][ti TEST_CASE("Tilt Reverse to Run Reverse transition with inertia", "[relay_chn][tilt][inertia]") { uint8_t ch = 0; - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // Prepare channel by running reverse first to set last_run_cmd, then tilt prepare_channel_for_tilt(ch, RELAY_CHN_CMD_REVERSE); relay_chn_tilt_reverse(ch); // Go to tilt state @@ -163,9 +149,6 @@ TEST_CASE("Tilt Reverse to Run Reverse transition with inertia", "[relay_chn][ti TEST_CASE("Tilt Forward to Run Reverse transition without inertia", "[relay_chn][tilt][inertia]") { uint8_t ch = 0; - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // Prepare channel by running forward first to set last_run_cmd, then tilt prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD); relay_chn_tilt_forward(ch); // Go to tilt state @@ -183,9 +166,6 @@ TEST_CASE("Tilt Forward to Run Reverse transition without inertia", "[relay_chn] TEST_CASE("Tilt to Stop transition without immediate inertia for stop", "[relay_chn][tilt][inertia]") { uint8_t ch = 0; - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // Prepare channel by running forward first to set last_run_cmd, then tilt prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD); relay_chn_tilt_forward(ch); // Go to tilt state @@ -203,9 +183,6 @@ TEST_CASE("Tilt to Stop transition without immediate inertia for stop", "[relay_ TEST_CASE("tilt_forward with ID_ALL sets all channels to TILT_FORWARD", "[relay_chn][tilt][id_all]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // 1. Prepare all channels. for (uint8_t i = 0; i < relay_chn_count; i++) { prepare_channel_for_tilt(i, RELAY_CHN_CMD_FORWARD); @@ -223,9 +200,6 @@ TEST_CASE("tilt_forward with ID_ALL sets all channels to TILT_FORWARD", "[relay_ TEST_CASE("tilt_reverse with ID_ALL sets all channels to TILT_REVERSE", "[relay_chn][tilt][id_all]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // 1. Prepare all channels. for (uint8_t i = 0; i < relay_chn_count; i++) { prepare_channel_for_tilt(i, RELAY_CHN_CMD_REVERSE); @@ -243,9 +217,6 @@ TEST_CASE("tilt_reverse with ID_ALL sets all channels to TILT_REVERSE", "[relay_ TEST_CASE("tilt_stop with ID_ALL stops all tilting channels", "[relay_chn][tilt][id_all]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // 1. Prepare and start all channels tilting forward for (uint8_t i = 0; i < relay_chn_count; i++) { prepare_channel_for_tilt(i, RELAY_CHN_CMD_REVERSE); @@ -265,9 +236,6 @@ TEST_CASE("tilt_stop with ID_ALL stops all tilting channels", "[relay_chn][tilt] TEST_CASE("tilt_auto with ID_ALL tilts channels based on last run direction", "[relay_chn][tilt][id_all]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // This test requires at least 2 channels to demonstrate different behaviors TEST_ASSERT_GREATER_OR_EQUAL_MESSAGE(2, relay_chn_count, "Test requires at least 2 channels"); @@ -287,9 +255,6 @@ TEST_CASE("tilt_auto with ID_ALL tilts channels based on last run direction", "[ // Test relay_chn_tilt_auto() chooses correct tilt direction TEST_CASE("relay_chn_tilt_auto chooses correct direction", "[relay_chn][tilt][auto]") { uint8_t ch = 0; - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // Prepare FORWARD prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD); relay_chn_tilt_auto(ch); @@ -309,9 +274,6 @@ TEST_CASE("relay_chn_tilt_auto chooses correct direction", "[relay_chn][tilt][au TEST_CASE("relay_chn_tilt_set_sensitivity and get", "[relay_chn][tilt][sensitivity]") { uint8_t ch = 0; uint8_t val = 0; - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - relay_chn_tilt_set_sensitivity(ch, 0); TEST_ESP_OK(relay_chn_tilt_get_sensitivity(ch, &val, 1)); TEST_ASSERT_EQUAL_UINT8(0, val); @@ -336,9 +298,6 @@ TEST_CASE("relay_chn_tilt_set_sensitivity and get", "[relay_chn][tilt][sensitivi // Test tilt counter logic: forward x3, reverse x3, extra reverse fails TEST_CASE("tilt counter logic: forward and reverse consumption", "[relay_chn][tilt][counter]") { uint8_t ch = 0; - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD); // Tilt forward 3 times @@ -372,9 +331,6 @@ TEST_CASE("tilt counter logic: forward and reverse consumption", "[relay_chn][ti // Test run command during TILT state TEST_CASE("run command during TILT state transitions correctly", "[relay_chn][tilt][run-during-tilt]") { uint8_t ch = 0; - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD); relay_chn_tilt_forward(ch); vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); diff --git a/test_apps/main/test_relay_chn_tilt_single.c b/test_apps/main/test_relay_chn_tilt_single.c index 7a0b8b0..b302838 100644 --- a/test_apps/main/test_relay_chn_tilt_single.c +++ b/test_apps/main/test_relay_chn_tilt_single.c @@ -14,6 +14,10 @@ // Helper function to prepare channel for tilt tests void prepare_channel_for_tilt(int initial_cmd) { + // Ensure the channel reset tilt control + relay_chn_tilt_stop(); + vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + // Ensure the channel has had a 'last_run_cmd' if (initial_cmd == RELAY_CHN_CMD_FORWARD) { relay_chn_run_forward(); @@ -29,9 +33,6 @@ void prepare_channel_for_tilt(int initial_cmd) { // TEST_CASE: Test transition from running forward to tilt forward // Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_tilt_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_FORWARD TEST_CASE("Run Forward to Tilt Forward transition with inertia", "[relay_chn][tilt][inertia]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // Prepare channel by running forward first to set last_run_cmd prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); @@ -54,9 +55,6 @@ TEST_CASE("Run Forward to Tilt Forward transition with inertia", "[relay_chn][ti // TEST_CASE: Test transition from running reverse to tilt reverse // Scenario: RELAY_CHN_STATE_REVERSE -> (relay_chn_tilt_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_REVERSE TEST_CASE("Run Reverse to Tilt Reverse transition with inertia", "[relay_chn][tilt][inertia]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // Prepare channel by running reverse first to set last_run_cmd prepare_channel_for_tilt(RELAY_CHN_CMD_REVERSE); @@ -77,9 +75,6 @@ TEST_CASE("Run Reverse to Tilt Reverse transition with inertia", "[relay_chn][ti // TEST_CASE: Test transition from FREE state to tilt forward (now with preparation) // Scenario: RELAY_CHN_STATE_IDLE -> (prepare) -> RELAY_CHN_STATE_IDLE -> (relay_chn_tilt_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_FORWARD TEST_CASE("FREE to Tilt Forward transition with inertia (prepared)", "[relay_chn][tilt][inertia]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // Prepare channel by running forward first to set last_run_cmd prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); // Ensure we are back to FREE @@ -94,9 +89,6 @@ TEST_CASE("FREE to Tilt Forward transition with inertia (prepared)", "[relay_chn // TEST_CASE: Test transition from FREE state to tilt reverse (now with preparation) // Scenario: RELAY_CHN_STATE_IDLE -> (prepare) -> RELAY_CHN_STATE_IDLE -> (relay_chn_tilt_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_REVERSE TEST_CASE("FREE to Tilt Reverse transition with inertia (prepared)", "[relay_chn][tilt][inertia]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // Prepare channel by running reverse first to set last_run_cmd prepare_channel_for_tilt(RELAY_CHN_CMD_REVERSE); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); // Ensure we are back to FREE @@ -110,9 +102,6 @@ TEST_CASE("FREE to Tilt Reverse transition with inertia (prepared)", "[relay_chn // TEST_CASE: Test transition from tilt forward to run forward (inertia expected for run) // Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD TEST_CASE("Tilt Forward to Run Forward transition with inertia", "[relay_chn][tilt][inertia]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // Prepare channel by running forward first to set last_run_cmd, then tilt prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); relay_chn_tilt_forward(); // Go to tilt state @@ -130,9 +119,6 @@ TEST_CASE("Tilt Forward to Run Forward transition with inertia", "[relay_chn][ti // TEST_CASE: Test transition from tilt reverse to run reverse (no inertia expected for run) // Scenario: RELAY_CHN_STATE_TILT_REVERSE -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_REVERSE TEST_CASE("Tilt Reverse to Run Reverse transition with inertia", "[relay_chn][tilt][inertia]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // Prepare channel by running reverse first to set last_run_cmd, then tilt prepare_channel_for_tilt(RELAY_CHN_CMD_REVERSE); relay_chn_tilt_reverse(); // Go to tilt state @@ -149,9 +135,6 @@ TEST_CASE("Tilt Reverse to Run Reverse transition with inertia", "[relay_chn][ti // TEST_CASE: Test transition from tilt forward to run reverse (without inertia) // Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_REVERSE TEST_CASE("Tilt Forward to Run Reverse transition without inertia", "[relay_chn][tilt][inertia]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // Prepare channel by running forward first to set last_run_cmd, then tilt prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); relay_chn_tilt_forward(); // Go to tilt state @@ -167,9 +150,6 @@ TEST_CASE("Tilt Forward to Run Reverse transition without inertia", "[relay_chn] // TEST_CASE: Test stopping from a tilt state (no inertia for stop command itself) // Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_stop) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_IDLE TEST_CASE("Tilt to Stop transition without immediate inertia for stop", "[relay_chn][tilt][inertia]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // Prepare channel by running forward first to set last_run_cmd, then tilt prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); relay_chn_tilt_forward(); // Go to tilt state @@ -185,9 +165,6 @@ TEST_CASE("Tilt to Stop transition without immediate inertia for stop", "[relay_ // Test relay_chn_tilt_auto() chooses correct tilt direction TEST_CASE("relay_chn_tilt_auto chooses correct direction", "[relay_chn][tilt][auto]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - // Prepare FORWARD prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); relay_chn_tilt_auto(); @@ -205,9 +182,6 @@ TEST_CASE("relay_chn_tilt_auto chooses correct direction", "[relay_chn][tilt][au // Test sensitivity set/get TEST_CASE("relay_chn_tilt_set_sensitivity and get", "[relay_chn][tilt][sensitivity]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - relay_chn_tilt_set_sensitivity(0); TEST_ASSERT_EQUAL_UINT8(0, relay_chn_tilt_get_sensitivity()); @@ -223,9 +197,6 @@ TEST_CASE("relay_chn_tilt_set_sensitivity and get", "[relay_chn][tilt][sensitivi // Test tilt counter logic: forward x3, reverse x3, extra reverse fails TEST_CASE("tilt counter logic: forward and reverse consumption", "[relay_chn][tilt][counter]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); // Tilt forward 3 times @@ -258,9 +229,6 @@ TEST_CASE("tilt counter logic: forward and reverse consumption", "[relay_chn][ti // Test run command during TILT state TEST_CASE("run command during TILT state transitions correctly", "[relay_chn][tilt][run-during-tilt]") { - TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); - g_is_component_initialized = true; - prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); relay_chn_tilt_forward(); vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); diff --git a/test_apps/partition_table/partitionTable.csv b/test_apps/partition_table/partitionTable.csv deleted file mode 100644 index 4f87a1f..0000000 --- a/test_apps/partition_table/partitionTable.csv +++ /dev/null @@ -1,5 +0,0 @@ -# ESP-IDF Partition Table -# Name, Type, SubType, Offset, Size, Flags -nvs,data,nvs,0xa000,24K, -phy_init,data,phy,0x10000,4K, -factory,app,factory,0x20000,1M, diff --git a/test_apps/partitions/part_nvs.csv b/test_apps/partitions/part_nvs.csv new file mode 100644 index 0000000..ef2a150 --- /dev/null +++ b/test_apps/partitions/part_nvs.csv @@ -0,0 +1,6 @@ +# ESP-IDF Partition Table +# Name, Type, SubType, Offset, Size, Flags +nvs,data,nvs,0xa000,24K,, +phy_init,data,phy,0x10000,4K,, +factory,app,factory,0x20000,1M,, +app_data,data,nvs,,8K,, diff --git a/test_apps/sdkconfig b/test_apps/sdkconfig index a20e982..386f402 100644 --- a/test_apps/sdkconfig +++ b/test_apps/sdkconfig @@ -395,13 +395,13 @@ CONFIG_ESPTOOLPY_MONITOR_BAUD=115200 # # Partition Table # -CONFIG_PARTITION_TABLE_SINGLE_APP=y +# CONFIG_PARTITION_TABLE_SINGLE_APP is not set # CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE is not set # CONFIG_PARTITION_TABLE_TWO_OTA is not set # CONFIG_PARTITION_TABLE_TWO_OTA_LARGE is not set -# CONFIG_PARTITION_TABLE_CUSTOM is not set -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" -CONFIG_PARTITION_TABLE_FILENAME="partitions_singleapp.csv" +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/part_nvs.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions/part_nvs.csv" CONFIG_PARTITION_TABLE_OFFSET=0x8000 CONFIG_PARTITION_TABLE_MD5=y # end of Partition Table @@ -1162,6 +1162,13 @@ CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC_HRT=y # CONFIG_NEWLIB_TIME_SYSCALL_USE_NONE is not set # end of Newlib +# +# NVS +# +# CONFIG_NVS_ASSERT_ERROR_CHECK is not set +# CONFIG_NVS_LEGACY_DUP_KEYS_COMPATIBILITY is not set +# end of NVS + # # PThreads # @@ -1263,7 +1270,16 @@ CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=y CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 CONFIG_RELAY_CHN_COUNT=1 CONFIG_RELAY_CHN_ENABLE_TILTING=y +CONFIG_RELAY_CHN_ENABLE_NVS=y # end of Relay Channel Driver Configuration + +# +# Relay Channel NVS Storage Configuration +# +CONFIG_RELAY_CHN_NVS_NAMESPACE="relay_chn" +CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION=y +CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME="app_data" +# end of Relay Channel NVS Storage Configuration # end of Component config # CONFIG_IDF_EXPERIMENTAL_FEATURES is not set diff --git a/test_apps/sdkconfig.defaults b/test_apps/sdkconfig.defaults index b7641e3..53c4d00 100644 --- a/test_apps/sdkconfig.defaults +++ b/test_apps/sdkconfig.defaults @@ -5,4 +5,5 @@ CONFIG_ESP_TASK_WDT_INIT=n # Keep this as short as possible for tests CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 CONFIG_RELAY_CHN_COUNT=2 -CONFIG_RELAY_CHN_ENABLE_TILTING=y \ No newline at end of file +CONFIG_RELAY_CHN_ENABLE_TILTING=y +CONFIG_RELAY_CHN_ENABLE_NVS=y \ No newline at end of file diff --git a/test_apps/sdkconfig.defaults.custom_nvs b/test_apps/sdkconfig.defaults.custom_nvs new file mode 100644 index 0000000..cc1df53 --- /dev/null +++ b/test_apps/sdkconfig.defaults.custom_nvs @@ -0,0 +1,15 @@ +# Disable task WDT for tests +CONFIG_ESP_TASK_WDT_INIT=n + +# Partition configuration +CONFIG_PARTITION_TABLE_SINGLE_APP=y +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/part_nvs.csv" + +# Relay Channel Driver Default Configuration for Testing +# Keep this as short as possible for tests +CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 +CONFIG_RELAY_CHN_COUNT=2 +CONFIG_RELAY_CHN_ENABLE_TILTING=y +CONFIG_RELAY_CHN_ENABLE_NVS=y +CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION=y \ No newline at end of file diff --git a/test_apps/sdkconfig.defaults.single b/test_apps/sdkconfig.defaults.single index 13ad935..b468a54 100644 --- a/test_apps/sdkconfig.defaults.single +++ b/test_apps/sdkconfig.defaults.single @@ -5,4 +5,5 @@ CONFIG_ESP_TASK_WDT_INIT=n # Keep this as short as possible for tests CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 CONFIG_RELAY_CHN_COUNT=1 -CONFIG_RELAY_CHN_ENABLE_TILTING=y \ No newline at end of file +CONFIG_RELAY_CHN_ENABLE_TILTING=y +CONFIG_RELAY_CHN_ENABLE_NVS=y \ No newline at end of file diff --git a/test_apps/sdkconfig.defaults.single.custom_nvs b/test_apps/sdkconfig.defaults.single.custom_nvs new file mode 100644 index 0000000..9cbcb0c --- /dev/null +++ b/test_apps/sdkconfig.defaults.single.custom_nvs @@ -0,0 +1,15 @@ +# Disable task WDT for tests +CONFIG_ESP_TASK_WDT_INIT=n + +# Partition configuration +CONFIG_PARTITION_TABLE_SINGLE_APP=y +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/part_nvs.csv" + +# Relay Channel Driver Default Configuration for Testing +# Keep this as short as possible for tests +CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 +CONFIG_RELAY_CHN_COUNT=1 +CONFIG_RELAY_CHN_ENABLE_TILTING=y +CONFIG_RELAY_CHN_ENABLE_NVS=y +CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION=y \ No newline at end of file From 96bb139751d6ccb0be43c1fc9943eb23ea0f8301 Mon Sep 17 00:00:00 2001 From: ismail Date: Tue, 19 Aug 2025 17:40:26 +0300 Subject: [PATCH 07/69] Add new utility script and update test tags - Added new utility script run_tests_all_cfgs.sh to run tests for all configurations and with test tag support. - Updated run_tests.sh to add the new test tag for NVS unit tests. --- scripts/run_tests.sh | 4 ++-- scripts/run_tests_all_cfgs.sh | 30 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) create mode 100755 scripts/run_tests_all_cfgs.sh diff --git a/scripts/run_tests.sh b/scripts/run_tests.sh index cc39ffb..5c4d82e 100755 --- a/scripts/run_tests.sh +++ b/scripts/run_tests.sh @@ -10,7 +10,7 @@ if [[ -z "$IDF_PATH" ]]; then fi # ==== 2. Valid Modes and Defaults ==== -valid_test_tags=("core" "tilt" "listener" "all" "relay_chn") +valid_test_tags=("core" "tilt" "listener" "all" "relay_chn" "nvs") arg_tag="all" # Default to 'all' if no tag specified arg_clean=false arg_log=false @@ -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|all] Specify which test tag to run." + echo " -t, --tag [relay_chn|core|tilt|listener|nvs|all] Specify which test tag to run." echo "" echo " If no tag is specified, it defaults to 'all'." echo "" diff --git a/scripts/run_tests_all_cfgs.sh b/scripts/run_tests_all_cfgs.sh new file mode 100755 index 0000000..7703627 --- /dev/null +++ b/scripts/run_tests_all_cfgs.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +set -e + +# Check tag argument +arg_tag=$1 +if [[ -z "$arg_tag" ]]; then + arg_tag="all" +fi + +# Resolve Paths and Switch to Working Directory +script_dir=$(dirname "$(readlink -f "$0")") +project_root=$(dirname "$script_dir") + +echo "Script dir: ${script_dir}" +echo "Project root: ${project_root}" + +echo "🔍 Searching for 'test_apps' directory in '$project_root'..." +test_apps_dir=$(find "$project_root" -type d -name "test_apps" | head -n 1) +echo "test_apps dir: ${test_apps_dir}" + +# Execute tests for all configs +mapfile -t sdkcfg_files < <(find "$test_apps_dir" -maxdepth 1 -type f -name "sdkconfig.defaults*") + +for sdkcfg_file in "${sdkcfg_files[@]}"; do + echo "🔧 Running tests with config: $sdkcfg_file" + "${script_dir}"/run_tests.sh -c -f "$sdkcfg_file" -t "$arg_tag" || { + echo "❌ Tests failed with config: $sdkcfg_file" + exit 1 + } +done From aeeda44a2c42424ef149f70ee46b6bd9a406e2f7 Mon Sep 17 00:00:00 2001 From: ismail Date: Wed, 20 Aug 2025 10:41:30 +0300 Subject: [PATCH 08/69] Optimize and refactor tilt counting - Optimized tilt counting data by reducing the tilt counter variables into one for smaller memory footprint. So the `relay_chn_tilt_counter_t` type is replaced by a single `uint16_t` variable in the `relay_chn_tilt_ctl_t` structure. Hence the `relay_chn_tilt_counter_t` type has been removed since it is not necessary anymore. - Refactored tilt count handling in NVS: consolidate forward and reverse counts into a single tilt count parameter. - Updated NVS test files that affected by the data type and function signature changes. Fixes #1079 --- private_include/relay_chn_nvs.h | 10 +- src/relay_chn_nvs.c | 22 ++--- src/relay_chn_tilt.c | 108 +++++++++------------ test_apps/main/test_relay_chn_nvs_multi.c | 23 ++--- test_apps/main/test_relay_chn_nvs_single.c | 23 ++--- 5 files changed, 76 insertions(+), 110 deletions(-) diff --git a/private_include/relay_chn_nvs.h b/private_include/relay_chn_nvs.h index 6145494..7f44cee 100644 --- a/private_include/relay_chn_nvs.h +++ b/private_include/relay_chn_nvs.h @@ -68,21 +68,19 @@ esp_err_t relay_chn_nvs_get_tilt_sensitivity(uint8_t ch, uint8_t *sensitivity); * @brief Store tilt counters in NVS. * * @param[in] ch Channel number. - * @param[in] forward_count Forward tilt counter value. - * @param[in] reverse_count Reverse tilt counter value. + * @param[in] tilt_count Tilt count value. * @return ESP_OK on success, error code otherwise. */ -esp_err_t relay_chn_nvs_set_tilt_count(uint8_t ch, uint32_t forward_count, uint32_t reverse_count); +esp_err_t relay_chn_nvs_set_tilt_count(uint8_t ch, uint16_t tilt_count); /** * @brief Retrieve tilt counters from NVS. * * @param[in] ch Channel number. - * @param[out] forward_count Pointer to store forward tilt counter. - * @param[out] reverse_count Pointer to store reverse tilt counter. + * @param[out] tilt_count Pointer to store tilt count. * @return ESP_OK on success, error code otherwise. */ -esp_err_t relay_chn_nvs_get_tilt_count(uint8_t ch, uint32_t *forward_count, uint32_t *reverse_count); +esp_err_t relay_chn_nvs_get_tilt_count(uint8_t ch, uint16_t *tilt_count); #endif // RELAY_CHN_ENABLE_TILTING /** diff --git a/src/relay_chn_nvs.c b/src/relay_chn_nvs.c index 0f136f0..467ad12 100644 --- a/src/relay_chn_nvs.c +++ b/src/relay_chn_nvs.c @@ -10,8 +10,7 @@ #define RELAY_CHN_KEY_DIR "dir" #ifdef RELAY_CHN_ENABLE_TILTING #define RELAY_CHN_KEY_SENS(ch) "sens_%d" -#define RELAY_CHN_KEY_TFWD(ch) "tfwd_%d" -#define RELAY_CHN_KEY_TREV(ch) "trev_%d" +#define RELAY_CHN_KEY_TCNT(ch) "tcnt_%d" /*!< Tilt count key */ #endif static const char *TAG = "RELAY_CHN_STORAGE_NVS"; @@ -85,26 +84,19 @@ esp_err_t relay_chn_nvs_get_tilt_sensitivity(uint8_t ch, uint8_t *sensitivity) return nvs_get_u8(relay_chn_nvs, RELAY_CHN_KEY_SENS(ch), sensitivity); } -esp_err_t relay_chn_nvs_set_tilt_count(uint8_t ch, uint32_t forward_count, uint32_t reverse_count) +esp_err_t relay_chn_nvs_set_tilt_count(uint8_t ch, uint16_t tilt_count) { esp_err_t ret; - ret = nvs_set_u32(relay_chn_nvs, RELAY_CHN_KEY_TFWD(ch), forward_count); - ESP_RETURN_ON_ERROR(ret, TAG, "Failed to save forward_count tilt counter"); - ret = nvs_set_u32(relay_chn_nvs, RELAY_CHN_KEY_TREV(ch), reverse_count); - ESP_RETURN_ON_ERROR(ret, TAG, "Failed to save reverse_count tilt counter"); + ret = nvs_set_u16(relay_chn_nvs, RELAY_CHN_KEY_TCNT(ch), tilt_count); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to save tilt_count tilt counter"); return nvs_commit(relay_chn_nvs); } -esp_err_t relay_chn_nvs_get_tilt_count(uint8_t ch, uint32_t *forward_count, uint32_t *reverse_count) +esp_err_t relay_chn_nvs_get_tilt_count(uint8_t ch, uint16_t *tilt_count) { - ESP_RETURN_ON_FALSE(forward_count != NULL && reverse_count != NULL, + ESP_RETURN_ON_FALSE(tilt_count != NULL, ESP_ERR_INVALID_ARG, TAG, "Counter pointers are NULL"); - - esp_err_t ret = nvs_get_u32(relay_chn_nvs, RELAY_CHN_KEY_TFWD(ch), forward_count); - if (ret != ESP_OK) { - return ret; // Return error if the key does not exist - } - return nvs_get_u32(relay_chn_nvs, RELAY_CHN_KEY_TREV(ch), reverse_count); + return nvs_get_u16(relay_chn_nvs, RELAY_CHN_KEY_TCNT(ch), tilt_count); } #endif // RELAY_CHN_ENABLE_TILTING diff --git a/src/relay_chn_tilt.c b/src/relay_chn_tilt.c index c89c6e9..c1f4a2d 100644 --- a/src/relay_chn_tilt.c +++ b/src/relay_chn_tilt.c @@ -57,19 +57,13 @@ typedef struct { uint32_t pause_time_ms; /*!< Pause time in milliseconds */ } relay_chn_tilt_timing_t; -/// @brief Tilt counter structure to manage tilt count. -typedef struct { - uint32_t tilt_forward_count; /*!< Tilt forward count */ - uint32_t tilt_reverse_count; /*!< Tilt reverse count */ -} relay_chn_tilt_counter_t; - /// @brief Tilt control structure to manage tilt operations. typedef struct relay_chn_tilt_ctl { relay_chn_ctl_t *chn_ctl; /*!< The relay channel control structure */ relay_chn_tilt_cmd_t cmd; /*!< The tilt command in process */ relay_chn_tilt_step_t step; /*!< Current tilt step */ relay_chn_tilt_timing_t tilt_timing; /*!< Tilt timing structure */ - relay_chn_tilt_counter_t tilt_counter; /*!< Tilt counter structure */ + uint16_t tilt_count; /*!< Tilt count to manage forward and reverse tilts */ esp_timer_handle_t tilt_timer; /*!< Tilt timer handle */ #if RELAY_CHN_ENABLE_NVS == 1 esp_timer_handle_t flush_timer; /*!< Flush timer to avoid frequent write of tilt counters */ @@ -401,8 +395,7 @@ uint8_t relay_chn_tilt_get_sensitivity() void relay_chn_tilt_reset_count(relay_chn_tilt_ctl_t *tilt_ctl) { - tilt_ctl->tilt_counter.tilt_forward_count = 0; - tilt_ctl->tilt_counter.tilt_reverse_count = 0; + tilt_ctl->tilt_count = 0; #if RELAY_CHN_ENABLE_NVS == 1 esp_timer_stop(tilt_ctl->flush_timer); @@ -415,74 +408,70 @@ void relay_chn_tilt_reset_count(relay_chn_tilt_ctl_t *tilt_ctl) * This helper function updates the relevant tilt count depending on the * last run info and helps the tilt module in deciding whether the requested * tilt should execute or not. - * This is useful to control reverse tilting particularly. For example: + * + * This is useful to control reverse tilting for the same direction particularly. + * For example: * - If the channel's last run was FORWARD and a TILT_FORWARD is requested, - * then the tilt counter will count up on the - * relay_chn_tilt_counter_type::tilt_forward_count and the function will - * return the actual count. + * then the tilt count will count up on the relay_chn_tilt_ctl_t::tilt_count + * and the function will return the actual count. * - If the channel's last run was FORWARD and a TILT_REVERSE is requested, - * then the relay_chn_tilt_counter_type::tilt_forward_count will be checked - * against zero first, and then it will count down and return the actual count - * if it is greater than 0, else the function will return 0. + * then the relay_chn_tilt_ctl_t::tilt_count will be checked against zero first, + * and then it will count down and return the actual count if it is greater + * than 0, else the function will return 0. * - If the tilt command is irrelevant then the function will return 0. * - If the last run is irrelevant then the function will return 0. * * @param tilt_ctl The relay channel handle. - * @return uint32_t The actual value of the relevant counter. + * + * @return The actual value of the relevant count. + * @return 1 if the last tilt_count was 1 and decremented to 0. * @return 0 if: - * - related counter is already 0. + * - related count is already 0. * - tilt command is irrelevant. * - last run info is irrelevant. */ -static uint32_t relay_chn_tilt_count_update(relay_chn_tilt_ctl_t *tilt_ctl) +static uint16_t relay_chn_tilt_count_update(relay_chn_tilt_ctl_t *tilt_ctl) { relay_chn_cmd_t last_run_cmd = relay_chn_run_info_get_last_run_cmd(tilt_ctl->chn_ctl->run_info); if (last_run_cmd == RELAY_CHN_CMD_FORWARD) { if (tilt_ctl->cmd == RELAY_CHN_TILT_CMD_FORWARD) { - return ++tilt_ctl->tilt_counter.tilt_forward_count; + return ++tilt_ctl->tilt_count; } else if (tilt_ctl->cmd == RELAY_CHN_TILT_CMD_REVERSE) { - if (tilt_ctl->tilt_counter.tilt_forward_count > 0) { - --tilt_ctl->tilt_counter.tilt_forward_count; + if (tilt_ctl->tilt_count > 0) { + --tilt_ctl->tilt_count; // Still should do one more move, return non-zero value return 1; } else return 0; } - else { - relay_chn_tilt_reset_count(tilt_ctl); - return 0; - } } else if (last_run_cmd == RELAY_CHN_CMD_REVERSE) { if (tilt_ctl->cmd == RELAY_CHN_TILT_CMD_REVERSE) { - return ++tilt_ctl->tilt_counter.tilt_reverse_count; + return ++tilt_ctl->tilt_count; } else if (tilt_ctl->cmd == RELAY_CHN_TILT_CMD_FORWARD) { - if (tilt_ctl->tilt_counter.tilt_reverse_count > 0) { - --tilt_ctl->tilt_counter.tilt_reverse_count; + if (tilt_ctl->tilt_count > 0) { + --tilt_ctl->tilt_count; // Still should do one more move, return non-zero value return 1; } else return 0; } - else { - relay_chn_tilt_reset_count(tilt_ctl); - return 0; - } } + + // Irrelevant case -> reset + tilt_ctl->tilt_count = 0; return 0; } #if RELAY_CHN_ENABLE_NVS == 1 -static esp_err_t relay_chn_tilt_save_tilt_counter(relay_chn_tilt_ctl_t *tilt_ctl) +static esp_err_t relay_chn_tilt_save_tilt_count(relay_chn_tilt_ctl_t *tilt_ctl) { // Save the tilt count to NVS storage - esp_err_t ret = relay_chn_nvs_set_tilt_count(tilt_ctl->chn_ctl->id, - tilt_ctl->tilt_counter.tilt_forward_count, - tilt_ctl->tilt_counter.tilt_reverse_count); + esp_err_t ret = relay_chn_nvs_set_tilt_count(tilt_ctl->chn_ctl->id, tilt_ctl->tilt_count); if (ret != ESP_OK) { ESP_LOGE(TAG, "relay_chn_tilt_execute_stop: Failed to save tilt count for channel #%d: %s", tilt_ctl->chn_ctl->id, esp_err_to_name(ret)); } @@ -494,7 +483,7 @@ static void relay_chn_tilt_flush_timer_cb(void *arg) relay_chn_tilt_ctl_t* tilt_ctl = (relay_chn_tilt_ctl_t*) arg; ESP_RETURN_VOID_ON_FALSE(tilt_ctl != NULL, TAG, "relay_chn_tilt_flush_timer_cb: timer arg is NULL"); // Save the tilt count to storage - esp_err_t ret = relay_chn_tilt_save_tilt_counter(tilt_ctl); + esp_err_t ret = relay_chn_tilt_save_tilt_count(tilt_ctl); if (ret != ESP_OK) { ESP_LOGE(TAG, "relay_chn_tilt_execute_stop: Failed to save tilt count for channel #%d: %s", tilt_ctl->chn_ctl->id, esp_err_to_name(ret)); } @@ -558,7 +547,7 @@ static void relay_chn_tilt_execute_pause(relay_chn_tilt_ctl_t *tilt_ctl) return; } - // Update the tilt counter before the next move and expect the return value to be greater than 0 + // Update the tilt count before the next move and expect the return value to be greater than 0 if (relay_chn_tilt_count_update(tilt_ctl) == 0) { ESP_LOGD(TAG, "relay_chn_tilt_execute_pause: Relay channel cannot tilt anymore"); // Stop tilting since the tilting limit has been reached @@ -640,13 +629,12 @@ static esp_err_t relay_chn_tilt_load_sensitivity(uint8_t ch, uint8_t *sensitivit return ESP_OK; } -static esp_err_t relay_chn_tilt_load_tilt_counter(uint8_t ch, relay_chn_tilt_counter_t *tilt_counter) +static esp_err_t relay_chn_tilt_load_tilt_count(uint8_t ch, uint16_t *tilt_count) { - esp_err_t ret = relay_chn_nvs_get_tilt_count(ch, &tilt_counter->tilt_forward_count, &tilt_counter->tilt_reverse_count); + esp_err_t ret = relay_chn_nvs_get_tilt_count(ch, tilt_count); if (ret == ESP_ERR_NVS_NOT_FOUND) { - ESP_LOGD(TAG, "relay_chn_tilt_load_tilt_counter: No tilt counters found in NVS for channel %d, initializing to zero", ch); - tilt_counter->tilt_forward_count = 0; - tilt_counter->tilt_reverse_count = 0; + ESP_LOGD(TAG, "relay_chn_tilt_load_tilt_count: No tilt count found in NVS for channel %d, initializing to zero", ch); + tilt_count = 0; return ESP_OK; } ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load tilt counters for channel %d", ch); @@ -654,15 +642,15 @@ static esp_err_t relay_chn_tilt_load_tilt_counter(uint8_t ch, relay_chn_tilt_cou } #endif // RELAY_CHN_ENABLE_NVS -static esp_err_t relay_chn_tilt_ctl_init(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_ctl_t *chn_ctl, - relay_chn_tilt_counter_t *tilt_counter, uint8_t sensitivity) +static esp_err_t relay_chn_tilt_ctl_init(relay_chn_tilt_ctl_t *tilt_ctl, + relay_chn_ctl_t *chn_ctl, + uint16_t tilt_count , + uint8_t sensitivity) { tilt_ctl->cmd = RELAY_CHN_TILT_CMD_NONE; tilt_ctl->step = RELAY_CHN_TILT_STEP_NONE; relay_chn_tilt_compute_set_sensitivity(tilt_ctl, sensitivity); - // Init tilt counters - tilt_ctl->tilt_counter.tilt_forward_count = tilt_counter->tilt_forward_count; - tilt_ctl->tilt_counter.tilt_reverse_count = tilt_counter->tilt_reverse_count; + tilt_ctl->tilt_count = tilt_count; tilt_ctl->chn_ctl = chn_ctl; tilt_ctl->chn_ctl->tilt_ctl = tilt_ctl; @@ -692,33 +680,31 @@ static esp_err_t relay_chn_tilt_ctl_init(relay_chn_tilt_ctl_t *tilt_ctl, relay_c esp_err_t relay_chn_tilt_init(relay_chn_ctl_t *chn_ctls) { uint8_t sensitivity; - relay_chn_tilt_counter_t tilt_counter; + uint16_t tilt_count; #if RELAY_CHN_COUNT > 1 for (int i = 0; i < RELAY_CHN_COUNT; i++) { #if RELAY_CHN_ENABLE_NVS == 1 esp_err_t ret = relay_chn_tilt_load_sensitivity(i, &sensitivity); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load tilt sensitivity for channel %d", i); - ret = relay_chn_tilt_load_tilt_counter(i, &tilt_counter); - ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load tilt counters for channel %d", i); + ret = relay_chn_tilt_load_tilt_count(i, &tilt_count); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load tilt count for channel %d", i); #else sensitivity = RELAY_CHN_TILT_DEFAULT_SENSITIVITY; - tilt_counter.tilt_forward_count = 0; - tilt_counter.tilt_reverse_count = 0; + tilt_count = 0; #endif // RELAY_CHN_ENABLE_NVS == 1 -relay_chn_tilt_ctl_init(&tilt_ctls[i], &chn_ctls[i], &tilt_counter, sensitivity); -} + relay_chn_tilt_ctl_init(&tilt_ctls[i], &chn_ctls[i], tilt_count, sensitivity); + } #else sensitivity = RELAY_CHN_TILT_DEFAULT_SENSITIVITY; - tilt_counter.tilt_forward_count = 0; - tilt_counter.tilt_reverse_count = 0; + tilt_count = 0; #if RELAY_CHN_ENABLE_NVS == 1 esp_err_t ret = relay_chn_tilt_load_sensitivity(0, &sensitivity); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load tilt sensitivity for channel %d", 0); - ret = relay_chn_tilt_load_tilt_counter(0, &tilt_counter); - ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load tilt counters for channel %d", 0); + ret = relay_chn_tilt_load_tilt_count(0, &tilt_count); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load tilt count for channel %d", 0); #endif // RELAY_CHN_ENABLE_NVS == 1 - relay_chn_tilt_ctl_init(&tilt_ctl, chn_ctls, &tilt_counter, sensitivity); + relay_chn_tilt_ctl_init(&tilt_ctl, chn_ctls, tilt_count, sensitivity); #endif // RELAY_CHN_COUNT > 1 return esp_event_handler_register_with(relay_chn_event_loop, diff --git a/test_apps/main/test_relay_chn_nvs_multi.c b/test_apps/main/test_relay_chn_nvs_multi.c index c17721f..b21b3d5 100644 --- a/test_apps/main/test_relay_chn_nvs_multi.c +++ b/test_apps/main/test_relay_chn_nvs_multi.c @@ -62,7 +62,7 @@ TEST_CASE("Test relay_chn_nvs_erase_all", "[relay_chn][nvs]") uint8_t sensitivity = 50; for (int channel = 0; channel < RELAY_CHN_COUNT; channel++) { TEST_ESP_OK(relay_chn_nvs_set_tilt_sensitivity(0, sensitivity)); - TEST_ESP_OK(relay_chn_nvs_set_tilt_count(0, 100, 200)); + TEST_ESP_OK(relay_chn_nvs_set_tilt_count(0, 100)); } #endif @@ -77,8 +77,8 @@ TEST_CASE("Test relay_chn_nvs_erase_all", "[relay_chn][nvs]") uint8_t read_sensitivity; TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_tilt_sensitivity(0, &read_sensitivity)); - uint32_t fwd_count, rev_count; - TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_tilt_count(0, &fwd_count, &rev_count)); + uint16_t tilt_count; + TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_tilt_count(0, &tilt_count)); #endif TEST_ESP_OK(relay_chn_nvs_deinit()); @@ -106,17 +106,15 @@ TEST_CASE("Test tilt counter operations", "[relay_chn][nvs][tilt]") { TEST_ESP_OK(relay_chn_nvs_init()); - const uint32_t fwd_count = 100; - const uint32_t rev_count = 200; - uint32_t fwd_read, rev_read; + const uint16_t tilt_count = 100; + uint16_t tilt_count_read; // Test all channels for (int channel = 0; channel < RELAY_CHN_COUNT; channel++) { // Test setting counters - TEST_ESP_OK(relay_chn_nvs_set_tilt_count(channel, fwd_count, rev_count)); - TEST_ESP_OK(relay_chn_nvs_get_tilt_count(channel, &fwd_read, &rev_read)); - TEST_ASSERT_EQUAL(fwd_count, fwd_read); - TEST_ASSERT_EQUAL(rev_count, rev_read); + TEST_ESP_OK(relay_chn_nvs_set_tilt_count(channel, tilt_count)); + TEST_ESP_OK(relay_chn_nvs_get_tilt_count(channel, &tilt_count_read)); + TEST_ASSERT_EQUAL(tilt_count, tilt_count_read); } TEST_ESP_OK(relay_chn_nvs_deinit()); @@ -126,13 +124,10 @@ TEST_CASE("Test tilting invalid parameters", "[relay_chn][nvs][tilt]") { TEST_ESP_OK(relay_chn_nvs_init()); - uint32_t fwd_count, rev_count; - // Test NULL pointers for all channels for (int channel = 0; channel < RELAY_CHN_COUNT; channel++) { TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_tilt_sensitivity(channel, NULL)); - TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_tilt_count(channel, NULL, &rev_count)); - TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_tilt_count(channel, &fwd_count, NULL)); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_tilt_count(channel, NULL)); } TEST_ESP_OK(relay_chn_nvs_deinit()); diff --git a/test_apps/main/test_relay_chn_nvs_single.c b/test_apps/main/test_relay_chn_nvs_single.c index 3a21638..b6fb376 100644 --- a/test_apps/main/test_relay_chn_nvs_single.c +++ b/test_apps/main/test_relay_chn_nvs_single.c @@ -56,7 +56,7 @@ TEST_CASE("Test relay_chn_nvs_erase_all", "[relay_chn][nvs]") #ifdef RELAY_CHN_ENABLE_TILTING uint8_t sensitivity = 50; TEST_ESP_OK(relay_chn_nvs_set_tilt_sensitivity(0, sensitivity)); - TEST_ESP_OK(relay_chn_nvs_set_tilt_count(0, 100, 200)); + TEST_ESP_OK(relay_chn_nvs_set_tilt_count(0, 100)); #endif // Test erase all @@ -70,8 +70,8 @@ TEST_CASE("Test relay_chn_nvs_erase_all", "[relay_chn][nvs]") uint8_t read_sensitivity; TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_tilt_sensitivity(0, &read_sensitivity)); - uint32_t fwd_count, rev_count; - TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_tilt_count(0, &fwd_count, &rev_count)); + uint16_t tilt_count; + TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_tilt_count(0, &tilt_count)); #endif TEST_ESP_OK(relay_chn_nvs_deinit()); @@ -96,16 +96,14 @@ TEST_CASE("Test tilt counter operations", "[relay_chn][nvs][tilt]") { TEST_ESP_OK(relay_chn_nvs_init()); - const uint32_t fwd_count = 100; - const uint32_t rev_count = 200; + const uint16_t tilt_count = 100; // Test setting counters - TEST_ESP_OK(relay_chn_nvs_set_tilt_count(0, fwd_count, rev_count)); + TEST_ESP_OK(relay_chn_nvs_set_tilt_count(0, tilt_count)); - uint32_t fwd_read, rev_read; - TEST_ESP_OK(relay_chn_nvs_get_tilt_count(0, &fwd_read, &rev_read)); - TEST_ASSERT_EQUAL(fwd_count, fwd_read); - TEST_ASSERT_EQUAL(rev_count, rev_read); + uint16_t tilt_count_read; + TEST_ESP_OK(relay_chn_nvs_get_tilt_count(0, &tilt_count_read)); + TEST_ASSERT_EQUAL(tilt_count, tilt_count_read); TEST_ESP_OK(relay_chn_nvs_deinit()); } @@ -114,12 +112,9 @@ TEST_CASE("Test tilting invalid parameters", "[relay_chn][nvs][tilt]") { TEST_ESP_OK(relay_chn_nvs_init()); - uint32_t fwd_count, rev_count; - // Test NULL pointers TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_tilt_sensitivity(0, NULL)); - TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_tilt_count(0, NULL, &rev_count)); - TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_tilt_count(0, &fwd_count, NULL)); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_tilt_count(0, NULL)); TEST_ESP_OK(relay_chn_nvs_deinit()); } From c5fa8a63aed8a09e1c5799d6ccf954cc1409f7f9 Mon Sep 17 00:00:00 2001 From: ismail Date: Wed, 20 Aug 2025 10:42:26 +0300 Subject: [PATCH 09/69] Fix NVS module's tag value. Fix NVS module's tag string value to match the module name. IssueID #1081. --- src/relay_chn_nvs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/relay_chn_nvs.c b/src/relay_chn_nvs.c index 467ad12..c4fdb80 100644 --- a/src/relay_chn_nvs.c +++ b/src/relay_chn_nvs.c @@ -13,7 +13,7 @@ #define RELAY_CHN_KEY_TCNT(ch) "tcnt_%d" /*!< Tilt count key */ #endif -static const char *TAG = "RELAY_CHN_STORAGE_NVS"; +static const char *TAG = "RELAY_CHN_NVS"; static nvs_handle_t relay_chn_nvs; From b99622bd23c503f29f3613f5f4df4b1736fb2bc4 Mon Sep 17 00:00:00 2001 From: ismail Date: Wed, 20 Aug 2025 10:53:03 +0300 Subject: [PATCH 10/69] Fix and document tilt key names. Fix key names with more approprite ones and add documentation for them. Fixes #1081. --- src/relay_chn_nvs.c | 10 +++++----- test_apps/sdkconfig | 13 ++++++------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/relay_chn_nvs.c b/src/relay_chn_nvs.c index c4fdb80..e152693 100644 --- a/src/relay_chn_nvs.c +++ b/src/relay_chn_nvs.c @@ -7,10 +7,10 @@ #include "esp_check.h" #include "relay_chn_nvs.h" -#define RELAY_CHN_KEY_DIR "dir" +#define RELAY_CHN_KEY_DIR "dir" /*!< Direction key */ #ifdef RELAY_CHN_ENABLE_TILTING -#define RELAY_CHN_KEY_SENS(ch) "sens_%d" -#define RELAY_CHN_KEY_TCNT(ch) "tcnt_%d" /*!< Tilt count key */ +#define RELAY_CHN_KEY_TSENS(ch) "tsens_%d" /*!< Tilt sensitivity key */ +#define RELAY_CHN_KEY_TCNT(ch) "tcnt_%d" /*!< Tilt count key */ #endif static const char *TAG = "RELAY_CHN_NVS"; @@ -72,7 +72,7 @@ esp_err_t relay_chn_nvs_get_direction(uint8_t ch, relay_chn_direction_t *directi #ifdef RELAY_CHN_ENABLE_TILTING esp_err_t relay_chn_nvs_set_tilt_sensitivity(uint8_t ch, uint8_t sensitivity) { - esp_err_t ret = nvs_set_u8(relay_chn_nvs, RELAY_CHN_KEY_SENS(ch), sensitivity); + esp_err_t ret = nvs_set_u8(relay_chn_nvs, RELAY_CHN_KEY_TSENS(ch), sensitivity); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set tilt sensitivity for channel %d", ch); return nvs_commit(relay_chn_nvs); } @@ -81,7 +81,7 @@ esp_err_t relay_chn_nvs_get_tilt_sensitivity(uint8_t ch, uint8_t *sensitivity) { ESP_RETURN_ON_FALSE(sensitivity != NULL, ESP_ERR_INVALID_ARG, TAG, "Sensitivity pointer is NULL"); - return nvs_get_u8(relay_chn_nvs, RELAY_CHN_KEY_SENS(ch), sensitivity); + return nvs_get_u8(relay_chn_nvs, RELAY_CHN_KEY_TSENS(ch), sensitivity); } esp_err_t relay_chn_nvs_set_tilt_count(uint8_t ch, uint16_t tilt_count) diff --git a/test_apps/sdkconfig b/test_apps/sdkconfig index 386f402..f47128a 100644 --- a/test_apps/sdkconfig +++ b/test_apps/sdkconfig @@ -395,13 +395,13 @@ CONFIG_ESPTOOLPY_MONITOR_BAUD=115200 # # Partition Table # -# CONFIG_PARTITION_TABLE_SINGLE_APP is not set +CONFIG_PARTITION_TABLE_SINGLE_APP=y # CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE is not set # CONFIG_PARTITION_TABLE_TWO_OTA is not set # CONFIG_PARTITION_TABLE_TWO_OTA_LARGE is not set -CONFIG_PARTITION_TABLE_CUSTOM=y -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/part_nvs.csv" -CONFIG_PARTITION_TABLE_FILENAME="partitions/part_nvs.csv" +# CONFIG_PARTITION_TABLE_CUSTOM is not set +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions_singleapp.csv" CONFIG_PARTITION_TABLE_OFFSET=0x8000 CONFIG_PARTITION_TABLE_MD5=y # end of Partition Table @@ -1268,7 +1268,7 @@ CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=y # Relay Channel Driver Configuration # CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 -CONFIG_RELAY_CHN_COUNT=1 +CONFIG_RELAY_CHN_COUNT=2 CONFIG_RELAY_CHN_ENABLE_TILTING=y CONFIG_RELAY_CHN_ENABLE_NVS=y # end of Relay Channel Driver Configuration @@ -1277,8 +1277,7 @@ CONFIG_RELAY_CHN_ENABLE_NVS=y # Relay Channel NVS Storage Configuration # CONFIG_RELAY_CHN_NVS_NAMESPACE="relay_chn" -CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION=y -CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME="app_data" +# CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION is not set # end of Relay Channel NVS Storage Configuration # end of Component config From a3762cff5f993ef1e8c7cadaab7dc035480fcd3b Mon Sep 17 00:00:00 2001 From: ismail Date: Wed, 20 Aug 2025 16:30:12 +0300 Subject: [PATCH 11/69] Update README for NVS Updated README.md with detailed NVS storage configuration and initialization instructions. Fixes #1082. --- README.md | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0597737..46d6b1d 100644 --- a/README.md +++ b/README.md @@ -12,14 +12,20 @@ An ESP-IDF component for controlling relay channels, specifically designed for d - Direction flipping capability - State monitoring and reporting - Optional sensitivty adjustable tilting feature +- Optional NVS storage for persistent configuration ## Description -Each relay channel consists of 2 output relays controlled by 2 GPIO pins. The component provides APIs to control these relay pairs while ensuring safe operation, particularly for driving bipolar motors. To prevent mechanical strain on the motor, the component automatically manages direction changes with a configurable inertia delay, protecting it from abrupt reversals. +Each relay channel consists of 2 output relays controlled by 2 GPIO pins. The component provides APIs to control these relay pairs while ensuring safe operation, particularly for driving bipolar motors. To prevent mechanical strain on the motor, the component automatically manages direction changes with a configurable inertia delay, protecting it from abrupt reversals. Hence, the component handles all the required timing between the movement transitions automatically to ensure reliable operation. It also provides an optional tilting interface per channel base. Tilting makes a channel move with a specific pattern moving with small steps at a time. Tilting is specifically designed for controlling some types of curtains that need to be adjusted to let enter specific amount of day light. Since it operates on relays, the switching frequency is limited to 10Hz which complies with the most of the general purpose relays' requirements. The minimum frequency is 2Hz and the duty cycle is about 10% in all ranges. -The module also handles all the required timing between the movement transitions automatically to ensure reliable operation. + +Another optional feature is NVS storage, which saves the configuration permanently across reboots of the device. These configurations are: + +- Direction +- Tilt sensitivity +- Last tilt position ## Configuration @@ -28,6 +34,71 @@ Configure the component through menuconfig under "Relay Channel Driver Configura - `CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS`: Time to wait before changing direction (200-1500ms, default: 800ms) - `CONFIG_RELAY_CHN_COUNT`: Number of relay channels (1-8, default: 1) - `CONFIG_RELAY_CHN_ENABLE_TILTING`: Enable tilting interface on all channels. (default: n) +- `CONFIG_RELAY_CHN_ENABLE_NVS`: Enable persistent storage in NVS (default: n) + +When NVS storage is enabled (`CONFIG_RELAY_CHN_ENABLE_NVS`), additional configuration options become available: + +- `CONFIG_RELAY_CHN_NVS_NAMESPACE`: NVS namespace for storing relay channel data (default: "relay_chn") +- `CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION`: Use custom NVS partition instead of default (default: n) +- `CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME`: Name of the custom partition if enabled (default "app_data") + +### NVS Storage Prerequisites + +> [!WARNING] +> `relay_chn` component does not initialize the NVS flash. + +If NVS storage is enabled, you must initialize NVS flash before calling `relay_chn_create()` in your application code. The `relay_chn` component can use either the default or a custom NVS partition from your application, depending on the configuration settings. + +#### Initialize for Default Partition + +1. Enable NVS, but keep the custom partition option disabled in `menuconfig`: + +```ini +CONFIG_RELAY_CHN_ENABLE_NVS=y +CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION=n +``` + +2. Initialize the default NVS flash: + +```c +// Initialize default NVS partition +esp_err_t ret = nvs_flash_init(); +if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); +} +ESP_ERROR_CHECK(ret); + +// Now you can create relay channels +ret = relay_chn_create(gpio_map, gpio_count); +``` + +#### Initialize for Custom Partition + +1. Enable both NVS and custom partition, also set the custom partition name in `menuconfig`. + +```ini +CONFIG_RELAY_CHN_ENABLE_NVS=y +CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION=n +CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME=my_custom_partition +``` + +> [!IMPORTANT] +> The `CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME` **must match exactly the label** defined for the custom NVS partition in the partition table. Otherwise the component initialisation will fail due to the `ESP_ERR_NVS_PART_NOT_FOUND` error. + +2. Initialize the custom NVS partition: + +```c +esp_err_t ret = nvs_flash_init_partition(YOUR_CUSTOM_PARTITION_NAME); +if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase_partition(YOUR_CUSTOM_PARTITION_NAME)); + ret = nvs_flash_init_partition(YOUR_CUSTOM_PARTITION_NAME); +} +ESP_ERROR_CHECK(ret); + +// Now you can create relay channels +ret = relay_chn_create(gpio_map, gpio_count); +``` ## Installation From 40633e03d89e2704de2f2623b9a4dbd091b1cc9a Mon Sep 17 00:00:00 2001 From: ismail Date: Fri, 22 Aug 2025 12:29:07 +0300 Subject: [PATCH 12/69] Add run limit feature for relay channels with NVS support - Introduced configuration options for enabling run limits in Kconfig. - Added APIs to get and set run limits for individual relay channels. - Implemented run limit timer functionality to automatically stop channels. - Updated NVS handling to store and retrieve run limit values. - Enhanced documentation in README and code comments to reflect new feature. Closes #1080 --- Kconfig | 32 +++++++++++++++ README.md | 43 +++++++++++++++++++- include/relay_chn.h | 50 +++++++++++++++++++++++ include/relay_chn_adapter.h | 56 ++++++++++++++++++++++++++ include/relay_chn_defs.h | 7 ++++ private_include/relay_chn_core.h | 14 +++++++ private_include/relay_chn_nvs.h | 20 +++++++++ private_include/relay_chn_priv_types.h | 4 ++ scripts/run_tests.sh | 4 +- src/relay_chn_core.c | 36 +++++++++++++++++ src/relay_chn_ctl_multi.c | 54 +++++++++++++++++++++++++ src/relay_chn_ctl_single.c | 48 ++++++++++++++++++++++ src/relay_chn_nvs.c | 19 +++++++++ 13 files changed, 384 insertions(+), 3 deletions(-) diff --git a/Kconfig b/Kconfig index 638e846..2d743d9 100644 --- a/Kconfig +++ b/Kconfig @@ -17,6 +17,12 @@ menu "Relay Channel Driver Configuration" help Number of relay channels between 1 and 8. + config RELAY_CHN_ENABLE_RUN_LIMIT + bool "Enable run limit for channels" + default n + help + Enable run limit for channels as an extra layer of output protection. + config RELAY_CHN_ENABLE_TILTING bool "Enable tilting on relay channels" default n @@ -61,4 +67,30 @@ menu "Relay Channel NVS Storage Configuration" The name of the custom NVS partition used for storing relay channel configuration. Make sure the name is exactly the same as label defined in the relevant partition table. +endmenu + +menu "Relay Channel Run Limit Configuration" + depends on RELAY_CHN_ENABLE_RUN_LIMIT + + config RELAY_CHN_RUN_LIMIT_MIN_SEC + int "Minimum run limit in seconds" + range 1 60 + default 10 + help + Minimum run limit in seconds for channels. + + config RELAY_CHN_RUN_LIMIT_MAX_SEC + int "Maximum run limit in seconds" + range 60 3600 + default 600 + help + Maximum run limit in seconds for channels. + + config RELAY_CHN_RUN_LIMIT_DEFAULT_SEC + int "Default run limit in seconds" + range 10 3600 + default 60 + help + Default run limit in seconds for channels. + endmenu \ No newline at end of file diff --git a/README.md b/README.md index 46d6b1d..599a024 100644 --- a/README.md +++ b/README.md @@ -13,17 +13,21 @@ An ESP-IDF component for controlling relay channels, specifically designed for d - State monitoring and reporting - Optional sensitivty adjustable tilting feature - Optional NVS storage for persistent configuration +- Optional configurable run limit protection ## Description Each relay channel consists of 2 output relays controlled by 2 GPIO pins. The component provides APIs to control these relay pairs while ensuring safe operation, particularly for driving bipolar motors. To prevent mechanical strain on the motor, the component automatically manages direction changes with a configurable inertia delay, protecting it from abrupt reversals. Hence, the component handles all the required timing between the movement transitions automatically to ensure reliable operation. +The run limit feature provides an additional layer of protection by automatically stopping channels after a configurable time period. This is particularly useful for motor-driven applications where continuous operation beyond a certain duration could cause damage or safety issues. Each channel can have its own run limit setting, and when enabled, the component will automatically stop the channel once it has been running for the specified duration. + It also provides an optional tilting interface per channel base. Tilting makes a channel move with a specific pattern moving with small steps at a time. Tilting is specifically designed for controlling some types of curtains that need to be adjusted to let enter specific amount of day light. Since it operates on relays, the switching frequency is limited to 10Hz which complies with the most of the general purpose relays' requirements. The minimum frequency is 2Hz and the duty cycle is about 10% in all ranges. Another optional feature is NVS storage, which saves the configuration permanently across reboots of the device. These configurations are: - Direction +- Run limit duration - Tilt sensitivity - Last tilt position @@ -33,9 +37,16 @@ Configure the component through menuconfig under "Relay Channel Driver Configura - `CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS`: Time to wait before changing direction (200-1500ms, default: 800ms) - `CONFIG_RELAY_CHN_COUNT`: Number of relay channels (1-8, default: 1) +- `CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT`: Enable run limit protection (default: n) - `CONFIG_RELAY_CHN_ENABLE_TILTING`: Enable tilting interface on all channels. (default: n) - `CONFIG_RELAY_CHN_ENABLE_NVS`: Enable persistent storage in NVS (default: n) +When run limit is enabled (`CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT`), the following configuration options become available: + +- `CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC`: Minimum allowed run limit duration (1-60s, default: 10s) +- `CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC`: Maximum allowed run limit duration (60-3600s, default: 600s) +- `CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC`: Default run limit duration for channels (10-3600s, default: 60s) + When NVS storage is enabled (`CONFIG_RELAY_CHN_ENABLE_NVS`), additional configuration options become available: - `CONFIG_RELAY_CHN_NVS_NAMESPACE`: NVS namespace for storing relay channel data (default: "relay_chn") @@ -225,7 +236,37 @@ relay_chn_direction_t direction = relay_chn_get_direction(0); /* The listener is same for multi mode */ ``` -### 4. Tilting Interface (if enabled) +### 4. Run Limit Control (if enabled) + +For single mode: + +```c +// Assuming CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT is enabled + +// Get current run limit (in seconds) +uint16_t limit = relay_chn_get_run_limit(); + +// Set new run limit (in seconds) +relay_chn_set_run_limit(120); // Set to 120 seconds +``` + +For multi mode: + +```c +// Assuming CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT is enabled + +// Get run limit for channel #0 (in seconds) +uint16_t limit = relay_chn_get_run_limit(0); + +// Set new run limit for specific channels (in seconds) +relay_chn_set_run_limit(0, 120); // Set channel #0 to 120 seconds +relay_chn_set_run_limit(1, 180); // Set channel #1 to 180 seconds +relay_chn_set_run_limit(RELAY_CHN_ID_ALL, 90); // Set all channels to 90 seconds +``` +> [!NOTE] +> When a channel reaches its run limit, it will automatically stop. The run limit timer is reset whenever the channel starts running in either direction. + +### 5. Tilting Interface (if enabled) For single mode: diff --git a/include/relay_chn.h b/include/relay_chn.h index 9912860..9537a4b 100644 --- a/include/relay_chn.h +++ b/include/relay_chn.h @@ -146,6 +146,33 @@ void relay_chn_flip_direction(uint8_t chn_id); */ relay_chn_direction_t relay_chn_get_direction(uint8_t chn_id); +#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 +/** + * @brief Get the run limit for the specified channel + * + * @param chn_id The ID of the relay channel to query. + * + * @return The run limit value for the relevant channel if the channel ID is valid. + * 0 if the channel ID is invalid. + */ +uint16_t relay_chn_get_run_limit(uint8_t chn_id); + +/** + * @brief Set the run limit for the specified channel + * + * Sets the time limit in seconds for the specified channel. It will not proceed + * if the channel ID is invalid. + * If the time_sec value is lesser than the CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC, + * the value will be set to CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC. + * If the time_sec value is greater than the CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC, + * the value will be set to CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC. + * + * @param chn_id The ID of the relay channel to query. + * @param time_sec The run limit time in seconds. + */ +void relay_chn_set_run_limit(uint8_t chn_id, uint16_t time_sec); +#endif // RELAY_CHN_ENABLE_RUN_LIMIT == 1 + #if CONFIG_RELAY_CHN_ENABLE_TILTING == 1 @@ -275,6 +302,29 @@ void relay_chn_flip_direction(void); */ relay_chn_direction_t relay_chn_get_direction(void); +#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 +/** + * @brief Get the run limit for the channel + * + * @return The run limit value for the channel. + */ +uint16_t relay_chn_get_run_limit(void); + +/** + * @brief Set the run limit for the channel + * + * Sets the time limit in seconds for the channel. It will not proceed + * if the channel ID is invalid. + * If the time_sec value is lesser than the CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC, + * the value will be set to CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC. + * If the time_sec value is greater than the CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC, + * the value will be set to CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC. + * + * @param time_sec The run limit time in seconds. + */ +void relay_chn_set_run_limit(uint16_t time_sec); +#endif // RELAY_CHN_ENABLE_RUN_LIMIT == 1 + #if CONFIG_RELAY_CHN_ENABLE_TILTING == 1 diff --git a/include/relay_chn_adapter.h b/include/relay_chn_adapter.h index dd66907..9db6196 100644 --- a/include/relay_chn_adapter.h +++ b/include/relay_chn_adapter.h @@ -101,6 +101,36 @@ static inline relay_chn_direction_t relay_chn_get_direction(uint8_t chn_id) return relay_chn_ctl_get_direction(chn_id); } +#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 +/** + * @brief Get the run limit for the specified channel + * + * @param chn_id The ID of the relay channel to query. + * + * @return The run limit value for the relevant channel if the channel ID is valid. + * 0 if the channel ID is invalid. + */ +extern uint16_t relay_chn_ctl_get_run_limit(uint8_t chn_id); + +/** + * @brief Set the run limit for the specified channel + * + * @param chn_id The ID of the relay channel to query. + * @param time_sec The run limit time in seconds. + */ +extern void relay_chn_ctl_set_run_limit(uint8_t chn_id, uint16_t time_sec); + +static inline uint16_t relay_chn_get_run_limit(uint8_t chn_id) +{ + return relay_chn_ctl_get_run_limit(chn_id); +} + +static inline void relay_chn_set_run_limit(uint8_t chn_id, uint16_t time_sec) +{ + relay_chn_ctl_set_run_limit(chn_id, time_sec); +} +#endif // RELAY_CHN_ENABLE_RUN_LIMIT == 1 + #else /** @@ -179,6 +209,32 @@ static inline relay_chn_direction_t relay_chn_get_direction(void) return relay_chn_ctl_get_direction(); } +#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 +/** + * @brief Get the run limit for the channel + * + * @return The run limit value for the channel. + */ +extern uint16_t relay_chn_ctl_get_run_limit(void); + +/** + * @brief Set the run limit for the channel + * + * @param time_sec The run limit time in seconds. + */ +extern void relay_chn_ctl_set_run_limit(uint16_t time_sec); + +static inline uint16_t relay_chn_get_run_limit(void) +{ + return relay_chn_ctl_get_run_limit(); +} + +static inline void relay_chn_set_run_limit(uint16_t time_sec) +{ + relay_chn_ctl_set_run_limit(time_sec); +} +#endif // RELAY_CHN_ENABLE_RUN_LIMIT == 1 + #endif // RELAY_CHN_COUNT > 1 #ifdef __cplusplus diff --git a/include/relay_chn_defs.h b/include/relay_chn_defs.h index 855905e..9c52236 100644 --- a/include/relay_chn_defs.h +++ b/include/relay_chn_defs.h @@ -15,6 +15,7 @@ extern "C" { #define RELAY_CHN_COUNT CONFIG_RELAY_CHN_COUNT #define RELAY_CHN_ENABLE_TILTING CONFIG_RELAY_CHN_ENABLE_TILTING #define RELAY_CHN_ENABLE_NVS CONFIG_RELAY_CHN_ENABLE_NVS +#define RELAY_CHN_ENABLE_RUN_LIMIT CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT #if RELAY_CHN_ENABLE_NVS == 1 #define RELAY_CHN_NVS_NAMESPACE CONFIG_RELAY_CHN_NVS_NAMESPACE @@ -28,6 +29,12 @@ extern "C" { #define RELAY_CHN_ID_ALL RELAY_CHN_COUNT /*!< Special ID to address all channels */ #endif +#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#define RELAY_CHN_RUN_LIMIT_MIN_SEC CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC +#define RELAY_CHN_RUN_LIMIT_MAX_SEC CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC +#define RELAY_CHN_RUN_LIMIT_DEFAULT_SEC CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC +#endif + #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/private_include/relay_chn_core.h b/private_include/relay_chn_core.h index d132e99..3f17928 100644 --- a/private_include/relay_chn_core.h +++ b/private_include/relay_chn_core.h @@ -33,6 +33,20 @@ ESP_EVENT_DECLARE_BASE(RELAY_CHN_CMD_EVENT); */ esp_err_t relay_chn_init_timer(relay_chn_ctl_t *chn_ctl); +#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 +/** + * @brief Initializes the relay channel run limit timer. + * + * This function creates a timer for the relay channel to handle run time limit. + * Required by *_ctl_* module. + * + * @param chn_ctl Pointer to the relay channel control structure. + * + * @return esp_err_t ESP_OK on success, or an error code on failure. + */ +esp_err_t relay_chn_init_run_limit_timer(relay_chn_ctl_t *chn_ctl); +#endif // RELAY_CHN_ENABLE_RUN_LIMIT + /** * @brief Issues a command to the relay channel. * diff --git a/private_include/relay_chn_nvs.h b/private_include/relay_chn_nvs.h index 7f44cee..09f84f2 100644 --- a/private_include/relay_chn_nvs.h +++ b/private_include/relay_chn_nvs.h @@ -45,6 +45,26 @@ esp_err_t relay_chn_nvs_set_direction(uint8_t ch, relay_chn_direction_t directio */ esp_err_t relay_chn_nvs_get_direction(uint8_t ch, relay_chn_direction_t *direction); +#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 +/** + * @brief Store relay channel run limit in NVS. + * + * @param[in] ch Channel number. + * @param[in] direction Run limit value to store. + * @return ESP_OK on success, error code otherwise. + */ +esp_err_t relay_chn_nvs_set_run_limit(uint8_t ch, uint16_t time_sec); + +/** + * @brief Retrieve relay channel run limit from NVS. + * + * @param[in] ch Channel number. + * @param[out] direction Pointer to store retrieved run limit value. + * @return ESP_OK on success, error code otherwise. + */ +esp_err_t relay_chn_nvs_get_run_limit(uint8_t ch, uint16_t *time_sec); +#endif // RELAY_CHN_ENABLE_RUN_LIMIT == 1 + #ifdef RELAY_CHN_ENABLE_TILTING /** * @brief Store tilt sensitivity in NVS. diff --git a/private_include/relay_chn_priv_types.h b/private_include/relay_chn_priv_types.h index 0967d86..c62a6f0 100644 --- a/private_include/relay_chn_priv_types.h +++ b/private_include/relay_chn_priv_types.h @@ -68,6 +68,10 @@ typedef struct { relay_chn_output_t *output; /*!< Output configuration of the relay channel */ relay_chn_cmd_t pending_cmd; /*!< The command that is pending to be issued */ esp_timer_handle_t inertia_timer; /*!< Timer to handle the opposite direction inertia time */ +#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 + esp_timer_handle_t run_limit_timer; /*!< Timer to handle the run limit */ + uint16_t run_limit_sec; /*!< Run limit in seconds */ +#endif #if RELAY_CHN_ENABLE_TILTING == 1 relay_chn_tilt_ctl_t *tilt_ctl; /*!< Pointer to the tilt control structure if tilting is enabled */ #endif diff --git a/scripts/run_tests.sh b/scripts/run_tests.sh index 5c4d82e..2320339 100755 --- a/scripts/run_tests.sh +++ b/scripts/run_tests.sh @@ -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") +valid_test_tags=("core" "tilt" "listener" "all" "relay_chn" "nvs" "run_limit") arg_tag="all" # Default to 'all' if no tag specified arg_clean=false arg_log=false @@ -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|all] Specify which test tag to run." + echo " -t, --tag [relay_chn|core|tilt|listener|nvs|run_limit|all] Specify which test tag to run." echo "" echo " If no tag is specified, it defaults to 'all'." echo "" diff --git a/src/relay_chn_core.c b/src/relay_chn_core.c index 0f2210d..a117ccf 100644 --- a/src/relay_chn_core.c +++ b/src/relay_chn_core.c @@ -46,6 +46,30 @@ esp_event_loop_handle_t relay_chn_event_loop = NULL; static void relay_chn_event_handler(void* handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data); +#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 +/* + * Run limit timer callback immediately dispatches a STOP command for the + * relevant channel as soon as the run limit time times out + */ +static void relay_chn_run_limit_timer_cb(void* arg) +{ + relay_chn_ctl_t* chn_ctl = (relay_chn_ctl_t*) arg; + relay_chn_dispatch_cmd(chn_ctl, RELAY_CHN_CMD_STOP); +} + +esp_err_t relay_chn_init_run_limit_timer(relay_chn_ctl_t *chn_ctl) +{ + char timer_name[32]; + snprintf(timer_name, sizeof(timer_name), "ch_%d_rlimit_timer", chn_ctl->id); + esp_timer_create_args_t timer_args = { + .callback = relay_chn_run_limit_timer_cb, + .arg = chn_ctl, + .name = timer_name + }; + return esp_timer_create(&timer_args, &chn_ctl->run_limit_timer); +} +#endif + // Timer callback function for relay channel direction change inertia. static void relay_chn_timer_cb(void* arg) { @@ -467,6 +491,10 @@ static void relay_chn_execute_stop(relay_chn_ctl_t *chn_ctl) // Invalidate the channel's timer if it is active esp_timer_stop(chn_ctl->inertia_timer); +#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 + esp_timer_stop(chn_ctl->run_limit_timer); +#endif + // Save the last run time only if the previous state was either STATE FORWARD // or STATE_REVERSE. Then schedule a free command. if (previous_state == RELAY_CHN_STATE_FORWARD || previous_state == RELAY_CHN_STATE_REVERSE) { @@ -489,6 +517,10 @@ static void relay_chn_execute_forward(relay_chn_ctl_t *chn_ctl) } relay_chn_run_info_set_last_run_cmd(chn_ctl->run_info, RELAY_CHN_CMD_FORWARD); relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_FORWARD); + +#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 + relay_chn_start_esp_timer_once(chn_ctl->run_limit_timer, chn_ctl->run_limit_sec * 1000); +#endif } static void relay_chn_execute_reverse(relay_chn_ctl_t *chn_ctl) @@ -499,6 +531,10 @@ static void relay_chn_execute_reverse(relay_chn_ctl_t *chn_ctl) } relay_chn_run_info_set_last_run_cmd(chn_ctl->run_info, RELAY_CHN_CMD_REVERSE); relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_REVERSE); + +#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 + relay_chn_start_esp_timer_once(chn_ctl->run_limit_timer, chn_ctl->run_limit_sec * 1000); +#endif } static void relay_chn_execute_flip(relay_chn_ctl_t *chn_ctl) diff --git a/src/relay_chn_ctl_multi.c b/src/relay_chn_ctl_multi.c index 13c97e8..336c5f9 100644 --- a/src/relay_chn_ctl_multi.c +++ b/src/relay_chn_ctl_multi.c @@ -10,6 +10,10 @@ #include "relay_chn_ctl.h" #include "relay_chn_output.h" +#if RELAY_CHN_ENABLE_NVS == 1 +#include "relay_chn_nvs.h" +#endif + static const char *TAG = "RELAY_CHN_CTL"; static relay_chn_ctl_t chn_ctls[RELAY_CHN_COUNT]; @@ -30,6 +34,19 @@ esp_err_t relay_chn_ctl_init(relay_chn_output_t *outputs, relay_chn_run_info_t * chn_ctl->output = output; chn_ctl->run_info = run_info; +#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 + uint16_t run_limit_sec = RELAY_CHN_RUN_LIMIT_DEFAULT_SEC; +#if RELAY_CHN_ENABLE_NVS == 1 + // Load run limit value from NVS + ret = relay_chn_nvs_get_run_limit(chn_ctl->id, &run_limit_sec); + if (ret != ESP_OK && ret != ESP_ERR_NVS_NOT_FOUND) { + ESP_LOGE(TAG, "Failed to load run limit from NVS for channel %d with error: %s", i, esp_err_to_name(ret)); + } +#endif + chn_ctl->run_limit_sec = run_limit_sec; + ret = relay_chn_init_run_limit_timer(chn_ctl); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize run limit timer"); +#endif ret = relay_chn_init_timer(chn_ctl); // Create direction change inertia timer ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create relay channel timer for channel %d", i); } @@ -44,6 +61,12 @@ void relay_chn_ctl_deinit() esp_timer_delete(chn_ctl->inertia_timer); chn_ctl->inertia_timer = NULL; } +#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 + if (chn_ctl->run_limit_timer != NULL) { + esp_timer_delete(chn_ctl->run_limit_timer); + chn_ctl->run_limit_timer = NULL; + } +#endif } } @@ -123,6 +146,37 @@ relay_chn_direction_t relay_chn_ctl_get_direction(uint8_t chn_id) return relay_chn_output_get_direction(chn_ctl->output); } +#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 +uint16_t relay_chn_ctl_get_run_limit(uint8_t chn_id) +{ + if (!relay_chn_is_channel_id_valid(chn_id) || chn_id == RELAY_CHN_ID_ALL) { + ESP_LOGE(TAG, "get_run_limit: Invalid channel ID: %d", chn_id); + return 0; + } + return chn_ctls[chn_id].run_limit_sec; +} + +void relay_chn_ctl_set_run_limit(uint8_t chn_id, uint16_t time_sec) +{ + if (!relay_chn_is_channel_id_valid(chn_id) || chn_id == RELAY_CHN_ID_ALL) { + ESP_LOGE(TAG, "set_run_limit: Invalid channel ID: %d", chn_id); + return; + } + + // Check for boundaries + if (time_sec > RELAY_CHN_RUN_LIMIT_MAX_SEC) + time_sec = RELAY_CHN_RUN_LIMIT_MAX_SEC; + else if (time_sec < RELAY_CHN_RUN_LIMIT_MIN_SEC) + time_sec = RELAY_CHN_RUN_LIMIT_MIN_SEC; + + chn_ctls[chn_id].run_limit_sec = time_sec; + +#if RELAY_CHN_ENABLE_NVS == 1 + relay_chn_nvs_set_run_limit(chn_id, time_sec); +#endif +} +#endif + relay_chn_ctl_t *relay_chn_ctl_get(uint8_t chn_id) { if (!relay_chn_is_channel_id_valid(chn_id)) { diff --git a/src/relay_chn_ctl_single.c b/src/relay_chn_ctl_single.c index 0435b33..a56f226 100644 --- a/src/relay_chn_ctl_single.c +++ b/src/relay_chn_ctl_single.c @@ -4,11 +4,17 @@ * SPDX-License-Identifier: MIT */ +#include "esp_check.h" #include "relay_chn_priv_types.h" #include "relay_chn_core.h" #include "relay_chn_ctl.h" #include "relay_chn_output.h" +#if RELAY_CHN_ENABLE_NVS == 1 +#include "relay_chn_nvs.h" +#endif + +static const char *TAG = "RELAY_CHN_CTL"; static relay_chn_ctl_t chn_ctl; @@ -21,6 +27,20 @@ esp_err_t relay_chn_ctl_init(relay_chn_output_t *output, relay_chn_run_info_t *r chn_ctl.pending_cmd = RELAY_CHN_CMD_NONE; chn_ctl.output = output; chn_ctl.run_info = run_info; +#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 + uint16_t run_limit_sec = RELAY_CHN_RUN_LIMIT_DEFAULT_SEC; + esp_err_t ret; +#if RELAY_CHN_ENABLE_NVS == 1 + // Load run limit value from NVS + ret = relay_chn_nvs_get_run_limit(chn_ctl.id, &run_limit_sec); + if (ret != ESP_OK && ret != ESP_ERR_NVS_NOT_FOUND) { + ESP_LOGE(TAG, "Failed to load run limit from NVS with error: %s", esp_err_to_name(ret)); + } +#endif + chn_ctl.run_limit_sec = run_limit_sec; + ret = relay_chn_init_run_limit_timer(&chn_ctl); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize run limit timer"); +#endif return relay_chn_init_timer(&chn_ctl); // Create direction change inertia timer } @@ -31,6 +51,12 @@ void relay_chn_ctl_deinit() esp_timer_delete(chn_ctl.inertia_timer); chn_ctl.inertia_timer = NULL; } +#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 + if (chn_ctl.run_limit_timer != NULL) { + esp_timer_delete(chn_ctl.run_limit_timer); + chn_ctl.run_limit_timer = NULL; + } +#endif } /* relay_chn APIs */ @@ -68,6 +94,28 @@ relay_chn_direction_t relay_chn_ctl_get_direction() { return relay_chn_output_get_direction(chn_ctl.output); } + +#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 +uint16_t relay_chn_ctl_get_run_limit() +{ + return chn_ctl.run_limit_sec; +} + +void relay_chn_ctl_set_run_limit(uint16_t time_sec) +{ + // Check for boundaries + if (time_sec > RELAY_CHN_RUN_LIMIT_MAX_SEC) + time_sec = RELAY_CHN_RUN_LIMIT_MAX_SEC; + else if (time_sec < RELAY_CHN_RUN_LIMIT_MIN_SEC) + time_sec = RELAY_CHN_RUN_LIMIT_MIN_SEC; + + chn_ctl.run_limit_sec = time_sec; + +#if RELAY_CHN_ENABLE_NVS == 1 + relay_chn_nvs_set_run_limit(chn_ctl.id, time_sec); +#endif +} +#endif /* relay_chn APIs */ relay_chn_ctl_t *relay_chn_ctl_get() diff --git a/src/relay_chn_nvs.c b/src/relay_chn_nvs.c index e152693..04ec8df 100644 --- a/src/relay_chn_nvs.c +++ b/src/relay_chn_nvs.c @@ -8,6 +8,9 @@ #include "relay_chn_nvs.h" #define RELAY_CHN_KEY_DIR "dir" /*!< Direction key */ +#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#define RELAY_CHN_KEY_RLIM(ch) "rlim_%d" /*!< Run limit key */ +#endif #ifdef RELAY_CHN_ENABLE_TILTING #define RELAY_CHN_KEY_TSENS(ch) "tsens_%d" /*!< Tilt sensitivity key */ #define RELAY_CHN_KEY_TCNT(ch) "tcnt_%d" /*!< Tilt count key */ @@ -69,6 +72,22 @@ esp_err_t relay_chn_nvs_get_direction(uint8_t ch, relay_chn_direction_t *directi return ESP_OK; } +#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 +esp_err_t relay_chn_nvs_set_run_limit(uint8_t ch, uint16_t time_sec) +{ + esp_err_t ret = nvs_set_u16(relay_chn_nvs, RELAY_CHN_KEY_RLIM(ch), time_sec); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set run limit for channel %d", ch); + return nvs_commit(relay_chn_nvs); +} + +esp_err_t relay_chn_nvs_get_run_limit(uint8_t ch, uint16_t *time_sec) +{ + ESP_RETURN_ON_FALSE(time_sec != NULL, ESP_ERR_INVALID_ARG, TAG, "Run limit value pointer is NULL"); + + return nvs_get_u16(relay_chn_nvs, RELAY_CHN_KEY_RLIM(ch), time_sec); +} +#endif // RELAY_CHN_ENABLE_RUN_LIMIT == 1 + #ifdef RELAY_CHN_ENABLE_TILTING esp_err_t relay_chn_nvs_set_tilt_sensitivity(uint8_t ch, uint8_t sensitivity) { From 29803c063e5607e49ab0747d72e1927208ca25c1 Mon Sep 17 00:00:00 2001 From: ismail Date: Fri, 22 Aug 2025 12:41:58 +0300 Subject: [PATCH 13/69] Enhance unit test default sdkconfig files The sdkconfig.defaults files were enhanced to test the component against all individual configurations for more granularity, as well as with all combinations to ensure consistency and integrity across all configurations. --- ...config.defaults => sdkconfig.defaults.multi} | 4 +--- ..._nvs => sdkconfig.defaults.multi.custom_nvs} | 3 +-- test_apps/sdkconfig.defaults.multi.full | 17 +++++++++++++++++ test_apps/sdkconfig.defaults.multi.nvs | 8 ++++++++ test_apps/sdkconfig.defaults.multi.run_limit | 9 +++++++++ test_apps/sdkconfig.defaults.multi.tilt | 8 ++++++++ test_apps/sdkconfig.defaults.single | 5 +---- test_apps/sdkconfig.defaults.single.custom_nvs | 2 -- test_apps/sdkconfig.defaults.single.full | 16 ++++++++++++++++ test_apps/sdkconfig.defaults.single.nvs | 7 +++++++ test_apps/sdkconfig.defaults.single.run_limit | 8 ++++++++ test_apps/sdkconfig.defaults.single.tilt | 7 +++++++ 12 files changed, 83 insertions(+), 11 deletions(-) rename test_apps/{sdkconfig.defaults => sdkconfig.defaults.multi} (69%) rename test_apps/{sdkconfig.defaults.custom_nvs => sdkconfig.defaults.multi.custom_nvs} (79%) create mode 100644 test_apps/sdkconfig.defaults.multi.full create mode 100644 test_apps/sdkconfig.defaults.multi.nvs create mode 100644 test_apps/sdkconfig.defaults.multi.run_limit create mode 100644 test_apps/sdkconfig.defaults.multi.tilt create mode 100644 test_apps/sdkconfig.defaults.single.full create mode 100644 test_apps/sdkconfig.defaults.single.nvs create mode 100644 test_apps/sdkconfig.defaults.single.run_limit create mode 100644 test_apps/sdkconfig.defaults.single.tilt diff --git a/test_apps/sdkconfig.defaults b/test_apps/sdkconfig.defaults.multi similarity index 69% rename from test_apps/sdkconfig.defaults rename to test_apps/sdkconfig.defaults.multi index 53c4d00..6262a4c 100644 --- a/test_apps/sdkconfig.defaults +++ b/test_apps/sdkconfig.defaults.multi @@ -4,6 +4,4 @@ CONFIG_ESP_TASK_WDT_INIT=n # Relay Channel Driver Default Configuration for Testing # Keep this as short as possible for tests CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 -CONFIG_RELAY_CHN_COUNT=2 -CONFIG_RELAY_CHN_ENABLE_TILTING=y -CONFIG_RELAY_CHN_ENABLE_NVS=y \ No newline at end of file +CONFIG_RELAY_CHN_COUNT=8 \ No newline at end of file diff --git a/test_apps/sdkconfig.defaults.custom_nvs b/test_apps/sdkconfig.defaults.multi.custom_nvs similarity index 79% rename from test_apps/sdkconfig.defaults.custom_nvs rename to test_apps/sdkconfig.defaults.multi.custom_nvs index cc1df53..040c093 100644 --- a/test_apps/sdkconfig.defaults.custom_nvs +++ b/test_apps/sdkconfig.defaults.multi.custom_nvs @@ -9,7 +9,6 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/part_nvs.csv" # Relay Channel Driver Default Configuration for Testing # Keep this as short as possible for tests CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 -CONFIG_RELAY_CHN_COUNT=2 -CONFIG_RELAY_CHN_ENABLE_TILTING=y +CONFIG_RELAY_CHN_COUNT=8 CONFIG_RELAY_CHN_ENABLE_NVS=y CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION=y \ No newline at end of file diff --git a/test_apps/sdkconfig.defaults.multi.full b/test_apps/sdkconfig.defaults.multi.full new file mode 100644 index 0000000..0388bf1 --- /dev/null +++ b/test_apps/sdkconfig.defaults.multi.full @@ -0,0 +1,17 @@ +# Disable task WDT for tests +CONFIG_ESP_TASK_WDT_INIT=n + +# Partition configuration +CONFIG_PARTITION_TABLE_SINGLE_APP=y +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/part_nvs.csv" + +# Relay Channel Driver Default Configuration for Testing +# Keep this as short as possible for tests +CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 +CONFIG_RELAY_CHN_COUNT=8 +CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT=y +CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC=1 +CONFIG_RELAY_CHN_ENABLE_TILTING=y +CONFIG_RELAY_CHN_ENABLE_NVS=y +CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION=y \ No newline at end of file diff --git a/test_apps/sdkconfig.defaults.multi.nvs b/test_apps/sdkconfig.defaults.multi.nvs new file mode 100644 index 0000000..5e3bbdd --- /dev/null +++ b/test_apps/sdkconfig.defaults.multi.nvs @@ -0,0 +1,8 @@ +# Disable task WDT for tests +CONFIG_ESP_TASK_WDT_INIT=n + +# Relay Channel Driver Default Configuration for Testing +# Keep this as short as possible for tests +CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 +CONFIG_RELAY_CHN_COUNT=8 +CONFIG_RELAY_CHN_ENABLE_NVS=y \ No newline at end of file diff --git a/test_apps/sdkconfig.defaults.multi.run_limit b/test_apps/sdkconfig.defaults.multi.run_limit new file mode 100644 index 0000000..b5d1df4 --- /dev/null +++ b/test_apps/sdkconfig.defaults.multi.run_limit @@ -0,0 +1,9 @@ +# Disable task WDT for tests +CONFIG_ESP_TASK_WDT_INIT=n + +# Relay Channel Driver Default Configuration for Testing +# Keep this as short as possible for tests +CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 +CONFIG_RELAY_CHN_COUNT=8 +CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT=y +CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC=1 \ No newline at end of file diff --git a/test_apps/sdkconfig.defaults.multi.tilt b/test_apps/sdkconfig.defaults.multi.tilt new file mode 100644 index 0000000..e3c4d79 --- /dev/null +++ b/test_apps/sdkconfig.defaults.multi.tilt @@ -0,0 +1,8 @@ +# Disable task WDT for tests +CONFIG_ESP_TASK_WDT_INIT=n + +# Relay Channel Driver Default Configuration for Testing +# Keep this as short as possible for tests +CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 +CONFIG_RELAY_CHN_COUNT=8 +CONFIG_RELAY_CHN_ENABLE_TILTING=y \ No newline at end of file diff --git a/test_apps/sdkconfig.defaults.single b/test_apps/sdkconfig.defaults.single index b468a54..00de340 100644 --- a/test_apps/sdkconfig.defaults.single +++ b/test_apps/sdkconfig.defaults.single @@ -3,7 +3,4 @@ CONFIG_ESP_TASK_WDT_INIT=n # Relay Channel Driver Default Configuration for Testing # Keep this as short as possible for tests -CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 -CONFIG_RELAY_CHN_COUNT=1 -CONFIG_RELAY_CHN_ENABLE_TILTING=y -CONFIG_RELAY_CHN_ENABLE_NVS=y \ No newline at end of file +CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 \ No newline at end of file diff --git a/test_apps/sdkconfig.defaults.single.custom_nvs b/test_apps/sdkconfig.defaults.single.custom_nvs index 9cbcb0c..46f1e8e 100644 --- a/test_apps/sdkconfig.defaults.single.custom_nvs +++ b/test_apps/sdkconfig.defaults.single.custom_nvs @@ -9,7 +9,5 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/part_nvs.csv" # Relay Channel Driver Default Configuration for Testing # Keep this as short as possible for tests CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 -CONFIG_RELAY_CHN_COUNT=1 -CONFIG_RELAY_CHN_ENABLE_TILTING=y CONFIG_RELAY_CHN_ENABLE_NVS=y CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION=y \ No newline at end of file diff --git a/test_apps/sdkconfig.defaults.single.full b/test_apps/sdkconfig.defaults.single.full new file mode 100644 index 0000000..79085de --- /dev/null +++ b/test_apps/sdkconfig.defaults.single.full @@ -0,0 +1,16 @@ +# Disable task WDT for tests +CONFIG_ESP_TASK_WDT_INIT=n + +# Partition configuration +CONFIG_PARTITION_TABLE_SINGLE_APP=y +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/part_nvs.csv" + +# Relay Channel Driver Default Configuration for Testing +# Keep this as short as possible for tests +CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 +CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT=y +CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC=1 +CONFIG_RELAY_CHN_ENABLE_TILTING=y +CONFIG_RELAY_CHN_ENABLE_NVS=y +CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION=y \ No newline at end of file diff --git a/test_apps/sdkconfig.defaults.single.nvs b/test_apps/sdkconfig.defaults.single.nvs new file mode 100644 index 0000000..d8a62fd --- /dev/null +++ b/test_apps/sdkconfig.defaults.single.nvs @@ -0,0 +1,7 @@ +# Disable task WDT for tests +CONFIG_ESP_TASK_WDT_INIT=n + +# Relay Channel Driver Default Configuration for Testing +# Keep this as short as possible for tests +CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 +CONFIG_RELAY_CHN_ENABLE_NVS=y \ No newline at end of file diff --git a/test_apps/sdkconfig.defaults.single.run_limit b/test_apps/sdkconfig.defaults.single.run_limit new file mode 100644 index 0000000..71c2df9 --- /dev/null +++ b/test_apps/sdkconfig.defaults.single.run_limit @@ -0,0 +1,8 @@ +# Disable task WDT for tests +CONFIG_ESP_TASK_WDT_INIT=n + +# Relay Channel Driver Default Configuration for Testing +# Keep this as short as possible for tests +CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 +CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT=y +CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC=1 \ No newline at end of file diff --git a/test_apps/sdkconfig.defaults.single.tilt b/test_apps/sdkconfig.defaults.single.tilt new file mode 100644 index 0000000..e111deb --- /dev/null +++ b/test_apps/sdkconfig.defaults.single.tilt @@ -0,0 +1,7 @@ +# Disable task WDT for tests +CONFIG_ESP_TASK_WDT_INIT=n + +# Relay Channel Driver Default Configuration for Testing +# Keep this as short as possible for tests +CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 +CONFIG_RELAY_CHN_ENABLE_TILTING=y \ No newline at end of file From fb4f34e895875c08530ef81c5d3afd4546e6b65b Mon Sep 17 00:00:00 2001 From: ismail Date: Fri, 22 Aug 2025 12:42:53 +0300 Subject: [PATCH 14/69] Add and execute unit tests for run limit feature --- test_apps/main/test_app_main.c | 10 ++- test_apps/main/test_relay_chn_core_multi.c | 98 ++++++++++++++++++++- test_apps/main/test_relay_chn_core_single.c | 78 +++++++++++++++- test_apps/main/test_relay_chn_nvs_multi.c | 17 ++++ test_apps/main/test_relay_chn_nvs_single.c | 16 ++++ test_apps/sdkconfig | 22 +++-- 6 files changed, 232 insertions(+), 9 deletions(-) diff --git a/test_apps/main/test_app_main.c b/test_apps/main/test_app_main.c index edabae1..460659c 100644 --- a/test_apps/main/test_app_main.c +++ b/test_apps/main/test_app_main.c @@ -28,6 +28,7 @@ void tearDown() reset_channels_to_idle_state(); } +#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 static void test_nvs_flash_init(void) { esp_err_t ret; @@ -52,9 +53,11 @@ static void test_nvs_flash_init(void) } } #endif - TEST_ESP_OK(ret); +TEST_ESP_OK(ret); } +#endif +#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 static void test_nvs_flash_deinit(void) { esp_err_t ret; @@ -65,11 +68,14 @@ static void test_nvs_flash_deinit(void) #endif TEST_ESP_OK(ret); } +#endif void app_main(void) { +#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 // Init NVS once for all tests test_nvs_flash_init(); +#endif // Create relay_chn once for all tests TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); @@ -92,8 +98,10 @@ void app_main(void) // Destroy relay_chn relay_chn_destroy(); +#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 // Deinit NVS test_nvs_flash_deinit(); +#endif ESP_LOGI(TEST_TAG, "All tests complete."); diff --git a/test_apps/main/test_relay_chn_core_multi.c b/test_apps/main/test_relay_chn_core_multi.c index 8b35c5c..b7904dd 100644 --- a/test_apps/main/test_relay_chn_core_multi.c +++ b/test_apps/main/test_relay_chn_core_multi.c @@ -344,4 +344,100 @@ TEST_CASE("Direction flip handles invalid channel ID gracefully", "[relay_chn][c relay_chn_flip_direction(invalid_ch); // Call with an invalid ID TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(invalid_ch)); -} \ No newline at end of file +} + +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#define TEST_RUN_LIMIT_SEC 5 +#define TEST_SHORT_RUN_LIMIT_SEC 2 +// ### Run Limit Tests +TEST_CASE("Test run limit initialization", "[relay_chn][run_limit]") +{ + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + // Should initialize with default value + TEST_ASSERT_EQUAL(CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC, relay_chn_get_run_limit(i)); + } +} + +TEST_CASE("Test run limit setting boundaries", "[relay_chn][run_limit]") +{ + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + // Test minimum boundary + relay_chn_set_run_limit(i, CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC - 1); + TEST_ASSERT_EQUAL(CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC, relay_chn_get_run_limit(i)); + + // Test maximum boundary + relay_chn_set_run_limit(i, CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC + 1); + TEST_ASSERT_EQUAL(CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC, relay_chn_get_run_limit(i)); + + // Test valid value + relay_chn_set_run_limit(i, TEST_RUN_LIMIT_SEC); + TEST_ASSERT_EQUAL(TEST_RUN_LIMIT_SEC, relay_chn_get_run_limit(i)); + } +} + +TEST_CASE("Test run limit stops channel after timeout", "[relay_chn][run_limit]") +{ + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + // Set a short run limit for testing + relay_chn_set_run_limit(i, TEST_SHORT_RUN_LIMIT_SEC); + } + + relay_chn_run_forward(RELAY_CHN_ID_ALL); + + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + // Check running forward + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(i)); + } + + // Wait for run limit timeout + vTaskDelay(pdMS_TO_TICKS(TEST_SHORT_RUN_LIMIT_SEC * 1000 + test_delay_margin_ms)); + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(i)); + } +} + +TEST_CASE("Test run limit reset on direction change and time out finally", "[relay_chn][run_limit]") +{ + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + // Set a short run limit + relay_chn_set_run_limit(i, TEST_SHORT_RUN_LIMIT_SEC); + + // Start running forward + relay_chn_run_forward(i); + } + + vTaskDelay(1000 / portTICK_PERIOD_MS); // Wait 1 second + + // Change direction before timeout + relay_chn_run_reverse(RELAY_CHN_ID_ALL); + + // Wait for the inertia period (after which the reverse command will be dispatched) + vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(i)); + } + + // Timer should time out and stop the channel after the run limit time + vTaskDelay(pdMS_TO_TICKS(TEST_SHORT_RUN_LIMIT_SEC * 1000 + test_delay_margin_ms)); + + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(i)); + } +} + +TEST_CASE("Test run limit persistence across stop/start", "[relay_chn][run_limit]") +{ + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + // Set initial run limit + relay_chn_set_run_limit(i, TEST_RUN_LIMIT_SEC); + + // Stop and start channel + relay_chn_stop(i); + relay_chn_run_forward(i); + + // Run limit should persist + TEST_ASSERT_EQUAL(TEST_RUN_LIMIT_SEC, relay_chn_get_run_limit(i)); + } +} +#endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 \ No newline at end of file diff --git a/test_apps/main/test_relay_chn_core_single.c b/test_apps/main/test_relay_chn_core_single.c index 3c3f4a8..b21823f 100644 --- a/test_apps/main/test_relay_chn_core_single.c +++ b/test_apps/main/test_relay_chn_core_single.c @@ -171,4 +171,80 @@ TEST_CASE("Flipping a running channel stops it and flips direction", "[relay_chn vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction()); -} \ No newline at end of file +} + +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#define TEST_RUN_LIMIT_SEC 5 +#define TEST_SHORT_RUN_LIMIT_SEC 2 +// ### Run Limit Tests +TEST_CASE("Test run limit initialization", "[relay_chn][run_limit]") +{ + // Should initialize with default value + TEST_ASSERT_EQUAL(CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC, relay_chn_get_run_limit()); +} + +TEST_CASE("Test run limit setting boundaries", "[relay_chn][run_limit]") +{ + // Test minimum boundary + relay_chn_set_run_limit(CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC - 1); + TEST_ASSERT_EQUAL(CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC, relay_chn_get_run_limit()); + + // Test maximum boundary + relay_chn_set_run_limit(CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC + 1); + TEST_ASSERT_EQUAL(CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC, relay_chn_get_run_limit()); + + // Test valid value + relay_chn_set_run_limit(TEST_RUN_LIMIT_SEC); + TEST_ASSERT_EQUAL(TEST_RUN_LIMIT_SEC, relay_chn_get_run_limit()); +} + +TEST_CASE("Test run limit stops channel after timeout", "[relay_chn][run_limit]") +{ + // Set a short run limit for testing + relay_chn_set_run_limit(TEST_SHORT_RUN_LIMIT_SEC); + + // Start running forward + relay_chn_run_forward(); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); + + // Wait for run limit timeout + vTaskDelay(pdMS_TO_TICKS(TEST_SHORT_RUN_LIMIT_SEC * 1000 + test_delay_margin_ms)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state()); +} + +TEST_CASE("Test run limit reset on direction change and time out finally", "[relay_chn][run_limit]") +{ + // Set a short run limit + relay_chn_set_run_limit(TEST_SHORT_RUN_LIMIT_SEC); + + // Start running forward + relay_chn_run_forward(); + vTaskDelay(1000 / portTICK_PERIOD_MS); // Wait 1 second + + // Change direction before timeout + relay_chn_run_reverse(); + + // Wait for the inertia period (after which the reverse command will be dispatched) + vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state()); + + // Timer should time out and stop the channel after the run limit time + vTaskDelay(pdMS_TO_TICKS(TEST_SHORT_RUN_LIMIT_SEC * 1000 + test_delay_margin_ms)); + + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state()); +} + +TEST_CASE("Test run limit persistence across stop/start", "[relay_chn][run_limit]") +{ + // Set initial run limit + relay_chn_set_run_limit(TEST_RUN_LIMIT_SEC); + + // Stop and start channel + relay_chn_stop(); + relay_chn_run_forward(); + + // Run limit should persist + TEST_ASSERT_EQUAL(TEST_RUN_LIMIT_SEC, relay_chn_get_run_limit()); +} +#endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 \ No newline at end of file diff --git a/test_apps/main/test_relay_chn_nvs_multi.c b/test_apps/main/test_relay_chn_nvs_multi.c index b21b3d5..d591dbb 100644 --- a/test_apps/main/test_relay_chn_nvs_multi.c +++ b/test_apps/main/test_relay_chn_nvs_multi.c @@ -84,6 +84,23 @@ TEST_CASE("Test relay_chn_nvs_erase_all", "[relay_chn][nvs]") TEST_ESP_OK(relay_chn_nvs_deinit()); } +#ifdef CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT +TEST_CASE("Test run limit setting and getting", "[relay_chn][nvs][run_limit]") +{ + TEST_ESP_OK(relay_chn_nvs_init()); + + const uint16_t run_limit_sec = 32; + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + TEST_ESP_OK(relay_chn_nvs_set_run_limit(i, run_limit_sec)); + + uint16_t run_limit_read; + TEST_ESP_OK(relay_chn_nvs_get_run_limit(i, &run_limit_read)); + TEST_ASSERT_EQUAL(run_limit_sec, run_limit_read); + } + TEST_ESP_OK(relay_chn_nvs_deinit()); +} +#endif + #ifdef RELAY_CHN_ENABLE_TILTING TEST_CASE("Test sensitivity setting and getting", "[relay_chn][nvs][tilt]") { diff --git a/test_apps/main/test_relay_chn_nvs_single.c b/test_apps/main/test_relay_chn_nvs_single.c index b6fb376..2872d5c 100644 --- a/test_apps/main/test_relay_chn_nvs_single.c +++ b/test_apps/main/test_relay_chn_nvs_single.c @@ -77,6 +77,22 @@ TEST_CASE("Test relay_chn_nvs_erase_all", "[relay_chn][nvs]") TEST_ESP_OK(relay_chn_nvs_deinit()); } +#ifdef CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT +TEST_CASE("Test run limit setting and getting", "[relay_chn][nvs][run_limit]") +{ + TEST_ESP_OK(relay_chn_nvs_init()); + + const uint16_t run_limit_sec = 32; + TEST_ESP_OK(relay_chn_nvs_set_run_limit(0, run_limit_sec)); + + uint16_t run_limit_read; + TEST_ESP_OK(relay_chn_nvs_get_run_limit(0, &run_limit_read)); + TEST_ASSERT_EQUAL(run_limit_sec, run_limit_read); + + TEST_ESP_OK(relay_chn_nvs_deinit()); +} +#endif + #ifdef RELAY_CHN_ENABLE_TILTING TEST_CASE("Test sensitivity setting and getting", "[relay_chn][nvs][tilt]") { diff --git a/test_apps/sdkconfig b/test_apps/sdkconfig index f47128a..7810c58 100644 --- a/test_apps/sdkconfig +++ b/test_apps/sdkconfig @@ -395,13 +395,13 @@ CONFIG_ESPTOOLPY_MONITOR_BAUD=115200 # # Partition Table # -CONFIG_PARTITION_TABLE_SINGLE_APP=y +# CONFIG_PARTITION_TABLE_SINGLE_APP is not set # CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE is not set # CONFIG_PARTITION_TABLE_TWO_OTA is not set # CONFIG_PARTITION_TABLE_TWO_OTA_LARGE is not set -# CONFIG_PARTITION_TABLE_CUSTOM is not set -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" -CONFIG_PARTITION_TABLE_FILENAME="partitions_singleapp.csv" +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/part_nvs.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions/part_nvs.csv" CONFIG_PARTITION_TABLE_OFFSET=0x8000 CONFIG_PARTITION_TABLE_MD5=y # end of Partition Table @@ -1268,7 +1268,8 @@ CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=y # Relay Channel Driver Configuration # CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 -CONFIG_RELAY_CHN_COUNT=2 +CONFIG_RELAY_CHN_COUNT=8 +CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT=y CONFIG_RELAY_CHN_ENABLE_TILTING=y CONFIG_RELAY_CHN_ENABLE_NVS=y # end of Relay Channel Driver Configuration @@ -1277,8 +1278,17 @@ CONFIG_RELAY_CHN_ENABLE_NVS=y # Relay Channel NVS Storage Configuration # CONFIG_RELAY_CHN_NVS_NAMESPACE="relay_chn" -# CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION is not set +CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION=y +CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME="app_data" # end of Relay Channel NVS Storage Configuration + +# +# Relay Channel Run Limit Configuration +# +CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC=1 +CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC=600 +CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC=60 +# end of Relay Channel Run Limit Configuration # end of Component config # CONFIG_IDF_EXPERIMENTAL_FEATURES is not set From e73c205e3d33d0be0afc135f341f402d01696f70 Mon Sep 17 00:00:00 2001 From: ismail Date: Fri, 22 Aug 2025 15:06:37 +0300 Subject: [PATCH 15/69] Optimize event loop resource size The event loop queue and task stack size is optimized to be determined by config factors. Fixes #1083 --- src/relay_chn_core.c | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/relay_chn_core.c b/src/relay_chn_core.c index a117ccf..d3913a5 100644 --- a/src/relay_chn_core.c +++ b/src/relay_chn_core.c @@ -22,6 +22,26 @@ #include "relay_chn_core.h" +/* + Determine the event loop's queue and task stack sizes depending on + the configuration and memory requirements +*/ +#if RELAY_CHN_ENABLE_TILTING == 1 +#define EVENT_QUEUE_BASE_SIZE 32 +#define EVENT_QUEUE_SIZE_BY_CHN 24 +#define TASK_STACK_BASE_SIZE 4096 +#else +#define EVENT_QUEUE_BASE_SIZE 16 +#define EVENT_QUEUE_SIZE_BY_CHN 12 +#define TASK_STACK_BASE_SIZE 2048 +#endif + +#define RELAY_CHN_EVENT_LOOP_QUEUE_SIZE \ + (EVENT_QUEUE_BASE_SIZE + (EVENT_QUEUE_SIZE_BY_CHN * RELAY_CHN_COUNT)) + +#define RELAY_CHN_EVENT_LOOP_TASK_STACK_SIZE \ + (TASK_STACK_BASE_SIZE + (RELAY_CHN_EVENT_LOOP_QUEUE_SIZE * RELAY_CHN_COUNT)) + static const char *TAG = "RELAY_CHN_CORE"; @@ -99,10 +119,10 @@ esp_err_t relay_chn_init_timer(relay_chn_ctl_t *chn_ctl) static esp_err_t relay_chn_create_event_loop() { esp_event_loop_args_t loop_args = { - .queue_size = RELAY_CHN_COUNT * 8, + .queue_size = RELAY_CHN_EVENT_LOOP_QUEUE_SIZE, .task_name = "relay_chn_event_loop", .task_priority = ESP_TASKD_EVENT_PRIO - 1, - .task_stack_size = 2048, + .task_stack_size = RELAY_CHN_EVENT_LOOP_TASK_STACK_SIZE, .task_core_id = tskNO_AFFINITY }; esp_err_t ret = esp_event_loop_create(&loop_args, &relay_chn_event_loop); From 7a0f9b1420fb252572e9f46e71d5be3d2e5554d9 Mon Sep 17 00:00:00 2001 From: ismail Date: Fri, 22 Aug 2025 17:41:08 +0300 Subject: [PATCH 16/69] Optimize timer callbacks It turned out that esp_event was adding extra complexity to the code base and it was completely unnecessary. So it has been removed from the component completely. The actions are now executed directly in the `relay_chn_distpacth_cmd()` and `relay_chn_tilt_dispatch_cmd()` functions. This change has simplified the component as well as reduced the memory footprint. Fixes #1084, refs #1083 --- CMakeLists.txt | 2 +- include/relay_chn.h | 3 - private_include/relay_chn_core.h | 12 +--- private_include/relay_chn_tilt.h | 4 +- src/relay_chn_core.c | 118 ++++++------------------------- src/relay_chn_tilt.c | 43 +++-------- 6 files changed, 34 insertions(+), 148 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e7cda68..6f103e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,4 +23,4 @@ endif() idf_component_register(SRCS ${srcs} INCLUDE_DIRS ${include_dirs} PRIV_INCLUDE_DIRS ${priv_include_dirs} - REQUIRES driver esp_timer esp_event nvs_flash) + REQUIRES driver esp_timer nvs_flash) diff --git a/include/relay_chn.h b/include/relay_chn.h index 9537a4b..de3b94c 100644 --- a/include/relay_chn.h +++ b/include/relay_chn.h @@ -9,9 +9,6 @@ * To prevent mechanical strain on the motor, the component automatically manages direction changes * with a configurable inertia delay, protecting it from abrupt reversals. * The STOP command overrides any other command and clears the pending command if any. - * - * The module internally uses a custom esp event loop to handle relay commands serially to ensure - * reliability and prevent conflict operations. Also, the esp timer is used to manage the direction change inertia. */ #pragma once diff --git a/private_include/relay_chn_core.h b/private_include/relay_chn_core.h index 3f17928..3577b91 100644 --- a/private_include/relay_chn_core.h +++ b/private_include/relay_chn_core.h @@ -8,8 +8,6 @@ #include "esp_err.h" #include "esp_log.h" -#include "esp_event_base.h" -#include "esp_event.h" #include "esp_timer.h" #include "relay_chn_defs.h" #include "relay_chn_types.h" @@ -19,9 +17,6 @@ extern "C" { #endif -/// Event base used by *_core, *_ctl, and *_tilt modules. -ESP_EVENT_DECLARE_BASE(RELAY_CHN_CMD_EVENT); - /** * @brief Initializes the relay channel timer. * @@ -59,7 +54,7 @@ esp_err_t relay_chn_init_run_limit_timer(relay_chn_ctl_t *chn_ctl); void relay_chn_issue_cmd(relay_chn_ctl_t* chn_ctl, relay_chn_cmd_t cmd); /** - * @brief Dispatches a relay channel command to the event loop. + * @brief Dispatches a relay channel command. * * @param chn_ctl Pointer to the relay channel control structure. * @param cmd The command to dispatch. @@ -118,11 +113,6 @@ char *relay_chn_state_str(relay_chn_state_t state); bool relay_chn_is_channel_id_valid(uint8_t chn_id); #endif // RELAY_CHN_COUNT > 1 -#if RELAY_CHN_ENABLE_TILTING == 1 -/// Relay channel event loop handle declaration for *_tilt module. -extern esp_event_loop_handle_t relay_chn_event_loop; -#endif // RELAY_CHN_ENABLE_TILTING - #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/private_include/relay_chn_tilt.h b/private_include/relay_chn_tilt.h index ab1b5c9..30db7aa 100644 --- a/private_include/relay_chn_tilt.h +++ b/private_include/relay_chn_tilt.h @@ -15,7 +15,7 @@ extern "C" { /** * @brief Initialize relay channel tilt controls. * - * Sets up tilt functionality for relay channels including timers and event handlers. + * Sets up tilt functionality for relay channels including timers. * Must be called before using any other tilt functions. * * @param[in] chn_ctls Array of relay channel control structures. @@ -27,7 +27,7 @@ esp_err_t relay_chn_tilt_init(relay_chn_ctl_t *chn_ctls); /** * @brief Deinitialize relay channel tilt controls. * - * Cleans up tilt resources including timers and event handlers. + * Cleans up tilt resources including timers. * Should be called when tilt functionality is no longer needed. */ void relay_chn_tilt_deinit(void); diff --git a/src/relay_chn_core.c b/src/relay_chn_core.c index d3913a5..48a6321 100644 --- a/src/relay_chn_core.c +++ b/src/relay_chn_core.c @@ -22,31 +22,9 @@ #include "relay_chn_core.h" -/* - Determine the event loop's queue and task stack sizes depending on - the configuration and memory requirements -*/ -#if RELAY_CHN_ENABLE_TILTING == 1 -#define EVENT_QUEUE_BASE_SIZE 32 -#define EVENT_QUEUE_SIZE_BY_CHN 24 -#define TASK_STACK_BASE_SIZE 4096 -#else -#define EVENT_QUEUE_BASE_SIZE 16 -#define EVENT_QUEUE_SIZE_BY_CHN 12 -#define TASK_STACK_BASE_SIZE 2048 -#endif - -#define RELAY_CHN_EVENT_LOOP_QUEUE_SIZE \ - (EVENT_QUEUE_BASE_SIZE + (EVENT_QUEUE_SIZE_BY_CHN * RELAY_CHN_COUNT)) - -#define RELAY_CHN_EVENT_LOOP_TASK_STACK_SIZE \ - (TASK_STACK_BASE_SIZE + (RELAY_CHN_EVENT_LOOP_QUEUE_SIZE * RELAY_CHN_COUNT)) - static const char *TAG = "RELAY_CHN_CORE"; -ESP_EVENT_DEFINE_BASE(RELAY_CHN_CMD_EVENT); - // Structure to hold a listener entry in the linked list. typedef struct relay_chn_listener_entry_type { @@ -57,15 +35,6 @@ typedef struct relay_chn_listener_entry_type { // The list that holds references to the registered listeners. static List_t relay_chn_listener_list; -// Define the event loop for global access both for this module and tilt module. -esp_event_loop_handle_t relay_chn_event_loop = NULL; - - -// Private function declarations -// Event handler for the relay channel command event -static void relay_chn_event_handler(void* handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data); - - #if RELAY_CHN_ENABLE_RUN_LIMIT == 1 /* * Run limit timer callback immediately dispatches a STOP command for the @@ -116,24 +85,6 @@ esp_err_t relay_chn_init_timer(relay_chn_ctl_t *chn_ctl) return esp_timer_create(&timer_args, &chn_ctl->inertia_timer); } -static esp_err_t relay_chn_create_event_loop() -{ - esp_event_loop_args_t loop_args = { - .queue_size = RELAY_CHN_EVENT_LOOP_QUEUE_SIZE, - .task_name = "relay_chn_event_loop", - .task_priority = ESP_TASKD_EVENT_PRIO - 1, - .task_stack_size = RELAY_CHN_EVENT_LOOP_TASK_STACK_SIZE, - .task_core_id = tskNO_AFFINITY - }; - esp_err_t ret = esp_event_loop_create(&loop_args, &relay_chn_event_loop); - ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create event loop for relay channel"); - ret = esp_event_handler_register_with(relay_chn_event_loop, - RELAY_CHN_CMD_EVENT, - ESP_EVENT_ANY_ID, - relay_chn_event_handler, NULL); - return ret; -} - esp_err_t relay_chn_create(const uint8_t* gpio_map, uint8_t gpio_count) { ESP_RETURN_ON_FALSE(gpio_map != NULL, ESP_ERR_INVALID_ARG, TAG, "gpio_map cannot be NULL"); @@ -163,10 +114,6 @@ esp_err_t relay_chn_create(const uint8_t* gpio_map, uint8_t gpio_count) ret = relay_chn_ctl_init(outputs, run_infos); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize relay channel control"); - // Create relay channel command event loop - ret = relay_chn_create_event_loop(); - ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create relay channel event loop"); - #if RELAY_CHN_ENABLE_TILTING == 1 // Initialize the tilt feature #if RELAY_CHN_COUNT > 1 @@ -196,10 +143,6 @@ void relay_chn_destroy(void) relay_chn_nvs_deinit(); #endif - // Destroy the event loop - esp_event_loop_delete(relay_chn_event_loop); - relay_chn_event_loop = NULL; - // Free the listeners while (listCURRENT_LIST_LENGTH(&relay_chn_listener_list) > 0) { ListItem_t *pxItem = listGET_HEAD_ENTRY(&relay_chn_listener_list); @@ -283,31 +226,6 @@ void relay_chn_unregister_listener(relay_chn_state_listener_t listener) } -// Dispatch relay channel command to its event loop -void relay_chn_dispatch_cmd(relay_chn_ctl_t *chn_ctl, relay_chn_cmd_t cmd) { - if (cmd == RELAY_CHN_CMD_NONE) { - return; - } - - // Since the event_loop library creates a deep copy of the event data, - // and we need to pass the pointer of the relevant channel, here we need - // to pass the pointer to the pointer of the channel (&chn_ctl) so that - // the pointer value is preserved in the event data. - esp_event_post_to(relay_chn_event_loop, - RELAY_CHN_CMD_EVENT, - cmd, - &chn_ctl, - sizeof(chn_ctl), - portMAX_DELAY); - -#if RELAY_CHN_ENABLE_TILTING == 1 - // Reset the tilt counter when the command is either FORWARD or REVERSE - if (cmd == RELAY_CHN_CMD_FORWARD || cmd == RELAY_CHN_CMD_REVERSE) { - relay_chn_tilt_reset_count(chn_ctl->tilt_ctl); - } -#endif -} - 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); @@ -498,6 +416,14 @@ bool relay_chn_is_channel_id_valid(uint8_t chn_id) #endif // RELAY_CHN_COUNT > 1 +static void relay_chn_execute_idle(relay_chn_ctl_t *chn_ctl) +{ + chn_ctl->pending_cmd = RELAY_CHN_CMD_NONE; + // Invalidate the channel's timer if it is active + esp_timer_stop(chn_ctl->inertia_timer); + relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_IDLE); +} + static void relay_chn_execute_stop(relay_chn_ctl_t *chn_ctl) { if (relay_chn_output_stop(chn_ctl->output) != ESP_OK) { @@ -525,7 +451,8 @@ static void relay_chn_execute_stop(relay_chn_ctl_t *chn_ctl) relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, RELAY_CHN_OPPOSITE_INERTIA_MS); } else { // If the channel was not running one of the run or fwd, issue a free command immediately - relay_chn_dispatch_cmd(chn_ctl, RELAY_CHN_CMD_IDLE); + // relay_chn_dispatch_cmd(chn_ctl, RELAY_CHN_CMD_IDLE); + relay_chn_execute_idle(chn_ctl); } } @@ -565,21 +492,11 @@ static void relay_chn_execute_flip(relay_chn_ctl_t *chn_ctl) relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, RELAY_CHN_OPPOSITE_INERTIA_MS); } -void relay_chn_execute_idle(relay_chn_ctl_t *chn_ctl) -{ - chn_ctl->pending_cmd = RELAY_CHN_CMD_NONE; - // Invalidate the channel's timer if it is active - esp_timer_stop(chn_ctl->inertia_timer); - relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_IDLE); -} +// Dispatch relay channel command +void relay_chn_dispatch_cmd(relay_chn_ctl_t *chn_ctl, relay_chn_cmd_t cmd) { + ESP_LOGD(TAG, "relay_chn_dispatch_cmd: Command: %d", cmd); -static void relay_chn_event_handler(void* handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data) -{ - relay_chn_ctl_t* chn_ctl = *(relay_chn_ctl_t**) event_data; - ESP_RETURN_VOID_ON_FALSE(chn_ctl != NULL, TAG, "event_data is NULL"); - ESP_LOGD(TAG, "relay_chn_event_handler: Command: %s", relay_chn_cmd_str(event_id)); - - switch (event_id) { + switch (cmd) { case RELAY_CHN_CMD_STOP: relay_chn_execute_stop(chn_ctl); break; @@ -598,6 +515,13 @@ static void relay_chn_event_handler(void* handler_arg, esp_event_base_t event_ba default: ESP_LOGD(TAG, "Unknown relay channel command!"); } + +#if RELAY_CHN_ENABLE_TILTING == 1 + // Reset the tilt counter when the command is either FORWARD or REVERSE + if (cmd == RELAY_CHN_CMD_FORWARD || cmd == RELAY_CHN_CMD_REVERSE) { + relay_chn_tilt_reset_count(chn_ctl->tilt_ctl); + } +#endif } char *relay_chn_cmd_str(relay_chn_cmd_t cmd) diff --git a/src/relay_chn_tilt.c b/src/relay_chn_tilt.c index c1f4a2d..7eaf97c 100644 --- a/src/relay_chn_tilt.c +++ b/src/relay_chn_tilt.c @@ -39,8 +39,6 @@ static const char *TAG = "RELAY_CHN_TILT"; * 100 / (RELAY_CHN_TILT_RUN_MAX_MS - RELAY_CHN_TILT_RUN_MIN_MS) ) /**@}*/ -ESP_EVENT_DEFINE_BASE(RELAY_CHN_TILT_CMD_EVENT_BASE); - /// @brief Tilt steps. typedef enum { @@ -78,21 +76,6 @@ static relay_chn_tilt_ctl_t tilt_ctl; #endif -esp_err_t relay_chn_tilt_dispatch_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_tilt_cmd_t cmd) -{ - if (cmd == RELAY_CHN_TILT_CMD_NONE) return ESP_ERR_INVALID_ARG; - - // Since the event_loop library creates a deep copy of the event data, - // and we need to pass the pointer of the relevant tilt control, here we need - // to pass the pointer to the pointer of the tilt_control (&tilt_ctl) so that - // the pointer value is preserved in the event data. - return esp_event_post_to(relay_chn_event_loop, - RELAY_CHN_TILT_CMD_EVENT_BASE, - cmd, - &tilt_ctl, - sizeof(tilt_ctl), portMAX_DELAY); -} - // Returns the required timing before tilting depending on the last run. static uint32_t relay_chn_tilt_get_required_timing_before_tilting(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_tilt_cmd_t cmd) { @@ -561,13 +544,11 @@ static void relay_chn_tilt_execute_pause(relay_chn_tilt_ctl_t *tilt_ctl) tilt_ctl->step = RELAY_CHN_TILT_STEP_MOVE; } -static void relay_chn_tilt_event_handler(void *handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data) +esp_err_t relay_chn_tilt_dispatch_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_tilt_cmd_t cmd) { - relay_chn_tilt_ctl_t* tilt_ctl = *(relay_chn_tilt_ctl_t**) event_data; - ESP_RETURN_VOID_ON_FALSE(tilt_ctl != NULL, TAG, "event_data is NULL"); - ESP_LOGD(TAG, "relay_chn_event_handler: Command: %s", relay_chn_cmd_str(event_id)); + ESP_LOGD(TAG, "relay_chn_tilt_dispatch_cmd: Command: %d", cmd); - switch(event_id) { + switch(cmd) { case RELAY_CHN_TILT_CMD_STOP: relay_chn_tilt_execute_stop(tilt_ctl); break; @@ -582,8 +563,9 @@ static void relay_chn_tilt_event_handler(void *handler_arg, esp_event_base_t eve relay_chn_update_state(tilt_ctl->chn_ctl, RELAY_CHN_STATE_TILT_REVERSE); break; default: - ESP_LOGW(TAG, "Unexpected relay channel tilt command: %ld!", event_id); + ESP_LOGW(TAG, "Unexpected relay channel tilt command: %d!", cmd); } + return ESP_OK; } // Timer callback for the relay_chn_tilt_control_t::tilt_timer @@ -693,8 +675,10 @@ esp_err_t relay_chn_tilt_init(relay_chn_ctl_t *chn_ctls) sensitivity = RELAY_CHN_TILT_DEFAULT_SENSITIVITY; tilt_count = 0; #endif // RELAY_CHN_ENABLE_NVS == 1 - relay_chn_tilt_ctl_init(&tilt_ctls[i], &chn_ctls[i], tilt_count, sensitivity); + ret = relay_chn_tilt_ctl_init(&tilt_ctls[i], &chn_ctls[i], tilt_count, sensitivity); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to init tilt control for channel %d", i); } + return ESP_OK; #else sensitivity = RELAY_CHN_TILT_DEFAULT_SENSITIVITY; tilt_count = 0; @@ -704,13 +688,8 @@ esp_err_t relay_chn_tilt_init(relay_chn_ctl_t *chn_ctls) ret = relay_chn_tilt_load_tilt_count(0, &tilt_count); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load tilt count for channel %d", 0); #endif // RELAY_CHN_ENABLE_NVS == 1 - relay_chn_tilt_ctl_init(&tilt_ctl, chn_ctls, tilt_count, sensitivity); + return relay_chn_tilt_ctl_init(&tilt_ctl, chn_ctls, tilt_count, sensitivity); #endif // RELAY_CHN_COUNT > 1 - - return esp_event_handler_register_with(relay_chn_event_loop, - RELAY_CHN_TILT_CMD_EVENT_BASE, - ESP_EVENT_ANY_ID, - relay_chn_tilt_event_handler, NULL); } void relay_chn_tilt_ctl_deinit(relay_chn_tilt_ctl_t *tilt_ctl) @@ -736,8 +715,4 @@ void relay_chn_tilt_deinit() #else relay_chn_tilt_ctl_deinit(&tilt_ctl); #endif // RELAY_CHN_COUNT > 1 - esp_event_handler_unregister_with(relay_chn_event_loop, - RELAY_CHN_TILT_CMD_EVENT_BASE, - ESP_EVENT_ANY_ID, - relay_chn_tilt_event_handler); } \ No newline at end of file From cb38f71d8e39d55a7cf0f79e965a308b9c976987 Mon Sep 17 00:00:00 2001 From: ismail Date: Fri, 22 Aug 2025 17:42:24 +0300 Subject: [PATCH 17/69] Optimize a flip test case and execute tests upon removing esp_event --- test_apps/main/test_relay_chn_core_multi.c | 2 -- test_apps/sdkconfig | 11 ----------- 2 files changed, 13 deletions(-) diff --git a/test_apps/main/test_relay_chn_core_multi.c b/test_apps/main/test_relay_chn_core_multi.c index b7904dd..1abcd7b 100644 --- a/test_apps/main/test_relay_chn_core_multi.c +++ b/test_apps/main/test_relay_chn_core_multi.c @@ -322,12 +322,10 @@ TEST_CASE("Flipping a running channel stops it and flips direction", "[relay_chn // 1. Start channel running and verify state relay_chn_run_forward(ch); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch)); // 2. Flip the direction while running relay_chn_flip_direction(ch); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Give time for events to process // 3. The channel should stop as part of the flip process TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(ch)); diff --git a/test_apps/sdkconfig b/test_apps/sdkconfig index 7810c58..bc3790f 100644 --- a/test_apps/sdkconfig +++ b/test_apps/sdkconfig @@ -643,14 +643,6 @@ CONFIG_SPI_SLAVE_ISR_IN_IRAM=y # CONFIG_UART_ISR_IN_IRAM is not set # end of ESP-Driver:UART Configurations -# -# Event Loop Library -# -# CONFIG_ESP_EVENT_LOOP_PROFILING is not set -CONFIG_ESP_EVENT_POST_FROM_ISR=y -CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y -# end of Event Loop Library - # # Hardware Settings # @@ -1330,9 +1322,6 @@ CONFIG_STACK_CHECK_NONE=y # CONFIG_WARN_WRITE_STRINGS is not set CONFIG_ADC2_DISABLE_DAC=y # CONFIG_MCPWM_ISR_IN_IRAM is not set -# CONFIG_EVENT_LOOP_PROFILING is not set -CONFIG_POST_EVENTS_FROM_ISR=y -CONFIG_POST_EVENTS_FROM_IRAM_ISR=y # CONFIG_TWO_UNIVERSAL_MAC_ADDRESS is not set CONFIG_FOUR_UNIVERSAL_MAC_ADDRESS=y CONFIG_NUMBER_OF_UNIVERSAL_MAC_ADDRESS=4 From 9d3f8ddbfff86c6fecac400fe934e29582939e94 Mon Sep 17 00:00:00 2001 From: ismail Date: Mon, 25 Aug 2025 09:40:18 +0300 Subject: [PATCH 18/69] Fix implicit-fallthrough warning. Refs #1085. --- src/relay_chn_tilt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/relay_chn_tilt.c b/src/relay_chn_tilt.c index 7eaf97c..826c352 100644 --- a/src/relay_chn_tilt.c +++ b/src/relay_chn_tilt.c @@ -116,7 +116,7 @@ static void relay_chn_tilt_issue_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_t case RELAY_CHN_STATE_REVERSE_PENDING: // Issue a stop command first so that the timer and pending cmd get cleared relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP); - // break not put intentionally + // FALLTHRU case RELAY_CHN_STATE_STOPPED: { // Check if channel needs timing before tilting uint32_t req_timing_ms = relay_chn_tilt_get_required_timing_before_tilting(tilt_ctl, cmd); From 5afefc4dc040bde515328f4af60a576978fc3e7f Mon Sep 17 00:00:00 2001 From: ismail Date: Mon, 25 Aug 2025 10:24:13 +0300 Subject: [PATCH 19/69] Use original names for config parameters The config parameter names defined in the relay_chn_defs.h file have been changed back to their original names (with the CONFIG_ prefix), so that they are not confused with application-level defines. Refs #1085 --- include/relay_chn.h | 14 ++-- include/relay_chn_adapter.h | 14 ++-- include/relay_chn_defs.h | 21 ------ private_include/relay_chn_core.h | 8 +-- private_include/relay_chn_ctl.h | 4 +- private_include/relay_chn_nvs.h | 8 +-- private_include/relay_chn_output.h | 4 +- private_include/relay_chn_priv_types.h | 6 +- private_include/relay_chn_run_info.h | 4 +- src/relay_chn_core.c | 50 ++++++------- src/relay_chn_ctl_multi.c | 30 ++++---- src/relay_chn_ctl_single.c | 22 +++--- src/relay_chn_nvs.c | 30 ++++---- src/relay_chn_output.c | 40 +++++------ src/relay_chn_run_info.c | 12 ++-- src/relay_chn_tilt.c | 80 ++++++++++----------- test_apps/main/test_app_main.c | 14 ++-- test_apps/main/test_relay_chn_core_multi.c | 2 +- test_apps/main/test_relay_chn_core_single.c | 2 +- test_apps/main/test_relay_chn_nvs_multi.c | 20 +++--- test_apps/main/test_relay_chn_nvs_single.c | 8 +-- 21 files changed, 186 insertions(+), 207 deletions(-) diff --git a/include/relay_chn.h b/include/relay_chn.h index de3b94c..5f4d0be 100644 --- a/include/relay_chn.h +++ b/include/relay_chn.h @@ -64,7 +64,7 @@ esp_err_t relay_chn_register_listener(relay_chn_state_listener_t listener); */ void relay_chn_unregister_listener(relay_chn_state_listener_t listener); -#if RELAY_CHN_COUNT > 1 +#if CONFIG_RELAY_CHN_COUNT > 1 /** * @brief Get the state of the specified relay channel. * @@ -143,7 +143,7 @@ void relay_chn_flip_direction(uint8_t chn_id); */ relay_chn_direction_t relay_chn_get_direction(uint8_t chn_id); -#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 /** * @brief Get the run limit for the specified channel * @@ -168,7 +168,7 @@ uint16_t relay_chn_get_run_limit(uint8_t chn_id); * @param time_sec The run limit time in seconds. */ void relay_chn_set_run_limit(uint8_t chn_id, uint16_t time_sec); -#endif // RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 #if CONFIG_RELAY_CHN_ENABLE_TILTING == 1 @@ -239,7 +239,7 @@ esp_err_t relay_chn_tilt_get_sensitivity(uint8_t chn_id, uint8_t *sensitivity, s #endif // CONFIG_RELAY_CHN_ENABLE_TILTING -#else // RELAY_CHN_COUNT > 1 +#else // CONFIG_RELAY_CHN_COUNT > 1 /** * @brief Get the state of the relay channel. @@ -299,7 +299,7 @@ void relay_chn_flip_direction(void); */ relay_chn_direction_t relay_chn_get_direction(void); -#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 /** * @brief Get the run limit for the channel * @@ -320,7 +320,7 @@ uint16_t relay_chn_get_run_limit(void); * @param time_sec The run limit time in seconds. */ void relay_chn_set_run_limit(uint16_t time_sec); -#endif // RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 #if CONFIG_RELAY_CHN_ENABLE_TILTING == 1 @@ -377,7 +377,7 @@ uint8_t relay_chn_tilt_get_sensitivity(void); #endif // CONFIG_RELAY_CHN_ENABLE_TILTING -#endif // RELAY_CHN_COUNT > 1 +#endif // CONFIG_RELAY_CHN_COUNT > 1 #ifdef __cplusplus } diff --git a/include/relay_chn_adapter.h b/include/relay_chn_adapter.h index 9db6196..d14abc0 100644 --- a/include/relay_chn_adapter.h +++ b/include/relay_chn_adapter.h @@ -4,7 +4,7 @@ * SPDX-License-Identifier: MIT * * An adapter header to expose the appropriate API functions to the public API - * depending on the RELAY_CHN_COUNT value which determines single or multi mode. + * depending on the CONFIG_RELAY_CHN_COUNT value which determines single or multi mode. */ #pragma once @@ -13,7 +13,7 @@ extern "C" { #endif -#if RELAY_CHN_COUNT > 1 +#if CONFIG_RELAY_CHN_COUNT > 1 /** * @brief Get the current state of a relay channel. * @@ -101,7 +101,7 @@ static inline relay_chn_direction_t relay_chn_get_direction(uint8_t chn_id) return relay_chn_ctl_get_direction(chn_id); } -#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 /** * @brief Get the run limit for the specified channel * @@ -129,7 +129,7 @@ static inline void relay_chn_set_run_limit(uint8_t chn_id, uint16_t time_sec) { relay_chn_ctl_set_run_limit(chn_id, time_sec); } -#endif // RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 #else @@ -209,7 +209,7 @@ static inline relay_chn_direction_t relay_chn_get_direction(void) return relay_chn_ctl_get_direction(); } -#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 /** * @brief Get the run limit for the channel * @@ -233,9 +233,9 @@ static inline void relay_chn_set_run_limit(uint16_t time_sec) { relay_chn_ctl_set_run_limit(time_sec); } -#endif // RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 -#endif // RELAY_CHN_COUNT > 1 +#endif // CONFIG_RELAY_CHN_COUNT > 1 #ifdef __cplusplus } diff --git a/include/relay_chn_defs.h b/include/relay_chn_defs.h index 9c52236..b044f8f 100644 --- a/include/relay_chn_defs.h +++ b/include/relay_chn_defs.h @@ -10,31 +10,10 @@ extern "C" { #endif -/* Config defines for covenient writing */ -#define RELAY_CHN_OPPOSITE_INERTIA_MS CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS -#define RELAY_CHN_COUNT CONFIG_RELAY_CHN_COUNT -#define RELAY_CHN_ENABLE_TILTING CONFIG_RELAY_CHN_ENABLE_TILTING -#define RELAY_CHN_ENABLE_NVS CONFIG_RELAY_CHN_ENABLE_NVS -#define RELAY_CHN_ENABLE_RUN_LIMIT CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT - -#if RELAY_CHN_ENABLE_NVS == 1 -#define RELAY_CHN_NVS_NAMESPACE CONFIG_RELAY_CHN_NVS_NAMESPACE -#define RELAY_CHN_NVS_CUSTOM_PARTITION CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION -#if RELAY_CHN_NVS_CUSTOM_PARTITION == 1 -#define RELAY_CHN_NVS_CUSTOM_PARTITION_NAME CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME -#endif -#endif - #if RELAY_CHN_COUNT > 1 #define RELAY_CHN_ID_ALL RELAY_CHN_COUNT /*!< Special ID to address all channels */ #endif -#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 -#define RELAY_CHN_RUN_LIMIT_MIN_SEC CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC -#define RELAY_CHN_RUN_LIMIT_MAX_SEC CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC -#define RELAY_CHN_RUN_LIMIT_DEFAULT_SEC CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC -#endif - #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/private_include/relay_chn_core.h b/private_include/relay_chn_core.h index 3577b91..6a0a621 100644 --- a/private_include/relay_chn_core.h +++ b/private_include/relay_chn_core.h @@ -28,7 +28,7 @@ extern "C" { */ esp_err_t relay_chn_init_timer(relay_chn_ctl_t *chn_ctl); -#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 /** * @brief Initializes the relay channel run limit timer. * @@ -40,7 +40,7 @@ esp_err_t relay_chn_init_timer(relay_chn_ctl_t *chn_ctl); * @return esp_err_t ESP_OK on success, or an error code on failure. */ esp_err_t relay_chn_init_run_limit_timer(relay_chn_ctl_t *chn_ctl); -#endif // RELAY_CHN_ENABLE_RUN_LIMIT +#endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT /** * @brief Issues a command to the relay channel. @@ -102,7 +102,7 @@ void relay_chn_update_state(relay_chn_ctl_t *chn_ctl, relay_chn_state_t new_stat */ char *relay_chn_state_str(relay_chn_state_t state); -#if RELAY_CHN_COUNT > 1 +#if CONFIG_RELAY_CHN_COUNT > 1 /** * @brief Check if the provided channel ID is valid. * @@ -111,7 +111,7 @@ char *relay_chn_state_str(relay_chn_state_t state); * @return false Channel ID is invalid. */ bool relay_chn_is_channel_id_valid(uint8_t chn_id); -#endif // RELAY_CHN_COUNT > 1 +#endif // CONFIG_RELAY_CHN_COUNT > 1 #ifdef __cplusplus } diff --git a/private_include/relay_chn_ctl.h b/private_include/relay_chn_ctl.h index 8f89a11..ba9b98c 100644 --- a/private_include/relay_chn_ctl.h +++ b/private_include/relay_chn_ctl.h @@ -31,7 +31,7 @@ esp_err_t relay_chn_ctl_init(relay_chn_output_t *output, relay_chn_run_info_t *r */ void relay_chn_ctl_deinit(void); -#if RELAY_CHN_COUNT > 1 +#if CONFIG_RELAY_CHN_COUNT > 1 /** * @brief Get the control structure for a specific relay channel. * @@ -49,7 +49,7 @@ relay_chn_ctl_t *relay_chn_ctl_get(uint8_t chn_id); relay_chn_ctl_t *relay_chn_ctl_get_all(void); #else relay_chn_ctl_t *relay_chn_ctl_get(void); -#endif // RELAY_CHN_COUNT > 1 +#endif // CONFIG_RELAY_CHN_COUNT > 1 #ifdef __cplusplus } diff --git a/private_include/relay_chn_nvs.h b/private_include/relay_chn_nvs.h index 09f84f2..043a4b2 100644 --- a/private_include/relay_chn_nvs.h +++ b/private_include/relay_chn_nvs.h @@ -45,7 +45,7 @@ esp_err_t relay_chn_nvs_set_direction(uint8_t ch, relay_chn_direction_t directio */ esp_err_t relay_chn_nvs_get_direction(uint8_t ch, relay_chn_direction_t *direction); -#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 /** * @brief Store relay channel run limit in NVS. * @@ -63,9 +63,9 @@ esp_err_t relay_chn_nvs_set_run_limit(uint8_t ch, uint16_t time_sec); * @return ESP_OK on success, error code otherwise. */ esp_err_t relay_chn_nvs_get_run_limit(uint8_t ch, uint16_t *time_sec); -#endif // RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 -#ifdef RELAY_CHN_ENABLE_TILTING +#ifdef CONFIG_RELAY_CHN_ENABLE_TILTING /** * @brief Store tilt sensitivity in NVS. * @@ -101,7 +101,7 @@ esp_err_t relay_chn_nvs_set_tilt_count(uint8_t ch, uint16_t tilt_count); * @return ESP_OK on success, error code otherwise. */ esp_err_t relay_chn_nvs_get_tilt_count(uint8_t ch, uint16_t *tilt_count); -#endif // RELAY_CHN_ENABLE_TILTING +#endif // CONFIG_RELAY_CHN_ENABLE_TILTING /** * @brief Erase all keys in the NVS namespace. diff --git a/private_include/relay_chn_output.h b/private_include/relay_chn_output.h index 27a8de9..d504fbf 100644 --- a/private_include/relay_chn_output.h +++ b/private_include/relay_chn_output.h @@ -36,7 +36,7 @@ esp_err_t relay_chn_output_init(const uint8_t* gpio_map, uint8_t gpio_count); */ void relay_chn_output_deinit(void); -#if RELAY_CHN_COUNT > 1 +#if CONFIG_RELAY_CHN_COUNT > 1 /** * @brief Get the relay channel output object for a specific channel. * @@ -59,7 +59,7 @@ relay_chn_output_t *relay_chn_output_get_all(void); * @return Pointer to relay channel output object. */ relay_chn_output_t *relay_chn_output_get(void); -#endif // RELAY_CHN_COUNT > 1 +#endif // CONFIG_RELAY_CHN_COUNT > 1 /** * @brief Stop the relay channel output. diff --git a/private_include/relay_chn_priv_types.h b/private_include/relay_chn_priv_types.h index c62a6f0..1b7b5de 100644 --- a/private_include/relay_chn_priv_types.h +++ b/private_include/relay_chn_priv_types.h @@ -45,7 +45,7 @@ typedef struct { uint32_t last_run_cmd_time_ms; /*!< The time in milliseconds when the last run command was issued */ } relay_chn_run_info_t; -#if RELAY_CHN_ENABLE_TILTING == 1 +#if CONFIG_RELAY_CHN_ENABLE_TILTING == 1 /// @brief Tilt commands. typedef enum { RELAY_CHN_TILT_CMD_NONE, /*!< No command */ @@ -68,11 +68,11 @@ typedef struct { relay_chn_output_t *output; /*!< Output configuration of the relay channel */ relay_chn_cmd_t pending_cmd; /*!< The command that is pending to be issued */ esp_timer_handle_t inertia_timer; /*!< Timer to handle the opposite direction inertia time */ -#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 esp_timer_handle_t run_limit_timer; /*!< Timer to handle the run limit */ uint16_t run_limit_sec; /*!< Run limit in seconds */ #endif -#if RELAY_CHN_ENABLE_TILTING == 1 +#if CONFIG_RELAY_CHN_ENABLE_TILTING == 1 relay_chn_tilt_ctl_t *tilt_ctl; /*!< Pointer to the tilt control structure if tilting is enabled */ #endif } relay_chn_ctl_t; diff --git a/private_include/relay_chn_run_info.h b/private_include/relay_chn_run_info.h index 0b14296..b3f9188 100644 --- a/private_include/relay_chn_run_info.h +++ b/private_include/relay_chn_run_info.h @@ -21,7 +21,7 @@ extern "C" { */ void relay_chn_run_info_init(void); -#if RELAY_CHN_COUNT > 1 +#if CONFIG_RELAY_CHN_COUNT > 1 /** * @brief Get run information object for a specific relay channel. * @@ -43,7 +43,7 @@ relay_chn_run_info_t *relay_chn_run_info_get_all(void); * @return Pointer to run information structure. */ relay_chn_run_info_t *relay_chn_run_info_get(void); -#endif // RELAY_CHN_COUNT > 1 +#endif // CONFIG_RELAY_CHN_COUNT > 1 /** * @brief Get the last run command for a relay channel. diff --git a/src/relay_chn_core.c b/src/relay_chn_core.c index 48a6321..66c0f34 100644 --- a/src/relay_chn_core.c +++ b/src/relay_chn_core.c @@ -12,11 +12,11 @@ #include "relay_chn_run_info.h" #include "relay_chn_ctl.h" -#if RELAY_CHN_ENABLE_TILTING == 1 +#if CONFIG_RELAY_CHN_ENABLE_TILTING == 1 #include "relay_chn_tilt.h" #endif -#if RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 #include "relay_chn_nvs.h" #endif @@ -35,7 +35,7 @@ typedef struct relay_chn_listener_entry_type { // The list that holds references to the registered listeners. static List_t relay_chn_listener_list; -#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 /* * Run limit timer callback immediately dispatches a STOP command for the * relevant channel as soon as the run limit time times out @@ -90,7 +90,7 @@ esp_err_t relay_chn_create(const uint8_t* gpio_map, uint8_t gpio_count) ESP_RETURN_ON_FALSE(gpio_map != NULL, ESP_ERR_INVALID_ARG, TAG, "gpio_map cannot be NULL"); esp_err_t ret; -#if RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 ret = relay_chn_nvs_init(); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize NVS for relay channel"); #endif @@ -102,7 +102,7 @@ esp_err_t relay_chn_create(const uint8_t* gpio_map, uint8_t gpio_count) // Initialize the run info relay_chn_run_info_init(); -#if RELAY_CHN_COUNT > 1 +#if CONFIG_RELAY_CHN_COUNT > 1 relay_chn_output_t *outputs = relay_chn_output_get_all(); relay_chn_run_info_t *run_infos = relay_chn_run_info_get_all(); #else @@ -114,13 +114,13 @@ esp_err_t relay_chn_create(const uint8_t* gpio_map, uint8_t gpio_count) ret = relay_chn_ctl_init(outputs, run_infos); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize relay channel control"); -#if RELAY_CHN_ENABLE_TILTING == 1 +#if CONFIG_RELAY_CHN_ENABLE_TILTING == 1 // Initialize the tilt feature -#if RELAY_CHN_COUNT > 1 +#if CONFIG_RELAY_CHN_COUNT > 1 relay_chn_ctl_t *chn_ctls = relay_chn_ctl_get_all(); #else relay_chn_ctl_t *chn_ctls = relay_chn_ctl_get(); -#endif // RELAY_CHN_COUNT > 1 +#endif // CONFIG_RELAY_CHN_COUNT > 1 ret = relay_chn_tilt_init(chn_ctls); // Initialize tilt feature ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize tilt feature"); #endif @@ -133,13 +133,13 @@ esp_err_t relay_chn_create(const uint8_t* gpio_map, uint8_t gpio_count) void relay_chn_destroy(void) { -#if RELAY_CHN_ENABLE_TILTING == 1 +#if CONFIG_RELAY_CHN_ENABLE_TILTING == 1 relay_chn_tilt_deinit(); #endif relay_chn_ctl_deinit(); relay_chn_output_deinit(); -#if RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 relay_chn_nvs_deinit(); #endif @@ -328,7 +328,7 @@ void relay_chn_issue_cmd(relay_chn_ctl_t* chn_ctl, relay_chn_cmd_t cmd) // since the last run command stopped and decide whether to run the command immediately or wait uint32_t last_run_cmd_time_ms = relay_chn_run_info_get_last_run_cmd_time_ms(chn_ctl->run_info); uint32_t inertia_time_passed_ms = (uint32_t) (esp_timer_get_time() / 1000) - last_run_cmd_time_ms; - uint32_t inertia_time_ms = RELAY_CHN_OPPOSITE_INERTIA_MS - inertia_time_passed_ms; + uint32_t inertia_time_ms = CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS - inertia_time_passed_ms; if (inertia_time_ms > 0) { chn_ctl->pending_cmd = cmd; relay_chn_state_t new_state = cmd == RELAY_CHN_CMD_FORWARD @@ -366,10 +366,10 @@ void relay_chn_issue_cmd(relay_chn_ctl_t* chn_ctl, relay_chn_cmd_t cmd) relay_chn_state_t new_state = cmd == RELAY_CHN_CMD_FORWARD ? RELAY_CHN_STATE_FORWARD_PENDING : RELAY_CHN_STATE_REVERSE_PENDING; relay_chn_update_state(chn_ctl, new_state); - relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, RELAY_CHN_OPPOSITE_INERTIA_MS); + relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS); break; -#if RELAY_CHN_ENABLE_TILTING == 1 +#if CONFIG_RELAY_CHN_ENABLE_TILTING == 1 case RELAY_CHN_STATE_TILT_FORWARD: // Terminate tilting first relay_chn_tilt_dispatch_cmd(chn_ctl->tilt_ctl, RELAY_CHN_TILT_CMD_STOP); @@ -377,7 +377,7 @@ void relay_chn_issue_cmd(relay_chn_ctl_t* chn_ctl, relay_chn_cmd_t cmd) // Schedule for running forward chn_ctl->pending_cmd = cmd; relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_FORWARD_PENDING); - relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, RELAY_CHN_OPPOSITE_INERTIA_MS); + relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS); } else if (cmd == RELAY_CHN_CMD_REVERSE) { // Run directly since it is the same direction relay_chn_dispatch_cmd(chn_ctl, cmd); @@ -395,7 +395,7 @@ void relay_chn_issue_cmd(relay_chn_ctl_t* chn_ctl, relay_chn_cmd_t cmd) // Schedule for running reverse chn_ctl->pending_cmd = cmd; relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_REVERSE_PENDING); - relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, RELAY_CHN_OPPOSITE_INERTIA_MS); + relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS); } break; #endif @@ -404,16 +404,16 @@ void relay_chn_issue_cmd(relay_chn_ctl_t* chn_ctl, relay_chn_cmd_t cmd) } } -#if RELAY_CHN_COUNT > 1 +#if CONFIG_RELAY_CHN_COUNT > 1 bool relay_chn_is_channel_id_valid(uint8_t chn_id) { - bool valid = (chn_id < RELAY_CHN_COUNT) || chn_id == RELAY_CHN_ID_ALL; + bool valid = (chn_id < CONFIG_RELAY_CHN_COUNT) || chn_id == RELAY_CHN_ID_ALL; if (!valid) { ESP_LOGE(TAG, "Invalid channel ID: %d", chn_id); } return valid; } -#endif // RELAY_CHN_COUNT > 1 +#endif // CONFIG_RELAY_CHN_COUNT > 1 static void relay_chn_execute_idle(relay_chn_ctl_t *chn_ctl) @@ -437,7 +437,7 @@ static void relay_chn_execute_stop(relay_chn_ctl_t *chn_ctl) // Invalidate the channel's timer if it is active esp_timer_stop(chn_ctl->inertia_timer); -#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 esp_timer_stop(chn_ctl->run_limit_timer); #endif @@ -448,7 +448,7 @@ static void relay_chn_execute_stop(relay_chn_ctl_t *chn_ctl) relay_chn_run_info_set_last_run_cmd_time_ms(chn_ctl->run_info, (uint32_t)(esp_timer_get_time() / 1000)); // Schedule a free command for the channel chn_ctl->pending_cmd = RELAY_CHN_CMD_IDLE; - relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, RELAY_CHN_OPPOSITE_INERTIA_MS); + relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS); } else { // If the channel was not running one of the run or fwd, issue a free command immediately // relay_chn_dispatch_cmd(chn_ctl, RELAY_CHN_CMD_IDLE); @@ -465,7 +465,7 @@ static void relay_chn_execute_forward(relay_chn_ctl_t *chn_ctl) relay_chn_run_info_set_last_run_cmd(chn_ctl->run_info, RELAY_CHN_CMD_FORWARD); relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_FORWARD); -#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 relay_chn_start_esp_timer_once(chn_ctl->run_limit_timer, chn_ctl->run_limit_sec * 1000); #endif } @@ -479,7 +479,7 @@ static void relay_chn_execute_reverse(relay_chn_ctl_t *chn_ctl) relay_chn_run_info_set_last_run_cmd(chn_ctl->run_info, RELAY_CHN_CMD_REVERSE); relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_REVERSE); -#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 relay_chn_start_esp_timer_once(chn_ctl->run_limit_timer, chn_ctl->run_limit_sec * 1000); #endif } @@ -489,7 +489,7 @@ static void relay_chn_execute_flip(relay_chn_ctl_t *chn_ctl) relay_chn_output_flip(chn_ctl->output); // Set an inertia on the channel to prevent any immediate movement chn_ctl->pending_cmd = RELAY_CHN_CMD_IDLE; - relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, RELAY_CHN_OPPOSITE_INERTIA_MS); + relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS); } // Dispatch relay channel command @@ -516,7 +516,7 @@ void relay_chn_dispatch_cmd(relay_chn_ctl_t *chn_ctl, relay_chn_cmd_t cmd) { ESP_LOGD(TAG, "Unknown relay channel command!"); } -#if RELAY_CHN_ENABLE_TILTING == 1 +#if CONFIG_RELAY_CHN_ENABLE_TILTING == 1 // Reset the tilt counter when the command is either FORWARD or REVERSE if (cmd == RELAY_CHN_CMD_FORWARD || cmd == RELAY_CHN_CMD_REVERSE) { relay_chn_tilt_reset_count(chn_ctl->tilt_ctl); @@ -557,7 +557,7 @@ char *relay_chn_state_str(relay_chn_state_t state) return "FORWARD_PENDING"; case RELAY_CHN_STATE_REVERSE_PENDING: return "REVERSE_PENDING"; -#if RELAY_CHN_ENABLE_TILTING == 1 +#if CONFIG_RELAY_CHN_ENABLE_TILTING == 1 case RELAY_CHN_STATE_TILT_FORWARD: return "TILT_FORWARD"; case RELAY_CHN_STATE_TILT_REVERSE: diff --git a/src/relay_chn_ctl_multi.c b/src/relay_chn_ctl_multi.c index 336c5f9..b18e58a 100644 --- a/src/relay_chn_ctl_multi.c +++ b/src/relay_chn_ctl_multi.c @@ -10,20 +10,20 @@ #include "relay_chn_ctl.h" #include "relay_chn_output.h" -#if RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 #include "relay_chn_nvs.h" #endif static const char *TAG = "RELAY_CHN_CTL"; -static relay_chn_ctl_t chn_ctls[RELAY_CHN_COUNT]; +static relay_chn_ctl_t chn_ctls[CONFIG_RELAY_CHN_COUNT]; esp_err_t relay_chn_ctl_init(relay_chn_output_t *outputs, relay_chn_run_info_t *run_infos) { // Initialize all relay channels esp_err_t ret; - for (int i = 0; i < RELAY_CHN_COUNT; i++) { + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { relay_chn_ctl_t* chn_ctl = &chn_ctls[i]; relay_chn_output_t* output = &outputs[i]; relay_chn_run_info_t* run_info = &run_infos[i]; @@ -34,9 +34,9 @@ esp_err_t relay_chn_ctl_init(relay_chn_output_t *outputs, relay_chn_run_info_t * chn_ctl->output = output; chn_ctl->run_info = run_info; -#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 - uint16_t run_limit_sec = RELAY_CHN_RUN_LIMIT_DEFAULT_SEC; -#if RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 + uint16_t run_limit_sec = CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC; +#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 // Load run limit value from NVS ret = relay_chn_nvs_get_run_limit(chn_ctl->id, &run_limit_sec); if (ret != ESP_OK && ret != ESP_ERR_NVS_NOT_FOUND) { @@ -55,13 +55,13 @@ esp_err_t relay_chn_ctl_init(relay_chn_output_t *outputs, relay_chn_run_info_t * void relay_chn_ctl_deinit() { - for (int i = 0; i < RELAY_CHN_COUNT; i++) { + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { relay_chn_ctl_t* chn_ctl = &chn_ctls[i]; if (chn_ctl->inertia_timer != NULL) { esp_timer_delete(chn_ctl->inertia_timer); chn_ctl->inertia_timer = NULL; } -#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 if (chn_ctl->run_limit_timer != NULL) { esp_timer_delete(chn_ctl->run_limit_timer); chn_ctl->run_limit_timer = NULL; @@ -88,7 +88,7 @@ char *relay_chn_ctl_get_state_str(uint8_t chn_id) static void relay_chn_ctl_issue_cmd_on_all_channels(relay_chn_cmd_t cmd) { - for (int i = 0; i < RELAY_CHN_COUNT; i++) { + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { relay_chn_issue_cmd(&chn_ctls[i], cmd); } } @@ -146,7 +146,7 @@ relay_chn_direction_t relay_chn_ctl_get_direction(uint8_t chn_id) return relay_chn_output_get_direction(chn_ctl->output); } -#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 uint16_t relay_chn_ctl_get_run_limit(uint8_t chn_id) { if (!relay_chn_is_channel_id_valid(chn_id) || chn_id == RELAY_CHN_ID_ALL) { @@ -164,14 +164,14 @@ void relay_chn_ctl_set_run_limit(uint8_t chn_id, uint16_t time_sec) } // Check for boundaries - if (time_sec > RELAY_CHN_RUN_LIMIT_MAX_SEC) - time_sec = RELAY_CHN_RUN_LIMIT_MAX_SEC; - else if (time_sec < RELAY_CHN_RUN_LIMIT_MIN_SEC) - time_sec = RELAY_CHN_RUN_LIMIT_MIN_SEC; + if (time_sec > CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC) + time_sec = CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC; + else if (time_sec < CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC) + time_sec = CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC; chn_ctls[chn_id].run_limit_sec = time_sec; -#if RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 relay_chn_nvs_set_run_limit(chn_id, time_sec); #endif } diff --git a/src/relay_chn_ctl_single.c b/src/relay_chn_ctl_single.c index a56f226..94ca732 100644 --- a/src/relay_chn_ctl_single.c +++ b/src/relay_chn_ctl_single.c @@ -10,7 +10,7 @@ #include "relay_chn_ctl.h" #include "relay_chn_output.h" -#if RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 #include "relay_chn_nvs.h" #endif @@ -27,10 +27,10 @@ esp_err_t relay_chn_ctl_init(relay_chn_output_t *output, relay_chn_run_info_t *r chn_ctl.pending_cmd = RELAY_CHN_CMD_NONE; chn_ctl.output = output; chn_ctl.run_info = run_info; -#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 - uint16_t run_limit_sec = RELAY_CHN_RUN_LIMIT_DEFAULT_SEC; +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 + uint16_t run_limit_sec = CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC; esp_err_t ret; -#if RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 // Load run limit value from NVS ret = relay_chn_nvs_get_run_limit(chn_ctl.id, &run_limit_sec); if (ret != ESP_OK && ret != ESP_ERR_NVS_NOT_FOUND) { @@ -51,7 +51,7 @@ void relay_chn_ctl_deinit() esp_timer_delete(chn_ctl.inertia_timer); chn_ctl.inertia_timer = NULL; } -#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 if (chn_ctl.run_limit_timer != NULL) { esp_timer_delete(chn_ctl.run_limit_timer); chn_ctl.run_limit_timer = NULL; @@ -95,7 +95,7 @@ relay_chn_direction_t relay_chn_ctl_get_direction() return relay_chn_output_get_direction(chn_ctl.output); } -#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 uint16_t relay_chn_ctl_get_run_limit() { return chn_ctl.run_limit_sec; @@ -104,14 +104,14 @@ uint16_t relay_chn_ctl_get_run_limit() void relay_chn_ctl_set_run_limit(uint16_t time_sec) { // Check for boundaries - if (time_sec > RELAY_CHN_RUN_LIMIT_MAX_SEC) - time_sec = RELAY_CHN_RUN_LIMIT_MAX_SEC; - else if (time_sec < RELAY_CHN_RUN_LIMIT_MIN_SEC) - time_sec = RELAY_CHN_RUN_LIMIT_MIN_SEC; + if (time_sec > CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC) + time_sec = CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC; + else if (time_sec < CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC) + time_sec = CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC; chn_ctl.run_limit_sec = time_sec; -#if RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 relay_chn_nvs_set_run_limit(chn_ctl.id, time_sec); #endif } diff --git a/src/relay_chn_nvs.c b/src/relay_chn_nvs.c index 04ec8df..eff7332 100644 --- a/src/relay_chn_nvs.c +++ b/src/relay_chn_nvs.c @@ -8,10 +8,10 @@ #include "relay_chn_nvs.h" #define RELAY_CHN_KEY_DIR "dir" /*!< Direction key */ -#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 #define RELAY_CHN_KEY_RLIM(ch) "rlim_%d" /*!< Run limit key */ #endif -#ifdef RELAY_CHN_ENABLE_TILTING +#ifdef CONFIG_RELAY_CHN_ENABLE_TILTING #define RELAY_CHN_KEY_TSENS(ch) "tsens_%d" /*!< Tilt sensitivity key */ #define RELAY_CHN_KEY_TCNT(ch) "tcnt_%d" /*!< Tilt count key */ #endif @@ -23,22 +23,22 @@ static nvs_handle_t relay_chn_nvs; esp_err_t relay_chn_nvs_init() { esp_err_t ret; -#if RELAY_CHN_NVS_CUSTOM_PARTITION == 1 - ret = nvs_open_from_partition(RELAY_CHN_NVS_CUSTOM_PARTITION_NAME, - RELAY_CHN_NVS_NAMESPACE, +#if CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION == 1 + ret = nvs_open_from_partition(CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME, + CONFIG_RELAY_CHN_NVS_NAMESPACE, NVS_READWRITE, &relay_chn_nvs); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to open NVS namespace '%s' from partition '%s' with error %s", - RELAY_CHN_NVS_NAMESPACE, - RELAY_CHN_NVS_CUSTOM_PARTITION_NAME, + CONFIG_RELAY_CHN_NVS_NAMESPACE, + CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME, esp_err_to_name(ret)); #else - ret = nvs_open(RELAY_CHN_NVS_NAMESPACE, NVS_READWRITE, &relay_chn_nvs); - ESP_RETURN_ON_ERROR(ret, TAG, "Failed to open NVS namespace '%s'", RELAY_CHN_NVS_NAMESPACE); -#endif // RELAY_CHN_NVS_CUSTOM_PARTITION + ret = nvs_open(CONFIG_RELAY_CHN_NVS_NAMESPACE, NVS_READWRITE, &relay_chn_nvs); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to open NVS namespace '%s'", CONFIG_RELAY_CHN_NVS_NAMESPACE); +#endif // CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION return ESP_OK; } @@ -72,7 +72,7 @@ esp_err_t relay_chn_nvs_get_direction(uint8_t ch, relay_chn_direction_t *directi return ESP_OK; } -#if RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 esp_err_t relay_chn_nvs_set_run_limit(uint8_t ch, uint16_t time_sec) { esp_err_t ret = nvs_set_u16(relay_chn_nvs, RELAY_CHN_KEY_RLIM(ch), time_sec); @@ -86,9 +86,9 @@ esp_err_t relay_chn_nvs_get_run_limit(uint8_t ch, uint16_t *time_sec) return nvs_get_u16(relay_chn_nvs, RELAY_CHN_KEY_RLIM(ch), time_sec); } -#endif // RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 -#ifdef RELAY_CHN_ENABLE_TILTING +#ifdef CONFIG_RELAY_CHN_ENABLE_TILTING esp_err_t relay_chn_nvs_set_tilt_sensitivity(uint8_t ch, uint8_t sensitivity) { esp_err_t ret = nvs_set_u8(relay_chn_nvs, RELAY_CHN_KEY_TSENS(ch), sensitivity); @@ -117,13 +117,13 @@ esp_err_t relay_chn_nvs_get_tilt_count(uint8_t ch, uint16_t *tilt_count) ESP_ERR_INVALID_ARG, TAG, "Counter pointers are NULL"); return nvs_get_u16(relay_chn_nvs, RELAY_CHN_KEY_TCNT(ch), tilt_count); } -#endif // RELAY_CHN_ENABLE_TILTING +#endif // CONFIG_RELAY_CHN_ENABLE_TILTING esp_err_t relay_chn_nvs_erase_all() { // Erase all key-value pairs in the relay_chn NVS namespace esp_err_t ret = nvs_erase_all(relay_chn_nvs); - ESP_RETURN_ON_ERROR(ret, TAG, "Failed to erase all keys in NVS namespace '%s'", RELAY_CHN_NVS_NAMESPACE); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to erase all keys in NVS namespace '%s'", CONFIG_RELAY_CHN_NVS_NAMESPACE); // Commit the changes return nvs_commit(relay_chn_nvs); diff --git a/src/relay_chn_output.c b/src/relay_chn_output.c index 44c1465..828ad0a 100644 --- a/src/relay_chn_output.c +++ b/src/relay_chn_output.c @@ -10,15 +10,15 @@ #include "relay_chn_output.h" #include "relay_chn_core.h" -#if RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 #include "relay_chn_nvs.h" #endif static const char *TAG = "RELAY_CHN_OUTPUT"; -#if RELAY_CHN_COUNT > 1 -static relay_chn_output_t outputs[RELAY_CHN_COUNT]; +#if CONFIG_RELAY_CHN_COUNT > 1 +static relay_chn_output_t outputs[CONFIG_RELAY_CHN_COUNT]; #else static relay_chn_output_t output; #endif @@ -27,16 +27,16 @@ static relay_chn_output_t output; static esp_err_t relay_chn_output_check_gpio_capabilities(uint8_t gpio_count) { // Check if the device's GPIOs are enough for the number of channels - if (RELAY_CHN_COUNT > (GPIO_PIN_COUNT / 2)) { + if (CONFIG_RELAY_CHN_COUNT > (GPIO_PIN_COUNT / 2)) { ESP_LOGE(TAG, "Not enough GPIOs for the number of channels!"); - ESP_LOGE(TAG, "Max available num of channels: %d, requested channels: %d", GPIO_PIN_COUNT / 2, RELAY_CHN_COUNT); + ESP_LOGE(TAG, "Max available num of channels: %d, requested channels: %d", GPIO_PIN_COUNT / 2, CONFIG_RELAY_CHN_COUNT); return ESP_ERR_INVALID_ARG; } // Check if the provided GPIOs correspond to the number of channels - if (gpio_count != RELAY_CHN_COUNT * 2) { + if (gpio_count != CONFIG_RELAY_CHN_COUNT * 2) { ESP_LOGE(TAG, "Invalid number of GPIOs provided: %d", gpio_count); - ESP_LOGE(TAG, "Expected number of GPIOs: %d", RELAY_CHN_COUNT * 2); + ESP_LOGE(TAG, "Expected number of GPIOs: %d", CONFIG_RELAY_CHN_COUNT * 2); return ESP_ERR_INVALID_ARG; } return ESP_OK; @@ -73,7 +73,7 @@ static esp_err_t relay_chn_output_ctl_init(relay_chn_output_t *output, return ESP_OK; } -#if RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 static esp_err_t relay_chn_output_load_direction(uint8_t ch, relay_chn_direction_t *direction) { esp_err_t ret = relay_chn_nvs_get_direction(ch, direction); @@ -93,15 +93,15 @@ esp_err_t relay_chn_output_init(const uint8_t* gpio_map, uint8_t gpio_count) ret = relay_chn_output_check_gpio_capabilities(gpio_count); ESP_RETURN_ON_ERROR(ret, TAG, "Device does not support the provided GPIOs"); -#if RELAY_CHN_COUNT > 1 - for (int i = 0; i < RELAY_CHN_COUNT; i++) { +#if CONFIG_RELAY_CHN_COUNT > 1 + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { relay_chn_output_t* output = &outputs[i]; int gpio_index = i << 1; // gpio_index = i * 2 gpio_num_t forward_pin = (gpio_num_t) gpio_map[gpio_index]; gpio_num_t reverse_pin = (gpio_num_t) gpio_map[gpio_index + 1]; relay_chn_direction_t direction = RELAY_CHN_DIRECTION_DEFAULT; -#if RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 // If NVS storage is enabled, retrieve the direction from storage ret = relay_chn_output_load_direction(i, &direction); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load direction from storage for channel %d", i); @@ -111,7 +111,7 @@ esp_err_t relay_chn_output_init(const uint8_t* gpio_map, uint8_t gpio_count) } #else relay_chn_direction_t direction = RELAY_CHN_DIRECTION_DEFAULT; -#if RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 // If NVS storage is enabled, retrieve the direction from storage ret = relay_chn_output_load_direction(0, &direction); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load direction from storage for channel %d", 0); @@ -130,16 +130,16 @@ static void relay_chn_output_ctl_deinit(relay_chn_output_t *output) void relay_chn_output_deinit() { -#if RELAY_CHN_COUNT > 1 - for (int i = 0; i < RELAY_CHN_COUNT; i++) { +#if CONFIG_RELAY_CHN_COUNT > 1 + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { relay_chn_output_ctl_deinit(&outputs[i]); } #else relay_chn_output_ctl_deinit(&output); -#endif // RELAY_CHN_COUNT > 1 +#endif // CONFIG_RELAY_CHN_COUNT > 1 } -#if RELAY_CHN_COUNT > 1 +#if CONFIG_RELAY_CHN_COUNT > 1 relay_chn_output_t *relay_chn_output_get(uint8_t chn_id) { if (!relay_chn_is_channel_id_valid(chn_id)) { @@ -157,7 +157,7 @@ relay_chn_output_t *relay_chn_output_get(void) { return &output; } -#endif // RELAY_CHN_COUNT > 1 +#endif // CONFIG_RELAY_CHN_COUNT > 1 esp_err_t relay_chn_output_stop(relay_chn_output_t *output) { @@ -194,10 +194,10 @@ void relay_chn_output_flip(relay_chn_output_t *output) ? RELAY_CHN_DIRECTION_FLIPPED : RELAY_CHN_DIRECTION_DEFAULT; -#if RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 uint8_t ch = 0; -#if RELAY_CHN_COUNT > 1 - for (uint8_t i = 0; i < RELAY_CHN_COUNT; i++) { +#if CONFIG_RELAY_CHN_COUNT > 1 + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { if (output == &outputs[i]) { ch = i; break; diff --git a/src/relay_chn_run_info.c b/src/relay_chn_run_info.c index e021023..9a21f47 100644 --- a/src/relay_chn_run_info.c +++ b/src/relay_chn_run_info.c @@ -8,16 +8,16 @@ #include "relay_chn_run_info.h" -#if RELAY_CHN_COUNT > 1 -static relay_chn_run_info_t run_infos[RELAY_CHN_COUNT]; +#if CONFIG_RELAY_CHN_COUNT > 1 +static relay_chn_run_info_t run_infos[CONFIG_RELAY_CHN_COUNT]; #else static relay_chn_run_info_t run_info; #endif void relay_chn_run_info_init() { -#if RELAY_CHN_COUNT > 1 - for (int i = 0; i < RELAY_CHN_COUNT; i++) { +#if CONFIG_RELAY_CHN_COUNT > 1 + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { run_infos[i].last_run_cmd = RELAY_CHN_CMD_NONE; run_infos[i].last_run_cmd_time_ms = 0; } @@ -27,7 +27,7 @@ void relay_chn_run_info_init() #endif } -#if RELAY_CHN_COUNT > 1 +#if CONFIG_RELAY_CHN_COUNT > 1 relay_chn_run_info_t *relay_chn_run_info_get(uint8_t chn_id) { if (!relay_chn_is_channel_id_valid(chn_id)) { @@ -45,7 +45,7 @@ relay_chn_run_info_t *relay_chn_run_info_get() { return &run_info; } -#endif // RELAY_CHN_COUNT > 1 +#endif // CONFIG_RELAY_CHN_COUNT > 1 relay_chn_cmd_t relay_chn_run_info_get_last_run_cmd(relay_chn_run_info_t *run_info) { diff --git a/src/relay_chn_tilt.c b/src/relay_chn_tilt.c index 826c352..e99dea0 100644 --- a/src/relay_chn_tilt.c +++ b/src/relay_chn_tilt.c @@ -10,7 +10,7 @@ #include "relay_chn_run_info.h" #include "relay_chn_tilt.h" -#if RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 #include "relay_chn_nvs.h" #define RELAY_CHN_TILT_FLUSH_DEBOUNCE_MS 3000 @@ -63,14 +63,14 @@ typedef struct relay_chn_tilt_ctl { relay_chn_tilt_timing_t tilt_timing; /*!< Tilt timing structure */ uint16_t tilt_count; /*!< Tilt count to manage forward and reverse tilts */ esp_timer_handle_t tilt_timer; /*!< Tilt timer handle */ -#if RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 esp_timer_handle_t flush_timer; /*!< Flush timer to avoid frequent write of tilt counters */ #endif } relay_chn_tilt_ctl_t; -#if RELAY_CHN_COUNT > 1 -static relay_chn_tilt_ctl_t tilt_ctls[RELAY_CHN_COUNT]; +#if CONFIG_RELAY_CHN_COUNT > 1 +static relay_chn_tilt_ctl_t tilt_ctls[CONFIG_RELAY_CHN_COUNT]; #else static relay_chn_tilt_ctl_t tilt_ctl; #endif @@ -87,7 +87,7 @@ static uint32_t relay_chn_tilt_get_required_timing_before_tilting(relay_chn_tilt uint32_t last_run_cmd_time_ms = relay_chn_run_info_get_last_run_cmd_time_ms(tilt_ctl->chn_ctl->run_info); uint32_t inertia_time_passed_ms = (uint32_t) (esp_timer_get_time() / 1000) - last_run_cmd_time_ms; - return RELAY_CHN_OPPOSITE_INERTIA_MS - inertia_time_passed_ms; + return CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS - inertia_time_passed_ms; } // Issue a tilt command to a specific relay channel. @@ -136,7 +136,7 @@ static void relay_chn_tilt_issue_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_t relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP); // Schedule for tilting tilt_ctl->step = RELAY_CHN_TILT_STEP_PENDING; - relay_chn_start_esp_timer_once(tilt_ctl->tilt_timer, RELAY_CHN_OPPOSITE_INERTIA_MS); + relay_chn_start_esp_timer_once(tilt_ctl->tilt_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS); } else if (cmd == RELAY_CHN_TILT_CMD_REVERSE) { // Stop the running channel first relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP); @@ -151,7 +151,7 @@ static void relay_chn_tilt_issue_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_t relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP); // Schedule for tilting tilt_ctl->step = RELAY_CHN_TILT_STEP_PENDING; - relay_chn_start_esp_timer_once(tilt_ctl->tilt_timer, RELAY_CHN_OPPOSITE_INERTIA_MS); + relay_chn_start_esp_timer_once(tilt_ctl->tilt_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS); } else if (cmd == RELAY_CHN_TILT_CMD_FORWARD) { // Stop the running channel first relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP); @@ -176,7 +176,7 @@ static void relay_chn_tilt_issue_auto(relay_chn_tilt_ctl_t *tilt_ctl) } } -#if RELAY_CHN_COUNT > 1 +#if CONFIG_RELAY_CHN_COUNT > 1 void relay_chn_tilt_auto(uint8_t chn_id) { if (!relay_chn_is_channel_id_valid(chn_id)) { @@ -185,7 +185,7 @@ void relay_chn_tilt_auto(uint8_t chn_id) // Execute for all channels if (chn_id == RELAY_CHN_ID_ALL) { - for (int i = 0; i < RELAY_CHN_COUNT; i++) { + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { relay_chn_tilt_issue_auto(&tilt_ctls[i]); } } @@ -198,7 +198,7 @@ void relay_chn_tilt_auto(uint8_t chn_id) static void relay_chn_tilt_issue_cmd_on_all_channels(relay_chn_tilt_cmd_t cmd) { - for (int i = 0; i < RELAY_CHN_COUNT; i++) { + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { relay_chn_tilt_ctl_t* tilt_ctl = &tilt_ctls[i]; relay_chn_tilt_issue_cmd(tilt_ctl, cmd); } @@ -239,7 +239,7 @@ void relay_chn_tilt_stop(uint8_t chn_id) } if (chn_id == RELAY_CHN_ID_ALL) { - for (int i = 0; i < RELAY_CHN_COUNT; i++) { + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { relay_chn_tilt_dispatch_cmd(&tilt_ctls[i], RELAY_CHN_TILT_CMD_STOP); } } @@ -248,7 +248,7 @@ void relay_chn_tilt_stop(uint8_t chn_id) } } -#else // RELAY_CHN_COUNT > 1 +#else // CONFIG_RELAY_CHN_COUNT > 1 void relay_chn_tilt_auto() { @@ -269,7 +269,7 @@ void relay_chn_tilt_stop() { relay_chn_tilt_dispatch_cmd(&tilt_ctl, RELAY_CHN_TILT_CMD_STOP); } -#endif // RELAY_CHN_COUNT > 1 +#endif // CONFIG_RELAY_CHN_COUNT > 1 static void relay_chn_tilt_set_timing_values(relay_chn_tilt_timing_t *tilt_timing, uint8_t sensitivity, @@ -314,7 +314,7 @@ static void relay_chn_tilt_compute_set_sensitivity(relay_chn_tilt_ctl_t *tilt_ct } } -#if RELAY_CHN_COUNT > 1 +#if CONFIG_RELAY_CHN_COUNT > 1 void relay_chn_tilt_set_sensitivity(uint8_t chn_id, uint8_t sensitivity) { if (!relay_chn_is_channel_id_valid(chn_id)) { @@ -322,7 +322,7 @@ void relay_chn_tilt_set_sensitivity(uint8_t chn_id, uint8_t sensitivity) } if (chn_id == RELAY_CHN_ID_ALL) { - for (int i = 0; i < RELAY_CHN_COUNT; i++) { + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { relay_chn_tilt_compute_set_sensitivity(&tilt_ctls[i], sensitivity); } } @@ -330,9 +330,9 @@ void relay_chn_tilt_set_sensitivity(uint8_t chn_id, uint8_t sensitivity) relay_chn_tilt_compute_set_sensitivity(&tilt_ctls[chn_id], sensitivity); } -#if RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 relay_chn_nvs_set_tilt_sensitivity(chn_id, sensitivity); -#endif // RELAY_CHN_ENABLE_NVS +#endif // CONFIG_RELAY_CHN_ENABLE_NVS } esp_err_t relay_chn_tilt_get_sensitivity(uint8_t chn_id, uint8_t *sensitivity, size_t length) @@ -345,12 +345,12 @@ esp_err_t relay_chn_tilt_get_sensitivity(uint8_t chn_id, uint8_t *sensitivity, s return ESP_ERR_INVALID_ARG; } if (chn_id == RELAY_CHN_ID_ALL) { - if (length < RELAY_CHN_COUNT) { + if (length < CONFIG_RELAY_CHN_COUNT) { ESP_LOGD(TAG, "relay_chn_tilt_get_sensitivity: length is too short to store all sensitivity values"); return ESP_ERR_INVALID_ARG; } - for (int i = 0; i < RELAY_CHN_COUNT; i++) { + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { sensitivity[i] = tilt_ctls[i].tilt_timing.sensitivity; } return ESP_OK; @@ -365,22 +365,22 @@ void relay_chn_tilt_set_sensitivity(uint8_t sensitivity) { relay_chn_tilt_compute_set_sensitivity(&tilt_ctl, sensitivity); -#if RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 relay_chn_nvs_set_tilt_sensitivity(0, sensitivity); -#endif // RELAY_CHN_ENABLE_NVS +#endif // CONFIG_RELAY_CHN_ENABLE_NVS } uint8_t relay_chn_tilt_get_sensitivity() { return tilt_ctl.tilt_timing.sensitivity; } -#endif // RELAY_CHN_COUNT > 1 +#endif // CONFIG_RELAY_CHN_COUNT > 1 void relay_chn_tilt_reset_count(relay_chn_tilt_ctl_t *tilt_ctl) { tilt_ctl->tilt_count = 0; -#if RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 esp_timer_stop(tilt_ctl->flush_timer); #endif } @@ -450,7 +450,7 @@ static uint16_t relay_chn_tilt_count_update(relay_chn_tilt_ctl_t *tilt_ctl) return 0; } -#if RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 static esp_err_t relay_chn_tilt_save_tilt_count(relay_chn_tilt_ctl_t *tilt_ctl) { // Save the tilt count to NVS storage @@ -486,7 +486,7 @@ static void relay_chn_tilt_execute_stop(relay_chn_tilt_ctl_t *tilt_ctl) } relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP); -#if RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 // Start the flush debounce timer relay_chn_start_esp_timer_once(tilt_ctl->flush_timer, RELAY_CHN_TILT_FLUSH_DEBOUNCE_MS); #endif @@ -599,7 +599,7 @@ static void relay_chn_tilt_timer_cb(void *arg) } } -#if RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 static esp_err_t relay_chn_tilt_load_sensitivity(uint8_t ch, uint8_t *sensitivity) { esp_err_t ret = relay_chn_nvs_get_tilt_sensitivity(ch, sensitivity); @@ -622,7 +622,7 @@ static esp_err_t relay_chn_tilt_load_tilt_count(uint8_t ch, uint16_t *tilt_count ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load tilt counters for channel %d", ch); return ESP_OK; } -#endif // RELAY_CHN_ENABLE_NVS +#endif // CONFIG_RELAY_CHN_ENABLE_NVS static esp_err_t relay_chn_tilt_ctl_init(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_ctl_t *chn_ctl, @@ -648,7 +648,7 @@ static esp_err_t relay_chn_tilt_ctl_init(relay_chn_tilt_ctl_t *tilt_ctl, esp_err_t ret = esp_timer_create(&timer_args, &tilt_ctl->tilt_timer); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create tilt timer for channel %d", chn_ctl->id); -#if RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 // Create flush timer for the tilt counters snprintf(timer_name, sizeof(timer_name), "relay_chn_%2d_tilt_flush_timer", chn_ctl->id); timer_args.callback = relay_chn_tilt_flush_timer_cb; @@ -664,9 +664,9 @@ esp_err_t relay_chn_tilt_init(relay_chn_ctl_t *chn_ctls) uint8_t sensitivity; uint16_t tilt_count; -#if RELAY_CHN_COUNT > 1 - for (int i = 0; i < RELAY_CHN_COUNT; i++) { -#if RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_COUNT > 1 + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { +#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 esp_err_t ret = relay_chn_tilt_load_sensitivity(i, &sensitivity); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load tilt sensitivity for channel %d", i); ret = relay_chn_tilt_load_tilt_count(i, &tilt_count); @@ -674,7 +674,7 @@ esp_err_t relay_chn_tilt_init(relay_chn_ctl_t *chn_ctls) #else sensitivity = RELAY_CHN_TILT_DEFAULT_SENSITIVITY; tilt_count = 0; -#endif // RELAY_CHN_ENABLE_NVS == 1 +#endif // CONFIG_RELAY_CHN_ENABLE_NVS == 1 ret = relay_chn_tilt_ctl_init(&tilt_ctls[i], &chn_ctls[i], tilt_count, sensitivity); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to init tilt control for channel %d", i); } @@ -682,14 +682,14 @@ esp_err_t relay_chn_tilt_init(relay_chn_ctl_t *chn_ctls) #else sensitivity = RELAY_CHN_TILT_DEFAULT_SENSITIVITY; tilt_count = 0; -#if RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 esp_err_t ret = relay_chn_tilt_load_sensitivity(0, &sensitivity); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load tilt sensitivity for channel %d", 0); ret = relay_chn_tilt_load_tilt_count(0, &tilt_count); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load tilt count for channel %d", 0); -#endif // RELAY_CHN_ENABLE_NVS == 1 +#endif // CONFIG_RELAY_CHN_ENABLE_NVS == 1 return relay_chn_tilt_ctl_init(&tilt_ctl, chn_ctls, tilt_count, sensitivity); -#endif // RELAY_CHN_COUNT > 1 +#endif // CONFIG_RELAY_CHN_COUNT > 1 } void relay_chn_tilt_ctl_deinit(relay_chn_tilt_ctl_t *tilt_ctl) @@ -698,21 +698,21 @@ void relay_chn_tilt_ctl_deinit(relay_chn_tilt_ctl_t *tilt_ctl) esp_timer_delete(tilt_ctl->tilt_timer); tilt_ctl->tilt_timer = NULL; } -#if RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 if (tilt_ctl->flush_timer != NULL) { esp_timer_delete(tilt_ctl->flush_timer); tilt_ctl->flush_timer = NULL; } -#endif // RELAY_CHN_ENABLE_NVS == 1 +#endif // CONFIG_RELAY_CHN_ENABLE_NVS == 1 } void relay_chn_tilt_deinit() { -#if RELAY_CHN_COUNT > 1 - for (int i = 0; i < RELAY_CHN_COUNT; i++) { +#if CONFIG_RELAY_CHN_COUNT > 1 + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { relay_chn_tilt_ctl_deinit(&tilt_ctls[i]); } #else relay_chn_tilt_ctl_deinit(&tilt_ctl); -#endif // RELAY_CHN_COUNT > 1 +#endif // CONFIG_RELAY_CHN_COUNT > 1 } \ No newline at end of file diff --git a/test_apps/main/test_app_main.c b/test_apps/main/test_app_main.c index 460659c..0a4501e 100644 --- a/test_apps/main/test_app_main.c +++ b/test_apps/main/test_app_main.c @@ -7,7 +7,7 @@ #include "unity_test_runner.h" #include "test_common.h" -#if RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 #include "nvs_flash.h" #include "relay_chn_nvs.h" #endif @@ -32,14 +32,14 @@ void tearDown() static void test_nvs_flash_init(void) { esp_err_t ret; -#if RELAY_CHN_NVS_CUSTOM_PARTITION == 1 - ret = nvs_flash_init_partition(RELAY_CHN_NVS_CUSTOM_PARTITION_NAME); +#if CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION == 1 + ret = nvs_flash_init_partition(CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME); ESP_LOGI(TEST_TAG, "test_nvs_flash_init: NVS flash init partition return: %s", esp_err_to_name(ret)); if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { // NVS partition is truncated and needs to be erased - ret = nvs_flash_erase_partition(RELAY_CHN_NVS_CUSTOM_PARTITION_NAME); + ret = nvs_flash_erase_partition(CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME); if (ret == ESP_OK) { - ret = nvs_flash_init_partition(RELAY_CHN_NVS_CUSTOM_PARTITION_NAME); + ret = nvs_flash_init_partition(CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME); } } #else @@ -61,8 +61,8 @@ TEST_ESP_OK(ret); static void test_nvs_flash_deinit(void) { esp_err_t ret; -#if RELAY_CHN_NVS_CUSTOM_PARTITION == 1 - ret = nvs_flash_deinit_partition(RELAY_CHN_NVS_CUSTOM_PARTITION_NAME); +#if CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION == 1 + ret = nvs_flash_deinit_partition(CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME); #else ret = nvs_flash_deinit(); #endif diff --git a/test_apps/main/test_relay_chn_core_multi.c b/test_apps/main/test_relay_chn_core_multi.c index 1abcd7b..9015228 100644 --- a/test_apps/main/test_relay_chn_core_multi.c +++ b/test_apps/main/test_relay_chn_core_multi.c @@ -8,7 +8,7 @@ TEST_CASE("relay_chn_create handles invalid arguments", "[relay_chn][core]") // 1. Test with NULL gpio_map TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(NULL, gpio_count)); - // 2. Test with incorrect gpio_count (must be RELAY_CHN_COUNT * 2) + // 2. Test with incorrect gpio_count (must be CONFIG_RELAY_CHN_COUNT * 2) TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(gpio_map, gpio_count - 1)); TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(gpio_map, 1)); TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(gpio_map, 0)); diff --git a/test_apps/main/test_relay_chn_core_single.c b/test_apps/main/test_relay_chn_core_single.c index b21823f..b1d58de 100644 --- a/test_apps/main/test_relay_chn_core_single.c +++ b/test_apps/main/test_relay_chn_core_single.c @@ -8,7 +8,7 @@ TEST_CASE("relay_chn_create handles invalid arguments", "[relay_chn][core]") // 1. Test with NULL gpio_map TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(NULL, gpio_count)); - // 2. Test with incorrect gpio_count (must be RELAY_CHN_COUNT * 2) + // 2. Test with incorrect gpio_count (must be CONFIG_RELAY_CHN_COUNT * 2) TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(gpio_map, gpio_count - 1)); TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(gpio_map, 1)); TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(gpio_map, 0)); diff --git a/test_apps/main/test_relay_chn_nvs_multi.c b/test_apps/main/test_relay_chn_nvs_multi.c index d591dbb..920e34d 100644 --- a/test_apps/main/test_relay_chn_nvs_multi.c +++ b/test_apps/main/test_relay_chn_nvs_multi.c @@ -41,7 +41,7 @@ TEST_CASE("Test invalid parameters", "[relay_chn][nvs]") TEST_ESP_OK(relay_chn_nvs_init()); // Test NULL pointer for all channels - for (int channel = 0; channel < RELAY_CHN_COUNT; channel++) { + for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) { TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_direction(channel, NULL)); } @@ -54,13 +54,13 @@ TEST_CASE("Test relay_chn_nvs_erase_all", "[relay_chn][nvs]") // Store some test data first relay_chn_direction_t direction = RELAY_CHN_DIRECTION_FLIPPED; - for (int channel = 0; channel < RELAY_CHN_COUNT; channel++) { + for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) { TEST_ESP_OK(relay_chn_nvs_set_direction(0, direction)); } -#ifdef RELAY_CHN_ENABLE_TILTING +#ifdef CONFIG_RELAY_CHN_ENABLE_TILTING uint8_t sensitivity = 50; - for (int channel = 0; channel < RELAY_CHN_COUNT; channel++) { + for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) { TEST_ESP_OK(relay_chn_nvs_set_tilt_sensitivity(0, sensitivity)); TEST_ESP_OK(relay_chn_nvs_set_tilt_count(0, 100)); } @@ -73,7 +73,7 @@ TEST_CASE("Test relay_chn_nvs_erase_all", "[relay_chn][nvs]") relay_chn_direction_t read_direction; TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_direction(0, &read_direction)); -#ifdef RELAY_CHN_ENABLE_TILTING +#ifdef CONFIG_RELAY_CHN_ENABLE_TILTING uint8_t read_sensitivity; TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_tilt_sensitivity(0, &read_sensitivity)); @@ -101,7 +101,7 @@ TEST_CASE("Test run limit setting and getting", "[relay_chn][nvs][run_limit]") } #endif -#ifdef RELAY_CHN_ENABLE_TILTING +#ifdef CONFIG_RELAY_CHN_ENABLE_TILTING TEST_CASE("Test sensitivity setting and getting", "[relay_chn][nvs][tilt]") { TEST_ESP_OK(relay_chn_nvs_init()); @@ -110,7 +110,7 @@ TEST_CASE("Test sensitivity setting and getting", "[relay_chn][nvs][tilt]") uint8_t sensitivity; // Test all channels - for (int channel = 0; channel < RELAY_CHN_COUNT; channel++) { + for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) { TEST_ESP_OK(relay_chn_nvs_set_tilt_sensitivity(channel, test_sensitivity)); TEST_ESP_OK(relay_chn_nvs_get_tilt_sensitivity(channel, &sensitivity)); TEST_ASSERT_EQUAL(test_sensitivity, sensitivity); @@ -127,7 +127,7 @@ TEST_CASE("Test tilt counter operations", "[relay_chn][nvs][tilt]") uint16_t tilt_count_read; // Test all channels - for (int channel = 0; channel < RELAY_CHN_COUNT; channel++) { + for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) { // Test setting counters TEST_ESP_OK(relay_chn_nvs_set_tilt_count(channel, tilt_count)); TEST_ESP_OK(relay_chn_nvs_get_tilt_count(channel, &tilt_count_read)); @@ -142,11 +142,11 @@ TEST_CASE("Test tilting invalid parameters", "[relay_chn][nvs][tilt]") TEST_ESP_OK(relay_chn_nvs_init()); // Test NULL pointers for all channels - for (int channel = 0; channel < RELAY_CHN_COUNT; channel++) { + for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) { TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_tilt_sensitivity(channel, NULL)); TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_tilt_count(channel, NULL)); } TEST_ESP_OK(relay_chn_nvs_deinit()); } -#endif // RELAY_CHN_ENABLE_TILTING \ No newline at end of file +#endif // CONFIG_RELAY_CHN_ENABLE_TILTING \ No newline at end of file diff --git a/test_apps/main/test_relay_chn_nvs_single.c b/test_apps/main/test_relay_chn_nvs_single.c index 2872d5c..3bb2921 100644 --- a/test_apps/main/test_relay_chn_nvs_single.c +++ b/test_apps/main/test_relay_chn_nvs_single.c @@ -53,7 +53,7 @@ TEST_CASE("Test relay_chn_nvs_erase_all", "[relay_chn][nvs]") relay_chn_direction_t direction = RELAY_CHN_DIRECTION_FLIPPED; TEST_ESP_OK(relay_chn_nvs_set_direction(0, direction)); -#ifdef RELAY_CHN_ENABLE_TILTING +#ifdef CONFIG_RELAY_CHN_ENABLE_TILTING uint8_t sensitivity = 50; TEST_ESP_OK(relay_chn_nvs_set_tilt_sensitivity(0, sensitivity)); TEST_ESP_OK(relay_chn_nvs_set_tilt_count(0, 100)); @@ -66,7 +66,7 @@ TEST_CASE("Test relay_chn_nvs_erase_all", "[relay_chn][nvs]") relay_chn_direction_t read_direction; TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_direction(0, &read_direction)); -#ifdef RELAY_CHN_ENABLE_TILTING +#ifdef CONFIG_RELAY_CHN_ENABLE_TILTING uint8_t read_sensitivity; TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_tilt_sensitivity(0, &read_sensitivity)); @@ -93,7 +93,7 @@ TEST_CASE("Test run limit setting and getting", "[relay_chn][nvs][run_limit]") } #endif -#ifdef RELAY_CHN_ENABLE_TILTING +#ifdef CONFIG_RELAY_CHN_ENABLE_TILTING TEST_CASE("Test sensitivity setting and getting", "[relay_chn][nvs][tilt]") { TEST_ESP_OK(relay_chn_nvs_init()); @@ -134,4 +134,4 @@ TEST_CASE("Test tilting invalid parameters", "[relay_chn][nvs][tilt]") TEST_ESP_OK(relay_chn_nvs_deinit()); } -#endif // RELAY_CHN_ENABLE_TILTING \ No newline at end of file +#endif // CONFIG_RELAY_CHN_ENABLE_TILTING \ No newline at end of file From 6ff16b57971aae3d4af3a8af32dd1ee7cc340c61 Mon Sep 17 00:00:00 2001 From: ismail Date: Mon, 25 Aug 2025 10:56:01 +0300 Subject: [PATCH 20/69] Fix config parameters check Unnecessary `#if CONFIG_FOO == 1` checks were removed and the statements were simplified to` #if CONFIG_FOO`. `#ifdef CONFIG_FOO` statements were also changed to `#if CONFIG_FOO` to keep the style as uniform as possible. Refs #1085 --- include/relay_chn.h | 8 +++---- include/relay_chn_adapter.h | 4 ++-- include/relay_chn_types.h | 2 +- private_include/relay_chn_core.h | 2 +- private_include/relay_chn_nvs.h | 4 ++-- private_include/relay_chn_priv_types.h | 6 ++--- src/relay_chn_core.c | 26 ++++++++++----------- src/relay_chn_ctl_multi.c | 12 +++++----- src/relay_chn_ctl_single.c | 12 +++++----- src/relay_chn_nvs.c | 10 ++++---- src/relay_chn_output.c | 10 ++++---- src/relay_chn_tilt.c | 24 +++++++++---------- test_apps/main/test_app_main.c | 14 +++++------ test_apps/main/test_relay_chn_core_multi.c | 2 +- test_apps/main/test_relay_chn_core_single.c | 2 +- test_apps/main/test_relay_chn_nvs_multi.c | 8 +++---- test_apps/main/test_relay_chn_nvs_single.c | 8 +++---- 17 files changed, 77 insertions(+), 77 deletions(-) diff --git a/include/relay_chn.h b/include/relay_chn.h index 5f4d0be..6f7ee33 100644 --- a/include/relay_chn.h +++ b/include/relay_chn.h @@ -143,7 +143,7 @@ void relay_chn_flip_direction(uint8_t chn_id); */ relay_chn_direction_t relay_chn_get_direction(uint8_t chn_id); -#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT /** * @brief Get the run limit for the specified channel * @@ -171,7 +171,7 @@ void relay_chn_set_run_limit(uint8_t chn_id, uint16_t time_sec); #endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 -#if CONFIG_RELAY_CHN_ENABLE_TILTING == 1 +#if CONFIG_RELAY_CHN_ENABLE_TILTING /** * @brief Enables automatic tilting for the specified relay channel. @@ -299,7 +299,7 @@ void relay_chn_flip_direction(void); */ relay_chn_direction_t relay_chn_get_direction(void); -#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT /** * @brief Get the run limit for the channel * @@ -323,7 +323,7 @@ void relay_chn_set_run_limit(uint16_t time_sec); #endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 -#if CONFIG_RELAY_CHN_ENABLE_TILTING == 1 +#if CONFIG_RELAY_CHN_ENABLE_TILTING /** * @brief Enables automatic tilting for the relay channel. diff --git a/include/relay_chn_adapter.h b/include/relay_chn_adapter.h index d14abc0..885ab95 100644 --- a/include/relay_chn_adapter.h +++ b/include/relay_chn_adapter.h @@ -101,7 +101,7 @@ static inline relay_chn_direction_t relay_chn_get_direction(uint8_t chn_id) return relay_chn_ctl_get_direction(chn_id); } -#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT /** * @brief Get the run limit for the specified channel * @@ -209,7 +209,7 @@ static inline relay_chn_direction_t relay_chn_get_direction(void) return relay_chn_ctl_get_direction(); } -#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT /** * @brief Get the run limit for the channel * diff --git a/include/relay_chn_types.h b/include/relay_chn_types.h index 90db4ff..8960cc2 100644 --- a/include/relay_chn_types.h +++ b/include/relay_chn_types.h @@ -33,7 +33,7 @@ typedef enum relay_chn_state_enum { RELAY_CHN_STATE_REVERSE, /*!< The relay channel is running in the reverse direction */ RELAY_CHN_STATE_FORWARD_PENDING, /*!< The relay channel is pending to run in the forward direction */ RELAY_CHN_STATE_REVERSE_PENDING, /*!< The relay channel is pending to run in the reverse direction */ -#if CONFIG_RELAY_CHN_ENABLE_TILTING == 1 +#if CONFIG_RELAY_CHN_ENABLE_TILTING RELAY_CHN_STATE_TILT_FORWARD, /*!< The relay channel is tilting for forward */ RELAY_CHN_STATE_TILT_REVERSE, /*!< The relay channel is tilting for reverse */ #endif diff --git a/private_include/relay_chn_core.h b/private_include/relay_chn_core.h index 6a0a621..62d0e4c 100644 --- a/private_include/relay_chn_core.h +++ b/private_include/relay_chn_core.h @@ -28,7 +28,7 @@ extern "C" { */ esp_err_t relay_chn_init_timer(relay_chn_ctl_t *chn_ctl); -#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT /** * @brief Initializes the relay channel run limit timer. * diff --git a/private_include/relay_chn_nvs.h b/private_include/relay_chn_nvs.h index 043a4b2..8ec4df4 100644 --- a/private_include/relay_chn_nvs.h +++ b/private_include/relay_chn_nvs.h @@ -45,7 +45,7 @@ esp_err_t relay_chn_nvs_set_direction(uint8_t ch, relay_chn_direction_t directio */ esp_err_t relay_chn_nvs_get_direction(uint8_t ch, relay_chn_direction_t *direction); -#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT /** * @brief Store relay channel run limit in NVS. * @@ -65,7 +65,7 @@ esp_err_t relay_chn_nvs_set_run_limit(uint8_t ch, uint16_t time_sec); esp_err_t relay_chn_nvs_get_run_limit(uint8_t ch, uint16_t *time_sec); #endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 -#ifdef CONFIG_RELAY_CHN_ENABLE_TILTING +#if CONFIG_RELAY_CHN_ENABLE_TILTING /** * @brief Store tilt sensitivity in NVS. * diff --git a/private_include/relay_chn_priv_types.h b/private_include/relay_chn_priv_types.h index 1b7b5de..478e862 100644 --- a/private_include/relay_chn_priv_types.h +++ b/private_include/relay_chn_priv_types.h @@ -45,7 +45,7 @@ typedef struct { uint32_t last_run_cmd_time_ms; /*!< The time in milliseconds when the last run command was issued */ } relay_chn_run_info_t; -#if CONFIG_RELAY_CHN_ENABLE_TILTING == 1 +#if CONFIG_RELAY_CHN_ENABLE_TILTING /// @brief Tilt commands. typedef enum { RELAY_CHN_TILT_CMD_NONE, /*!< No command */ @@ -68,11 +68,11 @@ typedef struct { relay_chn_output_t *output; /*!< Output configuration of the relay channel */ relay_chn_cmd_t pending_cmd; /*!< The command that is pending to be issued */ esp_timer_handle_t inertia_timer; /*!< Timer to handle the opposite direction inertia time */ -#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT esp_timer_handle_t run_limit_timer; /*!< Timer to handle the run limit */ uint16_t run_limit_sec; /*!< Run limit in seconds */ #endif -#if CONFIG_RELAY_CHN_ENABLE_TILTING == 1 +#if CONFIG_RELAY_CHN_ENABLE_TILTING relay_chn_tilt_ctl_t *tilt_ctl; /*!< Pointer to the tilt control structure if tilting is enabled */ #endif } relay_chn_ctl_t; diff --git a/src/relay_chn_core.c b/src/relay_chn_core.c index 66c0f34..f57969e 100644 --- a/src/relay_chn_core.c +++ b/src/relay_chn_core.c @@ -12,11 +12,11 @@ #include "relay_chn_run_info.h" #include "relay_chn_ctl.h" -#if CONFIG_RELAY_CHN_ENABLE_TILTING == 1 +#if CONFIG_RELAY_CHN_ENABLE_TILTING #include "relay_chn_tilt.h" #endif -#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS #include "relay_chn_nvs.h" #endif @@ -35,7 +35,7 @@ typedef struct relay_chn_listener_entry_type { // The list that holds references to the registered listeners. static List_t relay_chn_listener_list; -#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT /* * Run limit timer callback immediately dispatches a STOP command for the * relevant channel as soon as the run limit time times out @@ -90,7 +90,7 @@ esp_err_t relay_chn_create(const uint8_t* gpio_map, uint8_t gpio_count) ESP_RETURN_ON_FALSE(gpio_map != NULL, ESP_ERR_INVALID_ARG, TAG, "gpio_map cannot be NULL"); esp_err_t ret; -#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS ret = relay_chn_nvs_init(); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize NVS for relay channel"); #endif @@ -114,7 +114,7 @@ esp_err_t relay_chn_create(const uint8_t* gpio_map, uint8_t gpio_count) ret = relay_chn_ctl_init(outputs, run_infos); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize relay channel control"); -#if CONFIG_RELAY_CHN_ENABLE_TILTING == 1 +#if CONFIG_RELAY_CHN_ENABLE_TILTING // Initialize the tilt feature #if CONFIG_RELAY_CHN_COUNT > 1 relay_chn_ctl_t *chn_ctls = relay_chn_ctl_get_all(); @@ -133,13 +133,13 @@ esp_err_t relay_chn_create(const uint8_t* gpio_map, uint8_t gpio_count) void relay_chn_destroy(void) { -#if CONFIG_RELAY_CHN_ENABLE_TILTING == 1 +#if CONFIG_RELAY_CHN_ENABLE_TILTING relay_chn_tilt_deinit(); #endif relay_chn_ctl_deinit(); relay_chn_output_deinit(); -#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS relay_chn_nvs_deinit(); #endif @@ -369,7 +369,7 @@ void relay_chn_issue_cmd(relay_chn_ctl_t* chn_ctl, relay_chn_cmd_t cmd) relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS); break; -#if CONFIG_RELAY_CHN_ENABLE_TILTING == 1 +#if CONFIG_RELAY_CHN_ENABLE_TILTING case RELAY_CHN_STATE_TILT_FORWARD: // Terminate tilting first relay_chn_tilt_dispatch_cmd(chn_ctl->tilt_ctl, RELAY_CHN_TILT_CMD_STOP); @@ -437,7 +437,7 @@ static void relay_chn_execute_stop(relay_chn_ctl_t *chn_ctl) // Invalidate the channel's timer if it is active esp_timer_stop(chn_ctl->inertia_timer); -#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT esp_timer_stop(chn_ctl->run_limit_timer); #endif @@ -465,7 +465,7 @@ static void relay_chn_execute_forward(relay_chn_ctl_t *chn_ctl) relay_chn_run_info_set_last_run_cmd(chn_ctl->run_info, RELAY_CHN_CMD_FORWARD); relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_FORWARD); -#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT relay_chn_start_esp_timer_once(chn_ctl->run_limit_timer, chn_ctl->run_limit_sec * 1000); #endif } @@ -479,7 +479,7 @@ static void relay_chn_execute_reverse(relay_chn_ctl_t *chn_ctl) relay_chn_run_info_set_last_run_cmd(chn_ctl->run_info, RELAY_CHN_CMD_REVERSE); relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_REVERSE); -#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT relay_chn_start_esp_timer_once(chn_ctl->run_limit_timer, chn_ctl->run_limit_sec * 1000); #endif } @@ -516,7 +516,7 @@ void relay_chn_dispatch_cmd(relay_chn_ctl_t *chn_ctl, relay_chn_cmd_t cmd) { ESP_LOGD(TAG, "Unknown relay channel command!"); } -#if CONFIG_RELAY_CHN_ENABLE_TILTING == 1 +#if CONFIG_RELAY_CHN_ENABLE_TILTING // Reset the tilt counter when the command is either FORWARD or REVERSE if (cmd == RELAY_CHN_CMD_FORWARD || cmd == RELAY_CHN_CMD_REVERSE) { relay_chn_tilt_reset_count(chn_ctl->tilt_ctl); @@ -557,7 +557,7 @@ char *relay_chn_state_str(relay_chn_state_t state) return "FORWARD_PENDING"; case RELAY_CHN_STATE_REVERSE_PENDING: return "REVERSE_PENDING"; -#if CONFIG_RELAY_CHN_ENABLE_TILTING == 1 +#if CONFIG_RELAY_CHN_ENABLE_TILTING case RELAY_CHN_STATE_TILT_FORWARD: return "TILT_FORWARD"; case RELAY_CHN_STATE_TILT_REVERSE: diff --git a/src/relay_chn_ctl_multi.c b/src/relay_chn_ctl_multi.c index b18e58a..212b941 100644 --- a/src/relay_chn_ctl_multi.c +++ b/src/relay_chn_ctl_multi.c @@ -10,7 +10,7 @@ #include "relay_chn_ctl.h" #include "relay_chn_output.h" -#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS #include "relay_chn_nvs.h" #endif @@ -34,9 +34,9 @@ esp_err_t relay_chn_ctl_init(relay_chn_output_t *outputs, relay_chn_run_info_t * chn_ctl->output = output; chn_ctl->run_info = run_info; -#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT uint16_t run_limit_sec = CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC; -#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS // Load run limit value from NVS ret = relay_chn_nvs_get_run_limit(chn_ctl->id, &run_limit_sec); if (ret != ESP_OK && ret != ESP_ERR_NVS_NOT_FOUND) { @@ -61,7 +61,7 @@ void relay_chn_ctl_deinit() esp_timer_delete(chn_ctl->inertia_timer); chn_ctl->inertia_timer = NULL; } -#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT if (chn_ctl->run_limit_timer != NULL) { esp_timer_delete(chn_ctl->run_limit_timer); chn_ctl->run_limit_timer = NULL; @@ -146,7 +146,7 @@ relay_chn_direction_t relay_chn_ctl_get_direction(uint8_t chn_id) return relay_chn_output_get_direction(chn_ctl->output); } -#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT uint16_t relay_chn_ctl_get_run_limit(uint8_t chn_id) { if (!relay_chn_is_channel_id_valid(chn_id) || chn_id == RELAY_CHN_ID_ALL) { @@ -171,7 +171,7 @@ void relay_chn_ctl_set_run_limit(uint8_t chn_id, uint16_t time_sec) chn_ctls[chn_id].run_limit_sec = time_sec; -#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS relay_chn_nvs_set_run_limit(chn_id, time_sec); #endif } diff --git a/src/relay_chn_ctl_single.c b/src/relay_chn_ctl_single.c index 94ca732..bf3e1cc 100644 --- a/src/relay_chn_ctl_single.c +++ b/src/relay_chn_ctl_single.c @@ -10,7 +10,7 @@ #include "relay_chn_ctl.h" #include "relay_chn_output.h" -#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS #include "relay_chn_nvs.h" #endif @@ -27,10 +27,10 @@ esp_err_t relay_chn_ctl_init(relay_chn_output_t *output, relay_chn_run_info_t *r chn_ctl.pending_cmd = RELAY_CHN_CMD_NONE; chn_ctl.output = output; chn_ctl.run_info = run_info; -#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT uint16_t run_limit_sec = CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC; esp_err_t ret; -#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS // Load run limit value from NVS ret = relay_chn_nvs_get_run_limit(chn_ctl.id, &run_limit_sec); if (ret != ESP_OK && ret != ESP_ERR_NVS_NOT_FOUND) { @@ -51,7 +51,7 @@ void relay_chn_ctl_deinit() esp_timer_delete(chn_ctl.inertia_timer); chn_ctl.inertia_timer = NULL; } -#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT if (chn_ctl.run_limit_timer != NULL) { esp_timer_delete(chn_ctl.run_limit_timer); chn_ctl.run_limit_timer = NULL; @@ -95,7 +95,7 @@ relay_chn_direction_t relay_chn_ctl_get_direction() return relay_chn_output_get_direction(chn_ctl.output); } -#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT uint16_t relay_chn_ctl_get_run_limit() { return chn_ctl.run_limit_sec; @@ -111,7 +111,7 @@ void relay_chn_ctl_set_run_limit(uint16_t time_sec) chn_ctl.run_limit_sec = time_sec; -#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS relay_chn_nvs_set_run_limit(chn_ctl.id, time_sec); #endif } diff --git a/src/relay_chn_nvs.c b/src/relay_chn_nvs.c index eff7332..3877162 100644 --- a/src/relay_chn_nvs.c +++ b/src/relay_chn_nvs.c @@ -8,10 +8,10 @@ #include "relay_chn_nvs.h" #define RELAY_CHN_KEY_DIR "dir" /*!< Direction key */ -#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT #define RELAY_CHN_KEY_RLIM(ch) "rlim_%d" /*!< Run limit key */ #endif -#ifdef CONFIG_RELAY_CHN_ENABLE_TILTING +#if CONFIG_RELAY_CHN_ENABLE_TILTING #define RELAY_CHN_KEY_TSENS(ch) "tsens_%d" /*!< Tilt sensitivity key */ #define RELAY_CHN_KEY_TCNT(ch) "tcnt_%d" /*!< Tilt count key */ #endif @@ -23,7 +23,7 @@ static nvs_handle_t relay_chn_nvs; esp_err_t relay_chn_nvs_init() { esp_err_t ret; -#if CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION == 1 +#if CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION ret = nvs_open_from_partition(CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME, CONFIG_RELAY_CHN_NVS_NAMESPACE, NVS_READWRITE, @@ -72,7 +72,7 @@ esp_err_t relay_chn_nvs_get_direction(uint8_t ch, relay_chn_direction_t *directi return ESP_OK; } -#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT esp_err_t relay_chn_nvs_set_run_limit(uint8_t ch, uint16_t time_sec) { esp_err_t ret = nvs_set_u16(relay_chn_nvs, RELAY_CHN_KEY_RLIM(ch), time_sec); @@ -88,7 +88,7 @@ esp_err_t relay_chn_nvs_get_run_limit(uint8_t ch, uint16_t *time_sec) } #endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 -#ifdef CONFIG_RELAY_CHN_ENABLE_TILTING +#if CONFIG_RELAY_CHN_ENABLE_TILTING esp_err_t relay_chn_nvs_set_tilt_sensitivity(uint8_t ch, uint8_t sensitivity) { esp_err_t ret = nvs_set_u8(relay_chn_nvs, RELAY_CHN_KEY_TSENS(ch), sensitivity); diff --git a/src/relay_chn_output.c b/src/relay_chn_output.c index 828ad0a..b7ea44e 100644 --- a/src/relay_chn_output.c +++ b/src/relay_chn_output.c @@ -10,7 +10,7 @@ #include "relay_chn_output.h" #include "relay_chn_core.h" -#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS #include "relay_chn_nvs.h" #endif @@ -73,7 +73,7 @@ static esp_err_t relay_chn_output_ctl_init(relay_chn_output_t *output, return ESP_OK; } -#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS static esp_err_t relay_chn_output_load_direction(uint8_t ch, relay_chn_direction_t *direction) { esp_err_t ret = relay_chn_nvs_get_direction(ch, direction); @@ -101,7 +101,7 @@ esp_err_t relay_chn_output_init(const uint8_t* gpio_map, uint8_t gpio_count) gpio_num_t reverse_pin = (gpio_num_t) gpio_map[gpio_index + 1]; relay_chn_direction_t direction = RELAY_CHN_DIRECTION_DEFAULT; -#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS // If NVS storage is enabled, retrieve the direction from storage ret = relay_chn_output_load_direction(i, &direction); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load direction from storage for channel %d", i); @@ -111,7 +111,7 @@ esp_err_t relay_chn_output_init(const uint8_t* gpio_map, uint8_t gpio_count) } #else relay_chn_direction_t direction = RELAY_CHN_DIRECTION_DEFAULT; -#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS // If NVS storage is enabled, retrieve the direction from storage ret = relay_chn_output_load_direction(0, &direction); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load direction from storage for channel %d", 0); @@ -194,7 +194,7 @@ void relay_chn_output_flip(relay_chn_output_t *output) ? RELAY_CHN_DIRECTION_FLIPPED : RELAY_CHN_DIRECTION_DEFAULT; -#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS uint8_t ch = 0; #if CONFIG_RELAY_CHN_COUNT > 1 for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { diff --git a/src/relay_chn_tilt.c b/src/relay_chn_tilt.c index e99dea0..c48c976 100644 --- a/src/relay_chn_tilt.c +++ b/src/relay_chn_tilt.c @@ -10,7 +10,7 @@ #include "relay_chn_run_info.h" #include "relay_chn_tilt.h" -#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS #include "relay_chn_nvs.h" #define RELAY_CHN_TILT_FLUSH_DEBOUNCE_MS 3000 @@ -63,7 +63,7 @@ typedef struct relay_chn_tilt_ctl { relay_chn_tilt_timing_t tilt_timing; /*!< Tilt timing structure */ uint16_t tilt_count; /*!< Tilt count to manage forward and reverse tilts */ esp_timer_handle_t tilt_timer; /*!< Tilt timer handle */ -#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS esp_timer_handle_t flush_timer; /*!< Flush timer to avoid frequent write of tilt counters */ #endif } relay_chn_tilt_ctl_t; @@ -330,7 +330,7 @@ void relay_chn_tilt_set_sensitivity(uint8_t chn_id, uint8_t sensitivity) relay_chn_tilt_compute_set_sensitivity(&tilt_ctls[chn_id], sensitivity); } -#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS relay_chn_nvs_set_tilt_sensitivity(chn_id, sensitivity); #endif // CONFIG_RELAY_CHN_ENABLE_NVS } @@ -365,7 +365,7 @@ void relay_chn_tilt_set_sensitivity(uint8_t sensitivity) { relay_chn_tilt_compute_set_sensitivity(&tilt_ctl, sensitivity); -#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS relay_chn_nvs_set_tilt_sensitivity(0, sensitivity); #endif // CONFIG_RELAY_CHN_ENABLE_NVS } @@ -380,7 +380,7 @@ void relay_chn_tilt_reset_count(relay_chn_tilt_ctl_t *tilt_ctl) { tilt_ctl->tilt_count = 0; -#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS esp_timer_stop(tilt_ctl->flush_timer); #endif } @@ -450,7 +450,7 @@ static uint16_t relay_chn_tilt_count_update(relay_chn_tilt_ctl_t *tilt_ctl) return 0; } -#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS static esp_err_t relay_chn_tilt_save_tilt_count(relay_chn_tilt_ctl_t *tilt_ctl) { // Save the tilt count to NVS storage @@ -486,7 +486,7 @@ static void relay_chn_tilt_execute_stop(relay_chn_tilt_ctl_t *tilt_ctl) } relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP); -#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS // Start the flush debounce timer relay_chn_start_esp_timer_once(tilt_ctl->flush_timer, RELAY_CHN_TILT_FLUSH_DEBOUNCE_MS); #endif @@ -599,7 +599,7 @@ static void relay_chn_tilt_timer_cb(void *arg) } } -#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS static esp_err_t relay_chn_tilt_load_sensitivity(uint8_t ch, uint8_t *sensitivity) { esp_err_t ret = relay_chn_nvs_get_tilt_sensitivity(ch, sensitivity); @@ -648,7 +648,7 @@ static esp_err_t relay_chn_tilt_ctl_init(relay_chn_tilt_ctl_t *tilt_ctl, esp_err_t ret = esp_timer_create(&timer_args, &tilt_ctl->tilt_timer); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create tilt timer for channel %d", chn_ctl->id); -#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS // Create flush timer for the tilt counters snprintf(timer_name, sizeof(timer_name), "relay_chn_%2d_tilt_flush_timer", chn_ctl->id); timer_args.callback = relay_chn_tilt_flush_timer_cb; @@ -666,7 +666,7 @@ esp_err_t relay_chn_tilt_init(relay_chn_ctl_t *chn_ctls) #if CONFIG_RELAY_CHN_COUNT > 1 for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { -#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS esp_err_t ret = relay_chn_tilt_load_sensitivity(i, &sensitivity); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load tilt sensitivity for channel %d", i); ret = relay_chn_tilt_load_tilt_count(i, &tilt_count); @@ -682,7 +682,7 @@ esp_err_t relay_chn_tilt_init(relay_chn_ctl_t *chn_ctls) #else sensitivity = RELAY_CHN_TILT_DEFAULT_SENSITIVITY; tilt_count = 0; -#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS esp_err_t ret = relay_chn_tilt_load_sensitivity(0, &sensitivity); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load tilt sensitivity for channel %d", 0); ret = relay_chn_tilt_load_tilt_count(0, &tilt_count); @@ -698,7 +698,7 @@ void relay_chn_tilt_ctl_deinit(relay_chn_tilt_ctl_t *tilt_ctl) esp_timer_delete(tilt_ctl->tilt_timer); tilt_ctl->tilt_timer = NULL; } -#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS if (tilt_ctl->flush_timer != NULL) { esp_timer_delete(tilt_ctl->flush_timer); tilt_ctl->flush_timer = NULL; diff --git a/test_apps/main/test_app_main.c b/test_apps/main/test_app_main.c index 0a4501e..8400843 100644 --- a/test_apps/main/test_app_main.c +++ b/test_apps/main/test_app_main.c @@ -7,7 +7,7 @@ #include "unity_test_runner.h" #include "test_common.h" -#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS #include "nvs_flash.h" #include "relay_chn_nvs.h" #endif @@ -28,11 +28,11 @@ void tearDown() reset_channels_to_idle_state(); } -#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS static void test_nvs_flash_init(void) { esp_err_t ret; -#if CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION == 1 +#if CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION ret = nvs_flash_init_partition(CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME); ESP_LOGI(TEST_TAG, "test_nvs_flash_init: NVS flash init partition return: %s", esp_err_to_name(ret)); if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { @@ -57,11 +57,11 @@ TEST_ESP_OK(ret); } #endif -#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS static void test_nvs_flash_deinit(void) { esp_err_t ret; -#if CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION == 1 +#if CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION ret = nvs_flash_deinit_partition(CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME); #else ret = nvs_flash_deinit(); @@ -72,7 +72,7 @@ static void test_nvs_flash_deinit(void) void app_main(void) { -#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS // Init NVS once for all tests test_nvs_flash_init(); #endif @@ -98,7 +98,7 @@ void app_main(void) // Destroy relay_chn relay_chn_destroy(); -#if CONFIG_RELAY_CHN_ENABLE_NVS == 1 +#if CONFIG_RELAY_CHN_ENABLE_NVS // Deinit NVS test_nvs_flash_deinit(); #endif diff --git a/test_apps/main/test_relay_chn_core_multi.c b/test_apps/main/test_relay_chn_core_multi.c index 9015228..f684535 100644 --- a/test_apps/main/test_relay_chn_core_multi.c +++ b/test_apps/main/test_relay_chn_core_multi.c @@ -344,7 +344,7 @@ TEST_CASE("Direction flip handles invalid channel ID gracefully", "[relay_chn][c TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(invalid_ch)); } -#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT #define TEST_RUN_LIMIT_SEC 5 #define TEST_SHORT_RUN_LIMIT_SEC 2 // ### Run Limit Tests diff --git a/test_apps/main/test_relay_chn_core_single.c b/test_apps/main/test_relay_chn_core_single.c index b1d58de..0945d0b 100644 --- a/test_apps/main/test_relay_chn_core_single.c +++ b/test_apps/main/test_relay_chn_core_single.c @@ -173,7 +173,7 @@ TEST_CASE("Flipping a running channel stops it and flips direction", "[relay_chn TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction()); } -#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT #define TEST_RUN_LIMIT_SEC 5 #define TEST_SHORT_RUN_LIMIT_SEC 2 // ### Run Limit Tests diff --git a/test_apps/main/test_relay_chn_nvs_multi.c b/test_apps/main/test_relay_chn_nvs_multi.c index 920e34d..1c9b243 100644 --- a/test_apps/main/test_relay_chn_nvs_multi.c +++ b/test_apps/main/test_relay_chn_nvs_multi.c @@ -58,7 +58,7 @@ TEST_CASE("Test relay_chn_nvs_erase_all", "[relay_chn][nvs]") TEST_ESP_OK(relay_chn_nvs_set_direction(0, direction)); } -#ifdef CONFIG_RELAY_CHN_ENABLE_TILTING +#if CONFIG_RELAY_CHN_ENABLE_TILTING uint8_t sensitivity = 50; for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) { TEST_ESP_OK(relay_chn_nvs_set_tilt_sensitivity(0, sensitivity)); @@ -73,7 +73,7 @@ TEST_CASE("Test relay_chn_nvs_erase_all", "[relay_chn][nvs]") relay_chn_direction_t read_direction; TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_direction(0, &read_direction)); -#ifdef CONFIG_RELAY_CHN_ENABLE_TILTING +#if CONFIG_RELAY_CHN_ENABLE_TILTING uint8_t read_sensitivity; TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_tilt_sensitivity(0, &read_sensitivity)); @@ -84,7 +84,7 @@ TEST_CASE("Test relay_chn_nvs_erase_all", "[relay_chn][nvs]") TEST_ESP_OK(relay_chn_nvs_deinit()); } -#ifdef CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT TEST_CASE("Test run limit setting and getting", "[relay_chn][nvs][run_limit]") { TEST_ESP_OK(relay_chn_nvs_init()); @@ -101,7 +101,7 @@ TEST_CASE("Test run limit setting and getting", "[relay_chn][nvs][run_limit]") } #endif -#ifdef CONFIG_RELAY_CHN_ENABLE_TILTING +#if CONFIG_RELAY_CHN_ENABLE_TILTING TEST_CASE("Test sensitivity setting and getting", "[relay_chn][nvs][tilt]") { TEST_ESP_OK(relay_chn_nvs_init()); diff --git a/test_apps/main/test_relay_chn_nvs_single.c b/test_apps/main/test_relay_chn_nvs_single.c index 3bb2921..06240a2 100644 --- a/test_apps/main/test_relay_chn_nvs_single.c +++ b/test_apps/main/test_relay_chn_nvs_single.c @@ -53,7 +53,7 @@ TEST_CASE("Test relay_chn_nvs_erase_all", "[relay_chn][nvs]") relay_chn_direction_t direction = RELAY_CHN_DIRECTION_FLIPPED; TEST_ESP_OK(relay_chn_nvs_set_direction(0, direction)); -#ifdef CONFIG_RELAY_CHN_ENABLE_TILTING +#if CONFIG_RELAY_CHN_ENABLE_TILTING uint8_t sensitivity = 50; TEST_ESP_OK(relay_chn_nvs_set_tilt_sensitivity(0, sensitivity)); TEST_ESP_OK(relay_chn_nvs_set_tilt_count(0, 100)); @@ -66,7 +66,7 @@ TEST_CASE("Test relay_chn_nvs_erase_all", "[relay_chn][nvs]") relay_chn_direction_t read_direction; TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_direction(0, &read_direction)); -#ifdef CONFIG_RELAY_CHN_ENABLE_TILTING +#if CONFIG_RELAY_CHN_ENABLE_TILTING uint8_t read_sensitivity; TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_tilt_sensitivity(0, &read_sensitivity)); @@ -77,7 +77,7 @@ TEST_CASE("Test relay_chn_nvs_erase_all", "[relay_chn][nvs]") TEST_ESP_OK(relay_chn_nvs_deinit()); } -#ifdef CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT TEST_CASE("Test run limit setting and getting", "[relay_chn][nvs][run_limit]") { TEST_ESP_OK(relay_chn_nvs_init()); @@ -93,7 +93,7 @@ TEST_CASE("Test run limit setting and getting", "[relay_chn][nvs][run_limit]") } #endif -#ifdef CONFIG_RELAY_CHN_ENABLE_TILTING +#if CONFIG_RELAY_CHN_ENABLE_TILTING TEST_CASE("Test sensitivity setting and getting", "[relay_chn][nvs][tilt]") { TEST_ESP_OK(relay_chn_nvs_init()); From be4a2ebef63b669232e2115b5d628c7731cf357a Mon Sep 17 00:00:00 2001 From: ismail Date: Mon, 25 Aug 2025 18:24:37 +0300 Subject: [PATCH 21/69] Fix build path --- .vscode/c_cpp_properties.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 7182d4a..b49b438 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -3,7 +3,7 @@ { "name": "ESP-IDF", "compilerPath": "${config:idf.toolsPath}/tools/riscv32-esp-elf/esp-14.2.0_20241119/riscv32-esp-elf/bin/riscv32-esp-elf-gcc", - "compileCommands": "${config:idf.buildPath}/compile_commands.json", + "compileCommands": "${workspaceFolder}/test_apps/build/compile_commands.json", "includePath": [ "${config:idf.espIdfPath}/components/**", "${config:idf.espIdfPathWin}/components/**", From 3831384169ac556dab633c7f7b0532dce030ce93 Mon Sep 17 00:00:00 2001 From: ismail Date: Mon, 25 Aug 2025 18:50:21 +0300 Subject: [PATCH 22/69] Implement specific *all functions Specific `_*all` and `_*all_with` functions were implemented to make the API more concise and clean, and to replace the use of `RELAY_CHN_ID_ALL`, which caused confusion within the API. Refs #1085 --- README.md | 48 ++++-- include/relay_chn.h | 158 ++++++++++++++++++- include/relay_chn_adapter.h | 146 ++++++++++++++++- include/relay_chn_defs.h | 19 --- include/relay_chn_types.h | 1 - private_include/relay_chn_core.h | 1 - scripts/run_tests.sh | 4 +- src/relay_chn_core.c | 2 +- src/relay_chn_ctl_multi.c | 175 ++++++++++++++------- src/relay_chn_output.c | 1 - src/relay_chn_tilt.c | 160 ++++++++++--------- test_apps/main/test_common.c | 2 +- test_apps/main/test_relay_chn_core_multi.c | 33 ++-- test_apps/main/test_relay_chn_tilt_multi.c | 49 +++--- 14 files changed, 578 insertions(+), 221 deletions(-) delete mode 100644 include/relay_chn_defs.h diff --git a/README.md b/README.md index 599a024..0814795 100644 --- a/README.md +++ b/README.md @@ -180,22 +180,22 @@ For multi mode // Run channel #0 forward relay_chn_run_forward(0); // Run all channels forward -relay_chn_run_forward(RELAY_CHN_ID_ALL); +relay_chn_run_forward_all(); // Run channel #1 reverse relay_chn_run_reverse(1); // Run all channels reverse -relay_chn_run_reverse(RELAY_CHN_ID_ALL); +relay_chn_run_reverse_all(); // Stop channel #1 relay_chn_stop(1); // Stop all channels -relay_chn_stop(RELAY_CHN_ID_ALL); +relay_chn_stop_all(); // Flip direction of channel #0 relay_chn_flip_direction(0); // Flip direction of all channels -relay_chn_flip_direction(RELAY_CHN_ID_ALL); +relay_chn_flip_direction_all(); ``` ### 3. Monitor channel state @@ -227,12 +227,21 @@ For multi mode: ```c // Get channel #0 state relay_chn_state_t state = relay_chn_get_state(0); + +// Get states for all channels +relay_chn_state_t states[CONFIG_RELAY_CHN_COUNT]; +relay_chn_get_states(states); + // Get the string representation of the state of the channel #0 char *state_str = relay_chn_get_state_str(0); // Get channel #0 direction relay_chn_direction_t direction = relay_chn_get_direction(0); +// Get directions for all channels +relay_chn_direction_t directions[CONFIG_RELAY_CHN_COUNT]; +relay_chn_get_directions(directions); + /* The listener is same for multi mode */ ``` @@ -261,7 +270,11 @@ uint16_t limit = relay_chn_get_run_limit(0); // Set new run limit for specific channels (in seconds) relay_chn_set_run_limit(0, 120); // Set channel #0 to 120 seconds relay_chn_set_run_limit(1, 180); // Set channel #1 to 180 seconds -relay_chn_set_run_limit(RELAY_CHN_ID_ALL, 90); // Set all channels to 90 seconds +relay_chn_set_run_limit_all_with(90); // Set all channels to 90 seconds + +// Assuming the CONFIG_RELAY_CHN_COUNT is 4 +uint16_t limits_sec[CONFIG_RELAY_CHN_COUNT] = { 30, 35, 40, 45 }; +relay_chn_set_run_limit_all(limits_sec); // Set all channels according to the array ``` > [!NOTE] > When a channel reaches its run limit, it will automatically stop. The run limit timer is reset whenever the channel starts running in either direction. @@ -286,7 +299,7 @@ relay_chn_tilt_reverse(); relay_chn_tilt_stop(); // Set tilting sensitivity (sensitivity as percentage) -relay_chn_tilt_sensitivity_set(90); +relay_chn_tilt_set_sensitivity(90); // Get tilting sensitivity (sensitivty as percentage) uint8_t sensitivity = relay_chn_tilt_get_sensitivity(); @@ -299,27 +312,34 @@ For multi mode: // Start tilting automatically on channel #0 relay_chn_tilt_auto(0); -relay_chn_tilt_auto(RELAY_CHN_ID_ALL); // on all channels +relay_chn_tilt_auto_all(); // on all channels // Tilt forward on channel #1 relay_chn_tilt_forward(1); -relay_chn_tilt_forward(RELAY_CHN_ID_ALL); +relay_chn_tilt_forward_all(); // Tilt reverse on channel #2 relay_chn_tilt_reverse(2); -relay_chn_tilt_reverse(RELAY_CHN_ID_ALL); +relay_chn_tilt_reverse_all(); // Stop tilting on channel #0 relay_chn_tilt_stop(0); -relay_chn_tilt_stop(RELAY_CHN_ID_ALL); +relay_chn_tilt_stop_all(); // Set tilting sensitivity (sensitivity as percentage) for channel #0 -relay_chn_tilt_sensitivity_set(0, 90); -relay_chn_tilt_sensitivity_set(RELAY_CHN_ID_ALL, 90); +relay_chn_tilt_set_sensitivity(0, 90); +relay_chn_tilt_set_sensitivity_all_with(90); + +// Assuming the CONFIG_RELAY_CHN_COUNT is 4 +uint8_t sensitivities[CONFIG_RELAY_CHN_COUNT] = { 90, 85, 80, 75 }; +relay_chn_tilt_set_sensitivity_all(sensitivity); // Set all channels according to the array + +// Get tilt sensitivity for channel #0 +uint8_t sensitivity = relay_chn_tilt_get_sensitivity(0); // Get tilting sensitivity (sensitivty as percentage) -uint8_t sensitivity; -relay_chn_tilt_get_sensitivity(0, &sensitivity, sizeof(sensitivity)); +uint8_t sensitivities[CONFIG_RELAY_CHN_COUNT]; +relay_chn_tilt_get_sensitivity_all(sensitivities); ``` ## License diff --git a/include/relay_chn.h b/include/relay_chn.h index 6f7ee33..9d7a12f 100644 --- a/include/relay_chn.h +++ b/include/relay_chn.h @@ -14,7 +14,6 @@ #pragma once #include "esp_err.h" -#include "relay_chn_defs.h" #include "relay_chn_types.h" #include "relay_chn_adapter.h" @@ -75,6 +74,18 @@ void relay_chn_unregister_listener(relay_chn_state_listener_t listener); */ relay_chn_state_t relay_chn_get_state(uint8_t chn_id); +/** + * @brief Gets the current state of all relay channels. + * + * This function populates an array with the current states of all configured + * relay channels. The caller must ensure the `states` array is large enough + * to hold `CONFIG_RELAY_CHN_COUNT` elements. + * + * @param states Pointer to an array where the states will be stored. + * @return ESP_OK on success, ESP_ERR_INVALID_ARG if `states` is NULL. + */ +esp_err_t relay_chn_ctl_get_state_all(relay_chn_state_t *states); + /** * @brief Get the state string of the specified relay channel. * @@ -100,6 +111,14 @@ char *relay_chn_get_state_str(uint8_t chn_id); */ void relay_chn_run_forward(uint8_t chn_id); +/** + * @brief Commands all configured relay channels to run in the forward direction. + * + * This function iterates through all configured relay channels and issues a command + * to each to move in the forward direction. + */ +void relay_chn_ctl_run_forward_all(void); + /** * @brief Runs the relay channel in reverse. * @@ -109,6 +128,14 @@ void relay_chn_run_forward(uint8_t chn_id); */ void relay_chn_run_reverse(uint8_t chn_id); +/** + * @brief Commands all configured relay channels to run in the reverse direction. + * + * This function iterates through all configured relay channels and issues a command + * to each to move in the reverse direction. + */ +void relay_chn_ctl_run_reverse_all(void); + /** * @brief Stops the relay channel specified by the channel ID. * @@ -120,6 +147,14 @@ void relay_chn_run_reverse(uint8_t chn_id); */ void relay_chn_stop(uint8_t chn_id); +/** + * @brief Commands all configured relay channels to stop. + * + * This function iterates through all configured relay channels and issues a command + * to each to stop any ongoing movement. + */ +void relay_chn_ctl_stop_all(void); + /** * @brief Flips the direction of the specified relay channel. * @@ -131,6 +166,14 @@ void relay_chn_stop(uint8_t chn_id); */ void relay_chn_flip_direction(uint8_t chn_id); +/** + * @brief Flips the logical direction of all configured relay channels. + * + * This function iterates through all configured relay channels and swaps the + * physical GPIO pins assigned to the forward and reverse directions for each. + */ +void relay_chn_ctl_flip_direction_all(void); + /** * @brief Get the direction of the specified relay channel. * @@ -143,6 +186,18 @@ void relay_chn_flip_direction(uint8_t chn_id); */ relay_chn_direction_t relay_chn_get_direction(uint8_t chn_id); +/** + * @brief Gets the current logical direction of all configured relay channels. + * + * This function populates an array with the current logical directions of all + * configured relay channels. The caller must ensure the `directions` array is + * large enough to hold `CONFIG_RELAY_CHN_COUNT` elements. + * + * @param directions Pointer to an array where the directions will be stored. + * @return ESP_OK on success, ESP_ERR_INVALID_ARG if `directions` is NULL. + */ +esp_err_t relay_chn_ctl_get_direction_all(relay_chn_direction_t *directions); + #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT /** * @brief Get the run limit for the specified channel @@ -154,6 +209,18 @@ relay_chn_direction_t relay_chn_get_direction(uint8_t chn_id); */ uint16_t relay_chn_get_run_limit(uint8_t chn_id); +/** + * @brief Gets the configured run limits for all configured relay channels. + * + * This function populates an array with the run limits (in seconds) of all + * configured relay channels. The caller must ensure the `limits_sec` array is + * large enough to hold `CONFIG_RELAY_CHN_COUNT` elements. + * + * @param limits_sec Pointer to an array where the run limits will be stored. + * @return ESP_OK on success, ESP_ERR_INVALID_ARG if `limits_sec` is NULL. + */ +esp_err_t relay_chn_ctl_get_run_limit_all(uint16_t *limits_sec); + /** * @brief Set the run limit for the specified channel * @@ -168,6 +235,31 @@ uint16_t relay_chn_get_run_limit(uint8_t chn_id); * @param time_sec The run limit time in seconds. */ void relay_chn_set_run_limit(uint8_t chn_id, uint16_t time_sec); + +/** + * @brief Sets the run limits for all configured relay channels. + * + * This function iterates through all configured relay channels and sets their + * run limits based on the values provided in the `limits_sec` array. Each value + * will be clamped within the configured `RELAY_CHN_RUN_LIMIT_MIN_SEC` and + * `RELAY_CHN_RUN_LIMIT_MAX_SEC` boundaries. The new run limits are persisted + * in NVS if enabled. + * + * @param limits_sec Pointer to an array containing the desired run limits in seconds. + * @return ESP_OK on success, ESP_ERR_INVALID_ARG if `limits_sec` is NULL. + */ +esp_err_t relay_chn_ctl_set_run_limit_all(uint16_t *limits_sec); + +/** + * @brief Sets a single run limit value for all configured relay channels. + * + * This function sets the same `limit_sec` value for all configured relay channels. + * The value will be clamped within the configured `RELAY_CHN_RUN_LIMIT_MIN_SEC` + * and `RELAY_CHN_RUN_LIMIT_MAX_SEC` boundaries. + * @param limit_sec The desired run limit in seconds to apply to all channels. + * @return ESP_OK on success. + */ +esp_err_t relay_chn_ctl_set_run_limit_all_with(uint16_t limit_sec); #endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 @@ -184,6 +276,14 @@ void relay_chn_set_run_limit(uint8_t chn_id, uint16_t time_sec); */ void relay_chn_tilt_auto(uint8_t chn_id); +/** + * @brief Initiates an automatic tilt operation for all configured relay channels. + * + * This function iterates through all configured relay channels and initiates an + * automatic tilt operation for each, based on their individual last run commands. + */ +void relay_chn_tilt_auto_all(void); + /** * @brief Tilts the specified relay channel forward. * @@ -193,6 +293,11 @@ void relay_chn_tilt_auto(uint8_t chn_id); */ void relay_chn_tilt_forward(uint8_t chn_id); +/** + * @brief Initiates a forward tilt operation for all configured relay channels. + */ +void relay_chn_tilt_forward_all(void); + /** * @brief Tilts the specified relay channel reverse. * @@ -202,6 +307,11 @@ void relay_chn_tilt_forward(uint8_t chn_id); */ void relay_chn_tilt_reverse(uint8_t chn_id); +/** + * @brief Initiates a reverse tilt operation for all configured relay channels. + */ +void relay_chn_tilt_reverse_all(void); + /** * @brief Stops the tilting action on the specified relay channel. * @@ -211,6 +321,11 @@ void relay_chn_tilt_reverse(uint8_t chn_id); */ void relay_chn_tilt_stop(uint8_t chn_id); +/** + * @brief Stops any ongoing tilt operation for all configured relay channels. + */ +void relay_chn_tilt_stop_all(void); + /** * @brief Sets the tilting sensitivity for the specified relay channel. * @@ -222,6 +337,33 @@ void relay_chn_tilt_stop(uint8_t chn_id); */ void relay_chn_tilt_set_sensitivity(uint8_t chn_id, uint8_t sensitivity); +/** + * @brief Sets the tilt sensitivity for all configured relay channels. + * + * This function sets the tilt sensitivity for each channel based on the values + * provided in the `sensitivities` array. Each sensitivity value (0-100%) + * determines the `move_time_ms` and `pause_time_ms` for tilt operations. + * The new sensitivities are persisted in NVS if enabled. + * + * @param sensitivities Pointer to an array containing the desired tilt sensitivities. + * + * @return + * - ESP_OK: Success + * - ESP_ERR_INVALID_ARG: When sensitivities parameter is NULL + */ +esp_err_t relay_chn_tilt_set_sensitivity_all(uint8_t *sensitivities); + +/** + * @brief Sets a single tilt sensitivity value for all configured relay channels. + * + * This function sets the same `sensitivity` value for all configured relay channels. + * The sensitivity value (0-100%) determines the `move_time_ms` and `pause_time_ms` + * for tilt operations. The new sensitivities are persisted in NVS if enabled. + * + * @param sensitivity The desired tilt sensitivity in percentage (0-100) to apply to all channels. + */ +void relay_chn_tilt_set_sensitivity_all_with(uint8_t sensitivity); + /** * @brief Gets the tilting sensitivity for the specified relay channel. * @@ -235,7 +377,19 @@ void relay_chn_tilt_set_sensitivity(uint8_t chn_id, uint8_t sensitivity); * - ESP_OK: Success * - ESP_ERR_INVALID_ARG: Invalid argument */ -esp_err_t relay_chn_tilt_get_sensitivity(uint8_t chn_id, uint8_t *sensitivity, size_t length); +uint8_t relay_chn_tilt_get_sensitivity(uint8_t chn_id); + +/** + * @brief Gets the current tilt sensitivities for all configured relay channels. + * + * This function populates an array with the current tilt sensitivities (0-100%) + * of all configured relay channels. The caller must ensure the `sensitivity` array + * is large enough to hold `CONFIG_RELAY_CHN_COUNT` elements. + * + * @param sensitivity Pointer to an array where the sensitivities will be stored. + * @return ESP_OK on success, ESP_ERR_INVALID_ARG if `sensitivity` is NULL. + */ +esp_err_t relay_chn_tilt_get_sensitivity_all(uint8_t *sensitivities); #endif // CONFIG_RELAY_CHN_ENABLE_TILTING diff --git a/include/relay_chn_adapter.h b/include/relay_chn_adapter.h index 885ab95..5e00d91 100644 --- a/include/relay_chn_adapter.h +++ b/include/relay_chn_adapter.h @@ -22,6 +22,19 @@ extern "C" { */ extern relay_chn_state_t relay_chn_ctl_get_state(uint8_t chn_id); +/** + * @brief Gets the current state of all relay channels. + * + * This function populates an array with the current states of all configured + * relay channels. The caller must ensure the `states` array is large enough + * to hold `CONFIG_RELAY_CHN_COUNT` elements. + * + * @param states Pointer to an array where the states will be stored. + * + * @return ESP_OK on success, ESP_ERR_INVALID_ARG if `states` is NULL. + */ +extern esp_err_t relay_chn_ctl_get_state_all(relay_chn_state_t *states); + /** * @brief Get string representation of a relay channel's state. * @@ -33,31 +46,62 @@ extern char *relay_chn_ctl_get_state_str(uint8_t chn_id); /** * @brief Run a relay channel in forward direction. * - * @param[in] chn_id Channel ID to run forward, or RELAY_CHN_ID_ALL for all channels. + * @param[in] chn_id Channel ID to run forward. */ extern void relay_chn_ctl_run_forward(uint8_t chn_id); +/** + * @brief Commands all configured relay channels to run in the forward direction. + * + * This function iterates through all configured relay channels and issues a command + * to each to move in the forward direction. + */ +extern void relay_chn_ctl_run_forward_all(void); + /** * @brief Run a relay channel in reverse direction. * - * @param[in] chn_id Channel ID to run reverse, or RELAY_CHN_ID_ALL for all channels. + * @param[in] chn_id Channel ID to run reverse. */ extern void relay_chn_ctl_run_reverse(uint8_t chn_id); +/** + * @brief Commands all configured relay channels to run in the reverse direction. + * + * This function iterates through all configured relay channels and issues a command + * to each to move in the reverse direction. + */ +extern void relay_chn_ctl_run_reverse_all(void); + /** * @brief Stop a relay channel. * - * @param[in] chn_id Channel ID to stop, or RELAY_CHN_ID_ALL for all channels. + * @param[in] chn_id Channel ID to stop. */ extern void relay_chn_ctl_stop(uint8_t chn_id); +/** + * @brief Commands all configured relay channels to stop. + * + * This function iterates through all configured relay channels and issues a command to each to stop any ongoing movement. + */ +extern void relay_chn_ctl_stop_all(void); + /** * @brief Flip the running direction of a relay channel. * - * @param[in] chn_id Channel ID to flip direction for, or RELAY_CHN_ID_ALL for all channels. + * @param[in] chn_id Channel ID to flip direction for. */ extern void relay_chn_ctl_flip_direction(uint8_t chn_id); +/** + * @brief Flips the logical direction of all configured relay channels. + * + * This function iterates through all configured relay channels and swaps the + * physical GPIO pins assigned to the forward and reverse directions for each. + */ +extern void relay_chn_ctl_flip_direction_all(void); + /** * @brief Get the current direction of a relay channel. * @@ -66,11 +110,28 @@ extern void relay_chn_ctl_flip_direction(uint8_t chn_id); */ extern relay_chn_direction_t relay_chn_ctl_get_direction(uint8_t chn_id); +/** + * @brief Gets the current logical direction of all configured relay channels. + * + * This function populates an array with the current logical directions of all + * configured relay channels. The caller must ensure the `directions` array is + * large enough to hold `CONFIG_RELAY_CHN_COUNT` elements. + * + * @param directions Pointer to an array where the directions will be stored. + * @return ESP_OK on success, ESP_ERR_INVALID_ARG if `directions` is NULL. + */ +esp_err_t relay_chn_ctl_get_direction_all(relay_chn_direction_t *directions); + static inline relay_chn_state_t relay_chn_get_state(uint8_t chn_id) { return relay_chn_ctl_get_state(chn_id); } +static inline esp_err_t relay_chn_get_state_all(relay_chn_state_t *states) +{ + return relay_chn_ctl_get_state_all(states); +} + static inline char *relay_chn_get_state_str(uint8_t chn_id) { return relay_chn_ctl_get_state_str(chn_id); @@ -81,26 +142,51 @@ static inline void relay_chn_run_forward(uint8_t chn_id) relay_chn_ctl_run_forward(chn_id); } +static inline void relay_chn_run_forward_all(void) +{ + relay_chn_ctl_run_forward_all(); +} + static inline void relay_chn_run_reverse(uint8_t chn_id) { relay_chn_ctl_run_reverse(chn_id); } +static inline void relay_chn_run_reverse_all(void) +{ + relay_chn_ctl_run_reverse_all(); +} + static inline void relay_chn_stop(uint8_t chn_id) { relay_chn_ctl_stop(chn_id); } +static inline void relay_chn_stop_all(void) +{ + relay_chn_ctl_stop_all(); +} + static inline void relay_chn_flip_direction(uint8_t chn_id) { relay_chn_ctl_flip_direction(chn_id); } +static inline void relay_chn_flip_direction_all(void) +{ + relay_chn_ctl_flip_direction_all(); +} + static inline relay_chn_direction_t relay_chn_get_direction(uint8_t chn_id) { return relay_chn_ctl_get_direction(chn_id); } +static inline esp_err_t relay_chn_get_direction_all(relay_chn_direction_t *directions) +{ + return relay_chn_ctl_get_direction_all(directions); +} + #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT /** * @brief Get the run limit for the specified channel @@ -112,6 +198,18 @@ static inline relay_chn_direction_t relay_chn_get_direction(uint8_t chn_id) */ extern uint16_t relay_chn_ctl_get_run_limit(uint8_t chn_id); +/** + * @brief Gets the configured run limits for all configured relay channels. + * + * This function populates an array with the run limits (in seconds) of all + * configured relay channels. The caller must ensure the `limits_sec` array is + * large enough to hold `CONFIG_RELAY_CHN_COUNT` elements. + * + * @param limits_sec Pointer to an array where the run limits will be stored. + * @return ESP_OK on success, ESP_ERR_INVALID_ARG if `limits_sec` is NULL. + */ +esp_err_t relay_chn_ctl_get_run_limit_all(uint16_t *limits_sec); + /** * @brief Set the run limit for the specified channel * @@ -120,15 +218,55 @@ extern uint16_t relay_chn_ctl_get_run_limit(uint8_t chn_id); */ extern void relay_chn_ctl_set_run_limit(uint8_t chn_id, uint16_t time_sec); +/** + * @brief Sets the run limits for all configured relay channels. + * + * This function iterates through all configured relay channels and sets their + * run limits based on the values provided in the `limits_sec` array. Each value + * will be clamped within the configured `RELAY_CHN_RUN_LIMIT_MIN_SEC` and + * `RELAY_CHN_RUN_LIMIT_MAX_SEC` boundaries. The new run limits are persisted + * in NVS if enabled. + * + * @param limits_sec Pointer to an array containing the desired run limits in seconds. + * @return ESP_OK on success, ESP_ERR_INVALID_ARG if `limits_sec` is NULL. + */ +esp_err_t relay_chn_ctl_set_run_limit_all(uint16_t *limits_sec); + +/** + * @brief Sets a single run limit value for all configured relay channels. + * + * This function sets the same `limit_sec` value for all configured relay channels. + * The value will be clamped within the configured `RELAY_CHN_RUN_LIMIT_MIN_SEC` + * and `RELAY_CHN_RUN_LIMIT_MAX_SEC` boundaries. + * @param limit_sec The desired run limit in seconds to apply to all channels. + * @return ESP_OK on success. + */ +esp_err_t relay_chn_ctl_set_run_limit_all_with(uint16_t limit_sec); + static inline uint16_t relay_chn_get_run_limit(uint8_t chn_id) { return relay_chn_ctl_get_run_limit(chn_id); } +static inline esp_err_t relay_chn_get_run_limit_all(uint16_t *limits_sec) +{ + return relay_chn_ctl_get_run_limit_all(limits_sec); +} + static inline void relay_chn_set_run_limit(uint8_t chn_id, uint16_t time_sec) { relay_chn_ctl_set_run_limit(chn_id, time_sec); } + +static inline esp_err_t relay_chn_set_run_limit_all(uint16_t *limits_sec) +{ + return relay_chn_ctl_set_run_limit_all(limits_sec); +} + +static inline esp_err_t relay_chn_set_run_limit_all_with(uint16_t limit_sec) +{ + return relay_chn_ctl_set_run_limit_all_with(limit_sec); +} #endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 #else diff --git a/include/relay_chn_defs.h b/include/relay_chn_defs.h deleted file mode 100644 index b044f8f..0000000 --- a/include/relay_chn_defs.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 Kozmotronik Tech - * - * SPDX-License-Identifier: MIT - */ - - #pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#if RELAY_CHN_COUNT > 1 -#define RELAY_CHN_ID_ALL RELAY_CHN_COUNT /*!< Special ID to address all channels */ -#endif - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/include/relay_chn_types.h b/include/relay_chn_types.h index 8960cc2..8f3a5e6 100644 --- a/include/relay_chn_types.h +++ b/include/relay_chn_types.h @@ -7,7 +7,6 @@ #pragma once #include -#include "relay_chn_defs.h" #ifdef __cplusplus extern "C" { diff --git a/private_include/relay_chn_core.h b/private_include/relay_chn_core.h index 62d0e4c..c98bb33 100644 --- a/private_include/relay_chn_core.h +++ b/private_include/relay_chn_core.h @@ -9,7 +9,6 @@ #include "esp_err.h" #include "esp_log.h" #include "esp_timer.h" -#include "relay_chn_defs.h" #include "relay_chn_types.h" #include "relay_chn_priv_types.h" diff --git a/scripts/run_tests.sh b/scripts/run_tests.sh index 2320339..5cd050e 100755 --- a/scripts/run_tests.sh +++ b/scripts/run_tests.sh @@ -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") +valid_test_tags=("core" "tilt" "listener" "all" "relay_chn" "nvs" "run_limit" "batch") arg_tag="all" # Default to 'all' if no tag specified arg_clean=false arg_log=false @@ -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|all] Specify which test tag to run." + echo " -t, --tag [relay_chn|core|tilt|listener|nvs|run_limit|batch|all] Specify which test tag to run." echo "" echo " If no tag is specified, it defaults to 'all'." echo "" diff --git a/src/relay_chn_core.c b/src/relay_chn_core.c index f57969e..f062e1b 100644 --- a/src/relay_chn_core.c +++ b/src/relay_chn_core.c @@ -407,7 +407,7 @@ void relay_chn_issue_cmd(relay_chn_ctl_t* chn_ctl, relay_chn_cmd_t cmd) #if CONFIG_RELAY_CHN_COUNT > 1 bool relay_chn_is_channel_id_valid(uint8_t chn_id) { - bool valid = (chn_id < CONFIG_RELAY_CHN_COUNT) || chn_id == RELAY_CHN_ID_ALL; + bool valid = chn_id < CONFIG_RELAY_CHN_COUNT; if (!valid) { ESP_LOGE(TAG, "Invalid channel ID: %d", chn_id); } diff --git a/src/relay_chn_ctl_multi.c b/src/relay_chn_ctl_multi.c index 212b941..14bf3a4 100644 --- a/src/relay_chn_ctl_multi.c +++ b/src/relay_chn_ctl_multi.c @@ -72,20 +72,33 @@ void relay_chn_ctl_deinit() relay_chn_state_t relay_chn_ctl_get_state(uint8_t chn_id) { - if (!relay_chn_is_channel_id_valid(chn_id) || chn_id == RELAY_CHN_ID_ALL) { - return RELAY_CHN_STATE_UNDEFINED; + return relay_chn_is_channel_id_valid(chn_id) ? + chn_ctls[chn_id].state : RELAY_CHN_STATE_UNDEFINED; +} + +esp_err_t relay_chn_ctl_get_state_all(relay_chn_state_t *states) +{ + ESP_RETURN_ON_FALSE(states != NULL, ESP_ERR_INVALID_ARG, TAG, "states cannot be NULL"); + + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + relay_chn_state_t *dest_state = &states[i]; + if (dest_state == NULL) { + ESP_LOGW(TAG, "get_state_all: States have been copied until channel %d since states[%d] is NULL", i, i); + break; + } + *dest_state = chn_ctls[i].state; } - return chn_ctls[chn_id].state; + return ESP_OK; } char *relay_chn_ctl_get_state_str(uint8_t chn_id) { - if (!relay_chn_is_channel_id_valid(chn_id) || chn_id == RELAY_CHN_ID_ALL) { - return relay_chn_state_str(RELAY_CHN_STATE_UNDEFINED); - } - return relay_chn_state_str(chn_ctls[chn_id].state); + return relay_chn_is_channel_id_valid(chn_id) + ? relay_chn_state_str(chn_ctls[chn_id].state) + : relay_chn_state_str(RELAY_CHN_STATE_UNDEFINED); } + static void relay_chn_ctl_issue_cmd_on_all_channels(relay_chn_cmd_t cmd) { for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { @@ -95,94 +108,142 @@ static void relay_chn_ctl_issue_cmd_on_all_channels(relay_chn_cmd_t cmd) void relay_chn_ctl_run_forward(uint8_t chn_id) { - if (!relay_chn_is_channel_id_valid(chn_id)) return; + if (relay_chn_is_channel_id_valid(chn_id)) + relay_chn_issue_cmd(&chn_ctls[chn_id], RELAY_CHN_CMD_FORWARD); +} - if (chn_id == RELAY_CHN_ID_ALL) { - relay_chn_ctl_issue_cmd_on_all_channels(RELAY_CHN_CMD_FORWARD); - return; - } - relay_chn_issue_cmd(&chn_ctls[chn_id], RELAY_CHN_CMD_FORWARD); +void relay_chn_ctl_run_forward_all() +{ + relay_chn_ctl_issue_cmd_on_all_channels(RELAY_CHN_CMD_FORWARD); } void relay_chn_ctl_run_reverse(uint8_t chn_id) { - if (!relay_chn_is_channel_id_valid(chn_id)) return; + if (relay_chn_is_channel_id_valid(chn_id)) + relay_chn_issue_cmd(&chn_ctls[chn_id], RELAY_CHN_CMD_REVERSE); +} - if (chn_id == RELAY_CHN_ID_ALL) { - relay_chn_ctl_issue_cmd_on_all_channels(RELAY_CHN_CMD_REVERSE); - return; - } - relay_chn_issue_cmd(&chn_ctls[chn_id], RELAY_CHN_CMD_REVERSE); +void relay_chn_ctl_run_reverse_all() +{ + relay_chn_ctl_issue_cmd_on_all_channels(RELAY_CHN_CMD_REVERSE); } void relay_chn_ctl_stop(uint8_t chn_id) { - if (!relay_chn_is_channel_id_valid(chn_id)) return; + if (relay_chn_is_channel_id_valid(chn_id)) + relay_chn_issue_cmd(&chn_ctls[chn_id], RELAY_CHN_CMD_STOP); +} - if (chn_id == RELAY_CHN_ID_ALL) { - relay_chn_ctl_issue_cmd_on_all_channels(RELAY_CHN_CMD_STOP); - return; - } - relay_chn_issue_cmd(&chn_ctls[chn_id], RELAY_CHN_CMD_STOP); +void relay_chn_ctl_stop_all() +{ + relay_chn_ctl_issue_cmd_on_all_channels(RELAY_CHN_CMD_STOP); } void relay_chn_ctl_flip_direction(uint8_t chn_id) { - if (!relay_chn_is_channel_id_valid(chn_id)) return; + if (relay_chn_is_channel_id_valid(chn_id)) + relay_chn_issue_cmd(&chn_ctls[chn_id], RELAY_CHN_CMD_FLIP); +} - if (chn_id == RELAY_CHN_ID_ALL) { - relay_chn_ctl_issue_cmd_on_all_channels(RELAY_CHN_CMD_FLIP); - return; - } - relay_chn_issue_cmd(&chn_ctls[chn_id], RELAY_CHN_CMD_FLIP); +void relay_chn_ctl_flip_direction_all() +{ + relay_chn_ctl_issue_cmd_on_all_channels(RELAY_CHN_CMD_FLIP); } relay_chn_direction_t relay_chn_ctl_get_direction(uint8_t chn_id) { - if (!relay_chn_is_channel_id_valid(chn_id)) { - return RELAY_CHN_DIRECTION_DEFAULT; + return relay_chn_is_channel_id_valid(chn_id) + ? relay_chn_output_get_direction(chn_ctls[chn_id].output) + : RELAY_CHN_DIRECTION_DEFAULT; +} + +esp_err_t relay_chn_ctl_get_direction_all(relay_chn_direction_t *directions) +{ + ESP_RETURN_ON_FALSE(directions != NULL, ESP_ERR_INVALID_ARG, TAG, "directions cannot be NULL"); + + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + relay_chn_direction_t *dest_direction = &directions[i]; + if (dest_direction == NULL) { + ESP_LOGW(TAG, "get_direction_all: Directions have been copied until channel %d since directions[%d] is NULL", i, i); + break; + } + *dest_direction = relay_chn_output_get_direction(chn_ctls[i].output); } - relay_chn_ctl_t *chn_ctl = &chn_ctls[chn_id]; - return relay_chn_output_get_direction(chn_ctl->output); + return ESP_OK; } #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT uint16_t relay_chn_ctl_get_run_limit(uint8_t chn_id) { - if (!relay_chn_is_channel_id_valid(chn_id) || chn_id == RELAY_CHN_ID_ALL) { - ESP_LOGE(TAG, "get_run_limit: Invalid channel ID: %d", chn_id); - return 0; - } - return chn_ctls[chn_id].run_limit_sec; + return relay_chn_is_channel_id_valid(chn_id) ? chn_ctls[chn_id].run_limit_sec : 0; } -void relay_chn_ctl_set_run_limit(uint8_t chn_id, uint16_t time_sec) +esp_err_t relay_chn_ctl_get_run_limit_all(uint16_t *limits_sec) { - if (!relay_chn_is_channel_id_valid(chn_id) || chn_id == RELAY_CHN_ID_ALL) { + ESP_RETURN_ON_FALSE(limits_sec != NULL, ESP_ERR_INVALID_ARG, TAG, "limits_sec cannot be NULL"); + + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + uint16_t *dest_limit_sec = &limits_sec[i]; + if (dest_limit_sec == NULL) { + ESP_LOGW(TAG, "get_run_limit_all: Run limits have been copied until channel %d since limits_sec[%d] is NULL", i, i); + break; + } + *dest_limit_sec = chn_ctls[i].run_limit_sec; + } + return ESP_OK; +} + +static void relay_chn_ctl_set_run_limit_common(uint8_t chn_id, uint16_t limit_sec) +{ + // Check for boundaries + if (limit_sec > CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC) + limit_sec = CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC; + else if (limit_sec < CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC) + limit_sec = CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC; + + chn_ctls[chn_id].run_limit_sec = limit_sec; + +#if CONFIG_RELAY_CHN_ENABLE_NVS + relay_chn_nvs_set_run_limit(chn_id, limit_sec); +#endif +} + +void relay_chn_ctl_set_run_limit(uint8_t chn_id, uint16_t limit_sec) +{ + if (!relay_chn_is_channel_id_valid(chn_id)) { ESP_LOGE(TAG, "set_run_limit: Invalid channel ID: %d", chn_id); return; } - - // Check for boundaries - if (time_sec > CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC) - time_sec = CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC; - else if (time_sec < CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC) - time_sec = CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC; + relay_chn_ctl_set_run_limit_common(chn_id, limit_sec); +} - chn_ctls[chn_id].run_limit_sec = time_sec; +esp_err_t relay_chn_ctl_set_run_limit_all(uint16_t *limits_sec) +{ + ESP_RETURN_ON_FALSE(limits_sec != NULL, ESP_ERR_INVALID_ARG, TAG, "limits_sec cannot be NULL"); -#if CONFIG_RELAY_CHN_ENABLE_NVS - relay_chn_nvs_set_run_limit(chn_id, time_sec); -#endif -} + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + uint16_t *src_limit_sec = &limits_sec[i]; + if (src_limit_sec == NULL) { + ESP_LOGW(TAG, "set_run_limit_all: Run limits have been set until channel %d since limits_sec[%d] is NULL", i, i); + break; + } + relay_chn_ctl_set_run_limit_common(i, *src_limit_sec); + } + return ESP_OK; +} + +esp_err_t relay_chn_ctl_set_run_limit_all_with(uint16_t limit_sec) +{ + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + relay_chn_ctl_set_run_limit_common(i, limit_sec); + } + return ESP_OK; +} #endif relay_chn_ctl_t *relay_chn_ctl_get(uint8_t chn_id) { - if (!relay_chn_is_channel_id_valid(chn_id)) { - return NULL; - } - return &chn_ctls[chn_id]; + return relay_chn_is_channel_id_valid(chn_id) ? &chn_ctls[chn_id] : NULL; } relay_chn_ctl_t *relay_chn_ctl_get_all(void) diff --git a/src/relay_chn_output.c b/src/relay_chn_output.c index b7ea44e..b0d2c0f 100644 --- a/src/relay_chn_output.c +++ b/src/relay_chn_output.c @@ -6,7 +6,6 @@ #include "esp_check.h" #include "esp_log.h" -#include "relay_chn_defs.h" #include "relay_chn_output.h" #include "relay_chn_core.h" diff --git a/src/relay_chn_tilt.c b/src/relay_chn_tilt.c index c48c976..99f2409 100644 --- a/src/relay_chn_tilt.c +++ b/src/relay_chn_tilt.c @@ -106,6 +106,7 @@ static void relay_chn_tilt_issue_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_t // Set the command that will be processed tilt_ctl->cmd = cmd; + ESP_LOGI(TAG, "relay_chn_tilt_issue_cmd: Command-chn: %d-%d", cmd, tilt_ctl->chn_ctl->id); // TODO delete switch (tilt_ctl->chn_ctl->state) { case RELAY_CHN_STATE_IDLE: // Relay channel is free, tilt can be issued immediately @@ -176,78 +177,67 @@ static void relay_chn_tilt_issue_auto(relay_chn_tilt_ctl_t *tilt_ctl) } } + #if CONFIG_RELAY_CHN_COUNT > 1 -void relay_chn_tilt_auto(uint8_t chn_id) -{ - if (!relay_chn_is_channel_id_valid(chn_id)) { - return; - } - - // Execute for all channels - if (chn_id == RELAY_CHN_ID_ALL) { - for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { - relay_chn_tilt_issue_auto(&tilt_ctls[i]); - } - } - // Execute for a single channel - else { - relay_chn_tilt_ctl_t* tilt_ctl = &tilt_ctls[chn_id]; - relay_chn_tilt_issue_auto(tilt_ctl); - } -} - static void relay_chn_tilt_issue_cmd_on_all_channels(relay_chn_tilt_cmd_t cmd) { for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { relay_chn_tilt_ctl_t* tilt_ctl = &tilt_ctls[i]; + ESP_LOGI(TAG, "issue_cmd_on_all_channels: Command|chn|ctl.id: %d|%d|%d", cmd, i, tilt_ctl->chn_ctl->id); // TODO delete relay_chn_tilt_issue_cmd(tilt_ctl, cmd); } } +void relay_chn_tilt_auto(uint8_t chn_id) +{ + if (relay_chn_is_channel_id_valid(chn_id)) { + relay_chn_tilt_issue_auto(&tilt_ctls[chn_id]); + } +} + +void relay_chn_tilt_auto_all() +{ + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + relay_chn_tilt_issue_auto(&tilt_ctls[i]); + } +} + void relay_chn_tilt_forward(uint8_t chn_id) { - if (!relay_chn_is_channel_id_valid(chn_id)) { - return; - } - - if (chn_id == RELAY_CHN_ID_ALL) - relay_chn_tilt_issue_cmd_on_all_channels(RELAY_CHN_TILT_CMD_FORWARD); - else { - relay_chn_tilt_ctl_t* tilt_ctl = &tilt_ctls[chn_id]; - relay_chn_tilt_issue_cmd(tilt_ctl, RELAY_CHN_TILT_CMD_FORWARD); + if (relay_chn_is_channel_id_valid(chn_id)) { + relay_chn_tilt_issue_cmd(&tilt_ctls[chn_id], RELAY_CHN_TILT_CMD_FORWARD); } } +void relay_chn_tilt_forward_all() +{ + relay_chn_tilt_issue_cmd_on_all_channels(RELAY_CHN_TILT_CMD_FORWARD); +} + void relay_chn_tilt_reverse(uint8_t chn_id) { - if (!relay_chn_is_channel_id_valid(chn_id)) { - return; + if (relay_chn_is_channel_id_valid(chn_id)) { + relay_chn_tilt_issue_cmd(&tilt_ctls[chn_id], RELAY_CHN_TILT_CMD_REVERSE); } - - if (chn_id == RELAY_CHN_ID_ALL) +} + +void relay_chn_tilt_reverse_all() +{ relay_chn_tilt_issue_cmd_on_all_channels(RELAY_CHN_TILT_CMD_REVERSE); - else { - relay_chn_tilt_ctl_t* tilt_ctl = &tilt_ctls[chn_id]; - relay_chn_tilt_issue_cmd(tilt_ctl, RELAY_CHN_TILT_CMD_REVERSE); - } } void relay_chn_tilt_stop(uint8_t chn_id) { if (!relay_chn_is_channel_id_valid(chn_id)) { - return; - } - - if (chn_id == RELAY_CHN_ID_ALL) { - for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { - relay_chn_tilt_dispatch_cmd(&tilt_ctls[i], RELAY_CHN_TILT_CMD_STOP); - } - } - else { relay_chn_tilt_dispatch_cmd(&tilt_ctls[chn_id], RELAY_CHN_TILT_CMD_STOP); } } +void relay_chn_tilt_stop_all() +{ + relay_chn_tilt_issue_cmd_on_all_channels(RELAY_CHN_TILT_CMD_STOP); +} + #else // CONFIG_RELAY_CHN_COUNT > 1 void relay_chn_tilt_auto() @@ -317,45 +307,61 @@ static void relay_chn_tilt_compute_set_sensitivity(relay_chn_tilt_ctl_t *tilt_ct #if CONFIG_RELAY_CHN_COUNT > 1 void relay_chn_tilt_set_sensitivity(uint8_t chn_id, uint8_t sensitivity) { - if (!relay_chn_is_channel_id_valid(chn_id)) { - return; - } - - if (chn_id == RELAY_CHN_ID_ALL) { - for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { - relay_chn_tilt_compute_set_sensitivity(&tilt_ctls[i], sensitivity); - } - } - else { + if (relay_chn_is_channel_id_valid(chn_id)) { relay_chn_tilt_compute_set_sensitivity(&tilt_ctls[chn_id], sensitivity); - } - + #if CONFIG_RELAY_CHN_ENABLE_NVS - relay_chn_nvs_set_tilt_sensitivity(chn_id, sensitivity); + relay_chn_nvs_set_tilt_sensitivity(chn_id, sensitivity); #endif // CONFIG_RELAY_CHN_ENABLE_NVS + } } -esp_err_t relay_chn_tilt_get_sensitivity(uint8_t chn_id, uint8_t *sensitivity, size_t length) +esp_err_t relay_chn_tilt_set_sensitivity_all(uint8_t *sensitivities) { - if (!relay_chn_is_channel_id_valid(chn_id)) { - return ESP_ERR_INVALID_ARG; - } - if (sensitivity == NULL) { - ESP_LOGD(TAG, "relay_chn_tilt_get_sensitivity: sensitivity is NULL"); - return ESP_ERR_INVALID_ARG; - } - if (chn_id == RELAY_CHN_ID_ALL) { - if (length < CONFIG_RELAY_CHN_COUNT) { - ESP_LOGD(TAG, "relay_chn_tilt_get_sensitivity: length is too short to store all sensitivity values"); - return ESP_ERR_INVALID_ARG; + ESP_RETURN_ON_FALSE(sensitivities != NULL, ESP_ERR_INVALID_ARG, TAG, "set_sensitivity_all: sensitivities is NULL"); + + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + uint8_t *src_sensitivity = &sensitivities[i]; + if (src_sensitivity == NULL) { + ESP_LOGW(TAG, "set_sensitivity_all: Run limits have been set until channel %d since sensitivities[%d] is NULL", i, i); + break; } + relay_chn_tilt_compute_set_sensitivity(&tilt_ctls[i], *src_sensitivity); +#if CONFIG_RELAY_CHN_ENABLE_NVS + relay_chn_nvs_set_tilt_sensitivity(i, *src_sensitivity); +#endif // CONFIG_RELAY_CHN_ENABLE_NVS + } + return ESP_OK; +} - for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { - sensitivity[i] = tilt_ctls[i].tilt_timing.sensitivity; - } - return ESP_OK; +void relay_chn_tilt_set_sensitivity_all_with(uint8_t sensitivity) +{ + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + relay_chn_tilt_compute_set_sensitivity(&tilt_ctls[i], sensitivity); +#if CONFIG_RELAY_CHN_ENABLE_NVS + relay_chn_nvs_set_tilt_sensitivity(i, sensitivity); +#endif // CONFIG_RELAY_CHN_ENABLE_NVS + } +} + +uint8_t relay_chn_tilt_get_sensitivity(uint8_t chn_id) +{ + return relay_chn_is_channel_id_valid(chn_id) ? + tilt_ctls[chn_id].tilt_timing.sensitivity : 0; +} + +esp_err_t relay_chn_tilt_get_sensitivity_all(uint8_t *sensitivities) +{ + ESP_RETURN_ON_FALSE(sensitivities != NULL, ESP_ERR_INVALID_ARG, TAG, "get_sensitivity_all: sensitivities is NULL"); + + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + uint8_t *dest_sensitivity = &sensitivities[i]; + if (dest_sensitivity == NULL) { + ESP_LOGW(TAG, "get_sensitivity_all: Sensitivites have been copied until channel %d since sensitivities[%d] is NULL", i, i); + break; + } + *dest_sensitivity = tilt_ctls[i].tilt_timing.sensitivity; } - *sensitivity = tilt_ctls[chn_id].tilt_timing.sensitivity; return ESP_OK; } @@ -547,6 +553,7 @@ static void relay_chn_tilt_execute_pause(relay_chn_tilt_ctl_t *tilt_ctl) esp_err_t relay_chn_tilt_dispatch_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_tilt_cmd_t cmd) { ESP_LOGD(TAG, "relay_chn_tilt_dispatch_cmd: Command: %d", cmd); + ESP_LOGI(TAG, "tilt_dispatch_cmd: Command-chn: %d-%d", cmd, tilt_ctl->chn_ctl->id); // TODO delete switch(cmd) { case RELAY_CHN_TILT_CMD_STOP: @@ -666,8 +673,9 @@ esp_err_t relay_chn_tilt_init(relay_chn_ctl_t *chn_ctls) #if CONFIG_RELAY_CHN_COUNT > 1 for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + esp_err_t ret; #if CONFIG_RELAY_CHN_ENABLE_NVS - esp_err_t ret = relay_chn_tilt_load_sensitivity(i, &sensitivity); + ret = relay_chn_tilt_load_sensitivity(i, &sensitivity); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load tilt sensitivity for channel %d", i); ret = relay_chn_tilt_load_tilt_count(i, &tilt_count); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load tilt count for channel %d", i); diff --git a/test_apps/main/test_common.c b/test_apps/main/test_common.c index 3db95de..21986a4 100644 --- a/test_apps/main/test_common.c +++ b/test_apps/main/test_common.c @@ -39,7 +39,7 @@ const uint8_t gpio_count = sizeof(gpio_map) / sizeof(gpio_map[0]); void reset_channels_to_idle_state() { #if CONFIG_RELAY_CHN_COUNT > 1 - relay_chn_stop(RELAY_CHN_ID_ALL); + relay_chn_stop_all(); vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i)); diff --git a/test_apps/main/test_relay_chn_core_multi.c b/test_apps/main/test_relay_chn_core_multi.c index f684535..b33ab9a 100644 --- a/test_apps/main/test_relay_chn_core_multi.c +++ b/test_apps/main/test_relay_chn_core_multi.c @@ -70,11 +70,10 @@ TEST_CASE("Relay channels run reverse and update state", "[relay_chn][core]") { } -// ### Broadcast Command (RELAY_CHN_ID_ALL) Tests - -TEST_CASE("run_forward with ID_ALL sets all channels to FORWARD", "[relay_chn][core][id_all]") +// ### Batch Control Tests +TEST_CASE("run_forward_all sets all channels to FORWARD", "[relay_chn][core][batch]") { - relay_chn_run_forward(RELAY_CHN_ID_ALL); + relay_chn_run_forward_all(); vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); for (uint8_t i = 0; i < relay_chn_count; i++) { @@ -82,9 +81,9 @@ TEST_CASE("run_forward with ID_ALL sets all channels to FORWARD", "[relay_chn][c } } -TEST_CASE("run_reverse with ID_ALL sets all channels to REVERSE", "[relay_chn][core][id_all]") +TEST_CASE("run_reverse_all sets all channels to REVERSE", "[relay_chn][core][batch]") { - relay_chn_run_reverse(RELAY_CHN_ID_ALL); + relay_chn_run_reverse_all(); vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); for (uint8_t i = 0; i < relay_chn_count; i++) { @@ -92,14 +91,14 @@ TEST_CASE("run_reverse with ID_ALL sets all channels to REVERSE", "[relay_chn][c } } -TEST_CASE("stop with ID_ALL stops all running channels", "[relay_chn][core][id_all]") +TEST_CASE("stop_all stops all running channels", "[relay_chn][core][batch]") { // 1. Start all channels forward to ensure they are in a known running state - relay_chn_run_forward(RELAY_CHN_ID_ALL); + relay_chn_run_forward_all(); vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); - // 2. Stop all channels using the broadcast command - relay_chn_stop(RELAY_CHN_ID_ALL); + // 2. Stop all channels + relay_chn_stop_all(); vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); // 3. Verify all channels have transitioned to the FREE state @@ -138,7 +137,7 @@ TEST_CASE("Get state returns UNDEFINED when id is invalid", "[relay_chn][core]") TEST_ASSERT_EQUAL(RELAY_CHN_STATE_UNDEFINED, relay_chn_get_state(invalid_id)); } // Test for running states also - relay_chn_run_forward(RELAY_CHN_ID_ALL); + relay_chn_run_forward_all(); vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); for (uint8_t i = 0; i < relay_chn_count; i++) { int invalid_id = relay_chn_count * 2 + i; @@ -153,7 +152,7 @@ TEST_CASE("Get state string returns UNKNOWN when id is invalid", "[relay_chn][co TEST_ASSERT_EQUAL_STRING("UNKNOWN", relay_chn_get_state_str(invalid_id)); } // Test for running states also - relay_chn_run_forward(RELAY_CHN_ID_ALL); + relay_chn_run_forward_all(); vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); for (uint8_t i = 0; i < relay_chn_count; i++) { int invalid_id = relay_chn_count * 2 + i; @@ -295,10 +294,10 @@ TEST_CASE("Single channel direction can be flipped", "[relay_chn][core][directio TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(ch)); } -TEST_CASE("All channels direction can be flipped simultaneously", "[relay_chn][core][direction][id_all]") +TEST_CASE("All channels direction can be flipped simultaneously", "[relay_chn][core][direction][batch]") { // 1. Flip all channels - relay_chn_flip_direction(RELAY_CHN_ID_ALL); + relay_chn_flip_direction_all(); vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); // 2. Verify all channels are flipped @@ -307,7 +306,7 @@ TEST_CASE("All channels direction can be flipped simultaneously", "[relay_chn][c } // 3. Flip all back - relay_chn_flip_direction(RELAY_CHN_ID_ALL); + relay_chn_flip_direction_all(); vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); // 4. Verify all channels are back to default @@ -380,7 +379,7 @@ TEST_CASE("Test run limit stops channel after timeout", "[relay_chn][run_limit]" relay_chn_set_run_limit(i, TEST_SHORT_RUN_LIMIT_SEC); } - relay_chn_run_forward(RELAY_CHN_ID_ALL); + relay_chn_run_forward_all(); for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { // Check running forward @@ -407,7 +406,7 @@ TEST_CASE("Test run limit reset on direction change and time out finally", "[rel vTaskDelay(1000 / portTICK_PERIOD_MS); // Wait 1 second // Change direction before timeout - relay_chn_run_reverse(RELAY_CHN_ID_ALL); + relay_chn_run_reverse_all(); // Wait for the inertia period (after which the reverse command will be dispatched) vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); diff --git a/test_apps/main/test_relay_chn_tilt_multi.c b/test_apps/main/test_relay_chn_tilt_multi.c index 2f17e47..bc4aa32 100644 --- a/test_apps/main/test_relay_chn_tilt_multi.c +++ b/test_apps/main/test_relay_chn_tilt_multi.c @@ -162,7 +162,7 @@ TEST_CASE("Tilt Forward to Run Reverse transition without inertia", "[relay_chn] } // TEST_CASE: Test stopping from a tilt state (no inertia for stop command itself) -// Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_stop) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_IDLE +// Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_tilt_stop) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_IDLE TEST_CASE("Tilt to Stop transition without immediate inertia for stop", "[relay_chn][tilt][inertia]") { uint8_t ch = 0; @@ -173,15 +173,15 @@ TEST_CASE("Tilt to Stop transition without immediate inertia for stop", "[relay_ TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch)); // 2. Issue stop command - relay_chn_stop(ch); + relay_chn_tilt_stop(ch); // Stop command should apply immediately, setting state to FREE since last state was tilt. vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(ch)); } -// ### Tilt Broadcast Command (RELAY_CHN_ID_ALL) Tests +// ### Batch Tilt Control Tests -TEST_CASE("tilt_forward with ID_ALL sets all channels to TILT_FORWARD", "[relay_chn][tilt][id_all]") +TEST_CASE("tilt_forward_all sets all channels to TILT_FORWARD", "[relay_chn][tilt][batch]") { // 1. Prepare all channels. for (uint8_t i = 0; i < relay_chn_count; i++) { @@ -189,16 +189,17 @@ TEST_CASE("tilt_forward with ID_ALL sets all channels to TILT_FORWARD", "[relay_ } // 2. Issue tilt forward to all channels - relay_chn_tilt_forward(RELAY_CHN_ID_ALL); + relay_chn_tilt_forward_all(); vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Tilt from FREE doesn't have stop-inertia // 3. Verify all channels are tilting forward for (uint8_t i = 0; i < relay_chn_count; i++) { + ESP_LOGI(TEST_TAG, "Checking channel %d", i); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(i)); } } -TEST_CASE("tilt_reverse with ID_ALL sets all channels to TILT_REVERSE", "[relay_chn][tilt][id_all]") +TEST_CASE("tilt_reverse_all sets all channels to TILT_REVERSE", "[relay_chn][tilt][batch]") { // 1. Prepare all channels. for (uint8_t i = 0; i < relay_chn_count; i++) { @@ -206,7 +207,7 @@ TEST_CASE("tilt_reverse with ID_ALL sets all channels to TILT_REVERSE", "[relay_ } // 2. Issue tilt reverse to all channels - relay_chn_tilt_reverse(RELAY_CHN_ID_ALL); + relay_chn_tilt_reverse_all(); vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // 3. Verify all channels are tilting reverse @@ -215,26 +216,27 @@ TEST_CASE("tilt_reverse with ID_ALL sets all channels to TILT_REVERSE", "[relay_ } } -TEST_CASE("tilt_stop with ID_ALL stops all tilting channels", "[relay_chn][tilt][id_all]") +TEST_CASE("tilt_stop_all stops all tilting channels", "[relay_chn][tilt][batch]") { // 1. Prepare and start all channels tilting forward for (uint8_t i = 0; i < relay_chn_count; i++) { prepare_channel_for_tilt(i, RELAY_CHN_CMD_REVERSE); } - relay_chn_tilt_forward(RELAY_CHN_ID_ALL); + relay_chn_tilt_forward_all(); vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // 2. Stop tilting on all channels - relay_chn_tilt_stop(RELAY_CHN_ID_ALL); + relay_chn_tilt_stop_all(); vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); // 3. Verify all channels are free for (uint8_t i = 0; i < relay_chn_count; i++) { + ESP_LOGI(TEST_TAG, "Checking channel %d", i); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i)); } } -TEST_CASE("tilt_auto with ID_ALL tilts channels based on last run direction", "[relay_chn][tilt][id_all]") +TEST_CASE("tilt_auto_all tilts channels based on last run direction", "[relay_chn][tilt][batch]") { // This test requires at least 2 channels to demonstrate different behaviors TEST_ASSERT_GREATER_OR_EQUAL_MESSAGE(2, relay_chn_count, "Test requires at least 2 channels"); @@ -244,7 +246,7 @@ TEST_CASE("tilt_auto with ID_ALL tilts channels based on last run direction", "[ prepare_channel_for_tilt(1, RELAY_CHN_CMD_REVERSE); // 2. Issue auto tilt command to all channels - relay_chn_tilt_auto(RELAY_CHN_ID_ALL); + relay_chn_tilt_auto_all(); vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Tilt from FREE state is dispatched immediately // 3. Verify channel 0 tilts forward (last run was forward) and channel 1 tilts reverse (last run was reverse) @@ -273,26 +275,23 @@ TEST_CASE("relay_chn_tilt_auto chooses correct direction", "[relay_chn][tilt][au // Test sensitivity set/get TEST_CASE("relay_chn_tilt_set_sensitivity and get", "[relay_chn][tilt][sensitivity]") { uint8_t ch = 0; - uint8_t val = 0; relay_chn_tilt_set_sensitivity(ch, 0); - TEST_ESP_OK(relay_chn_tilt_get_sensitivity(ch, &val, 1)); - TEST_ASSERT_EQUAL_UINT8(0, val); + TEST_ASSERT_EQUAL_UINT8(0, relay_chn_tilt_get_sensitivity(ch)); relay_chn_tilt_set_sensitivity(ch, 50); - TEST_ESP_OK(relay_chn_tilt_get_sensitivity(ch, &val, 1)); - TEST_ASSERT_EQUAL_UINT8(50, val); + TEST_ASSERT_EQUAL_UINT8(50, relay_chn_tilt_get_sensitivity(ch)); relay_chn_tilt_set_sensitivity(ch, 100); - TEST_ESP_OK(relay_chn_tilt_get_sensitivity(ch, &val, 1)); - TEST_ASSERT_EQUAL_UINT8(100, val); - + TEST_ASSERT_EQUAL_UINT8(100, relay_chn_tilt_get_sensitivity(ch)); // Set all channels - relay_chn_tilt_set_sensitivity(RELAY_CHN_ID_ALL, 42); + relay_chn_tilt_set_sensitivity_all_with(42); + uint8_t vals[CONFIG_RELAY_CHN_COUNT] = {0}; - TEST_ESP_OK(relay_chn_tilt_get_sensitivity(RELAY_CHN_ID_ALL, vals, relay_chn_count)); - for (int i = 0; i < relay_chn_count; ++i) { - TEST_ASSERT_EQUAL_UINT8(42, vals[i]); - } + uint8_t expect[CONFIG_RELAY_CHN_COUNT]; + memset(expect, 42, CONFIG_RELAY_CHN_COUNT); + + TEST_ESP_OK(relay_chn_tilt_get_sensitivity_all(vals)); + TEST_ASSERT_EQUAL_UINT8_ARRAY(expect, vals, CONFIG_RELAY_CHN_COUNT); } // Test tilt counter logic: forward x3, reverse x3, extra reverse fails From 396a02b5ae4cd548c91490b557a40fe64152f606 Mon Sep 17 00:00:00 2001 From: ismail Date: Tue, 26 Aug 2025 09:23:06 +0300 Subject: [PATCH 23/69] Cleanup and replace constants - Delete unused declaration of `g_is_component_initialized`. - Replace the following constants with approprite config options: + `relay_chn_count` > `CONFIG_RELAY_CHN_COUNT` + `opposite_inerta_ms` > `CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS` - Replace the definition of the `test_delay_margin_ms` constant with `#define TEST_DELAY_MARGIN_MS 50` for preprocessor calculations. --- test_apps/main/test_common.c | 8 +- test_apps/main/test_common.h | 7 +- test_apps/main/test_relay_chn_core_multi.c | 114 +++++++++--------- test_apps/main/test_relay_chn_core_single.c | 44 +++---- .../main/test_relay_chn_listener_multi.c | 8 +- .../main/test_relay_chn_listener_single.c | 8 +- test_apps/main/test_relay_chn_tilt_multi.c | 82 ++++++------- test_apps/main/test_relay_chn_tilt_single.c | 58 ++++----- 8 files changed, 160 insertions(+), 169 deletions(-) diff --git a/test_apps/main/test_common.c b/test_apps/main/test_common.c index 21986a4..1dcdfad 100644 --- a/test_apps/main/test_common.c +++ b/test_apps/main/test_common.c @@ -2,10 +2,6 @@ const char *TEST_TAG = "RELAY_CHN_TEST"; -const uint8_t relay_chn_count = CONFIG_RELAY_CHN_COUNT; -const uint32_t opposite_inertia_ms = CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS; -const uint32_t test_delay_margin_ms = 50; // ms tolerance - // Test-wide GPIO map #if CONFIG_RELAY_CHN_COUNT > 1 const uint8_t gpio_map[] = { @@ -40,13 +36,13 @@ void reset_channels_to_idle_state() { #if CONFIG_RELAY_CHN_COUNT > 1 relay_chn_stop_all(); - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i)); } #else relay_chn_stop(); - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); #endif } \ No newline at end of file diff --git a/test_apps/main/test_common.h b/test_apps/main/test_common.h index 7a17e80..1b9c94a 100644 --- a/test_apps/main/test_common.h +++ b/test_apps/main/test_common.h @@ -13,14 +13,9 @@ extern const char *TEST_TAG; // GPIO configurations extern const uint8_t gpio_map[]; extern const uint8_t gpio_count; -extern const uint8_t relay_chn_count; // Config variables for tests -extern const uint32_t opposite_inertia_ms; -extern const uint32_t test_delay_margin_ms; - -// Init state -extern bool g_is_component_initialized; +#define TEST_DELAY_MARGIN_MS 50 // Reset channels to Idle state void reset_channels_to_idle_state(void); \ No newline at end of file diff --git a/test_apps/main/test_relay_chn_core_multi.c b/test_apps/main/test_relay_chn_core_multi.c index b33ab9a..e24a387 100644 --- a/test_apps/main/test_relay_chn_core_multi.c +++ b/test_apps/main/test_relay_chn_core_multi.c @@ -22,28 +22,28 @@ TEST_CASE("relay_chn_create handles invalid arguments", "[relay_chn][core]") // TEST_CASE: Test that relay channels initialize correctly to RELAY_CHN_STATE_IDLE TEST_CASE("Relay channels initialize correctly to FREE state", "[relay_chn][core]") { - for (uint8_t i = 0; i < relay_chn_count; i++) { + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i)); } } // TEST_CASE: Test that relays do nothing when an invlid channel id given TEST_CASE("Run forward does nothing if channel id is invalid", "[relay_chn][core]") { - for (uint8_t i = 0; i < relay_chn_count; i++) { - int invalid_id = relay_chn_count * 2 + i; + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + int invalid_id = CONFIG_RELAY_CHN_COUNT * 2 + i; relay_chn_run_forward(invalid_id); // relay_chn_run_forward returns void // Short delay for state to update - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i)); } } // TEST_CASE: Test that relays run in the forward direction and update their state TEST_CASE("Relay channels run forward and update state", "[relay_chn][core]") { - for (uint8_t i = 0; i < relay_chn_count; i++) { + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { relay_chn_run_forward(i); // relay_chn_run_forward returns void // Short delay for state to update - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(i)); } } @@ -51,20 +51,20 @@ TEST_CASE("Relay channels run forward and update state", "[relay_chn][core]") { // TEST_CASE: Test that relays do nothing when an invlid channel id given TEST_CASE("Run reverse does nothing if channel id is invalid", "[relay_chn][core]") { // Verify that no valid channels were affected - for (uint8_t i = 0; i < relay_chn_count; i++) { - int invalid_id = relay_chn_count * 2 + i; + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + int invalid_id = CONFIG_RELAY_CHN_COUNT * 2 + i; // Call run_reverse with an invalid ID relay_chn_run_reverse(invalid_id); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i)); } } // TEST_CASE: Test that relays run in the reverse direction and update their state TEST_CASE("Relay channels run reverse and update state", "[relay_chn][core]") { - for (uint8_t i = 0; i < relay_chn_count; i++) { + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { relay_chn_run_reverse(i); // relay_chn_run_reverse returns void - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(i)); } } @@ -74,9 +74,9 @@ TEST_CASE("Relay channels run reverse and update state", "[relay_chn][core]") { TEST_CASE("run_forward_all sets all channels to FORWARD", "[relay_chn][core][batch]") { relay_chn_run_forward_all(); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - for (uint8_t i = 0; i < relay_chn_count; i++) { + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(i)); } } @@ -84,9 +84,9 @@ TEST_CASE("run_forward_all sets all channels to FORWARD", "[relay_chn][core][bat TEST_CASE("run_reverse_all sets all channels to REVERSE", "[relay_chn][core][batch]") { relay_chn_run_reverse_all(); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - for (uint8_t i = 0; i < relay_chn_count; i++) { + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(i)); } } @@ -95,14 +95,14 @@ TEST_CASE("stop_all stops all running channels", "[relay_chn][core][batch]") { // 1. Start all channels forward to ensure they are in a known running state relay_chn_run_forward_all(); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // 2. Stop all channels relay_chn_stop_all(); - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); // 3. Verify all channels have transitioned to the FREE state - for (uint8_t i = 0; i < relay_chn_count; i++) { + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i)); } } @@ -112,78 +112,78 @@ TEST_CASE("stop_all stops all running channels", "[relay_chn][core][batch]") // TEST_CASE: Test that relays stop and transition to RELAY_CHN_STATE_IDLE // This test also verifies the transition to FREE state after a STOP command. TEST_CASE("Relay channels stop and update to FREE state", "[relay_chn][core]") { - for (uint8_t i = 0; i < relay_chn_count; i++) { + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { // First, run forward to test stopping and transitioning to FREE state relay_chn_run_forward(i); // relay_chn_run_forward returns void - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(i)); // Now, issue the stop command relay_chn_stop(i); // relay_chn_stop returns void // Immediately after stop, state should be STOPPED - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(i)); // Then, wait for the inertia period for it to transition to RELAY_CHN_STATE_IDLE - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i)); } } // TEST_CASE: Get state should return UNDEFINED when id is not valid TEST_CASE("Get state returns UNDEFINED when id is invalid", "[relay_chn][core]") { - for (uint8_t i = 0; i < relay_chn_count; i++) { - int invalid_id = relay_chn_count * 2 + i; + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + int invalid_id = CONFIG_RELAY_CHN_COUNT * 2 + i; TEST_ASSERT_EQUAL(RELAY_CHN_STATE_UNDEFINED, relay_chn_get_state(invalid_id)); } // Test for running states also relay_chn_run_forward_all(); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); - for (uint8_t i = 0; i < relay_chn_count; i++) { - int invalid_id = relay_chn_count * 2 + i; + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + int invalid_id = CONFIG_RELAY_CHN_COUNT * 2 + i; TEST_ASSERT_EQUAL(RELAY_CHN_STATE_UNDEFINED, relay_chn_get_state(invalid_id)); } } // TEST_CASE: Get state string should return "UNKNOWN" when id is not valid TEST_CASE("Get state string returns UNKNOWN when id is invalid", "[relay_chn][core]") { - for (uint8_t i = 0; i < relay_chn_count; i++) { - int invalid_id = relay_chn_count * 2 + i; + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + int invalid_id = CONFIG_RELAY_CHN_COUNT * 2 + i; TEST_ASSERT_EQUAL_STRING("UNKNOWN", relay_chn_get_state_str(invalid_id)); } // Test for running states also relay_chn_run_forward_all(); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); - for (uint8_t i = 0; i < relay_chn_count; i++) { - int invalid_id = relay_chn_count * 2 + i; + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + int invalid_id = CONFIG_RELAY_CHN_COUNT * 2 + i; TEST_ASSERT_EQUAL_STRING("UNKNOWN", relay_chn_get_state_str(invalid_id)); } } // TEST_CASE: Test independent operation of multiple relay channels TEST_CASE("Multiple channels can operate independently", "[relay_chn][core]") { - if (relay_chn_count >= 2) { + if (CONFIG_RELAY_CHN_COUNT >= 2) { // Start Channel 0 in forward direction relay_chn_run_forward(0); // relay_chn_run_forward returns void - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(0)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(1)); // Other channel should not be affected // Start Channel 1 in reverse direction relay_chn_run_reverse(1); // relay_chn_run_reverse returns void - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(0)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(1)); // Stop Channel 0 and wait for it to become FREE relay_chn_stop(0); // relay_chn_stop returns void - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(0)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(1)); // Other channel should continue running // Stop Channel 1 and wait for it to become FREE relay_chn_stop(1); // relay_chn_stop returns void - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(0)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(1)); } else { @@ -203,17 +203,17 @@ TEST_CASE("Forward to Reverse transition with opposite inertia", "[relay_chn][co // 1. Start in forward direction relay_chn_run_forward(ch); // relay_chn_run_forward returns void - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Short delay for state stabilization + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Short delay for state stabilization TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch)); // 2. Issue reverse command relay_chn_run_reverse(ch); // relay_chn_run_reverse returns void // Immediately after the command, the motor should be stopped - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE_PENDING, relay_chn_get_state(ch)); // Wait for the inertia period (after which the reverse command will be dispatched) - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch)); // Should now be in reverse state } @@ -224,16 +224,16 @@ TEST_CASE("Reverse to Forward transition with opposite inertia", "[relay_chn][co // 1. Start in reverse direction relay_chn_run_reverse(ch); // relay_chn_run_reverse returns void - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch)); // 2. Issue forward command relay_chn_run_forward(ch); // relay_chn_run_forward returns void - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD_PENDING, relay_chn_get_state(ch)); // Wait for inertia - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch)); } @@ -244,14 +244,14 @@ TEST_CASE("Running in same direction does not incur inertia", "[relay_chn][core] // 1. Start in forward direction relay_chn_run_forward(ch); // relay_chn_run_forward returns void - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch)); // 2. Issue the same forward command again relay_chn_run_forward(ch); // relay_chn_run_forward returns void // As per the code, is_direction_opposite_to_current_motion should return false, so no inertia. // Just a short delay to check state remains the same. - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch)); } @@ -266,7 +266,7 @@ TEST_CASE("FREE to Running transition without inertia", "[relay_chn][core][inert // Start in forward direction relay_chn_run_forward(ch); // relay_chn_run_forward returns void // No inertia is expected when starting from FREE state. - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch)); } @@ -281,14 +281,14 @@ TEST_CASE("Single channel direction can be flipped", "[relay_chn][core][directio // 2. Flip the direction relay_chn_flip_direction(ch); - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); // Wait for flip inertia + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); // Wait for flip inertia // 3. Verify direction is flipped TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction(ch)); // 4. Flip back relay_chn_flip_direction(ch); - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); // Wait for flip inertia + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); // Wait for flip inertia // 5. Verify direction is back to default TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(ch)); @@ -298,19 +298,19 @@ TEST_CASE("All channels direction can be flipped simultaneously", "[relay_chn][c { // 1. Flip all channels relay_chn_flip_direction_all(); - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); // 2. Verify all channels are flipped - for (uint8_t i = 0; i < relay_chn_count; i++) { + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction(i)); } // 3. Flip all back relay_chn_flip_direction_all(); - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); // 4. Verify all channels are back to default - for (uint8_t i = 0; i < relay_chn_count; i++) { + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(i)); } } @@ -330,14 +330,14 @@ TEST_CASE("Flipping a running channel stops it and flips direction", "[relay_chn TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(ch)); // 4. Wait for the flip inertia to pass, after which it should be FREE and FLIPPED - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(ch)); TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction(ch)); } TEST_CASE("Direction flip handles invalid channel ID gracefully", "[relay_chn][core][direction]") { - const uint8_t invalid_ch = relay_chn_count + 5; + const uint8_t invalid_ch = CONFIG_RELAY_CHN_COUNT + 5; relay_chn_flip_direction(invalid_ch); // Call with an invalid ID TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(invalid_ch)); @@ -387,7 +387,7 @@ TEST_CASE("Test run limit stops channel after timeout", "[relay_chn][run_limit]" } // Wait for run limit timeout - vTaskDelay(pdMS_TO_TICKS(TEST_SHORT_RUN_LIMIT_SEC * 1000 + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_SHORT_RUN_LIMIT_SEC * 1000 + TEST_DELAY_MARGIN_MS)); for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(i)); } @@ -409,14 +409,14 @@ TEST_CASE("Test run limit reset on direction change and time out finally", "[rel relay_chn_run_reverse_all(); // Wait for the inertia period (after which the reverse command will be dispatched) - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(i)); } // Timer should time out and stop the channel after the run limit time - vTaskDelay(pdMS_TO_TICKS(TEST_SHORT_RUN_LIMIT_SEC * 1000 + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_SHORT_RUN_LIMIT_SEC * 1000 + TEST_DELAY_MARGIN_MS)); for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(i)); diff --git a/test_apps/main/test_relay_chn_core_single.c b/test_apps/main/test_relay_chn_core_single.c index 0945d0b..0b82c40 100644 --- a/test_apps/main/test_relay_chn_core_single.c +++ b/test_apps/main/test_relay_chn_core_single.c @@ -29,14 +29,14 @@ TEST_CASE("Relay channels initialize correctly to IDLE state", "[relay_chn][core TEST_CASE("Relay channels run forward and update state", "[relay_chn][core]") { relay_chn_run_forward(); // Short delay for state to update - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); } // TEST_CASE: Test that relays run in the reverse direction and update their state TEST_CASE("Relay channels run reverse and update state", "[relay_chn][core]") { relay_chn_run_reverse(); // relay_chn_run_reverse returns void - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state()); } @@ -46,17 +46,17 @@ TEST_CASE("Relay channels run reverse and update state", "[relay_chn][core]") { TEST_CASE("Relay channels stop and update to IDLE state", "[relay_chn][core]") { // First, run forward to test stopping and transitioning to IDLE state relay_chn_run_forward(); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); // Now, issue the stop command relay_chn_stop(); // relay_chn_stop returns void // Immediately after stop, state should be STOPPED - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state()); // Then, wait for the inertia period for it to transition to RELAY_CHN_STATE_IDLE - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); } @@ -70,17 +70,17 @@ TEST_CASE("Relay channels stop and update to IDLE state", "[relay_chn][core]") { TEST_CASE("Forward to Reverse transition with opposite inertia", "[relay_chn][core][inertia]") { // 1. Start in forward direction relay_chn_run_forward(); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Short delay for state stabilization + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Short delay for state stabilization TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); // 2. Issue reverse command relay_chn_run_reverse(); // relay_chn_run_reverse returns void // Immediately after the command, the motor should be stopped - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE_PENDING, relay_chn_get_state()); // Wait for the inertia period (after which the reverse command will be dispatched) - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state()); // Should now be in reverse state } @@ -89,16 +89,16 @@ TEST_CASE("Forward to Reverse transition with opposite inertia", "[relay_chn][co TEST_CASE("Reverse to Forward transition with opposite inertia", "[relay_chn][core][inertia]") { // 1. Start in reverse direction relay_chn_run_reverse(); // relay_chn_run_reverse returns void - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state()); // 2. Issue forward command relay_chn_run_forward(); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD_PENDING, relay_chn_get_state()); // Wait for inertia - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); } @@ -107,14 +107,14 @@ TEST_CASE("Reverse to Forward transition with opposite inertia", "[relay_chn][co TEST_CASE("Running in same direction does not incur inertia", "[relay_chn][core][inertia]") { // 1. Start in forward direction relay_chn_run_forward(); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); // 2. Issue the same forward command again relay_chn_run_forward(); // As per the code, is_direction_opposite_to_current_motion should return false, so no inertia. // Just a short delay to check state remains the same. - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); } @@ -127,7 +127,7 @@ TEST_CASE("IDLE to Running transition without inertia", "[relay_chn][core][inert // Start in forward direction relay_chn_run_forward(); // No inertia is expected when starting from IDLE state. - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); } @@ -140,14 +140,14 @@ TEST_CASE("Single channel direction can be flipped", "[relay_chn][core][directio // 2. Flip the direction relay_chn_flip_direction(); - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); // Wait for flip inertia + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); // Wait for flip inertia // 3. Verify direction is flipped TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction()); // 4. Flip back relay_chn_flip_direction(); - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); // Wait for flip inertia + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); // Wait for flip inertia // 5. Verify direction is back to default TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction()); @@ -157,18 +157,18 @@ TEST_CASE("Flipping a running channel stops it and flips direction", "[relay_chn { // 1. Start channel running and verify state relay_chn_run_forward(); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); // 2. Flip the direction while running relay_chn_flip_direction(); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Give time for events to process + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Give time for events to process // 3. The channel should stop as part of the flip process TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state()); // 4. Wait for the flip inertia to pass, after which it should be IDLE and FLIPPED - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction()); } @@ -208,7 +208,7 @@ TEST_CASE("Test run limit stops channel after timeout", "[relay_chn][run_limit]" TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); // Wait for run limit timeout - vTaskDelay(pdMS_TO_TICKS(TEST_SHORT_RUN_LIMIT_SEC * 1000 + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_SHORT_RUN_LIMIT_SEC * 1000 + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state()); } @@ -225,12 +225,12 @@ TEST_CASE("Test run limit reset on direction change and time out finally", "[rel relay_chn_run_reverse(); // Wait for the inertia period (after which the reverse command will be dispatched) - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state()); // Timer should time out and stop the channel after the run limit time - vTaskDelay(pdMS_TO_TICKS(TEST_SHORT_RUN_LIMIT_SEC * 1000 + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_SHORT_RUN_LIMIT_SEC * 1000 + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state()); } diff --git a/test_apps/main/test_relay_chn_listener_multi.c b/test_apps/main/test_relay_chn_listener_multi.c index 1f8d979..117a4cd 100644 --- a/test_apps/main/test_relay_chn_listener_multi.c +++ b/test_apps/main/test_relay_chn_listener_multi.c @@ -44,7 +44,7 @@ TEST_CASE("Listener is called on state change", "[relay_chn][listener]") { // 2. Trigger a state change relay_chn_run_forward(ch); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Allow event to be processed + 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); @@ -66,7 +66,7 @@ TEST_CASE("Unregistered listener is not called", "[relay_chn][listener]") { // 2. Trigger a state change relay_chn_run_forward(ch); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // 3. Verify the listener was NOT called TEST_ASSERT_EQUAL(0, listener1_info.call_count); @@ -83,7 +83,7 @@ TEST_CASE("Multiple listeners are called on state change", "[relay_chn][listener // 2. Trigger a state change relay_chn_run_forward(ch); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // 3. Verify listener 1 was called correctly TEST_ASSERT_EQUAL(1, listener1_info.call_count); @@ -115,7 +115,7 @@ TEST_CASE("Listener registration handles invalid arguments and duplicates", "[re // 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)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(1, listener1_info.call_count); // 5. Clean up diff --git a/test_apps/main/test_relay_chn_listener_single.c b/test_apps/main/test_relay_chn_listener_single.c index 9d0970d..53e27ad 100644 --- a/test_apps/main/test_relay_chn_listener_single.c +++ b/test_apps/main/test_relay_chn_listener_single.c @@ -42,7 +42,7 @@ TEST_CASE("Listener is called on state change", "[relay_chn][listener]") { // 2. Trigger a state change relay_chn_run_forward(); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Allow event to be processed + 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); @@ -62,7 +62,7 @@ TEST_CASE("Unregistered listener is not called", "[relay_chn][listener]") { // 2. Trigger a state change relay_chn_run_forward(); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // 3. Verify the listener was NOT called TEST_ASSERT_EQUAL(0, listener1_info.call_count); @@ -78,7 +78,7 @@ TEST_CASE("Multiple listeners are called on state change", "[relay_chn][listener // 2. Trigger a state change relay_chn_run_forward(); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // 3. Verify listener 1 was called correctly TEST_ASSERT_EQUAL(1, listener1_info.call_count); @@ -110,7 +110,7 @@ TEST_CASE("Listener registration handles invalid arguments and duplicates", "[re // 4. Trigger a state change and verify the listener is only called ONCE relay_chn_run_forward(); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(1, listener1_info.call_count); // 5. Clean up diff --git a/test_apps/main/test_relay_chn_tilt_multi.c b/test_apps/main/test_relay_chn_tilt_multi.c index bc4aa32..7aa1034 100644 --- a/test_apps/main/test_relay_chn_tilt_multi.c +++ b/test_apps/main/test_relay_chn_tilt_multi.c @@ -16,7 +16,7 @@ void prepare_channel_for_tilt(uint8_t chn_id, int initial_cmd) { // Ensure the channel reset tilt control relay_chn_tilt_stop(chn_id); - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); // Ensure the channel has had a 'last_run_cmd' if (initial_cmd == RELAY_CHN_CMD_FORWARD) { @@ -24,9 +24,9 @@ void prepare_channel_for_tilt(uint8_t chn_id, int initial_cmd) { } else { // Assuming initial_cmd is RELAY_CHN_CMD_REVERSE relay_chn_run_reverse(chn_id); } - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Allow command to process + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow command to process relay_chn_stop(chn_id); // Stop it to set last_run_cmd but return to FREE for next test - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(chn_id)); } @@ -40,17 +40,17 @@ TEST_CASE("Run Forward to Tilt Forward transition with inertia", "[relay_chn][ti // 1. Start in forward direction relay_chn_run_forward(ch); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch)); // 2. Issue tilt forward command relay_chn_tilt_forward(ch); // After tilt command, it should immediately stop and then trigger inertia. - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(ch)); // Wait for the inertia period (after which the tilt command will be dispatched) - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch)); } @@ -64,15 +64,15 @@ TEST_CASE("Run Reverse to Tilt Reverse transition with inertia", "[relay_chn][ti // 1. Start in reverse direction relay_chn_run_reverse(ch); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch)); // 2. Issue tilt reverse command relay_chn_tilt_reverse(ch); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(ch)); - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(ch)); } @@ -88,7 +88,7 @@ TEST_CASE("FREE to Tilt Forward transition with inertia (prepared)", "[relay_chn // Issue tilt forward command relay_chn_tilt_forward(ch); // From FREE state, tilt command should still incur the inertia due to the internal timer logic - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch)); } @@ -103,7 +103,7 @@ TEST_CASE("FREE to Tilt Reverse transition with inertia (prepared)", "[relay_chn // Issue tilt reverse command relay_chn_tilt_reverse(ch); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(ch)); } @@ -115,14 +115,14 @@ TEST_CASE("Tilt Forward to Run Forward transition with inertia", "[relay_chn][ti // Prepare channel by running forward first to set last_run_cmd, then tilt prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD); relay_chn_tilt_forward(ch); // Go to tilt state - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch)); // 2. Issue run forward command relay_chn_run_forward(ch); // From Tilt to Run in the same logical name but in the opposite direction, inertia is expected. TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD_PENDING, relay_chn_get_state(ch)); - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch)); } @@ -134,13 +134,13 @@ TEST_CASE("Tilt Reverse to Run Reverse transition with inertia", "[relay_chn][ti // Prepare channel by running reverse first to set last_run_cmd, then tilt prepare_channel_for_tilt(ch, RELAY_CHN_CMD_REVERSE); relay_chn_tilt_reverse(ch); // Go to tilt state - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(ch)); // 2. Issue run reverse command relay_chn_run_reverse(ch); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE_PENDING, relay_chn_get_state(ch)); - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch)); } @@ -152,12 +152,12 @@ TEST_CASE("Tilt Forward to Run Reverse transition without inertia", "[relay_chn] // Prepare channel by running forward first to set last_run_cmd, then tilt prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD); relay_chn_tilt_forward(ch); // Go to tilt state - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch)); // 2. Issue run reverse command (opposite direction) relay_chn_run_reverse(ch); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch)); } @@ -169,13 +169,13 @@ TEST_CASE("Tilt to Stop transition without immediate inertia for stop", "[relay_ // Prepare channel by running forward first to set last_run_cmd, then tilt prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD); relay_chn_tilt_forward(ch); // Go to tilt state - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch)); // 2. Issue stop command relay_chn_tilt_stop(ch); // Stop command should apply immediately, setting state to FREE since last state was tilt. - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(ch)); } @@ -184,16 +184,16 @@ TEST_CASE("Tilt to Stop transition without immediate inertia for stop", "[relay_ TEST_CASE("tilt_forward_all sets all channels to TILT_FORWARD", "[relay_chn][tilt][batch]") { // 1. Prepare all channels. - for (uint8_t i = 0; i < relay_chn_count; i++) { + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { prepare_channel_for_tilt(i, RELAY_CHN_CMD_FORWARD); } // 2. Issue tilt forward to all channels relay_chn_tilt_forward_all(); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Tilt from FREE doesn't have stop-inertia + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Tilt from FREE doesn't have stop-inertia // 3. Verify all channels are tilting forward - for (uint8_t i = 0; i < relay_chn_count; i++) { + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { ESP_LOGI(TEST_TAG, "Checking channel %d", i); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(i)); } @@ -202,16 +202,16 @@ TEST_CASE("tilt_forward_all sets all channels to TILT_FORWARD", "[relay_chn][til TEST_CASE("tilt_reverse_all sets all channels to TILT_REVERSE", "[relay_chn][tilt][batch]") { // 1. Prepare all channels. - for (uint8_t i = 0; i < relay_chn_count; i++) { + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { prepare_channel_for_tilt(i, RELAY_CHN_CMD_REVERSE); } // 2. Issue tilt reverse to all channels relay_chn_tilt_reverse_all(); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // 3. Verify all channels are tilting reverse - for (uint8_t i = 0; i < relay_chn_count; i++) { + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(i)); } } @@ -219,18 +219,18 @@ TEST_CASE("tilt_reverse_all sets all channels to TILT_REVERSE", "[relay_chn][til TEST_CASE("tilt_stop_all stops all tilting channels", "[relay_chn][tilt][batch]") { // 1. Prepare and start all channels tilting forward - for (uint8_t i = 0; i < relay_chn_count; i++) { + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { prepare_channel_for_tilt(i, RELAY_CHN_CMD_REVERSE); } relay_chn_tilt_forward_all(); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // 2. Stop tilting on all channels relay_chn_tilt_stop_all(); - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); // 3. Verify all channels are free - for (uint8_t i = 0; i < relay_chn_count; i++) { + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { ESP_LOGI(TEST_TAG, "Checking channel %d", i); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i)); } @@ -239,7 +239,7 @@ TEST_CASE("tilt_stop_all stops all tilting channels", "[relay_chn][tilt][batch]" TEST_CASE("tilt_auto_all tilts channels based on last run direction", "[relay_chn][tilt][batch]") { // This test requires at least 2 channels to demonstrate different behaviors - TEST_ASSERT_GREATER_OR_EQUAL_MESSAGE(2, relay_chn_count, "Test requires at least 2 channels"); + TEST_ASSERT_GREATER_OR_EQUAL_MESSAGE(2, CONFIG_RELAY_CHN_COUNT, "Test requires at least 2 channels"); // 1. Prepare channel 0 with last run FORWARD and channel 1 with last run REVERSE prepare_channel_for_tilt(0, RELAY_CHN_CMD_FORWARD); @@ -247,7 +247,7 @@ TEST_CASE("tilt_auto_all tilts channels based on last run direction", "[relay_ch // 2. Issue auto tilt command to all channels relay_chn_tilt_auto_all(); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Tilt from FREE state is dispatched immediately + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Tilt from FREE state is dispatched immediately // 3. Verify channel 0 tilts forward (last run was forward) and channel 1 tilts reverse (last run was reverse) TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(0)); @@ -260,15 +260,15 @@ TEST_CASE("relay_chn_tilt_auto chooses correct direction", "[relay_chn][tilt][au // Prepare FORWARD prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD); relay_chn_tilt_auto(ch); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch)); relay_chn_tilt_stop(ch); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Prepare REVERSE prepare_channel_for_tilt(ch, RELAY_CHN_CMD_REVERSE); relay_chn_tilt_auto(ch); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(ch)); } @@ -302,26 +302,26 @@ TEST_CASE("tilt counter logic: forward and reverse consumption", "[relay_chn][ti // Tilt forward 3 times for (int i = 0; i < 3; ++i) { relay_chn_tilt_forward(ch); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch)); relay_chn_tilt_stop(ch); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); } // Now tilt reverse 3 times (should succeed) for (int i = 0; i < 3; ++i) { relay_chn_tilt_reverse(ch); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); if (i < 3) { TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(ch)); relay_chn_tilt_stop(ch); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); } } // Extra reverse tilt should fail (counter exhausted) relay_chn_tilt_reverse(ch); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Should not enter TILT_REVERSE, should remain FREE or STOPPED relay_chn_state_t state = relay_chn_get_state(ch); TEST_ASSERT(state != RELAY_CHN_STATE_TILT_REVERSE); @@ -332,12 +332,12 @@ TEST_CASE("run command during TILT state transitions correctly", "[relay_chn][ti uint8_t ch = 0; prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD); relay_chn_tilt_forward(ch); - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch)); // Issue run reverse while in TILT_FORWARD relay_chn_run_reverse(ch); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Should transition to REVERSE or REVERSE_PENDING depending on inertia logic relay_chn_state_t state = relay_chn_get_state(ch); TEST_ASSERT(state == RELAY_CHN_STATE_REVERSE || state == RELAY_CHN_STATE_REVERSE_PENDING); diff --git a/test_apps/main/test_relay_chn_tilt_single.c b/test_apps/main/test_relay_chn_tilt_single.c index b302838..c30242f 100644 --- a/test_apps/main/test_relay_chn_tilt_single.c +++ b/test_apps/main/test_relay_chn_tilt_single.c @@ -16,7 +16,7 @@ void prepare_channel_for_tilt(int initial_cmd) { // Ensure the channel reset tilt control relay_chn_tilt_stop(); - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); // Ensure the channel has had a 'last_run_cmd' if (initial_cmd == RELAY_CHN_CMD_FORWARD) { @@ -24,9 +24,9 @@ void prepare_channel_for_tilt(int initial_cmd) { } else { // Assuming initial_cmd is RELAY_CHN_CMD_REVERSE relay_chn_run_reverse(); } - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Allow command to process + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow command to process relay_chn_stop(); // Stop it to set last_run_cmd but return to FREE for next test - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); } @@ -38,17 +38,17 @@ TEST_CASE("Run Forward to Tilt Forward transition with inertia", "[relay_chn][ti // 1. Start in forward direction relay_chn_run_forward(); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); // 2. Issue tilt forward command relay_chn_tilt_forward(); // After tilt command, it should immediately stop and then trigger inertia. - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state()); // Wait for the inertia period (after which the tilt command will be dispatched) - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state()); } @@ -60,15 +60,15 @@ TEST_CASE("Run Reverse to Tilt Reverse transition with inertia", "[relay_chn][ti // 1. Start in reverse direction relay_chn_run_reverse(); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state()); // 2. Issue tilt reverse command relay_chn_tilt_reverse(); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state()); - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state()); } @@ -82,7 +82,7 @@ TEST_CASE("FREE to Tilt Forward transition with inertia (prepared)", "[relay_chn // Issue tilt forward command relay_chn_tilt_forward(); // From FREE state, tilt command should still incur the inertia due to the internal timer logic - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state()); } @@ -95,7 +95,7 @@ TEST_CASE("FREE to Tilt Reverse transition with inertia (prepared)", "[relay_chn // Issue tilt reverse command relay_chn_tilt_reverse(); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state()); } @@ -105,14 +105,14 @@ TEST_CASE("Tilt Forward to Run Forward transition with inertia", "[relay_chn][ti // Prepare channel by running forward first to set last_run_cmd, then tilt prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); relay_chn_tilt_forward(); // Go to tilt state - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state()); // 2. Issue run forward command relay_chn_run_forward(); // From Tilt to Run in the same logical name but in the opposite direction, inertia is expected. TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD_PENDING, relay_chn_get_state()); - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); } @@ -122,13 +122,13 @@ TEST_CASE("Tilt Reverse to Run Reverse transition with inertia", "[relay_chn][ti // Prepare channel by running reverse first to set last_run_cmd, then tilt prepare_channel_for_tilt(RELAY_CHN_CMD_REVERSE); relay_chn_tilt_reverse(); // Go to tilt state - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state()); // 2. Issue run reverse command relay_chn_run_reverse(); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE_PENDING, relay_chn_get_state()); - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state()); } @@ -138,12 +138,12 @@ TEST_CASE("Tilt Forward to Run Reverse transition without inertia", "[relay_chn] // Prepare channel by running forward first to set last_run_cmd, then tilt prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); relay_chn_tilt_forward(); // Go to tilt state - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state()); // 2. Issue run reverse command (opposite direction) relay_chn_run_reverse(); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state()); } @@ -153,13 +153,13 @@ TEST_CASE("Tilt to Stop transition without immediate inertia for stop", "[relay_ // Prepare channel by running forward first to set last_run_cmd, then tilt prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); relay_chn_tilt_forward(); // Go to tilt state - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state()); // 2. Issue stop command relay_chn_stop(); // Stop command should apply immediately, setting state to FREE since last state was tilt. - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); } @@ -168,15 +168,15 @@ TEST_CASE("relay_chn_tilt_auto chooses correct direction", "[relay_chn][tilt][au // Prepare FORWARD prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); relay_chn_tilt_auto(); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state()); relay_chn_tilt_stop(); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Prepare REVERSE prepare_channel_for_tilt(RELAY_CHN_CMD_REVERSE); relay_chn_tilt_auto(); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state()); } @@ -202,26 +202,26 @@ TEST_CASE("tilt counter logic: forward and reverse consumption", "[relay_chn][ti // Tilt forward 3 times for (int i = 0; i < 3; ++i) { relay_chn_tilt_forward(); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state()); relay_chn_tilt_stop(); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); } // Now tilt reverse 3 times (should succeed) for (int i = 0; i < 3; ++i) { relay_chn_tilt_reverse(); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); if (i < 3) { TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state()); relay_chn_tilt_stop(); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); } } // Extra reverse tilt should fail (counter exhausted) relay_chn_tilt_reverse(); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Should not enter TILT_REVERSE, should remain FREE or STOPPED relay_chn_state_t state = relay_chn_get_state(); TEST_ASSERT(state != RELAY_CHN_STATE_TILT_REVERSE); @@ -231,12 +231,12 @@ TEST_CASE("tilt counter logic: forward and reverse consumption", "[relay_chn][ti TEST_CASE("run command during TILT state transitions correctly", "[relay_chn][tilt][run-during-tilt]") { prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); relay_chn_tilt_forward(); - vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state()); // Issue run reverse while in TILT_FORWARD relay_chn_run_reverse(); - vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Should transition to REVERSE or REVERSE_PENDING depending on inertia logic relay_chn_state_t state = relay_chn_get_state(); TEST_ASSERT(state == RELAY_CHN_STATE_REVERSE || state == RELAY_CHN_STATE_REVERSE_PENDING); From 54c8dc26fcd36ad328810b251096a55650774eef Mon Sep 17 00:00:00 2001 From: ismail Date: Tue, 26 Aug 2025 12:10:02 +0300 Subject: [PATCH 24/69] Add .ESP-IDF vscode configuration files --- test_apps/.vscode/c_cpp_properties.json | 23 +++++++++++++++++++++++ test_apps/.vscode/launch.json | 15 +++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 test_apps/.vscode/c_cpp_properties.json create mode 100644 test_apps/.vscode/launch.json diff --git a/test_apps/.vscode/c_cpp_properties.json b/test_apps/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..fab3c8f --- /dev/null +++ b/test_apps/.vscode/c_cpp_properties.json @@ -0,0 +1,23 @@ +{ + "configurations": [ + { + "name": "ESP-IDF", + "compilerPath": "${config:idf.toolsPath}/tools/xtensa-esp-elf/esp-14.2.0_20241119/xtensa-esp-elf/bin/xtensa-esp32-elf-gcc", + "compileCommands": "${config:idf.buildPath}/compile_commands.json", + "includePath": [ + "${config:idf.espIdfPath}/components/**", + "${config:idf.espIdfPathWin}/components/**", + "${workspaceFolder}/**" + ], + "browse": { + "path": [ + "${config:idf.espIdfPath}/components", + "${config:idf.espIdfPathWin}/components", + "${workspaceFolder}" + ], + "limitSymbolsToIncludedHeaders": true + } + } + ], + "version": 4 +} diff --git a/test_apps/.vscode/launch.json b/test_apps/.vscode/launch.json new file mode 100644 index 0000000..2511a38 --- /dev/null +++ b/test_apps/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "gdbtarget", + "request": "attach", + "name": "Eclipse CDT GDB Adapter" + }, + { + "type": "espidf", + "name": "Launch", + "request": "launch" + } + ] +} \ No newline at end of file From 329812aecc81cb5e20d1e787758f3c4969cb4cec Mon Sep 17 00:00:00 2001 From: ismail Date: Tue, 26 Aug 2025 13:37:48 +0300 Subject: [PATCH 25/69] Enhance relay command handling - `TILT_STOP` command is prioritized in `relay_chn_tilt_issue_cmd()` because it should override any other tilt commands. - The debug logs are cleaned up. Fixes #1088 Refs #1085 --- src/relay_chn_tilt.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/relay_chn_tilt.c b/src/relay_chn_tilt.c index 99f2409..a7ea07a 100644 --- a/src/relay_chn_tilt.c +++ b/src/relay_chn_tilt.c @@ -93,6 +93,16 @@ static uint32_t relay_chn_tilt_get_required_timing_before_tilting(relay_chn_tilt // Issue a tilt command to a specific relay channel. static void relay_chn_tilt_issue_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_tilt_cmd_t cmd) { + // TILT_STOP is safe and high priority + if (cmd == RELAY_CHN_TILT_CMD_STOP) { + if (tilt_ctl->chn_ctl->state == RELAY_CHN_STATE_STOPPED) { + return; // Do nothing if already stopped + } + // If the command is TILT_STOP, issue it immediately + relay_chn_tilt_dispatch_cmd(tilt_ctl, cmd); + return; + } + if (relay_chn_run_info_get_last_run_cmd(tilt_ctl->chn_ctl->run_info) == RELAY_CHN_CMD_NONE) { // Do not tilt if the channel hasn't been run before ESP_LOGD(TAG, "relay_chn_tilt_issue_cmd: Tilt will not be executed since the channel hasn't been run yet"); @@ -106,7 +116,6 @@ static void relay_chn_tilt_issue_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_t // Set the command that will be processed tilt_ctl->cmd = cmd; - ESP_LOGI(TAG, "relay_chn_tilt_issue_cmd: Command-chn: %d-%d", cmd, tilt_ctl->chn_ctl->id); // TODO delete switch (tilt_ctl->chn_ctl->state) { case RELAY_CHN_STATE_IDLE: // Relay channel is free, tilt can be issued immediately @@ -183,7 +192,6 @@ static void relay_chn_tilt_issue_cmd_on_all_channels(relay_chn_tilt_cmd_t cmd) { for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { relay_chn_tilt_ctl_t* tilt_ctl = &tilt_ctls[i]; - ESP_LOGI(TAG, "issue_cmd_on_all_channels: Command|chn|ctl.id: %d|%d|%d", cmd, i, tilt_ctl->chn_ctl->id); // TODO delete relay_chn_tilt_issue_cmd(tilt_ctl, cmd); } } @@ -490,7 +498,7 @@ static void relay_chn_tilt_execute_stop(relay_chn_tilt_ctl_t *tilt_ctl) if (relay_chn_output_stop(tilt_ctl->chn_ctl->output) != ESP_OK) { ESP_LOGE(TAG, "relay_chn_tilt_execute_stop: Failed to output stop for relay channel #%d!", tilt_ctl->chn_ctl->id); } - relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP); + relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_IDLE); #if CONFIG_RELAY_CHN_ENABLE_NVS // Start the flush debounce timer @@ -553,7 +561,6 @@ static void relay_chn_tilt_execute_pause(relay_chn_tilt_ctl_t *tilt_ctl) esp_err_t relay_chn_tilt_dispatch_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_tilt_cmd_t cmd) { ESP_LOGD(TAG, "relay_chn_tilt_dispatch_cmd: Command: %d", cmd); - ESP_LOGI(TAG, "tilt_dispatch_cmd: Command-chn: %d-%d", cmd, tilt_ctl->chn_ctl->id); // TODO delete switch(cmd) { case RELAY_CHN_TILT_CMD_STOP: From 15d1673e773bb216caabff8cdd2f999df4e7e32f Mon Sep 17 00:00:00 2001 From: ismail Date: Tue, 26 Aug 2025 15:03:16 +0300 Subject: [PATCH 26/69] Refactor and improve for batch operations Refactor tilt test functions to use batch operations for all channels and improve state checks. Refs #1085 --- test_apps/main/test_relay_chn_tilt_multi.c | 268 +++++++++++---------- 1 file changed, 142 insertions(+), 126 deletions(-) diff --git a/test_apps/main/test_relay_chn_tilt_multi.c b/test_apps/main/test_relay_chn_tilt_multi.c index 7aa1034..5fab474 100644 --- a/test_apps/main/test_relay_chn_tilt_multi.c +++ b/test_apps/main/test_relay_chn_tilt_multi.c @@ -12,171 +12,197 @@ #define RELAY_CHN_CMD_FORWARD 1 #define RELAY_CHN_CMD_REVERSE 2 +void check_all_channels_for_state(relay_chn_state_t state) +{ + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + TEST_ASSERT_EQUAL(state, relay_chn_get_state(i)); + } +} + // Helper function to prepare channel for tilt tests -void prepare_channel_for_tilt(uint8_t chn_id, int initial_cmd) { +void prepare_channels_for_tilt_with_mixed_runs() { // Ensure the channel reset tilt control - relay_chn_tilt_stop(chn_id); + relay_chn_tilt_stop_all(); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + + // Ensure the channel has had a 'last_run_cmd' + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + if (i % 2 == 0) { + relay_chn_run_forward(i); + } else { // Assuming initial_cmd is RELAY_CHN_CMD_REVERSE + relay_chn_run_reverse(i); + } + } + + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow command to process + relay_chn_stop_all(); // Stop it to set last_run_cmd but return to FREE for next test vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); + check_all_channels_for_state(RELAY_CHN_STATE_IDLE); +} + +// Helper function to prepare channel for tilt tests +void prepare_all_channels_for_tilt(int initial_cmd) { + // Ensure the channel reset tilt control + relay_chn_tilt_stop_all(); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + + // If the channels are not IDLE yet, wait more + bool not_idle = false; + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + if (relay_chn_get_state(i) != RELAY_CHN_STATE_IDLE) { + not_idle = true; + break; + } + } + + if (not_idle) { + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS)); + } // Ensure the channel has had a 'last_run_cmd' if (initial_cmd == RELAY_CHN_CMD_FORWARD) { - relay_chn_run_forward(chn_id); + relay_chn_run_forward_all(); } else { // Assuming initial_cmd is RELAY_CHN_CMD_REVERSE - relay_chn_run_reverse(chn_id); + relay_chn_run_reverse_all(); } vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow command to process - relay_chn_stop(chn_id); // Stop it to set last_run_cmd but return to FREE for next test + relay_chn_stop_all(); // Stop all to set last_run_cmd but return to FREE for next test vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(chn_id)); + + check_all_channels_for_state(RELAY_CHN_STATE_IDLE); } // TEST_CASE: Test transition from running forward to tilt forward // Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_tilt_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_FORWARD TEST_CASE("Run Forward to Tilt Forward transition with inertia", "[relay_chn][tilt][inertia]") { - uint8_t ch = 0; - // Prepare channel by running forward first to set last_run_cmd - prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD); + prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD); // 1. Start in forward direction - relay_chn_run_forward(ch); + relay_chn_run_forward_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch)); + check_all_channels_for_state(RELAY_CHN_STATE_FORWARD); // 2. Issue tilt forward command - relay_chn_tilt_forward(ch); + relay_chn_tilt_forward_all(); // After tilt command, it should immediately stop and then trigger inertia. vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(ch)); + check_all_channels_for_state(RELAY_CHN_STATE_STOPPED); // Wait for the inertia period (after which the tilt command will be dispatched) - vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS)); + check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD); } // TEST_CASE: Test transition from running reverse to tilt reverse // Scenario: RELAY_CHN_STATE_REVERSE -> (relay_chn_tilt_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_REVERSE TEST_CASE("Run Reverse to Tilt Reverse transition with inertia", "[relay_chn][tilt][inertia]") { - uint8_t ch = 0; - // Prepare channel by running reverse first to set last_run_cmd - prepare_channel_for_tilt(ch, RELAY_CHN_CMD_REVERSE); + prepare_all_channels_for_tilt(RELAY_CHN_CMD_REVERSE); // 1. Start in reverse direction - relay_chn_run_reverse(ch); + relay_chn_run_reverse_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch)); + check_all_channels_for_state(RELAY_CHN_STATE_REVERSE); // 2. Issue tilt reverse command - relay_chn_tilt_reverse(ch); + relay_chn_tilt_reverse_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(ch)); + check_all_channels_for_state(RELAY_CHN_STATE_STOPPED); vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(ch)); + check_all_channels_for_state(RELAY_CHN_STATE_TILT_REVERSE); } // TEST_CASE: Test transition from FREE state to tilt forward (now with preparation) // Scenario: RELAY_CHN_STATE_IDLE -> (prepare) -> RELAY_CHN_STATE_IDLE -> (relay_chn_tilt_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_FORWARD TEST_CASE("FREE to Tilt Forward transition with inertia (prepared)", "[relay_chn][tilt][inertia]") { - uint8_t ch = 0; - // Prepare channel by running forward first to set last_run_cmd - prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(ch)); // Ensure we are back to FREE + prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD); // Issue tilt forward command - relay_chn_tilt_forward(ch); + relay_chn_tilt_forward_all(); // From FREE state, tilt command should still incur the inertia due to the internal timer logic vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch)); + check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD); } // TEST_CASE: Test transition from FREE state to tilt reverse (now with preparation) // Scenario: RELAY_CHN_STATE_IDLE -> (prepare) -> RELAY_CHN_STATE_IDLE -> (relay_chn_tilt_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_REVERSE TEST_CASE("FREE to Tilt Reverse transition with inertia (prepared)", "[relay_chn][tilt][inertia]") { - uint8_t ch = 0; - // Prepare channel by running reverse first to set last_run_cmd - prepare_channel_for_tilt(ch, RELAY_CHN_CMD_REVERSE); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(ch)); // Ensure we are back to FREE + prepare_all_channels_for_tilt(RELAY_CHN_CMD_REVERSE); // Issue tilt reverse command - relay_chn_tilt_reverse(ch); + relay_chn_tilt_reverse_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(ch)); + check_all_channels_for_state(RELAY_CHN_STATE_TILT_REVERSE); } // TEST_CASE: Test transition from tilt forward to run forward (inertia expected for run) // Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD TEST_CASE("Tilt Forward to Run Forward transition with inertia", "[relay_chn][tilt][inertia]") { - uint8_t ch = 0; - // Prepare channel by running forward first to set last_run_cmd, then tilt - prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD); - relay_chn_tilt_forward(ch); // Go to tilt state + prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD); + relay_chn_tilt_forward_all(); // Go to tilt state vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch)); + check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD); // 2. Issue run forward command - relay_chn_run_forward(ch); + relay_chn_run_forward_all(); // From Tilt to Run in the same logical name but in the opposite direction, inertia is expected. - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD_PENDING, relay_chn_get_state(ch)); + check_all_channels_for_state(RELAY_CHN_STATE_FORWARD_PENDING); vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch)); + check_all_channels_for_state(RELAY_CHN_STATE_FORWARD); } // TEST_CASE: Test transition from tilt reverse to run reverse (no inertia expected for run) // Scenario: RELAY_CHN_STATE_TILT_REVERSE -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_REVERSE TEST_CASE("Tilt Reverse to Run Reverse transition with inertia", "[relay_chn][tilt][inertia]") { - uint8_t ch = 0; - // Prepare channel by running reverse first to set last_run_cmd, then tilt - prepare_channel_for_tilt(ch, RELAY_CHN_CMD_REVERSE); - relay_chn_tilt_reverse(ch); // Go to tilt state + prepare_all_channels_for_tilt(RELAY_CHN_CMD_REVERSE); + relay_chn_tilt_reverse_all(); // Go to tilt state vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(ch)); + check_all_channels_for_state(RELAY_CHN_STATE_TILT_REVERSE); // 2. Issue run reverse command - relay_chn_run_reverse(ch); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE_PENDING, relay_chn_get_state(ch)); + relay_chn_run_reverse_all(); + check_all_channels_for_state(RELAY_CHN_STATE_REVERSE_PENDING); vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch)); + check_all_channels_for_state(RELAY_CHN_STATE_REVERSE); } // TEST_CASE: Test transition from tilt forward to run reverse (without inertia) // Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_REVERSE TEST_CASE("Tilt Forward to Run Reverse transition without inertia", "[relay_chn][tilt][inertia]") { - uint8_t ch = 0; - // Prepare channel by running forward first to set last_run_cmd, then tilt - prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD); - relay_chn_tilt_forward(ch); // Go to tilt state + prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD); + relay_chn_tilt_forward_all(); // Go to tilt state vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch)); + check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD); // 2. Issue run reverse command (opposite direction) - relay_chn_run_reverse(ch); + relay_chn_run_reverse_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch)); + check_all_channels_for_state(RELAY_CHN_STATE_REVERSE); } // TEST_CASE: Test stopping from a tilt state (no inertia for stop command itself) // Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_tilt_stop) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_IDLE TEST_CASE("Tilt to Stop transition without immediate inertia for stop", "[relay_chn][tilt][inertia]") { - uint8_t ch = 0; - - // Prepare channel by running forward first to set last_run_cmd, then tilt - prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD); - relay_chn_tilt_forward(ch); // Go to tilt state + // Prepare all channels by running forward first to set last_run_cmd, then tilt + prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD); + relay_chn_tilt_forward_all(); // Go to tilt state vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch)); + // Verify all channels are tilting forward + check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD); // 2. Issue stop command - relay_chn_tilt_stop(ch); + relay_chn_tilt_stop_all(); // Stop command should apply immediately, setting state to FREE since last state was tilt. - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(ch)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); + // Verify all channels are IDLE + check_all_channels_for_state(RELAY_CHN_STATE_IDLE); } // ### Batch Tilt Control Tests @@ -184,44 +210,34 @@ TEST_CASE("Tilt to Stop transition without immediate inertia for stop", "[relay_ TEST_CASE("tilt_forward_all sets all channels to TILT_FORWARD", "[relay_chn][tilt][batch]") { // 1. Prepare all channels. - for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { - prepare_channel_for_tilt(i, RELAY_CHN_CMD_FORWARD); - } + prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD); // 2. Issue tilt forward to all channels relay_chn_tilt_forward_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Tilt from FREE doesn't have stop-inertia // 3. Verify all channels are tilting forward - for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { - ESP_LOGI(TEST_TAG, "Checking channel %d", i); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(i)); - } + check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD); } TEST_CASE("tilt_reverse_all sets all channels to TILT_REVERSE", "[relay_chn][tilt][batch]") { // 1. Prepare all channels. - for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { - prepare_channel_for_tilt(i, RELAY_CHN_CMD_REVERSE); - } + prepare_all_channels_for_tilt(RELAY_CHN_CMD_REVERSE); // 2. Issue tilt reverse to all channels relay_chn_tilt_reverse_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // 3. Verify all channels are tilting reverse - for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(i)); - } + check_all_channels_for_state(RELAY_CHN_STATE_TILT_REVERSE); } TEST_CASE("tilt_stop_all stops all tilting channels", "[relay_chn][tilt][batch]") { // 1. Prepare and start all channels tilting forward - for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { - prepare_channel_for_tilt(i, RELAY_CHN_CMD_REVERSE); - } + prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD); + relay_chn_tilt_forward_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); @@ -230,10 +246,7 @@ TEST_CASE("tilt_stop_all stops all tilting channels", "[relay_chn][tilt][batch]" vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); // 3. Verify all channels are free - for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { - ESP_LOGI(TEST_TAG, "Checking channel %d", i); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i)); - } + check_all_channels_for_state(RELAY_CHN_STATE_IDLE); } TEST_CASE("tilt_auto_all tilts channels based on last run direction", "[relay_chn][tilt][batch]") @@ -242,34 +255,39 @@ TEST_CASE("tilt_auto_all tilts channels based on last run direction", "[relay_ch TEST_ASSERT_GREATER_OR_EQUAL_MESSAGE(2, CONFIG_RELAY_CHN_COUNT, "Test requires at least 2 channels"); // 1. Prepare channel 0 with last run FORWARD and channel 1 with last run REVERSE - prepare_channel_for_tilt(0, RELAY_CHN_CMD_FORWARD); - prepare_channel_for_tilt(1, RELAY_CHN_CMD_REVERSE); + prepare_channels_for_tilt_with_mixed_runs(0, RELAY_CHN_CMD_FORWARD); // 2. Issue auto tilt command to all channels relay_chn_tilt_auto_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Tilt from FREE state is dispatched immediately - // 3. Verify channel 0 tilts forward (last run was forward) and channel 1 tilts reverse (last run was reverse) - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(0)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(1)); + // 3. Verify even channels tilt forward (last run was forward) and odd channels tilt reverse (last run was reverse) + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + relay_chn_state_t state = i % 2 == 0 ? + RELAY_CHN_STATE_TILT_FORWARD : RELAY_CHN_STATE_TILT_REVERSE; + + TEST_ASSERT_EQUAL(state, relay_chn_get_state(i)); + } } // Test relay_chn_tilt_auto() chooses correct tilt direction TEST_CASE("relay_chn_tilt_auto chooses correct direction", "[relay_chn][tilt][auto]") { - uint8_t ch = 0; // Prepare FORWARD - prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD); - relay_chn_tilt_auto(ch); + prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD); + relay_chn_tilt_auto_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch)); - relay_chn_tilt_stop(ch); + // Verify all tilt forward + check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD); + + relay_chn_tilt_stop_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Prepare REVERSE - prepare_channel_for_tilt(ch, RELAY_CHN_CMD_REVERSE); - relay_chn_tilt_auto(ch); + prepare_all_channels_for_tilt(RELAY_CHN_CMD_REVERSE); + relay_chn_tilt_auto_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(ch)); + // Verify all tilt reverse + check_all_channels_for_state(RELAY_CHN_STATE_TILT_REVERSE); } // Test sensitivity set/get @@ -296,49 +314,47 @@ TEST_CASE("relay_chn_tilt_set_sensitivity and get", "[relay_chn][tilt][sensitivi // Test tilt counter logic: forward x3, reverse x3, extra reverse fails TEST_CASE("tilt counter logic: forward and reverse consumption", "[relay_chn][tilt][counter]") { - uint8_t ch = 0; - prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD); + +// Tilt execution time at 100% sensitivity in milliseconds (10 + 90) +#define TEST_TILT_EXECUTION_TIME_MS 100 + + prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD); + relay_chn_tilt_set_sensitivity_all_with(100); // Set sentivity to max for fastest execution // Tilt forward 3 times for (int i = 0; i < 3; ++i) { - relay_chn_tilt_forward(ch); - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch)); - relay_chn_tilt_stop(ch); - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + relay_chn_tilt_forward_all(); + vTaskDelay(pdMS_TO_TICKS(TEST_TILT_EXECUTION_TIME_MS)); + check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD); + relay_chn_tilt_stop_all(); } + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Now tilt reverse 3 times (should succeed) for (int i = 0; i < 3; ++i) { - relay_chn_tilt_reverse(ch); - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - if (i < 3) { - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(ch)); - relay_chn_tilt_stop(ch); - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - } + relay_chn_tilt_reverse_all(); + vTaskDelay(pdMS_TO_TICKS(TEST_TILT_EXECUTION_TIME_MS)); + check_all_channels_for_state(RELAY_CHN_STATE_TILT_REVERSE); + relay_chn_tilt_stop_all(); } // Extra reverse tilt should fail (counter exhausted) - relay_chn_tilt_reverse(ch); + relay_chn_tilt_reverse_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - // Should not enter TILT_REVERSE, should remain FREE or STOPPED - relay_chn_state_t state = relay_chn_get_state(ch); - TEST_ASSERT(state != RELAY_CHN_STATE_TILT_REVERSE); + // Should not enter TILT_REVERSE, should remain IDLE + check_all_channels_for_state(RELAY_CHN_STATE_IDLE); } // Test run command during TILT state TEST_CASE("run command during TILT state transitions correctly", "[relay_chn][tilt][run-during-tilt]") { - uint8_t ch = 0; - prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD); - relay_chn_tilt_forward(ch); + prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD); + relay_chn_tilt_forward_all(); vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch)); + check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD); // Issue run reverse while in TILT_FORWARD - relay_chn_run_reverse(ch); + relay_chn_run_reverse_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - // Should transition to REVERSE or REVERSE_PENDING depending on inertia logic - relay_chn_state_t state = relay_chn_get_state(ch); - TEST_ASSERT(state == RELAY_CHN_STATE_REVERSE || state == RELAY_CHN_STATE_REVERSE_PENDING); + // Should transition to REVERSE + check_all_channels_for_state(RELAY_CHN_STATE_REVERSE); } \ No newline at end of file From da953846c9bcbc78f43ec42b9eedc7da4ed515f5 Mon Sep 17 00:00:00 2001 From: ismail Date: Tue, 26 Aug 2025 15:19:09 +0300 Subject: [PATCH 27/69] Refactor function brackets Refactor the brackets of the test case functions to align with ESP-IDF style. Refs #1085 --- test_apps/main/test_relay_chn_core_multi.c | 39 ++++++++++++------- test_apps/main/test_relay_chn_core_single.c | 24 ++++++++---- .../main/test_relay_chn_listener_multi.c | 12 ++++-- .../main/test_relay_chn_listener_single.c | 12 ++++-- test_apps/main/test_relay_chn_tilt_multi.c | 36 +++++++++++------ test_apps/main/test_relay_chn_tilt_single.c | 36 +++++++++++------ 6 files changed, 106 insertions(+), 53 deletions(-) diff --git a/test_apps/main/test_relay_chn_core_multi.c b/test_apps/main/test_relay_chn_core_multi.c index e24a387..9ccdd28 100644 --- a/test_apps/main/test_relay_chn_core_multi.c +++ b/test_apps/main/test_relay_chn_core_multi.c @@ -21,14 +21,16 @@ TEST_CASE("relay_chn_create handles invalid arguments", "[relay_chn][core]") // --- Basic Functionality Tests --- // TEST_CASE: Test that relay channels initialize correctly to RELAY_CHN_STATE_IDLE -TEST_CASE("Relay channels initialize correctly to FREE state", "[relay_chn][core]") { +TEST_CASE("Relay channels initialize correctly to FREE state", "[relay_chn][core]") +{ for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i)); } } // TEST_CASE: Test that relays do nothing when an invlid channel id given -TEST_CASE("Run forward does nothing if channel id is invalid", "[relay_chn][core]") { +TEST_CASE("Run forward does nothing if channel id is invalid", "[relay_chn][core]") +{ for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { int invalid_id = CONFIG_RELAY_CHN_COUNT * 2 + i; relay_chn_run_forward(invalid_id); // relay_chn_run_forward returns void @@ -39,7 +41,8 @@ TEST_CASE("Run forward does nothing if channel id is invalid", "[relay_chn][core } // TEST_CASE: Test that relays run in the forward direction and update their state -TEST_CASE("Relay channels run forward and update state", "[relay_chn][core]") { +TEST_CASE("Relay channels run forward and update state", "[relay_chn][core]") +{ for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { relay_chn_run_forward(i); // relay_chn_run_forward returns void // Short delay for state to update @@ -49,7 +52,8 @@ TEST_CASE("Relay channels run forward and update state", "[relay_chn][core]") { } // TEST_CASE: Test that relays do nothing when an invlid channel id given -TEST_CASE("Run reverse does nothing if channel id is invalid", "[relay_chn][core]") { +TEST_CASE("Run reverse does nothing if channel id is invalid", "[relay_chn][core]") +{ // Verify that no valid channels were affected for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { int invalid_id = CONFIG_RELAY_CHN_COUNT * 2 + i; @@ -61,7 +65,8 @@ TEST_CASE("Run reverse does nothing if channel id is invalid", "[relay_chn][core } // TEST_CASE: Test that relays run in the reverse direction and update their state -TEST_CASE("Relay channels run reverse and update state", "[relay_chn][core]") { +TEST_CASE("Relay channels run reverse and update state", "[relay_chn][core]") +{ for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { relay_chn_run_reverse(i); // relay_chn_run_reverse returns void vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); @@ -111,7 +116,8 @@ TEST_CASE("stop_all stops all running channels", "[relay_chn][core][batch]") // TEST_CASE: Test that relays stop and transition to RELAY_CHN_STATE_IDLE // This test also verifies the transition to FREE state after a STOP command. -TEST_CASE("Relay channels stop and update to FREE state", "[relay_chn][core]") { +TEST_CASE("Relay channels stop and update to FREE state", "[relay_chn][core]") +{ for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { // First, run forward to test stopping and transitioning to FREE state relay_chn_run_forward(i); // relay_chn_run_forward returns void @@ -131,7 +137,8 @@ TEST_CASE("Relay channels stop and update to FREE state", "[relay_chn][core]") { } // TEST_CASE: Get state should return UNDEFINED when id is not valid -TEST_CASE("Get state returns UNDEFINED when id is invalid", "[relay_chn][core]") { +TEST_CASE("Get state returns UNDEFINED when id is invalid", "[relay_chn][core]") +{ for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { int invalid_id = CONFIG_RELAY_CHN_COUNT * 2 + i; TEST_ASSERT_EQUAL(RELAY_CHN_STATE_UNDEFINED, relay_chn_get_state(invalid_id)); @@ -146,7 +153,8 @@ TEST_CASE("Get state returns UNDEFINED when id is invalid", "[relay_chn][core]") } // TEST_CASE: Get state string should return "UNKNOWN" when id is not valid -TEST_CASE("Get state string returns UNKNOWN when id is invalid", "[relay_chn][core]") { +TEST_CASE("Get state string returns UNKNOWN when id is invalid", "[relay_chn][core]") +{ for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { int invalid_id = CONFIG_RELAY_CHN_COUNT * 2 + i; TEST_ASSERT_EQUAL_STRING("UNKNOWN", relay_chn_get_state_str(invalid_id)); @@ -161,7 +169,8 @@ TEST_CASE("Get state string returns UNKNOWN when id is invalid", "[relay_chn][co } // TEST_CASE: Test independent operation of multiple relay channels -TEST_CASE("Multiple channels can operate independently", "[relay_chn][core]") { +TEST_CASE("Multiple channels can operate independently", "[relay_chn][core]") +{ if (CONFIG_RELAY_CHN_COUNT >= 2) { // Start Channel 0 in forward direction relay_chn_run_forward(0); // relay_chn_run_forward returns void @@ -198,7 +207,8 @@ TEST_CASE("Multiple channels can operate independently", "[relay_chn][core]") { // TEST_CASE: Test transition from forward to reverse with inertia and state checks // Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_REVERSE -TEST_CASE("Forward to Reverse transition with opposite inertia", "[relay_chn][core][inertia]") { +TEST_CASE("Forward to Reverse transition with opposite inertia", "[relay_chn][core][inertia]") +{ uint8_t ch = 0; // Channel to test // 1. Start in forward direction @@ -219,7 +229,8 @@ TEST_CASE("Forward to Reverse transition with opposite inertia", "[relay_chn][co // TEST_CASE: Test transition from reverse to forward with inertia and state checks // Scenario: RELAY_CHN_STATE_REVERSE -> (relay_chn_run_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_FORWARD -TEST_CASE("Reverse to Forward transition with opposite inertia", "[relay_chn][core][inertia]") { +TEST_CASE("Reverse to Forward transition with opposite inertia", "[relay_chn][core][inertia]") +{ uint8_t ch = 0; // 1. Start in reverse direction @@ -239,7 +250,8 @@ TEST_CASE("Reverse to Forward transition with opposite inertia", "[relay_chn][co // TEST_CASE: Test issuing the same run command while already running (no inertia expected) // Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD -TEST_CASE("Running in same direction does not incur inertia", "[relay_chn][core][inertia]") { +TEST_CASE("Running in same direction does not incur inertia", "[relay_chn][core][inertia]") +{ uint8_t ch = 0; // 1. Start in forward direction @@ -257,7 +269,8 @@ TEST_CASE("Running in same direction does not incur inertia", "[relay_chn][core] // TEST_CASE: Test transition from FREE state to running (no inertia expected) // Scenario: RELAY_CHN_STATE_IDLE -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD -TEST_CASE("FREE to Running transition without inertia", "[relay_chn][core][inertia]") { +TEST_CASE("FREE to Running transition without inertia", "[relay_chn][core][inertia]") +{ uint8_t ch = 0; // setUp() should have already brought the channel to FREE state diff --git a/test_apps/main/test_relay_chn_core_single.c b/test_apps/main/test_relay_chn_core_single.c index 0b82c40..c26fe7c 100644 --- a/test_apps/main/test_relay_chn_core_single.c +++ b/test_apps/main/test_relay_chn_core_single.c @@ -21,12 +21,14 @@ TEST_CASE("relay_chn_create handles invalid arguments", "[relay_chn][core]") // --- Basic Functionality Tests --- // TEST_CASE: Test that relay channels initialize correctly to RELAY_CHN_STATE_IDLE -TEST_CASE("Relay channels initialize correctly to IDLE state", "[relay_chn][core]") { +TEST_CASE("Relay channels initialize correctly to IDLE state", "[relay_chn][core]") +{ TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); } // TEST_CASE: Test that relays run in the forward direction and update their state -TEST_CASE("Relay channels run forward and update state", "[relay_chn][core]") { +TEST_CASE("Relay channels run forward and update state", "[relay_chn][core]") +{ relay_chn_run_forward(); // Short delay for state to update vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); @@ -34,7 +36,8 @@ TEST_CASE("Relay channels run forward and update state", "[relay_chn][core]") { } // TEST_CASE: Test that relays run in the reverse direction and update their state -TEST_CASE("Relay channels run reverse and update state", "[relay_chn][core]") { +TEST_CASE("Relay channels run reverse and update state", "[relay_chn][core]") +{ relay_chn_run_reverse(); // relay_chn_run_reverse returns void vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state()); @@ -43,7 +46,8 @@ TEST_CASE("Relay channels run reverse and update state", "[relay_chn][core]") { // TEST_CASE: Test that relays stop and transition to RELAY_CHN_STATE_IDLE // This test also verifies the transition to IDLE state after a STOP command. -TEST_CASE("Relay channels stop and update to IDLE state", "[relay_chn][core]") { +TEST_CASE("Relay channels stop and update to IDLE state", "[relay_chn][core]") +{ // First, run forward to test stopping and transitioning to IDLE state relay_chn_run_forward(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); @@ -67,7 +71,8 @@ TEST_CASE("Relay channels stop and update to IDLE state", "[relay_chn][core]") { // TEST_CASE: Test transition from forward to reverse with inertia and state checks // Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_REVERSE -TEST_CASE("Forward to Reverse transition with opposite inertia", "[relay_chn][core][inertia]") { +TEST_CASE("Forward to Reverse transition with opposite inertia", "[relay_chn][core][inertia]") +{ // 1. Start in forward direction relay_chn_run_forward(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Short delay for state stabilization @@ -86,7 +91,8 @@ TEST_CASE("Forward to Reverse transition with opposite inertia", "[relay_chn][co // TEST_CASE: Test transition from reverse to forward with inertia and state checks // Scenario: RELAY_CHN_STATE_REVERSE -> (relay_chn_run_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_FORWARD -TEST_CASE("Reverse to Forward transition with opposite inertia", "[relay_chn][core][inertia]") { +TEST_CASE("Reverse to Forward transition with opposite inertia", "[relay_chn][core][inertia]") +{ // 1. Start in reverse direction relay_chn_run_reverse(); // relay_chn_run_reverse returns void vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); @@ -104,7 +110,8 @@ TEST_CASE("Reverse to Forward transition with opposite inertia", "[relay_chn][co // TEST_CASE: Test issuing the same run command while already running (no inertia expected) // Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD -TEST_CASE("Running in same direction does not incur inertia", "[relay_chn][core][inertia]") { +TEST_CASE("Running in same direction does not incur inertia", "[relay_chn][core][inertia]") +{ // 1. Start in forward direction relay_chn_run_forward(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); @@ -120,7 +127,8 @@ TEST_CASE("Running in same direction does not incur inertia", "[relay_chn][core] // TEST_CASE: Test transition from IDLE state to running (no inertia expected) // Scenario: RELAY_CHN_STATE_IDLE -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD -TEST_CASE("IDLE to Running transition without inertia", "[relay_chn][core][inertia]") { +TEST_CASE("IDLE to Running transition without inertia", "[relay_chn][core][inertia]") +{ // setUp() should have already brought the channel to IDLE state TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); diff --git a/test_apps/main/test_relay_chn_listener_multi.c b/test_apps/main/test_relay_chn_listener_multi.c index 117a4cd..aca0ea0 100644 --- a/test_apps/main/test_relay_chn_listener_multi.c +++ b/test_apps/main/test_relay_chn_listener_multi.c @@ -35,7 +35,8 @@ static void test_listener_2(uint8_t chn_id, relay_chn_state_t old_state, relay_c // ### Listener Functionality Tests -TEST_CASE("Listener is called on state change", "[relay_chn][listener]") { +TEST_CASE("Listener is called on state change", "[relay_chn][listener]") +{ uint8_t ch = 0; reset_listener_info(&listener1_info); @@ -56,7 +57,8 @@ 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][listener]") +{ uint8_t ch = 0; reset_listener_info(&listener1_info); @@ -72,7 +74,8 @@ 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][listener]") +{ uint8_t ch = 0; reset_listener_info(&listener1_info); reset_listener_info(&listener2_info); @@ -100,7 +103,8 @@ 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][listener]") +{ reset_listener_info(&listener1_info); // 1. Registering a NULL listener should fail diff --git a/test_apps/main/test_relay_chn_listener_single.c b/test_apps/main/test_relay_chn_listener_single.c index 53e27ad..155972c 100644 --- a/test_apps/main/test_relay_chn_listener_single.c +++ b/test_apps/main/test_relay_chn_listener_single.c @@ -34,7 +34,8 @@ static void test_listener_2(uint8_t chn_id, relay_chn_state_t old_state, relay_c // ### Listener Functionality Tests -TEST_CASE("Listener is called on state change", "[relay_chn][listener]") { +TEST_CASE("Listener is called on state change", "[relay_chn][listener]") +{ reset_listener_info(&listener1_info); // 1. Register the listener @@ -53,7 +54,8 @@ 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][listener]") +{ reset_listener_info(&listener1_info); // 1. Register and then immediately unregister the listener @@ -68,7 +70,8 @@ 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][listener]") +{ reset_listener_info(&listener1_info); reset_listener_info(&listener2_info); @@ -95,7 +98,8 @@ 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][listener]") +{ reset_listener_info(&listener1_info); // 1. Registering a NULL listener should fail diff --git a/test_apps/main/test_relay_chn_tilt_multi.c b/test_apps/main/test_relay_chn_tilt_multi.c index 5fab474..2a30196 100644 --- a/test_apps/main/test_relay_chn_tilt_multi.c +++ b/test_apps/main/test_relay_chn_tilt_multi.c @@ -74,7 +74,8 @@ void prepare_all_channels_for_tilt(int initial_cmd) { // TEST_CASE: Test transition from running forward to tilt forward // Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_tilt_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_FORWARD -TEST_CASE("Run Forward to Tilt Forward transition with inertia", "[relay_chn][tilt][inertia]") { +TEST_CASE("Run Forward to Tilt Forward transition with inertia", "[relay_chn][tilt][inertia]") +{ // Prepare channel by running forward first to set last_run_cmd prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD); @@ -96,7 +97,8 @@ TEST_CASE("Run Forward to Tilt Forward transition with inertia", "[relay_chn][ti // TEST_CASE: Test transition from running reverse to tilt reverse // Scenario: RELAY_CHN_STATE_REVERSE -> (relay_chn_tilt_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_REVERSE -TEST_CASE("Run Reverse to Tilt Reverse transition with inertia", "[relay_chn][tilt][inertia]") { +TEST_CASE("Run Reverse to Tilt Reverse transition with inertia", "[relay_chn][tilt][inertia]") +{ // Prepare channel by running reverse first to set last_run_cmd prepare_all_channels_for_tilt(RELAY_CHN_CMD_REVERSE); @@ -116,7 +118,8 @@ TEST_CASE("Run Reverse to Tilt Reverse transition with inertia", "[relay_chn][ti // TEST_CASE: Test transition from FREE state to tilt forward (now with preparation) // Scenario: RELAY_CHN_STATE_IDLE -> (prepare) -> RELAY_CHN_STATE_IDLE -> (relay_chn_tilt_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_FORWARD -TEST_CASE("FREE to Tilt Forward transition with inertia (prepared)", "[relay_chn][tilt][inertia]") { +TEST_CASE("FREE to Tilt Forward transition with inertia (prepared)", "[relay_chn][tilt][inertia]") +{ // Prepare channel by running forward first to set last_run_cmd prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD); @@ -129,7 +132,8 @@ TEST_CASE("FREE to Tilt Forward transition with inertia (prepared)", "[relay_chn // TEST_CASE: Test transition from FREE state to tilt reverse (now with preparation) // Scenario: RELAY_CHN_STATE_IDLE -> (prepare) -> RELAY_CHN_STATE_IDLE -> (relay_chn_tilt_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_REVERSE -TEST_CASE("FREE to Tilt Reverse transition with inertia (prepared)", "[relay_chn][tilt][inertia]") { +TEST_CASE("FREE to Tilt Reverse transition with inertia (prepared)", "[relay_chn][tilt][inertia]") +{ // Prepare channel by running reverse first to set last_run_cmd prepare_all_channels_for_tilt(RELAY_CHN_CMD_REVERSE); @@ -141,7 +145,8 @@ TEST_CASE("FREE to Tilt Reverse transition with inertia (prepared)", "[relay_chn // TEST_CASE: Test transition from tilt forward to run forward (inertia expected for run) // Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD -TEST_CASE("Tilt Forward to Run Forward transition with inertia", "[relay_chn][tilt][inertia]") { +TEST_CASE("Tilt Forward to Run Forward transition with inertia", "[relay_chn][tilt][inertia]") +{ // Prepare channel by running forward first to set last_run_cmd, then tilt prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD); relay_chn_tilt_forward_all(); // Go to tilt state @@ -158,7 +163,8 @@ TEST_CASE("Tilt Forward to Run Forward transition with inertia", "[relay_chn][ti // TEST_CASE: Test transition from tilt reverse to run reverse (no inertia expected for run) // Scenario: RELAY_CHN_STATE_TILT_REVERSE -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_REVERSE -TEST_CASE("Tilt Reverse to Run Reverse transition with inertia", "[relay_chn][tilt][inertia]") { +TEST_CASE("Tilt Reverse to Run Reverse transition with inertia", "[relay_chn][tilt][inertia]") +{ // Prepare channel by running reverse first to set last_run_cmd, then tilt prepare_all_channels_for_tilt(RELAY_CHN_CMD_REVERSE); relay_chn_tilt_reverse_all(); // Go to tilt state @@ -174,7 +180,8 @@ TEST_CASE("Tilt Reverse to Run Reverse transition with inertia", "[relay_chn][ti // TEST_CASE: Test transition from tilt forward to run reverse (without inertia) // Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_REVERSE -TEST_CASE("Tilt Forward to Run Reverse transition without inertia", "[relay_chn][tilt][inertia]") { +TEST_CASE("Tilt Forward to Run Reverse transition without inertia", "[relay_chn][tilt][inertia]") +{ // Prepare channel by running forward first to set last_run_cmd, then tilt prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD); relay_chn_tilt_forward_all(); // Go to tilt state @@ -189,7 +196,8 @@ TEST_CASE("Tilt Forward to Run Reverse transition without inertia", "[relay_chn] // TEST_CASE: Test stopping from a tilt state (no inertia for stop command itself) // Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_tilt_stop) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_IDLE -TEST_CASE("Tilt to Stop transition without immediate inertia for stop", "[relay_chn][tilt][inertia]") { +TEST_CASE("Tilt to Stop transition without immediate inertia for stop", "[relay_chn][tilt][inertia]") +{ // Prepare all channels by running forward first to set last_run_cmd, then tilt prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD); relay_chn_tilt_forward_all(); // Go to tilt state @@ -271,7 +279,8 @@ TEST_CASE("tilt_auto_all tilts channels based on last run direction", "[relay_ch } // Test relay_chn_tilt_auto() chooses correct tilt direction -TEST_CASE("relay_chn_tilt_auto chooses correct direction", "[relay_chn][tilt][auto]") { +TEST_CASE("relay_chn_tilt_auto chooses correct direction", "[relay_chn][tilt][auto]") +{ // Prepare FORWARD prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD); relay_chn_tilt_auto_all(); @@ -291,7 +300,8 @@ TEST_CASE("relay_chn_tilt_auto chooses correct direction", "[relay_chn][tilt][au } // Test sensitivity set/get -TEST_CASE("relay_chn_tilt_set_sensitivity and get", "[relay_chn][tilt][sensitivity]") { +TEST_CASE("relay_chn_tilt_set_sensitivity and get", "[relay_chn][tilt][sensitivity]") +{ uint8_t ch = 0; relay_chn_tilt_set_sensitivity(ch, 0); TEST_ASSERT_EQUAL_UINT8(0, relay_chn_tilt_get_sensitivity(ch)); @@ -313,7 +323,8 @@ TEST_CASE("relay_chn_tilt_set_sensitivity and get", "[relay_chn][tilt][sensitivi } // Test tilt counter logic: forward x3, reverse x3, extra reverse fails -TEST_CASE("tilt counter logic: forward and reverse consumption", "[relay_chn][tilt][counter]") { +TEST_CASE("tilt counter logic: forward and reverse consumption", "[relay_chn][tilt][counter]") +{ // Tilt execution time at 100% sensitivity in milliseconds (10 + 90) #define TEST_TILT_EXECUTION_TIME_MS 100 @@ -346,7 +357,8 @@ TEST_CASE("tilt counter logic: forward and reverse consumption", "[relay_chn][ti } // Test run command during TILT state -TEST_CASE("run command during TILT state transitions correctly", "[relay_chn][tilt][run-during-tilt]") { +TEST_CASE("run command during TILT state transitions correctly", "[relay_chn][tilt][run-during-tilt]") +{ prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD); relay_chn_tilt_forward_all(); vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); diff --git a/test_apps/main/test_relay_chn_tilt_single.c b/test_apps/main/test_relay_chn_tilt_single.c index c30242f..40f8dcb 100644 --- a/test_apps/main/test_relay_chn_tilt_single.c +++ b/test_apps/main/test_relay_chn_tilt_single.c @@ -32,7 +32,8 @@ void prepare_channel_for_tilt(int initial_cmd) { // TEST_CASE: Test transition from running forward to tilt forward // Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_tilt_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_FORWARD -TEST_CASE("Run Forward to Tilt Forward transition with inertia", "[relay_chn][tilt][inertia]") { +TEST_CASE("Run Forward to Tilt Forward transition with inertia", "[relay_chn][tilt][inertia]") +{ // Prepare channel by running forward first to set last_run_cmd prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); @@ -54,7 +55,8 @@ TEST_CASE("Run Forward to Tilt Forward transition with inertia", "[relay_chn][ti // TEST_CASE: Test transition from running reverse to tilt reverse // Scenario: RELAY_CHN_STATE_REVERSE -> (relay_chn_tilt_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_REVERSE -TEST_CASE("Run Reverse to Tilt Reverse transition with inertia", "[relay_chn][tilt][inertia]") { +TEST_CASE("Run Reverse to Tilt Reverse transition with inertia", "[relay_chn][tilt][inertia]") +{ // Prepare channel by running reverse first to set last_run_cmd prepare_channel_for_tilt(RELAY_CHN_CMD_REVERSE); @@ -74,7 +76,8 @@ TEST_CASE("Run Reverse to Tilt Reverse transition with inertia", "[relay_chn][ti // TEST_CASE: Test transition from FREE state to tilt forward (now with preparation) // Scenario: RELAY_CHN_STATE_IDLE -> (prepare) -> RELAY_CHN_STATE_IDLE -> (relay_chn_tilt_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_FORWARD -TEST_CASE("FREE to Tilt Forward transition with inertia (prepared)", "[relay_chn][tilt][inertia]") { +TEST_CASE("FREE to Tilt Forward transition with inertia (prepared)", "[relay_chn][tilt][inertia]") +{ // Prepare channel by running forward first to set last_run_cmd prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); // Ensure we are back to FREE @@ -88,7 +91,8 @@ TEST_CASE("FREE to Tilt Forward transition with inertia (prepared)", "[relay_chn // TEST_CASE: Test transition from FREE state to tilt reverse (now with preparation) // Scenario: RELAY_CHN_STATE_IDLE -> (prepare) -> RELAY_CHN_STATE_IDLE -> (relay_chn_tilt_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_REVERSE -TEST_CASE("FREE to Tilt Reverse transition with inertia (prepared)", "[relay_chn][tilt][inertia]") { +TEST_CASE("FREE to Tilt Reverse transition with inertia (prepared)", "[relay_chn][tilt][inertia]") +{ // Prepare channel by running reverse first to set last_run_cmd prepare_channel_for_tilt(RELAY_CHN_CMD_REVERSE); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); // Ensure we are back to FREE @@ -101,7 +105,8 @@ TEST_CASE("FREE to Tilt Reverse transition with inertia (prepared)", "[relay_chn // TEST_CASE: Test transition from tilt forward to run forward (inertia expected for run) // Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD -TEST_CASE("Tilt Forward to Run Forward transition with inertia", "[relay_chn][tilt][inertia]") { +TEST_CASE("Tilt Forward to Run Forward transition with inertia", "[relay_chn][tilt][inertia]") +{ // Prepare channel by running forward first to set last_run_cmd, then tilt prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); relay_chn_tilt_forward(); // Go to tilt state @@ -118,7 +123,8 @@ TEST_CASE("Tilt Forward to Run Forward transition with inertia", "[relay_chn][ti // TEST_CASE: Test transition from tilt reverse to run reverse (no inertia expected for run) // Scenario: RELAY_CHN_STATE_TILT_REVERSE -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_REVERSE -TEST_CASE("Tilt Reverse to Run Reverse transition with inertia", "[relay_chn][tilt][inertia]") { +TEST_CASE("Tilt Reverse to Run Reverse transition with inertia", "[relay_chn][tilt][inertia]") +{ // Prepare channel by running reverse first to set last_run_cmd, then tilt prepare_channel_for_tilt(RELAY_CHN_CMD_REVERSE); relay_chn_tilt_reverse(); // Go to tilt state @@ -134,7 +140,8 @@ TEST_CASE("Tilt Reverse to Run Reverse transition with inertia", "[relay_chn][ti // TEST_CASE: Test transition from tilt forward to run reverse (without inertia) // Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_REVERSE -TEST_CASE("Tilt Forward to Run Reverse transition without inertia", "[relay_chn][tilt][inertia]") { +TEST_CASE("Tilt Forward to Run Reverse transition without inertia", "[relay_chn][tilt][inertia]") +{ // Prepare channel by running forward first to set last_run_cmd, then tilt prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); relay_chn_tilt_forward(); // Go to tilt state @@ -149,7 +156,8 @@ TEST_CASE("Tilt Forward to Run Reverse transition without inertia", "[relay_chn] // TEST_CASE: Test stopping from a tilt state (no inertia for stop command itself) // Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_stop) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_IDLE -TEST_CASE("Tilt to Stop transition without immediate inertia for stop", "[relay_chn][tilt][inertia]") { +TEST_CASE("Tilt to Stop transition without immediate inertia for stop", "[relay_chn][tilt][inertia]") +{ // Prepare channel by running forward first to set last_run_cmd, then tilt prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); relay_chn_tilt_forward(); // Go to tilt state @@ -164,7 +172,8 @@ TEST_CASE("Tilt to Stop transition without immediate inertia for stop", "[relay_ } // Test relay_chn_tilt_auto() chooses correct tilt direction -TEST_CASE("relay_chn_tilt_auto chooses correct direction", "[relay_chn][tilt][auto]") { +TEST_CASE("relay_chn_tilt_auto chooses correct direction", "[relay_chn][tilt][auto]") +{ // Prepare FORWARD prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); relay_chn_tilt_auto(); @@ -181,7 +190,8 @@ TEST_CASE("relay_chn_tilt_auto chooses correct direction", "[relay_chn][tilt][au } // Test sensitivity set/get -TEST_CASE("relay_chn_tilt_set_sensitivity and get", "[relay_chn][tilt][sensitivity]") { +TEST_CASE("relay_chn_tilt_set_sensitivity and get", "[relay_chn][tilt][sensitivity]") +{ relay_chn_tilt_set_sensitivity(0); TEST_ASSERT_EQUAL_UINT8(0, relay_chn_tilt_get_sensitivity()); @@ -196,7 +206,8 @@ TEST_CASE("relay_chn_tilt_set_sensitivity and get", "[relay_chn][tilt][sensitivi } // Test tilt counter logic: forward x3, reverse x3, extra reverse fails -TEST_CASE("tilt counter logic: forward and reverse consumption", "[relay_chn][tilt][counter]") { +TEST_CASE("tilt counter logic: forward and reverse consumption", "[relay_chn][tilt][counter]") +{ prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); // Tilt forward 3 times @@ -228,7 +239,8 @@ TEST_CASE("tilt counter logic: forward and reverse consumption", "[relay_chn][ti } // Test run command during TILT state -TEST_CASE("run command during TILT state transitions correctly", "[relay_chn][tilt][run-during-tilt]") { +TEST_CASE("run command during TILT state transitions correctly", "[relay_chn][tilt][run-during-tilt]") +{ prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); relay_chn_tilt_forward(); vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); From 0b75df35d178438bd5cc677edee83bb4503f18a7 Mon Sep 17 00:00:00 2001 From: ismail Date: Tue, 26 Aug 2025 17:03:36 +0300 Subject: [PATCH 28/69] Use build profiles instead of providing sdkconfig.defaults manually ESP-IDF provides a more efficient way of handling and combining multi-configurations for a project than the way currently used: Build profiles. So I switched to this method instead of providing the sdkconfig.defaults.* files manually. This reduced the number of sdkconfig.defaults.* files as well as the configuration parameters defined in them. Refs #1085 --- scripts/run_tests.sh | 39 ++++++++----------- ..._all_cfgs.sh => run_tests_all_profiles.sh} | 15 ++++--- test_apps/profiles/full_multi | 1 + test_apps/profiles/full_single | 1 + test_apps/profiles/multi | 1 + test_apps/profiles/nvs | 1 + test_apps/profiles/nvs_custom | 1 + test_apps/profiles/run_limit | 1 + test_apps/profiles/tilt | 1 + test_apps/sdkconfig | 30 ++++---------- ...fig.defaults.single => sdkconfig.defaults} | 0 test_apps/sdkconfig.defaults.multi | 6 --- test_apps/sdkconfig.defaults.multi.custom_nvs | 14 ------- test_apps/sdkconfig.defaults.multi.full | 17 -------- test_apps/sdkconfig.defaults.multi.nvs | 8 ---- test_apps/sdkconfig.defaults.multi.run_limit | 9 ----- test_apps/sdkconfig.defaults.multi.tilt | 8 ---- test_apps/sdkconfig.defaults.nvs | 1 + test_apps/sdkconfig.defaults.nvs_custom | 6 +++ test_apps/sdkconfig.defaults.run_limit | 3 ++ .../sdkconfig.defaults.single.custom_nvs | 13 ------- test_apps/sdkconfig.defaults.single.full | 16 -------- test_apps/sdkconfig.defaults.single.nvs | 7 ---- test_apps/sdkconfig.defaults.single.run_limit | 8 ---- test_apps/sdkconfig.defaults.single.tilt | 7 ---- test_apps/sdkconfig.defaults.tilt | 1 + 26 files changed, 51 insertions(+), 164 deletions(-) rename scripts/{run_tests_all_cfgs.sh => run_tests_all_profiles.sh} (56%) create mode 100644 test_apps/profiles/full_multi create mode 100644 test_apps/profiles/full_single create mode 100644 test_apps/profiles/multi create mode 100644 test_apps/profiles/nvs create mode 100644 test_apps/profiles/nvs_custom create mode 100644 test_apps/profiles/run_limit create mode 100644 test_apps/profiles/tilt rename test_apps/{sdkconfig.defaults.single => sdkconfig.defaults} (100%) delete mode 100644 test_apps/sdkconfig.defaults.multi.custom_nvs delete mode 100644 test_apps/sdkconfig.defaults.multi.full delete mode 100644 test_apps/sdkconfig.defaults.multi.nvs delete mode 100644 test_apps/sdkconfig.defaults.multi.run_limit delete mode 100644 test_apps/sdkconfig.defaults.multi.tilt create mode 100644 test_apps/sdkconfig.defaults.nvs create mode 100644 test_apps/sdkconfig.defaults.nvs_custom create mode 100644 test_apps/sdkconfig.defaults.run_limit delete mode 100644 test_apps/sdkconfig.defaults.single.custom_nvs delete mode 100644 test_apps/sdkconfig.defaults.single.full delete mode 100644 test_apps/sdkconfig.defaults.single.nvs delete mode 100644 test_apps/sdkconfig.defaults.single.run_limit delete mode 100644 test_apps/sdkconfig.defaults.single.tilt create mode 100644 test_apps/sdkconfig.defaults.tilt diff --git a/scripts/run_tests.sh b/scripts/run_tests.sh index 5cd050e..46e946f 100755 --- a/scripts/run_tests.sh +++ b/scripts/run_tests.sh @@ -11,12 +11,12 @@ fi # ==== 2. Valid Modes and Defaults ==== valid_test_tags=("core" "tilt" "listener" "all" "relay_chn" "nvs" "run_limit" "batch") +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 arg_clean=false arg_log=false arg_dry_run=false -arg_sdkconfig_file="" -flag_file=false print_help() { echo "Usage: $0 -t [OPTIONS]" @@ -28,6 +28,10 @@ print_help() { echo "" echo " If no tag is specified, it defaults to 'all'." echo "" + echo " -p, --profile [run_limit|tilt|nvs|nvs_custom|multi|full_single|full_multi] Specify which test tag to run." + echo "" + echo " If no profile is specified, it defaults to 'full_multi'." + echo "" echo "Options:" echo " -f, --file Specify a custom sdkconfig file to use for the build." echo " Defaults to 'sdkconfig.defaults' if not provided." @@ -54,9 +58,8 @@ while [[ $# -gt 0 ]]; do arg_tag="$2" shift 2 ;; - --file|-f) - arg_sdkconfig_file="$2" - flag_file=true + --profile|-p) + arg_profile="$2" shift 2 ;; --clean|-c) @@ -86,6 +89,11 @@ if [[ ! " ${valid_test_tags[*]} " =~ " $arg_tag " ]]; then usage fi +if [[ ! " ${valid_test_profiles[*]} " =~ " $arg_profile " ]]; then + echo "❌ Invalid profile: '$arg_profile'" + usage +fi + # ==== 5. Resolve Paths and Switch to Working Directory ==== script_dir=$(dirname "$(readlink -f "$0")") project_root=$(dirname "$script_dir") @@ -98,21 +106,9 @@ if [[ -z "$test_apps_dir" || ! -d "$test_apps_dir" ]]; then echo " Please ensure the script is in a 'scripts' directory and 'test_apps' is a sibling." exit 1 fi + echo "✅ Found 'test_apps' at: $test_apps_dir" - -if $flag_file; then - if [[ -z "$arg_sdkconfig_file" || ! -f "$arg_sdkconfig_file" ]]; then - echo "❌ Invalid or missing file: '$arg_sdkconfig_file'" - usage - fi - # Resolve to an absolute path to work correctly after changing directory - arg_sdkconfig_file=$(readlink -f "$arg_sdkconfig_file") -else - echo "⚠️ No SDK configuration file provided. Using default sdkconfig." - arg_sdkconfig_file="$test_apps_dir/sdkconfig.defaults" -fi - -echo "🧪 Test mode: $arg_tag" +echo "🧪 Test mode: $arg_tag | Profile: $arg_profile" echo "🧹 Clean: $arg_clean | 📄 Log: $arg_log" echo "📂 Changing to working directory: $test_apps_dir" @@ -129,15 +125,14 @@ fi # In some locales, we can get errors like: "Error: unknown opcode or format name 'wsr.IBREAKA1'" # The 'LC_ALL=C' env variable is set to ensure consistent locale settings. LC_ALL=C \ -SDKCONFIG_DEFAULTS="$arg_sdkconfig_file" \ RELAY_CHN_UNITY_TEST_GROUP_TAG="$arg_tag" \ -idf.py reconfigure build +idf.py @profiles/"${arg_profile}" reconfigure build echo "🚀 Running test with QEMU..." if $arg_log; then TIMESTAMP=$(date +"%Y%m%d_%H%M%S") - LOGFILE="test_log_${arg_tag}_$TIMESTAMP.txt" + LOGFILE="test_log_${arg_profile}_${arg_tag}_$TIMESTAMP.txt" if $arg_dry_run; then echo "🔍 Dry run mode: Logging to $LOGFILE but not executing." | tee "$LOGFILE" echo "Command: idf.py qemu" | tee "$LOGFILE" diff --git a/scripts/run_tests_all_cfgs.sh b/scripts/run_tests_all_profiles.sh similarity index 56% rename from scripts/run_tests_all_cfgs.sh rename to scripts/run_tests_all_profiles.sh index 7703627..4a04ab4 100755 --- a/scripts/run_tests_all_cfgs.sh +++ b/scripts/run_tests_all_profiles.sh @@ -18,13 +18,16 @@ echo "🔍 Searching for 'test_apps' directory in '$project_root'..." test_apps_dir=$(find "$project_root" -type d -name "test_apps" | head -n 1) echo "test_apps dir: ${test_apps_dir}" -# Execute tests for all configs -mapfile -t sdkcfg_files < <(find "$test_apps_dir" -maxdepth 1 -type f -name "sdkconfig.defaults*") +# Execute tests for all profiles +mapfile -t profiles < <(find "${test_apps_dir}/profiles" -maxdepth 1 -type f) -for sdkcfg_file in "${sdkcfg_files[@]}"; do - echo "🔧 Running tests with config: $sdkcfg_file" - "${script_dir}"/run_tests.sh -c -f "$sdkcfg_file" -t "$arg_tag" || { - echo "❌ Tests failed with config: $sdkcfg_file" +for profile in "${profiles[@]}"; do + # Get only the name of the profile file + profile=$(basename "${profile}") + + echo "🔧 Running tests with profile: $profile" + "${script_dir}"/run_tests.sh -c -p "$profile" -t "$arg_tag" || { + echo "❌ Tests failed with profile: $profile" exit 1 } done diff --git a/test_apps/profiles/full_multi b/test_apps/profiles/full_multi new file mode 100644 index 0000000..809d252 --- /dev/null +++ b/test_apps/profiles/full_multi @@ -0,0 +1 @@ +-DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.multi;sdkconfig.defaults.run_limit;sdkconfig.defaults.tilt;sdkconfig.defaults.nvs;sdkconfig.defaults.nvs_custom" \ No newline at end of file diff --git a/test_apps/profiles/full_single b/test_apps/profiles/full_single new file mode 100644 index 0000000..2404204 --- /dev/null +++ b/test_apps/profiles/full_single @@ -0,0 +1 @@ +-DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.run_limit;sdkconfig.defaults.tilt;sdkconfig.defaults.nvs;sdkconfig.defaults.nvs_custom" \ No newline at end of file diff --git a/test_apps/profiles/multi b/test_apps/profiles/multi new file mode 100644 index 0000000..4f755f0 --- /dev/null +++ b/test_apps/profiles/multi @@ -0,0 +1 @@ +-DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.multi" \ No newline at end of file diff --git a/test_apps/profiles/nvs b/test_apps/profiles/nvs new file mode 100644 index 0000000..d20a2aa --- /dev/null +++ b/test_apps/profiles/nvs @@ -0,0 +1 @@ +-DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.nvs" \ No newline at end of file diff --git a/test_apps/profiles/nvs_custom b/test_apps/profiles/nvs_custom new file mode 100644 index 0000000..871184c --- /dev/null +++ b/test_apps/profiles/nvs_custom @@ -0,0 +1 @@ +-DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.nvs;sdkconfig.defaults.nvs_custom" \ No newline at end of file diff --git a/test_apps/profiles/run_limit b/test_apps/profiles/run_limit new file mode 100644 index 0000000..46146f0 --- /dev/null +++ b/test_apps/profiles/run_limit @@ -0,0 +1 @@ +-DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.run_limit" \ No newline at end of file diff --git a/test_apps/profiles/tilt b/test_apps/profiles/tilt new file mode 100644 index 0000000..8028041 --- /dev/null +++ b/test_apps/profiles/tilt @@ -0,0 +1 @@ +-DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.tilt" \ No newline at end of file diff --git a/test_apps/sdkconfig b/test_apps/sdkconfig index bc3790f..cdb482e 100644 --- a/test_apps/sdkconfig +++ b/test_apps/sdkconfig @@ -395,13 +395,13 @@ CONFIG_ESPTOOLPY_MONITOR_BAUD=115200 # # Partition Table # -# CONFIG_PARTITION_TABLE_SINGLE_APP is not set +CONFIG_PARTITION_TABLE_SINGLE_APP=y # CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE is not set # CONFIG_PARTITION_TABLE_TWO_OTA is not set # CONFIG_PARTITION_TABLE_TWO_OTA_LARGE is not set -CONFIG_PARTITION_TABLE_CUSTOM=y -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/part_nvs.csv" -CONFIG_PARTITION_TABLE_FILENAME="partitions/part_nvs.csv" +# CONFIG_PARTITION_TABLE_CUSTOM is not set +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions_singleapp.csv" CONFIG_PARTITION_TABLE_OFFSET=0x8000 CONFIG_PARTITION_TABLE_MD5=y # end of Partition Table @@ -1261,26 +1261,10 @@ CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=y # CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 CONFIG_RELAY_CHN_COUNT=8 -CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT=y -CONFIG_RELAY_CHN_ENABLE_TILTING=y -CONFIG_RELAY_CHN_ENABLE_NVS=y +# CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT is not set +# CONFIG_RELAY_CHN_ENABLE_TILTING is not set +# CONFIG_RELAY_CHN_ENABLE_NVS is not set # end of Relay Channel Driver Configuration - -# -# Relay Channel NVS Storage Configuration -# -CONFIG_RELAY_CHN_NVS_NAMESPACE="relay_chn" -CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION=y -CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME="app_data" -# end of Relay Channel NVS Storage Configuration - -# -# Relay Channel Run Limit Configuration -# -CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC=1 -CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC=600 -CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC=60 -# end of Relay Channel Run Limit Configuration # end of Component config # CONFIG_IDF_EXPERIMENTAL_FEATURES is not set diff --git a/test_apps/sdkconfig.defaults.single b/test_apps/sdkconfig.defaults similarity index 100% rename from test_apps/sdkconfig.defaults.single rename to test_apps/sdkconfig.defaults diff --git a/test_apps/sdkconfig.defaults.multi b/test_apps/sdkconfig.defaults.multi index 6262a4c..cf7f135 100644 --- a/test_apps/sdkconfig.defaults.multi +++ b/test_apps/sdkconfig.defaults.multi @@ -1,7 +1 @@ -# Disable task WDT for tests -CONFIG_ESP_TASK_WDT_INIT=n - -# Relay Channel Driver Default Configuration for Testing -# Keep this as short as possible for tests -CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 CONFIG_RELAY_CHN_COUNT=8 \ No newline at end of file diff --git a/test_apps/sdkconfig.defaults.multi.custom_nvs b/test_apps/sdkconfig.defaults.multi.custom_nvs deleted file mode 100644 index 040c093..0000000 --- a/test_apps/sdkconfig.defaults.multi.custom_nvs +++ /dev/null @@ -1,14 +0,0 @@ -# Disable task WDT for tests -CONFIG_ESP_TASK_WDT_INIT=n - -# Partition configuration -CONFIG_PARTITION_TABLE_SINGLE_APP=y -CONFIG_PARTITION_TABLE_CUSTOM=y -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/part_nvs.csv" - -# Relay Channel Driver Default Configuration for Testing -# Keep this as short as possible for tests -CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 -CONFIG_RELAY_CHN_COUNT=8 -CONFIG_RELAY_CHN_ENABLE_NVS=y -CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION=y \ No newline at end of file diff --git a/test_apps/sdkconfig.defaults.multi.full b/test_apps/sdkconfig.defaults.multi.full deleted file mode 100644 index 0388bf1..0000000 --- a/test_apps/sdkconfig.defaults.multi.full +++ /dev/null @@ -1,17 +0,0 @@ -# Disable task WDT for tests -CONFIG_ESP_TASK_WDT_INIT=n - -# Partition configuration -CONFIG_PARTITION_TABLE_SINGLE_APP=y -CONFIG_PARTITION_TABLE_CUSTOM=y -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/part_nvs.csv" - -# Relay Channel Driver Default Configuration for Testing -# Keep this as short as possible for tests -CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 -CONFIG_RELAY_CHN_COUNT=8 -CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT=y -CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC=1 -CONFIG_RELAY_CHN_ENABLE_TILTING=y -CONFIG_RELAY_CHN_ENABLE_NVS=y -CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION=y \ No newline at end of file diff --git a/test_apps/sdkconfig.defaults.multi.nvs b/test_apps/sdkconfig.defaults.multi.nvs deleted file mode 100644 index 5e3bbdd..0000000 --- a/test_apps/sdkconfig.defaults.multi.nvs +++ /dev/null @@ -1,8 +0,0 @@ -# Disable task WDT for tests -CONFIG_ESP_TASK_WDT_INIT=n - -# Relay Channel Driver Default Configuration for Testing -# Keep this as short as possible for tests -CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 -CONFIG_RELAY_CHN_COUNT=8 -CONFIG_RELAY_CHN_ENABLE_NVS=y \ No newline at end of file diff --git a/test_apps/sdkconfig.defaults.multi.run_limit b/test_apps/sdkconfig.defaults.multi.run_limit deleted file mode 100644 index b5d1df4..0000000 --- a/test_apps/sdkconfig.defaults.multi.run_limit +++ /dev/null @@ -1,9 +0,0 @@ -# Disable task WDT for tests -CONFIG_ESP_TASK_WDT_INIT=n - -# Relay Channel Driver Default Configuration for Testing -# Keep this as short as possible for tests -CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 -CONFIG_RELAY_CHN_COUNT=8 -CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT=y -CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC=1 \ No newline at end of file diff --git a/test_apps/sdkconfig.defaults.multi.tilt b/test_apps/sdkconfig.defaults.multi.tilt deleted file mode 100644 index e3c4d79..0000000 --- a/test_apps/sdkconfig.defaults.multi.tilt +++ /dev/null @@ -1,8 +0,0 @@ -# Disable task WDT for tests -CONFIG_ESP_TASK_WDT_INIT=n - -# Relay Channel Driver Default Configuration for Testing -# Keep this as short as possible for tests -CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 -CONFIG_RELAY_CHN_COUNT=8 -CONFIG_RELAY_CHN_ENABLE_TILTING=y \ No newline at end of file diff --git a/test_apps/sdkconfig.defaults.nvs b/test_apps/sdkconfig.defaults.nvs new file mode 100644 index 0000000..d31f8b9 --- /dev/null +++ b/test_apps/sdkconfig.defaults.nvs @@ -0,0 +1 @@ +CONFIG_RELAY_CHN_ENABLE_NVS=y \ No newline at end of file diff --git a/test_apps/sdkconfig.defaults.nvs_custom b/test_apps/sdkconfig.defaults.nvs_custom new file mode 100644 index 0000000..43d7bc4 --- /dev/null +++ b/test_apps/sdkconfig.defaults.nvs_custom @@ -0,0 +1,6 @@ +# Partition configuration +CONFIG_PARTITION_TABLE_SINGLE_APP=y +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/part_nvs.csv" + +CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION=y \ No newline at end of file diff --git a/test_apps/sdkconfig.defaults.run_limit b/test_apps/sdkconfig.defaults.run_limit new file mode 100644 index 0000000..78d933b --- /dev/null +++ b/test_apps/sdkconfig.defaults.run_limit @@ -0,0 +1,3 @@ +CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT=y +# Keep this as short as possible for tests +CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC=1 \ No newline at end of file diff --git a/test_apps/sdkconfig.defaults.single.custom_nvs b/test_apps/sdkconfig.defaults.single.custom_nvs deleted file mode 100644 index 46f1e8e..0000000 --- a/test_apps/sdkconfig.defaults.single.custom_nvs +++ /dev/null @@ -1,13 +0,0 @@ -# Disable task WDT for tests -CONFIG_ESP_TASK_WDT_INIT=n - -# Partition configuration -CONFIG_PARTITION_TABLE_SINGLE_APP=y -CONFIG_PARTITION_TABLE_CUSTOM=y -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/part_nvs.csv" - -# Relay Channel Driver Default Configuration for Testing -# Keep this as short as possible for tests -CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 -CONFIG_RELAY_CHN_ENABLE_NVS=y -CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION=y \ No newline at end of file diff --git a/test_apps/sdkconfig.defaults.single.full b/test_apps/sdkconfig.defaults.single.full deleted file mode 100644 index 79085de..0000000 --- a/test_apps/sdkconfig.defaults.single.full +++ /dev/null @@ -1,16 +0,0 @@ -# Disable task WDT for tests -CONFIG_ESP_TASK_WDT_INIT=n - -# Partition configuration -CONFIG_PARTITION_TABLE_SINGLE_APP=y -CONFIG_PARTITION_TABLE_CUSTOM=y -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/part_nvs.csv" - -# Relay Channel Driver Default Configuration for Testing -# Keep this as short as possible for tests -CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 -CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT=y -CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC=1 -CONFIG_RELAY_CHN_ENABLE_TILTING=y -CONFIG_RELAY_CHN_ENABLE_NVS=y -CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION=y \ No newline at end of file diff --git a/test_apps/sdkconfig.defaults.single.nvs b/test_apps/sdkconfig.defaults.single.nvs deleted file mode 100644 index d8a62fd..0000000 --- a/test_apps/sdkconfig.defaults.single.nvs +++ /dev/null @@ -1,7 +0,0 @@ -# Disable task WDT for tests -CONFIG_ESP_TASK_WDT_INIT=n - -# Relay Channel Driver Default Configuration for Testing -# Keep this as short as possible for tests -CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 -CONFIG_RELAY_CHN_ENABLE_NVS=y \ No newline at end of file diff --git a/test_apps/sdkconfig.defaults.single.run_limit b/test_apps/sdkconfig.defaults.single.run_limit deleted file mode 100644 index 71c2df9..0000000 --- a/test_apps/sdkconfig.defaults.single.run_limit +++ /dev/null @@ -1,8 +0,0 @@ -# Disable task WDT for tests -CONFIG_ESP_TASK_WDT_INIT=n - -# Relay Channel Driver Default Configuration for Testing -# Keep this as short as possible for tests -CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 -CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT=y -CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC=1 \ No newline at end of file diff --git a/test_apps/sdkconfig.defaults.single.tilt b/test_apps/sdkconfig.defaults.single.tilt deleted file mode 100644 index e111deb..0000000 --- a/test_apps/sdkconfig.defaults.single.tilt +++ /dev/null @@ -1,7 +0,0 @@ -# Disable task WDT for tests -CONFIG_ESP_TASK_WDT_INIT=n - -# Relay Channel Driver Default Configuration for Testing -# Keep this as short as possible for tests -CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 -CONFIG_RELAY_CHN_ENABLE_TILTING=y \ No newline at end of file diff --git a/test_apps/sdkconfig.defaults.tilt b/test_apps/sdkconfig.defaults.tilt new file mode 100644 index 0000000..7ececcd --- /dev/null +++ b/test_apps/sdkconfig.defaults.tilt @@ -0,0 +1 @@ +CONFIG_RELAY_CHN_ENABLE_TILTING=y \ No newline at end of file From 9a6b8c9f803b9de5fe830f8b274905c99090300e Mon Sep 17 00:00:00 2001 From: ismail Date: Tue, 26 Aug 2025 17:43:19 +0300 Subject: [PATCH 29/69] Add upper boundary checks for tilt sensitivity settings The tilt sensitivity values were passed to the NVS module without the boundaries being checked, even though the maximum percent value is 100. This commit fixes this issue. Also test cases are added to cover the upper boundary checks for the tilt sensitivity settings. Fixes #1086 --- src/relay_chn_tilt.c | 5 ++++ test_apps/main/test_relay_chn_tilt_multi.c | 29 +++++++++++++++++++++ test_apps/main/test_relay_chn_tilt_single.c | 12 +++++++++ 3 files changed, 46 insertions(+) diff --git a/src/relay_chn_tilt.c b/src/relay_chn_tilt.c index a7ea07a..fdc732f 100644 --- a/src/relay_chn_tilt.c +++ b/src/relay_chn_tilt.c @@ -39,6 +39,7 @@ static const char *TAG = "RELAY_CHN_TILT"; * 100 / (RELAY_CHN_TILT_RUN_MAX_MS - RELAY_CHN_TILT_RUN_MIN_MS) ) /**@}*/ +#define ADJUST_TILT_SENS_BOUNDARIES(sens) if (sens > 100) sens = 100 /// @brief Tilt steps. typedef enum { @@ -316,6 +317,7 @@ static void relay_chn_tilt_compute_set_sensitivity(relay_chn_tilt_ctl_t *tilt_ct void relay_chn_tilt_set_sensitivity(uint8_t chn_id, uint8_t sensitivity) { if (relay_chn_is_channel_id_valid(chn_id)) { + ADJUST_TILT_SENS_BOUNDARIES(sensitivity); relay_chn_tilt_compute_set_sensitivity(&tilt_ctls[chn_id], sensitivity); #if CONFIG_RELAY_CHN_ENABLE_NVS @@ -334,6 +336,7 @@ esp_err_t relay_chn_tilt_set_sensitivity_all(uint8_t *sensitivities) ESP_LOGW(TAG, "set_sensitivity_all: Run limits have been set until channel %d since sensitivities[%d] is NULL", i, i); break; } + ADJUST_TILT_SENS_BOUNDARIES(*src_sensitivity); relay_chn_tilt_compute_set_sensitivity(&tilt_ctls[i], *src_sensitivity); #if CONFIG_RELAY_CHN_ENABLE_NVS relay_chn_nvs_set_tilt_sensitivity(i, *src_sensitivity); @@ -345,6 +348,7 @@ esp_err_t relay_chn_tilt_set_sensitivity_all(uint8_t *sensitivities) void relay_chn_tilt_set_sensitivity_all_with(uint8_t sensitivity) { for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + ADJUST_TILT_SENS_BOUNDARIES(sensitivity); relay_chn_tilt_compute_set_sensitivity(&tilt_ctls[i], sensitivity); #if CONFIG_RELAY_CHN_ENABLE_NVS relay_chn_nvs_set_tilt_sensitivity(i, sensitivity); @@ -377,6 +381,7 @@ esp_err_t relay_chn_tilt_get_sensitivity_all(uint8_t *sensitivities) void relay_chn_tilt_set_sensitivity(uint8_t sensitivity) { + ADJUST_TILT_SENS_BOUNDARIES(sensitivity); relay_chn_tilt_compute_set_sensitivity(&tilt_ctl, sensitivity); #if CONFIG_RELAY_CHN_ENABLE_NVS diff --git a/test_apps/main/test_relay_chn_tilt_multi.c b/test_apps/main/test_relay_chn_tilt_multi.c index 2a30196..40e10a4 100644 --- a/test_apps/main/test_relay_chn_tilt_multi.c +++ b/test_apps/main/test_relay_chn_tilt_multi.c @@ -322,6 +322,35 @@ TEST_CASE("relay_chn_tilt_set_sensitivity and get", "[relay_chn][tilt][sensitivi TEST_ASSERT_EQUAL_UINT8_ARRAY(expect, vals, CONFIG_RELAY_CHN_COUNT); } +// Test sensitivity upper boundary for all set functions +TEST_CASE("relay_chn_tilt_set_sensitivity functions handle upper boundary", "[relay_chn][tilt][sensitivity]") +{ + // 1. Test relay_chn_tilt_set_sensitivity() for each channel + for (uint8_t ch = 0; ch < CONFIG_RELAY_CHN_COUNT; ch++) { + relay_chn_tilt_set_sensitivity(ch, 101); + TEST_ASSERT_EQUAL_UINT8(100, relay_chn_tilt_get_sensitivity(ch)); + + relay_chn_tilt_set_sensitivity(ch, 255); + TEST_ASSERT_EQUAL_UINT8(100, relay_chn_tilt_get_sensitivity(ch)); + } + + // 2. Test relay_chn_tilt_set_sensitivity_all_with() + relay_chn_tilt_set_sensitivity_all_with(150); + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + TEST_ASSERT_EQUAL_UINT8(100, relay_chn_tilt_get_sensitivity(i)); + } + + // 3. Test relay_chn_tilt_set_sensitivity_all() + uint8_t sensitivities[CONFIG_RELAY_CHN_COUNT]; + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + sensitivities[i] = 100 + i * 10; // Values like 100, 110, 120... + } + TEST_ESP_OK(relay_chn_tilt_set_sensitivity_all(sensitivities)); + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + TEST_ASSERT_EQUAL_UINT8(100, relay_chn_tilt_get_sensitivity(i)); + } +} + // Test tilt counter logic: forward x3, reverse x3, extra reverse fails TEST_CASE("tilt counter logic: forward and reverse consumption", "[relay_chn][tilt][counter]") { diff --git a/test_apps/main/test_relay_chn_tilt_single.c b/test_apps/main/test_relay_chn_tilt_single.c index 40f8dcb..4cef671 100644 --- a/test_apps/main/test_relay_chn_tilt_single.c +++ b/test_apps/main/test_relay_chn_tilt_single.c @@ -205,6 +205,18 @@ TEST_CASE("relay_chn_tilt_set_sensitivity and get", "[relay_chn][tilt][sensitivi TEST_ASSERT_EQUAL_UINT8(42, relay_chn_tilt_get_sensitivity()); } +// Test sensitivity upper boundary +TEST_CASE("relay_chn_tilt_set_sensitivity handles upper boundary", "[relay_chn][tilt][sensitivity]") +{ + // Set sensitivity to a value greater than 100 + relay_chn_tilt_set_sensitivity(101); + // It should be capped at 100 + TEST_ASSERT_EQUAL_UINT8(100, relay_chn_tilt_get_sensitivity()); + + relay_chn_tilt_set_sensitivity(200); + TEST_ASSERT_EQUAL_UINT8(100, relay_chn_tilt_get_sensitivity()); +} + // Test tilt counter logic: forward x3, reverse x3, extra reverse fails TEST_CASE("tilt counter logic: forward and reverse consumption", "[relay_chn][tilt][counter]") { From 79a66c19d75d62be809643a7143e58c1b4462347 Mon Sep 17 00:00:00 2001 From: ismail Date: Tue, 26 Aug 2025 18:08:05 +0300 Subject: [PATCH 30/69] Rename time_sec to limit_sec in relay channel functions for clarity Fixes #1087 --- include/relay_chn.h | 16 ++++++++-------- include/relay_chn_adapter.h | 16 ++++++++-------- private_include/relay_chn_nvs.h | 4 ++-- src/relay_chn_ctl_single.c | 14 +++++++------- src/relay_chn_nvs.c | 10 +++++----- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/include/relay_chn.h b/include/relay_chn.h index 9d7a12f..424add7 100644 --- a/include/relay_chn.h +++ b/include/relay_chn.h @@ -226,15 +226,15 @@ esp_err_t relay_chn_ctl_get_run_limit_all(uint16_t *limits_sec); * * Sets the time limit in seconds for the specified channel. It will not proceed * if the channel ID is invalid. - * If the time_sec value is lesser than the CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC, + * If the limit_sec value is lesser than the CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC, * the value will be set to CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC. - * If the time_sec value is greater than the CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC, + * If the limit_sec value is greater than the CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC, * the value will be set to CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC. * * @param chn_id The ID of the relay channel to query. - * @param time_sec The run limit time in seconds. + * @param limit_sec The run limit time in seconds. */ -void relay_chn_set_run_limit(uint8_t chn_id, uint16_t time_sec); +void relay_chn_set_run_limit(uint8_t chn_id, uint16_t limit_sec); /** * @brief Sets the run limits for all configured relay channels. @@ -466,14 +466,14 @@ uint16_t relay_chn_get_run_limit(void); * * Sets the time limit in seconds for the channel. It will not proceed * if the channel ID is invalid. - * If the time_sec value is lesser than the CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC, + * If the limit_sec value is lesser than the CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC, * the value will be set to CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC. - * If the time_sec value is greater than the CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC, + * If the limit_sec value is greater than the CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC, * the value will be set to CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC. * - * @param time_sec The run limit time in seconds. + * @param limit_sec The run limit time in seconds. */ -void relay_chn_set_run_limit(uint16_t time_sec); +void relay_chn_set_run_limit(uint16_t limit_sec); #endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 diff --git a/include/relay_chn_adapter.h b/include/relay_chn_adapter.h index 5e00d91..85b0093 100644 --- a/include/relay_chn_adapter.h +++ b/include/relay_chn_adapter.h @@ -214,9 +214,9 @@ esp_err_t relay_chn_ctl_get_run_limit_all(uint16_t *limits_sec); * @brief Set the run limit for the specified channel * * @param chn_id The ID of the relay channel to query. - * @param time_sec The run limit time in seconds. + * @param limit_sec The run limit time in seconds. */ -extern void relay_chn_ctl_set_run_limit(uint8_t chn_id, uint16_t time_sec); +extern void relay_chn_ctl_set_run_limit(uint8_t chn_id, uint16_t limit_sec); /** * @brief Sets the run limits for all configured relay channels. @@ -253,9 +253,9 @@ static inline esp_err_t relay_chn_get_run_limit_all(uint16_t *limits_sec) return relay_chn_ctl_get_run_limit_all(limits_sec); } -static inline void relay_chn_set_run_limit(uint8_t chn_id, uint16_t time_sec) +static inline void relay_chn_set_run_limit(uint8_t chn_id, uint16_t limit_sec) { - relay_chn_ctl_set_run_limit(chn_id, time_sec); + relay_chn_ctl_set_run_limit(chn_id, limit_sec); } static inline esp_err_t relay_chn_set_run_limit_all(uint16_t *limits_sec) @@ -358,18 +358,18 @@ extern uint16_t relay_chn_ctl_get_run_limit(void); /** * @brief Set the run limit for the channel * - * @param time_sec The run limit time in seconds. + * @param limit_sec The run limit time in seconds. */ -extern void relay_chn_ctl_set_run_limit(uint16_t time_sec); +extern void relay_chn_ctl_set_run_limit(uint16_t limit_sec); static inline uint16_t relay_chn_get_run_limit(void) { return relay_chn_ctl_get_run_limit(); } -static inline void relay_chn_set_run_limit(uint16_t time_sec) +static inline void relay_chn_set_run_limit(uint16_t limit_sec) { - relay_chn_ctl_set_run_limit(time_sec); + relay_chn_ctl_set_run_limit(limit_sec); } #endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 diff --git a/private_include/relay_chn_nvs.h b/private_include/relay_chn_nvs.h index 8ec4df4..6dab6a3 100644 --- a/private_include/relay_chn_nvs.h +++ b/private_include/relay_chn_nvs.h @@ -53,7 +53,7 @@ esp_err_t relay_chn_nvs_get_direction(uint8_t ch, relay_chn_direction_t *directi * @param[in] direction Run limit value to store. * @return ESP_OK on success, error code otherwise. */ -esp_err_t relay_chn_nvs_set_run_limit(uint8_t ch, uint16_t time_sec); +esp_err_t relay_chn_nvs_set_run_limit(uint8_t ch, uint16_t limit_sec); /** * @brief Retrieve relay channel run limit from NVS. @@ -62,7 +62,7 @@ esp_err_t relay_chn_nvs_set_run_limit(uint8_t ch, uint16_t time_sec); * @param[out] direction Pointer to store retrieved run limit value. * @return ESP_OK on success, error code otherwise. */ -esp_err_t relay_chn_nvs_get_run_limit(uint8_t ch, uint16_t *time_sec); +esp_err_t relay_chn_nvs_get_run_limit(uint8_t ch, uint16_t *limit_sec); #endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 #if CONFIG_RELAY_CHN_ENABLE_TILTING diff --git a/src/relay_chn_ctl_single.c b/src/relay_chn_ctl_single.c index bf3e1cc..af7aba1 100644 --- a/src/relay_chn_ctl_single.c +++ b/src/relay_chn_ctl_single.c @@ -101,18 +101,18 @@ uint16_t relay_chn_ctl_get_run_limit() return chn_ctl.run_limit_sec; } -void relay_chn_ctl_set_run_limit(uint16_t time_sec) +void relay_chn_ctl_set_run_limit(uint16_t limit_sec) { // Check for boundaries - if (time_sec > CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC) - time_sec = CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC; - else if (time_sec < CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC) - time_sec = CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC; + if (limit_sec > CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC) + limit_sec = CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC; + else if (limit_sec < CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC) + limit_sec = CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC; - chn_ctl.run_limit_sec = time_sec; + chn_ctl.run_limit_sec = limit_sec; #if CONFIG_RELAY_CHN_ENABLE_NVS - relay_chn_nvs_set_run_limit(chn_ctl.id, time_sec); + relay_chn_nvs_set_run_limit(chn_ctl.id, limit_sec); #endif } #endif diff --git a/src/relay_chn_nvs.c b/src/relay_chn_nvs.c index 3877162..5ba4fe0 100644 --- a/src/relay_chn_nvs.c +++ b/src/relay_chn_nvs.c @@ -73,18 +73,18 @@ esp_err_t relay_chn_nvs_get_direction(uint8_t ch, relay_chn_direction_t *directi } #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT -esp_err_t relay_chn_nvs_set_run_limit(uint8_t ch, uint16_t time_sec) +esp_err_t relay_chn_nvs_set_run_limit(uint8_t ch, uint16_t limit_sec) { - esp_err_t ret = nvs_set_u16(relay_chn_nvs, RELAY_CHN_KEY_RLIM(ch), time_sec); + esp_err_t ret = nvs_set_u16(relay_chn_nvs, RELAY_CHN_KEY_RLIM(ch), limit_sec); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set run limit for channel %d", ch); return nvs_commit(relay_chn_nvs); } -esp_err_t relay_chn_nvs_get_run_limit(uint8_t ch, uint16_t *time_sec) +esp_err_t relay_chn_nvs_get_run_limit(uint8_t ch, uint16_t *limit_sec) { - ESP_RETURN_ON_FALSE(time_sec != NULL, ESP_ERR_INVALID_ARG, TAG, "Run limit value pointer is NULL"); + ESP_RETURN_ON_FALSE(limit_sec != NULL, ESP_ERR_INVALID_ARG, TAG, "Run limit value pointer is NULL"); - return nvs_get_u16(relay_chn_nvs, RELAY_CHN_KEY_RLIM(ch), time_sec); + return nvs_get_u16(relay_chn_nvs, RELAY_CHN_KEY_RLIM(ch), limit_sec); } #endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 From ae33204a87cdc55d230ac540ac65855468d01c82 Mon Sep 17 00:00:00 2001 From: ismail Date: Wed, 27 Aug 2025 11:03:55 +0300 Subject: [PATCH 31/69] Optimize internal stop calls for output errors --- src/relay_chn_tilt.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/relay_chn_tilt.c b/src/relay_chn_tilt.c index fdc732f..f07e36f 100644 --- a/src/relay_chn_tilt.c +++ b/src/relay_chn_tilt.c @@ -516,7 +516,7 @@ static void relay_chn_tilt_execute_forward(relay_chn_tilt_ctl_t *tilt_ctl) if (relay_chn_output_reverse(tilt_ctl->chn_ctl->output) != ESP_OK) { ESP_LOGE(TAG, "relay_chn_tilt_execute_forward: Failed to output reverse for relay channel #%d!", tilt_ctl->chn_ctl->id); // Stop tilting because of the error - relay_chn_tilt_dispatch_cmd(tilt_ctl, RELAY_CHN_TILT_CMD_STOP); + relay_chn_tilt_execute_stop(tilt_ctl); return; } // Set the move time timer @@ -530,7 +530,7 @@ static void relay_chn_tilt_execute_reverse(relay_chn_tilt_ctl_t *tilt_ctl) if (relay_chn_output_forward(tilt_ctl->chn_ctl->output) != ESP_OK) { ESP_LOGE(TAG, "relay_chn_tilt_execute_reverse: Failed to output forward for relay channel #%d!", tilt_ctl->chn_ctl->id); // Stop tilting because of the error - relay_chn_tilt_dispatch_cmd(tilt_ctl, RELAY_CHN_TILT_CMD_STOP); + relay_chn_tilt_execute_stop(tilt_ctl); return; } // Set the move time timer @@ -545,7 +545,7 @@ static void relay_chn_tilt_execute_pause(relay_chn_tilt_ctl_t *tilt_ctl) if (relay_chn_output_stop(tilt_ctl->chn_ctl->output) != ESP_OK) { ESP_LOGE(TAG, "relay_chn_tilt_execute_pause: Failed to output stop for relay channel #%d!", tilt_ctl->chn_ctl->id); // Stop tilting because of the error - relay_chn_tilt_dispatch_cmd(tilt_ctl, RELAY_CHN_TILT_CMD_STOP); + relay_chn_tilt_execute_stop(tilt_ctl); return; } @@ -553,7 +553,7 @@ static void relay_chn_tilt_execute_pause(relay_chn_tilt_ctl_t *tilt_ctl) if (relay_chn_tilt_count_update(tilt_ctl) == 0) { ESP_LOGD(TAG, "relay_chn_tilt_execute_pause: Relay channel cannot tilt anymore"); // Stop tilting since the tilting limit has been reached - relay_chn_tilt_dispatch_cmd(tilt_ctl, RELAY_CHN_TILT_CMD_STOP); + relay_chn_tilt_execute_stop(tilt_ctl); return; } From 374647732c8c91fb6e27f4237f05325cd458f28d Mon Sep 17 00:00:00 2001 From: ismail Date: Wed, 27 Aug 2025 14:06:37 +0300 Subject: [PATCH 32/69] Fix possible integer underflow bug Fixed a possible integer underflow bug that may cause the timer to be set for an unexpectedly long duration. Fixes #1091 --- src/relay_chn_core.c | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/relay_chn_core.c b/src/relay_chn_core.c index f062e1b..4119062 100644 --- a/src/relay_chn_core.c +++ b/src/relay_chn_core.c @@ -327,19 +327,27 @@ void relay_chn_issue_cmd(relay_chn_ctl_t* chn_ctl, relay_chn_cmd_t cmd) // If the last run command is different from the current command, calculate the time passed // since the last run command stopped and decide whether to run the command immediately or wait uint32_t last_run_cmd_time_ms = relay_chn_run_info_get_last_run_cmd_time_ms(chn_ctl->run_info); - uint32_t inertia_time_passed_ms = (uint32_t) (esp_timer_get_time() / 1000) - last_run_cmd_time_ms; - uint32_t inertia_time_ms = CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS - inertia_time_passed_ms; - if (inertia_time_ms > 0) { - chn_ctl->pending_cmd = cmd; - relay_chn_state_t new_state = cmd == RELAY_CHN_CMD_FORWARD - ? RELAY_CHN_STATE_FORWARD_PENDING : RELAY_CHN_STATE_REVERSE_PENDING; - relay_chn_update_state(chn_ctl, new_state); - // If the time passed is less than the opposite inertia time, wait for the remaining time - relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, inertia_time_ms); - } - else { - // If the time passed is more than the opposite inertia time, run the command immediately + uint32_t current_time_ms = (uint32_t)(esp_timer_get_time() / 1000); + if (current_time_ms < last_run_cmd_time_ms) { // Timer overflow + // If timer overflowed, it's been a long time. Run immediately. relay_chn_dispatch_cmd(chn_ctl, cmd); + } else { + uint32_t inertia_time_passed_ms = current_time_ms - last_run_cmd_time_ms; + if (inertia_time_passed_ms < CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS) { + uint32_t inertia_time_ms = CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS - inertia_time_passed_ms; + chn_ctl->pending_cmd = cmd; + relay_chn_state_t new_state = cmd == RELAY_CHN_CMD_FORWARD + ? RELAY_CHN_STATE_FORWARD_PENDING : RELAY_CHN_STATE_REVERSE_PENDING; + relay_chn_update_state(chn_ctl, new_state); + // If the time passed is less than the opposite inertia time, wait for the remaining time + if (relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, inertia_time_ms) != ESP_OK) { + ESP_LOGE(TAG, "Failed to start inertia timer for ch %d", chn_ctl->id); + relay_chn_execute_idle(chn_ctl); + } + } else { + // If the time passed is more than the opposite inertia time, run the command immediately + relay_chn_dispatch_cmd(chn_ctl, cmd); + } } } break; From 71b632737e11b514f3d8cdf12464ce770a8e6470 Mon Sep 17 00:00:00 2001 From: ismail Date: Wed, 27 Aug 2025 15:56:24 +0300 Subject: [PATCH 33/69] Fix esp_timer_start error handling A helper function added for each module so that each module handles errors by itself. Fixes #1092 --- src/relay_chn_core.c | 23 ++++++++++++++++++----- src/relay_chn_tilt.c | 29 ++++++++++++++++++++++------- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/src/relay_chn_core.c b/src/relay_chn_core.c index 4119062..a1ae5a4 100644 --- a/src/relay_chn_core.c +++ b/src/relay_chn_core.c @@ -263,6 +263,18 @@ void relay_chn_update_state(relay_chn_ctl_t *chn_ctl, relay_chn_state_t new_stat } } +static void relay_chn_execute_idle(relay_chn_ctl_t *chn_ctl); + +static void relay_chn_start_timer_or_idle(relay_chn_ctl_t *chn_ctl, esp_timer_handle_t timer, uint32_t time_ms, const char* timer_name) +{ + if (relay_chn_start_esp_timer_once(timer, time_ms) != ESP_OK) { + ESP_LOGE(TAG, "Failed to start %s timer for ch %d", timer_name, chn_ctl->id); + // Attempt to go to a safe state. + // relay_chn_execute_idle is safe to call, it stops timers and sets state. + relay_chn_execute_idle(chn_ctl); + } +} + /** * @brief The command issuer function. * @@ -374,7 +386,7 @@ void relay_chn_issue_cmd(relay_chn_ctl_t* chn_ctl, relay_chn_cmd_t cmd) relay_chn_state_t new_state = cmd == RELAY_CHN_CMD_FORWARD ? RELAY_CHN_STATE_FORWARD_PENDING : RELAY_CHN_STATE_REVERSE_PENDING; relay_chn_update_state(chn_ctl, new_state); - relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS); + relay_chn_start_timer_or_idle(chn_ctl, chn_ctl->inertia_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS, "inertia"); break; #if CONFIG_RELAY_CHN_ENABLE_TILTING @@ -456,12 +468,13 @@ static void relay_chn_execute_stop(relay_chn_ctl_t *chn_ctl) relay_chn_run_info_set_last_run_cmd_time_ms(chn_ctl->run_info, (uint32_t)(esp_timer_get_time() / 1000)); // Schedule a free command for the channel chn_ctl->pending_cmd = RELAY_CHN_CMD_IDLE; - relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS); + relay_chn_start_timer_or_idle(chn_ctl, chn_ctl->inertia_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS, "idle"); } else { // If the channel was not running one of the run or fwd, issue a free command immediately // relay_chn_dispatch_cmd(chn_ctl, RELAY_CHN_CMD_IDLE); relay_chn_execute_idle(chn_ctl); } + ESP_LOGI(TAG, "execute_stop: #%d, state: %d", chn_ctl->id, chn_ctl->state); // TODO delete } static void relay_chn_execute_forward(relay_chn_ctl_t *chn_ctl) @@ -474,7 +487,7 @@ static void relay_chn_execute_forward(relay_chn_ctl_t *chn_ctl) relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_FORWARD); #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT - relay_chn_start_esp_timer_once(chn_ctl->run_limit_timer, chn_ctl->run_limit_sec * 1000); + relay_chn_start_timer_or_idle(chn_ctl, chn_ctl->run_limit_timer, chn_ctl->run_limit_sec * 1000, "run limit"); #endif } @@ -488,7 +501,7 @@ static void relay_chn_execute_reverse(relay_chn_ctl_t *chn_ctl) relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_REVERSE); #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT - relay_chn_start_esp_timer_once(chn_ctl->run_limit_timer, chn_ctl->run_limit_sec * 1000); + relay_chn_start_timer_or_idle(chn_ctl, chn_ctl->run_limit_timer, chn_ctl->run_limit_sec * 1000, "run limit"); #endif } @@ -497,7 +510,7 @@ static void relay_chn_execute_flip(relay_chn_ctl_t *chn_ctl) relay_chn_output_flip(chn_ctl->output); // Set an inertia on the channel to prevent any immediate movement chn_ctl->pending_cmd = RELAY_CHN_CMD_IDLE; - relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS); + relay_chn_start_timer_or_idle(chn_ctl, chn_ctl->inertia_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS, "flip inertia"); } // Dispatch relay channel command diff --git a/src/relay_chn_tilt.c b/src/relay_chn_tilt.c index f07e36f..98c7c7c 100644 --- a/src/relay_chn_tilt.c +++ b/src/relay_chn_tilt.c @@ -91,6 +91,18 @@ static uint32_t relay_chn_tilt_get_required_timing_before_tilting(relay_chn_tilt return CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS - inertia_time_passed_ms; } +static void relay_chn_tilt_execute_stop(relay_chn_tilt_ctl_t *tilt_ctl); + +static void relay_chn_tilt_start_timer_or_stop(relay_chn_tilt_ctl_t *tilt_ctl, esp_timer_handle_t timer, uint32_t time_ms, const char* timer_name) +{ + if (relay_chn_start_esp_timer_once(timer, time_ms) != ESP_OK) { + ESP_LOGE(TAG, "Failed to start %s timer for ch %d", timer_name, tilt_ctl->chn_ctl->id); + // Attempt to go to a safe state for tilt. + // relay_chn_tilt_execute_stop is safe to call, it stops timers and sets state. + relay_chn_tilt_execute_stop(tilt_ctl); + } +} + // Issue a tilt command to a specific relay channel. static void relay_chn_tilt_issue_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_tilt_cmd_t cmd) { @@ -136,7 +148,7 @@ static void relay_chn_tilt_issue_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_t } else { // Channel needs timing before running tilting action, schedule it tilt_ctl->step = RELAY_CHN_TILT_STEP_PENDING; - relay_chn_start_esp_timer_once(tilt_ctl->tilt_timer, req_timing_ms); + relay_chn_tilt_start_timer_or_stop(tilt_ctl, tilt_ctl->tilt_timer, req_timing_ms, "pending tilt"); } break; } @@ -147,7 +159,7 @@ static void relay_chn_tilt_issue_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_t relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP); // Schedule for tilting tilt_ctl->step = RELAY_CHN_TILT_STEP_PENDING; - relay_chn_start_esp_timer_once(tilt_ctl->tilt_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS); + relay_chn_tilt_start_timer_or_stop(tilt_ctl, tilt_ctl->tilt_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS, "tilt inertia"); } else if (cmd == RELAY_CHN_TILT_CMD_REVERSE) { // Stop the running channel first relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP); @@ -162,7 +174,7 @@ static void relay_chn_tilt_issue_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_t relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP); // Schedule for tilting tilt_ctl->step = RELAY_CHN_TILT_STEP_PENDING; - relay_chn_start_esp_timer_once(tilt_ctl->tilt_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS); + relay_chn_tilt_start_timer_or_stop(tilt_ctl, tilt_ctl->tilt_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS, "tilt inertia"); } else if (cmd == RELAY_CHN_TILT_CMD_FORWARD) { // Stop the running channel first relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP); @@ -507,7 +519,10 @@ static void relay_chn_tilt_execute_stop(relay_chn_tilt_ctl_t *tilt_ctl) #if CONFIG_RELAY_CHN_ENABLE_NVS // Start the flush debounce timer - relay_chn_start_esp_timer_once(tilt_ctl->flush_timer, RELAY_CHN_TILT_FLUSH_DEBOUNCE_MS); + if (relay_chn_start_esp_timer_once(tilt_ctl->flush_timer, RELAY_CHN_TILT_FLUSH_DEBOUNCE_MS) != ESP_OK) { + ESP_LOGE(TAG, "Failed to start tilt flush timer for ch %d", tilt_ctl->chn_ctl->id); + // This is not a critical failure, just log it. The count will be saved on next stop. + } #endif } @@ -520,7 +535,7 @@ static void relay_chn_tilt_execute_forward(relay_chn_tilt_ctl_t *tilt_ctl) return; } // Set the move time timer - relay_chn_start_esp_timer_once(tilt_ctl->tilt_timer, tilt_ctl->tilt_timing.move_time_ms); + relay_chn_tilt_start_timer_or_stop(tilt_ctl, tilt_ctl->tilt_timer, tilt_ctl->tilt_timing.move_time_ms, "tilt move"); // Set to pause step tilt_ctl->step = RELAY_CHN_TILT_STEP_PAUSE; } @@ -534,7 +549,7 @@ static void relay_chn_tilt_execute_reverse(relay_chn_tilt_ctl_t *tilt_ctl) return; } // Set the move time timer - relay_chn_start_esp_timer_once(tilt_ctl->tilt_timer, tilt_ctl->tilt_timing.move_time_ms); + relay_chn_tilt_start_timer_or_stop(tilt_ctl, tilt_ctl->tilt_timer, tilt_ctl->tilt_timing.move_time_ms, "tilt move"); // Set to pause step tilt_ctl->step = RELAY_CHN_TILT_STEP_PAUSE; } @@ -558,7 +573,7 @@ static void relay_chn_tilt_execute_pause(relay_chn_tilt_ctl_t *tilt_ctl) } // Set the pause time timer - relay_chn_start_esp_timer_once(tilt_ctl->tilt_timer, tilt_ctl->tilt_timing.pause_time_ms); + relay_chn_tilt_start_timer_or_stop(tilt_ctl, tilt_ctl->tilt_timer, tilt_ctl->tilt_timing.pause_time_ms, "tilt pause"); // Set to move step tilt_ctl->step = RELAY_CHN_TILT_STEP_MOVE; } From 7d597f37250a2310c6b50d731af038b7a0f61182 Mon Sep 17 00:00:00 2001 From: ismail Date: Wed, 27 Aug 2025 17:02:02 +0300 Subject: [PATCH 34/69] Fix STOP command does not interrupt An if statement has been added to handle the STOP command properly when the `previous_state` is one of the `*PENDING` states. Fixes #1093 --- src/relay_chn_core.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/relay_chn_core.c b/src/relay_chn_core.c index a1ae5a4..be24eb0 100644 --- a/src/relay_chn_core.c +++ b/src/relay_chn_core.c @@ -454,13 +454,20 @@ static void relay_chn_execute_stop(relay_chn_ctl_t *chn_ctl) // If there is any pending command, cancel it since the STOP command is issued right after it chn_ctl->pending_cmd = RELAY_CHN_CMD_NONE; - // Invalidate the channel's timer if it is active - esp_timer_stop(chn_ctl->inertia_timer); #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT esp_timer_stop(chn_ctl->run_limit_timer); #endif + if (previous_state == RELAY_CHN_STATE_FORWARD_PENDING || previous_state == RELAY_CHN_STATE_REVERSE_PENDING) { + chn_ctl->pending_cmd = RELAY_CHN_CMD_IDLE; + // Do nothing more and let the timer set channel idle when it expires + return; + } + + // Invalidate the channel's timer if it is active + esp_timer_stop(chn_ctl->inertia_timer); + // Save the last run time only if the previous state was either STATE FORWARD // or STATE_REVERSE. Then schedule a free command. if (previous_state == RELAY_CHN_STATE_FORWARD || previous_state == RELAY_CHN_STATE_REVERSE) { @@ -474,7 +481,6 @@ static void relay_chn_execute_stop(relay_chn_ctl_t *chn_ctl) // relay_chn_dispatch_cmd(chn_ctl, RELAY_CHN_CMD_IDLE); relay_chn_execute_idle(chn_ctl); } - ESP_LOGI(TAG, "execute_stop: #%d, state: %d", chn_ctl->id, chn_ctl->state); // TODO delete } static void relay_chn_execute_forward(relay_chn_ctl_t *chn_ctl) From 4eb1bb03a0089b1b64b1567789109f256d138b0a Mon Sep 17 00:00:00 2001 From: ismail Date: Wed, 27 Aug 2025 17:16:06 +0300 Subject: [PATCH 35/69] Fix function call Refs #1090 --- test_apps/main/test_relay_chn_tilt_multi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_apps/main/test_relay_chn_tilt_multi.c b/test_apps/main/test_relay_chn_tilt_multi.c index 40e10a4..3904123 100644 --- a/test_apps/main/test_relay_chn_tilt_multi.c +++ b/test_apps/main/test_relay_chn_tilt_multi.c @@ -263,7 +263,7 @@ TEST_CASE("tilt_auto_all tilts channels based on last run direction", "[relay_ch TEST_ASSERT_GREATER_OR_EQUAL_MESSAGE(2, CONFIG_RELAY_CHN_COUNT, "Test requires at least 2 channels"); // 1. Prepare channel 0 with last run FORWARD and channel 1 with last run REVERSE - prepare_channels_for_tilt_with_mixed_runs(0, RELAY_CHN_CMD_FORWARD); + prepare_channels_for_tilt_with_mixed_runs(); // 2. Issue auto tilt command to all channels relay_chn_tilt_auto_all(); From ec1b25d489034310d60e09b7e0029d7d7526f467 Mon Sep 17 00:00:00 2001 From: ismail Date: Wed, 27 Aug 2025 17:26:32 +0300 Subject: [PATCH 36/69] Add missing test cases Some missing test cases for the public API have been added. Closes #1090 --- test_apps/main/test_relay_chn_core_multi.c | 31 ++++++++++- test_apps/main/test_relay_chn_core_single.c | 58 ++++++++++++++++++++- test_apps/main/test_relay_chn_tilt_single.c | 48 ++++++++++++++--- 3 files changed, 127 insertions(+), 10 deletions(-) diff --git a/test_apps/main/test_relay_chn_core_multi.c b/test_apps/main/test_relay_chn_core_multi.c index 9ccdd28..23a8e2a 100644 --- a/test_apps/main/test_relay_chn_core_multi.c +++ b/test_apps/main/test_relay_chn_core_multi.c @@ -450,4 +450,33 @@ TEST_CASE("Test run limit persistence across stop/start", "[relay_chn][run_limit TEST_ASSERT_EQUAL(TEST_RUN_LIMIT_SEC, relay_chn_get_run_limit(i)); } } -#endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 \ No newline at end of file + +TEST_CASE("Run limit functions handle invalid channel ID", "[relay_chn][run_limit]") +{ + const uint8_t invalid_ch = CONFIG_RELAY_CHN_COUNT + 5; + const uint16_t original_limit = relay_chn_get_run_limit(0); + + // get_run_limit with invalid ID should return 0 + TEST_ASSERT_EQUAL(0, relay_chn_get_run_limit(invalid_ch)); + + // set_run_limit with invalid ID should not crash or affect other channels + relay_chn_set_run_limit(invalid_ch, 999); + TEST_ASSERT_EQUAL(original_limit, relay_chn_get_run_limit(0)); +} +#endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT + +TEST_CASE("relay_chn_destroy allows clean-up and re-creation", "[relay_chn][core]") +{ + relay_chn_run_forward_all(); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(i)); + } + + relay_chn_destroy(); + TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); + + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i)); + } +} diff --git a/test_apps/main/test_relay_chn_core_single.c b/test_apps/main/test_relay_chn_core_single.c index c26fe7c..171f98a 100644 --- a/test_apps/main/test_relay_chn_core_single.c +++ b/test_apps/main/test_relay_chn_core_single.c @@ -255,4 +255,60 @@ TEST_CASE("Test run limit persistence across stop/start", "[relay_chn][run_limit // Run limit should persist TEST_ASSERT_EQUAL(TEST_RUN_LIMIT_SEC, relay_chn_get_run_limit()); } -#endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 \ No newline at end of file +#endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 + +TEST_CASE("relay_chn_get_state_str returns correct strings", "[relay_chn][core]") +{ + // This test is a bit contrived as it's hard to force every state + // without complex sequences. We will test the most common ones. + TEST_ASSERT_EQUAL_STRING("IDLE", relay_chn_get_state_str()); + + relay_chn_run_forward(); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + TEST_ASSERT_EQUAL_STRING("FORWARD", relay_chn_get_state_str()); + + relay_chn_run_reverse(); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + TEST_ASSERT_EQUAL_STRING("REVERSE_PENDING", relay_chn_get_state_str()); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS)); + TEST_ASSERT_EQUAL_STRING("REVERSE", relay_chn_get_state_str()); + + relay_chn_stop(); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + TEST_ASSERT_EQUAL_STRING("STOPPED", relay_chn_get_state_str()); +} + +TEST_CASE("Stop command interrupts pending commands", "[relay_chn][core][inertia]") +{ + // 1. Start running forward + relay_chn_run_forward(); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); + + // 2. Issue a reverse command, which will make the state REVERSE_PENDING + relay_chn_run_reverse(); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE_PENDING, relay_chn_get_state()); + + // 3. Before the inertia timer fires, issue a stop command + relay_chn_stop(); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state()); + + // 4. Wait for more than the inertia period + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); + // The channel should transition to IDLE, not REVERSE, because stop cancelled the pending command. + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); +} + +TEST_CASE("relay_chn_destroy allows clean-up and re-creation", "[relay_chn][core]") +{ + relay_chn_run_forward(); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); + + relay_chn_destroy(); + TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count)); + + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); +} diff --git a/test_apps/main/test_relay_chn_tilt_single.c b/test_apps/main/test_relay_chn_tilt_single.c index 4cef671..5f8b36b 100644 --- a/test_apps/main/test_relay_chn_tilt_single.c +++ b/test_apps/main/test_relay_chn_tilt_single.c @@ -30,7 +30,7 @@ void prepare_channel_for_tilt(int initial_cmd) { TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); } -// TEST_CASE: Test transition from running forward to tilt forward +// Test transition from running forward to tilt forward // Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_tilt_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_FORWARD TEST_CASE("Run Forward to Tilt Forward transition with inertia", "[relay_chn][tilt][inertia]") { @@ -53,7 +53,7 @@ TEST_CASE("Run Forward to Tilt Forward transition with inertia", "[relay_chn][ti TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state()); } -// TEST_CASE: Test transition from running reverse to tilt reverse +// Test transition from running reverse to tilt reverse // Scenario: RELAY_CHN_STATE_REVERSE -> (relay_chn_tilt_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_REVERSE TEST_CASE("Run Reverse to Tilt Reverse transition with inertia", "[relay_chn][tilt][inertia]") { @@ -74,7 +74,7 @@ TEST_CASE("Run Reverse to Tilt Reverse transition with inertia", "[relay_chn][ti TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state()); } -// TEST_CASE: Test transition from FREE state to tilt forward (now with preparation) +// Test transition from FREE state to tilt forward (now with preparation) // Scenario: RELAY_CHN_STATE_IDLE -> (prepare) -> RELAY_CHN_STATE_IDLE -> (relay_chn_tilt_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_FORWARD TEST_CASE("FREE to Tilt Forward transition with inertia (prepared)", "[relay_chn][tilt][inertia]") { @@ -89,7 +89,7 @@ TEST_CASE("FREE to Tilt Forward transition with inertia (prepared)", "[relay_chn TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state()); } -// TEST_CASE: Test transition from FREE state to tilt reverse (now with preparation) +// Test transition from FREE state to tilt reverse (now with preparation) // Scenario: RELAY_CHN_STATE_IDLE -> (prepare) -> RELAY_CHN_STATE_IDLE -> (relay_chn_tilt_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_REVERSE TEST_CASE("FREE to Tilt Reverse transition with inertia (prepared)", "[relay_chn][tilt][inertia]") { @@ -103,7 +103,7 @@ TEST_CASE("FREE to Tilt Reverse transition with inertia (prepared)", "[relay_chn TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state()); } -// TEST_CASE: Test transition from tilt forward to run forward (inertia expected for run) +// Test transition from tilt forward to run forward (inertia expected for run) // Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD TEST_CASE("Tilt Forward to Run Forward transition with inertia", "[relay_chn][tilt][inertia]") { @@ -121,7 +121,7 @@ TEST_CASE("Tilt Forward to Run Forward transition with inertia", "[relay_chn][ti TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); } -// TEST_CASE: Test transition from tilt reverse to run reverse (no inertia expected for run) +// Test transition from tilt reverse to run reverse (no inertia expected for run) // Scenario: RELAY_CHN_STATE_TILT_REVERSE -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_REVERSE TEST_CASE("Tilt Reverse to Run Reverse transition with inertia", "[relay_chn][tilt][inertia]") { @@ -138,7 +138,7 @@ TEST_CASE("Tilt Reverse to Run Reverse transition with inertia", "[relay_chn][ti TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state()); } -// TEST_CASE: Test transition from tilt forward to run reverse (without inertia) +// Test transition from tilt forward to run reverse (without inertia) // Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_REVERSE TEST_CASE("Tilt Forward to Run Reverse transition without inertia", "[relay_chn][tilt][inertia]") { @@ -154,7 +154,7 @@ TEST_CASE("Tilt Forward to Run Reverse transition without inertia", "[relay_chn] TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state()); } -// TEST_CASE: Test stopping from a tilt state (no inertia for stop command itself) +// Test stopping from a tilt state (no inertia for stop command itself) // Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_stop) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_IDLE TEST_CASE("Tilt to Stop transition without immediate inertia for stop", "[relay_chn][tilt][inertia]") { @@ -264,4 +264,36 @@ TEST_CASE("run command during TILT state transitions correctly", "[relay_chn][ti // Should transition to REVERSE or REVERSE_PENDING depending on inertia logic relay_chn_state_t state = relay_chn_get_state(); TEST_ASSERT(state == RELAY_CHN_STATE_REVERSE || state == RELAY_CHN_STATE_REVERSE_PENDING); +} + +// Test run command during active tilt cycle (move/pause) +TEST_CASE("run command during active tilt cycle stops tilt", "[relay_chn][tilt][interrupt]") +{ + // Set a known sensitivity for predictable timing. + // For sensitivity=50, move_time=30ms, pause_time=270ms. + relay_chn_tilt_set_sensitivity(50); + const uint32_t move_time_ms = 30; + + // --- Test interrupting during MOVE step --- + prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); + relay_chn_tilt_forward(); + vTaskDelay(pdMS_TO_TICKS(move_time_ms / 2)); // Wait for half of the move time + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state()); + + // Interrupt with run_reverse while in the MOVE part of the cycle + relay_chn_run_reverse(); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + // Should stop tilting and go to REVERSE immediately (no inertia from TILT_FORWARD) + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state()); + + // --- Test interrupting during PAUSE step --- + prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); + relay_chn_tilt_forward(); + vTaskDelay(pdMS_TO_TICKS(move_time_ms + TEST_DELAY_MARGIN_MS)); // Wait past MOVE, into PAUSE + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state()); + + // Interrupt with run_forward while in the PAUSE part of the cycle + relay_chn_run_forward(); + // Should stop tilting and go to FORWARD_PENDING (inertia from TILT_FORWARD) + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD_PENDING, relay_chn_get_state()); } \ No newline at end of file From 0cd6b4e263e5a152616a16f158e8ebdb3b7646d7 Mon Sep 17 00:00:00 2001 From: ismail Date: Thu, 28 Aug 2025 09:30:43 +0300 Subject: [PATCH 37/69] Refactor and enhance reset_channels_to_idle_state - Refactor reset_channels_to_idle_state to reset_channels_to_defaults and enhance functionality with direction reset logic. This is because some tilt test cases were failing due to modified run limit values in some of the previous core test cases. See #1089-3. - A relay channel listener has been added to diagnose channel states during tests. Refs #1089 --- test_apps/main/test_app_main.c | 2 +- test_apps/main/test_common.c | 25 ++++++++++++++++++++++++- test_apps/main/test_common.h | 5 ++++- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/test_apps/main/test_app_main.c b/test_apps/main/test_app_main.c index 8400843..1f8d4f9 100644 --- a/test_apps/main/test_app_main.c +++ b/test_apps/main/test_app_main.c @@ -25,7 +25,7 @@ void setUp() void tearDown() { - reset_channels_to_idle_state(); + reset_channels_to_defaults(); } #if CONFIG_RELAY_CHN_ENABLE_NVS diff --git a/test_apps/main/test_common.c b/test_apps/main/test_common.c index 1dcdfad..d8fdb4d 100644 --- a/test_apps/main/test_common.c +++ b/test_apps/main/test_common.c @@ -32,17 +32,40 @@ const uint8_t gpio_map[] = {4, 5}; const uint8_t gpio_count = sizeof(gpio_map) / sizeof(gpio_map[0]); -void reset_channels_to_idle_state() +void reset_channels_to_defaults() { #if CONFIG_RELAY_CHN_COUNT > 1 relay_chn_stop_all(); +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT + relay_chn_set_run_limit_all_with(CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC); +#endif vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i)); + + // Reset directions + if (relay_chn_get_direction(i) != RELAY_CHN_DIRECTION_DEFAULT) { + relay_chn_flip_direction(i); + TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(i)); + } } #else relay_chn_stop(); +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT + relay_chn_set_run_limit(CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC); +#endif + + // Reset direction + if (relay_chn_get_direction() != RELAY_CHN_DIRECTION_DEFAULT) { + relay_chn_flip_direction(); + TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction()); + } vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); #endif +} + +void test_state_listener(uint8_t id, relay_chn_state_t old_state, relay_chn_state_t new_state) +{ + ESP_LOGI(TEST_TAG, "test_state_listener: id: %d, old_state: %d, new_state: %d", id, old_state, new_state); } \ No newline at end of file diff --git a/test_apps/main/test_common.h b/test_apps/main/test_common.h index 1b9c94a..b2c1c55 100644 --- a/test_apps/main/test_common.h +++ b/test_apps/main/test_common.h @@ -18,4 +18,7 @@ extern const uint8_t gpio_count; #define TEST_DELAY_MARGIN_MS 50 // Reset channels to Idle state -void reset_channels_to_idle_state(void); \ No newline at end of file +void reset_channels_to_defaults(void); + +// Relay channel state listener for tests +void test_state_listener(uint8_t id, relay_chn_state_t old_state, relay_chn_state_t new_state); \ No newline at end of file From a6d38327b7a9a5d8fbde9ced33ebdaaf927687bc Mon Sep 17 00:00:00 2001 From: ismail Date: Thu, 28 Aug 2025 09:34:19 +0300 Subject: [PATCH 38/69] Add test cases for *all and *all_with functions Added test cases for the recently implemented `*all` and `*all_with` functions. Closes #1089. --- test_apps/main/test_relay_chn_core_multi.c | 84 ++++++++++++++++++++++ test_apps/main/test_relay_chn_tilt_multi.c | 32 +++++++++ test_apps/sdkconfig | 32 ++++++--- 3 files changed, 140 insertions(+), 8 deletions(-) diff --git a/test_apps/main/test_relay_chn_core_multi.c b/test_apps/main/test_relay_chn_core_multi.c index 23a8e2a..ffb15e5 100644 --- a/test_apps/main/test_relay_chn_core_multi.c +++ b/test_apps/main/test_relay_chn_core_multi.c @@ -355,6 +355,65 @@ TEST_CASE("Direction flip handles invalid channel ID gracefully", "[relay_chn][c relay_chn_flip_direction(invalid_ch); // Call with an invalid ID TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(invalid_ch)); } +TEST_CASE("get_state_all retrieves all channel states", "[relay_chn][core][batch]") +{ + relay_chn_state_t states[CONFIG_RELAY_CHN_COUNT]; + + // 1. All should be IDLE initially + TEST_ESP_OK(relay_chn_get_state_all(states)); + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, states[i]); + } + + // 2. Set some states + if (CONFIG_RELAY_CHN_COUNT >= 2) { + relay_chn_run_forward(0); + relay_chn_run_reverse(1); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + } else { + relay_chn_run_forward(0); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + } + + // 3. Get all states and verify + TEST_ESP_OK(relay_chn_get_state_all(states)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, states[0]); + if (CONFIG_RELAY_CHN_COUNT >= 2) { + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, states[1]); + } +} + +TEST_CASE("get_direction_all retrieves all channel directions", "[relay_chn][core][direction][batch]") +{ + relay_chn_direction_t directions[CONFIG_RELAY_CHN_COUNT]; + + // 1. All should be default initially + TEST_ESP_OK(relay_chn_get_direction_all(directions)); + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, directions[i]); + } + + // 2. Flip all + relay_chn_flip_direction_all(); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); + + // 3. Get all directions and verify + TEST_ESP_OK(relay_chn_get_direction_all(directions)); + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, directions[i]); + } +} + +TEST_CASE("get_all functions handle NULL arguments", "[relay_chn][core][batch]") +{ + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_get_state_all(NULL)); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_get_direction_all(NULL)); + #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_get_run_limit_all(NULL)); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_set_run_limit_all(NULL)); + #endif +} + #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT #define TEST_RUN_LIMIT_SEC 5 @@ -463,6 +522,31 @@ TEST_CASE("Run limit functions handle invalid channel ID", "[relay_chn][run_limi relay_chn_set_run_limit(invalid_ch, 999); TEST_ASSERT_EQUAL(original_limit, relay_chn_get_run_limit(0)); } + +TEST_CASE("Run limit _all functions work correctly", "[relay_chn][run_limit][batch]") +{ + // 1. Test set_run_limit_all_with + relay_chn_set_run_limit_all_with(TEST_RUN_LIMIT_SEC); + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + TEST_ASSERT_EQUAL(TEST_RUN_LIMIT_SEC, relay_chn_get_run_limit(i)); + } + + // 2. Test get_run_limit_all + uint16_t limits[CONFIG_RELAY_CHN_COUNT]; + TEST_ESP_OK(relay_chn_get_run_limit_all(limits)); + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + TEST_ASSERT_EQUAL(TEST_RUN_LIMIT_SEC, limits[i]); + } + + // 3. Test set_run_limit_all + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + limits[i] = TEST_RUN_LIMIT_SEC + i; + } + TEST_ESP_OK(relay_chn_set_run_limit_all(limits)); + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + TEST_ASSERT_EQUAL(TEST_RUN_LIMIT_SEC + i, relay_chn_get_run_limit(i)); + } +} #endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT TEST_CASE("relay_chn_destroy allows clean-up and re-creation", "[relay_chn][core]") diff --git a/test_apps/main/test_relay_chn_tilt_multi.c b/test_apps/main/test_relay_chn_tilt_multi.c index 3904123..1de119c 100644 --- a/test_apps/main/test_relay_chn_tilt_multi.c +++ b/test_apps/main/test_relay_chn_tilt_multi.c @@ -398,4 +398,36 @@ TEST_CASE("run command during TILT state transitions correctly", "[relay_chn][ti vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Should transition to REVERSE check_all_channels_for_state(RELAY_CHN_STATE_REVERSE); +} + +// Test run command during active tilt cycle (move/pause) +TEST_CASE("run_all command during active tilt cycle stops tilt", "[relay_chn][tilt][interrupt]") +{ + // Set a known sensitivity for predictable timing. + // For sensitivity=50, move_time=30ms, pause_time=270ms. + relay_chn_tilt_set_sensitivity_all_with(50); + const uint32_t move_time_ms = 30; + + // --- Test interrupting during MOVE step --- + prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD); + relay_chn_tilt_forward_all(); + vTaskDelay(pdMS_TO_TICKS(move_time_ms / 2)); // Wait for half of the move time + check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD); + + // Interrupt with run_reverse_all while in the MOVE part of the cycle + relay_chn_run_reverse_all(); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + // Should stop tilting and go to REVERSE immediately (no inertia from TILT_FORWARD) + check_all_channels_for_state(RELAY_CHN_STATE_REVERSE); + + // --- Test interrupting during PAUSE step --- + prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD); + relay_chn_tilt_forward_all(); + vTaskDelay(pdMS_TO_TICKS(move_time_ms + TEST_DELAY_MARGIN_MS)); // Wait past MOVE, into PAUSE + check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD); + + // Interrupt with run_forward_all while in the PAUSE part of the cycle + relay_chn_run_forward_all(); + // Should stop tilting and go to FORWARD_PENDING (inertia from TILT_FORWARD) + check_all_channels_for_state(RELAY_CHN_STATE_FORWARD_PENDING); } \ No newline at end of file diff --git a/test_apps/sdkconfig b/test_apps/sdkconfig index cdb482e..e935f96 100644 --- a/test_apps/sdkconfig +++ b/test_apps/sdkconfig @@ -395,13 +395,13 @@ CONFIG_ESPTOOLPY_MONITOR_BAUD=115200 # # Partition Table # -CONFIG_PARTITION_TABLE_SINGLE_APP=y +# CONFIG_PARTITION_TABLE_SINGLE_APP is not set # CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE is not set # CONFIG_PARTITION_TABLE_TWO_OTA is not set # CONFIG_PARTITION_TABLE_TWO_OTA_LARGE is not set -# CONFIG_PARTITION_TABLE_CUSTOM is not set -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" -CONFIG_PARTITION_TABLE_FILENAME="partitions_singleapp.csv" +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/part_nvs.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions/part_nvs.csv" CONFIG_PARTITION_TABLE_OFFSET=0x8000 CONFIG_PARTITION_TABLE_MD5=y # end of Partition Table @@ -1260,11 +1260,27 @@ CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=y # Relay Channel Driver Configuration # CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 -CONFIG_RELAY_CHN_COUNT=8 -# CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT is not set -# CONFIG_RELAY_CHN_ENABLE_TILTING is not set -# CONFIG_RELAY_CHN_ENABLE_NVS is not set +CONFIG_RELAY_CHN_COUNT=1 +CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT=y +CONFIG_RELAY_CHN_ENABLE_TILTING=y +CONFIG_RELAY_CHN_ENABLE_NVS=y # end of Relay Channel Driver Configuration + +# +# Relay Channel NVS Storage Configuration +# +CONFIG_RELAY_CHN_NVS_NAMESPACE="relay_chn" +CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION=y +CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME="app_data" +# end of Relay Channel NVS Storage Configuration + +# +# Relay Channel Run Limit Configuration +# +CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC=1 +CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC=600 +CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC=60 +# end of Relay Channel Run Limit Configuration # end of Component config # CONFIG_IDF_EXPERIMENTAL_FEATURES is not set From a2e8e3c1204ae8172ebc553ac4deebe7b5f4e286 Mon Sep 17 00:00:00 2001 From: ismail Date: Thu, 28 Aug 2025 17:43:14 +0300 Subject: [PATCH 39/69] Implement relay_chn_stop_prv function Implemented `relay_chn_stop_prv` to streamline stop command handling and avoid unnecessary code execution. Fixes #1095. --- src/relay_chn_core.c | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/src/relay_chn_core.c b/src/relay_chn_core.c index be24eb0..a8c7d08 100644 --- a/src/relay_chn_core.c +++ b/src/relay_chn_core.c @@ -275,6 +275,22 @@ static void relay_chn_start_timer_or_idle(relay_chn_ctl_t *chn_ctl, esp_timer_ha } } +static void relay_chn_stop_prv(relay_chn_ctl_t *chn_ctl) +{ + if (relay_chn_output_stop(chn_ctl->output) != ESP_OK) { + ESP_LOGE(TAG, "relay_chn_execute_stop: Failed to output stop for relay channel #%d!", chn_ctl->id); + } + relay_chn_state_t previous_state = chn_ctl->state; + relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_STOPPED); + + // Save the last run time only if the previous state was either STATE FORWARD + // or STATE_REVERSE. Then schedule a free command. + if (previous_state == RELAY_CHN_STATE_FORWARD || previous_state == RELAY_CHN_STATE_REVERSE) { + // Record the command's last run time + relay_chn_run_info_set_last_run_cmd_time_ms(chn_ctl->run_info, (uint32_t)(esp_timer_get_time() / 1000)); + } +} + /** * @brief The command issuer function. * @@ -368,7 +384,7 @@ void relay_chn_issue_cmd(relay_chn_ctl_t* chn_ctl, relay_chn_cmd_t cmd) case RELAY_CHN_STATE_REVERSE: if (cmd == RELAY_CHN_CMD_FLIP) { // If the command is FLIP, stop the running channel first, then issue the FLIP command - relay_chn_dispatch_cmd(chn_ctl, RELAY_CHN_CMD_STOP); + relay_chn_stop_prv(chn_ctl); relay_chn_dispatch_cmd(chn_ctl, cmd); return; } @@ -379,7 +395,7 @@ void relay_chn_issue_cmd(relay_chn_ctl_t* chn_ctl, relay_chn_cmd_t cmd) } // Stop the channel first before the schedule - relay_chn_dispatch_cmd(chn_ctl, RELAY_CHN_CMD_STOP); + relay_chn_stop_prv(chn_ctl); // If the last run command is different from the current command, wait for the opposite inertia time chn_ctl->pending_cmd = cmd; @@ -446,34 +462,21 @@ static void relay_chn_execute_idle(relay_chn_ctl_t *chn_ctl) static void relay_chn_execute_stop(relay_chn_ctl_t *chn_ctl) { - if (relay_chn_output_stop(chn_ctl->output) != ESP_OK) { - ESP_LOGE(TAG, "relay_chn_execute_stop: Failed to output stop for relay channel #%d!", chn_ctl->id); - } - relay_chn_state_t previous_state = chn_ctl->state; - relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_STOPPED); + relay_chn_stop_prv(chn_ctl); // If there is any pending command, cancel it since the STOP command is issued right after it - chn_ctl->pending_cmd = RELAY_CHN_CMD_NONE; + // chn_ctl->pending_cmd = RELAY_CHN_CMD_NONE; #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT esp_timer_stop(chn_ctl->run_limit_timer); #endif - if (previous_state == RELAY_CHN_STATE_FORWARD_PENDING || previous_state == RELAY_CHN_STATE_REVERSE_PENDING) { - chn_ctl->pending_cmd = RELAY_CHN_CMD_IDLE; - // Do nothing more and let the timer set channel idle when it expires - return; - } - // Invalidate the channel's timer if it is active esp_timer_stop(chn_ctl->inertia_timer); - // Save the last run time only if the previous state was either STATE FORWARD - // or STATE_REVERSE. Then schedule a free command. - if (previous_state == RELAY_CHN_STATE_FORWARD || previous_state == RELAY_CHN_STATE_REVERSE) { - // Record the command's last run time - relay_chn_run_info_set_last_run_cmd_time_ms(chn_ctl->run_info, (uint32_t)(esp_timer_get_time() / 1000)); - // Schedule a free command for the channel + relay_chn_cmd_t last_run_cmd = relay_chn_run_info_get_last_run_cmd(chn_ctl->run_info); + if (last_run_cmd == RELAY_CHN_CMD_FORWARD || last_run_cmd == RELAY_CHN_CMD_REVERSE ) { + // Schedule IDLE chn_ctl->pending_cmd = RELAY_CHN_CMD_IDLE; relay_chn_start_timer_or_idle(chn_ctl, chn_ctl->inertia_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS, "idle"); } else { @@ -483,6 +486,7 @@ static void relay_chn_execute_stop(relay_chn_ctl_t *chn_ctl) } } + static void relay_chn_execute_forward(relay_chn_ctl_t *chn_ctl) { if (relay_chn_output_forward(chn_ctl->output) != ESP_OK) { From dea9f1e9864e7c603bbe58e8953e2392bbccdbde Mon Sep 17 00:00:00 2001 From: ismail Date: Fri, 29 Aug 2025 14:10:18 +0300 Subject: [PATCH 40/69] Add missing test tags --- scripts/run_tests.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/run_tests.sh b/scripts/run_tests.sh index 46e946f..89fb816 100755 --- a/scripts/run_tests.sh +++ b/scripts/run_tests.sh @@ -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") +valid_test_tags=("core" "tilt" "listener" "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|all] Specify which test tag to run." + 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 "" echo " If no tag is specified, it defaults to 'all'." echo "" From db55c0b7e4e6f4f6c73b163bcd6b42ad7a3a5ae1 Mon Sep 17 00:00:00 2001 From: ismail Date: Fri, 29 Aug 2025 17:41:42 +0300 Subject: [PATCH 41/69] Fix single channel tests in multi mode tests Some test cases that were testing for only one channel in multi-channel mode are fixed to test all available channels. Fixes #1094 --- test_apps/main/test_relay_chn_core_multi.c | 248 +++++++++++---------- 1 file changed, 133 insertions(+), 115 deletions(-) diff --git a/test_apps/main/test_relay_chn_core_multi.c b/test_apps/main/test_relay_chn_core_multi.c index ffb15e5..7796039 100644 --- a/test_apps/main/test_relay_chn_core_multi.c +++ b/test_apps/main/test_relay_chn_core_multi.c @@ -1,5 +1,22 @@ #include "test_common.h" +relay_chn_state_t states[CONFIG_RELAY_CHN_COUNT], expect_states[CONFIG_RELAY_CHN_COUNT]; +relay_chn_direction_t directions[CONFIG_RELAY_CHN_COUNT], expect_directions[CONFIG_RELAY_CHN_COUNT]; + +static void test_set_expected_state_all(relay_chn_state_t state) +{ + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + expect_states[i] = state; + } +} + +static void test_set_expected_direction_all(relay_chn_direction_t direction) +{ + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + expect_directions[i] = direction; + } +} + // --- Initialization Tests --- @@ -33,7 +50,7 @@ TEST_CASE("Run forward does nothing if channel id is invalid", "[relay_chn][core { for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { int invalid_id = CONFIG_RELAY_CHN_COUNT * 2 + i; - relay_chn_run_forward(invalid_id); // relay_chn_run_forward returns void + relay_chn_run_forward(invalid_id); // Short delay for state to update vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i)); @@ -44,7 +61,7 @@ TEST_CASE("Run forward does nothing if channel id is invalid", "[relay_chn][core TEST_CASE("Relay channels run forward and update state", "[relay_chn][core]") { for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { - relay_chn_run_forward(i); // relay_chn_run_forward returns void + relay_chn_run_forward(i); // Short delay for state to update vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(i)); @@ -120,7 +137,7 @@ TEST_CASE("Relay channels stop and update to FREE state", "[relay_chn][core]") { for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { // First, run forward to test stopping and transitioning to FREE state - relay_chn_run_forward(i); // relay_chn_run_forward returns void + relay_chn_run_forward(i); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(i)); @@ -171,33 +188,29 @@ TEST_CASE("Get state string returns UNKNOWN when id is invalid", "[relay_chn][co // TEST_CASE: Test independent operation of multiple relay channels TEST_CASE("Multiple channels can operate independently", "[relay_chn][core]") { - if (CONFIG_RELAY_CHN_COUNT >= 2) { - // Start Channel 0 in forward direction - relay_chn_run_forward(0); // relay_chn_run_forward returns void - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(0)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(1)); // Other channel should not be affected + // Start Channel 0 in forward direction + relay_chn_run_forward(0); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(0)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(1)); // Other channel should not be affected - // Start Channel 1 in reverse direction - relay_chn_run_reverse(1); // relay_chn_run_reverse returns void - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(0)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(1)); + // Start Channel 1 in reverse direction + relay_chn_run_reverse(1); // relay_chn_run_reverse returns void + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(0)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(1)); - // Stop Channel 0 and wait for it to become FREE - relay_chn_stop(0); // relay_chn_stop returns void - vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(0)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(1)); // Other channel should continue running + // Stop Channel 0 and wait for it to become FREE + relay_chn_stop(0); // relay_chn_stop returns void + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(0)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(1)); // Other channel should continue running - // Stop Channel 1 and wait for it to become FREE - relay_chn_stop(1); // relay_chn_stop returns void - vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(0)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(1)); - } else { - ESP_LOGW("TEST", "Skipping 'Multiple channels can operate independently' test: Not enough channels available."); - } + // Stop Channel 1 and wait for it to become FREE + relay_chn_stop(1); // relay_chn_stop returns void + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(0)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(1)); } @@ -209,102 +222,105 @@ TEST_CASE("Multiple channels can operate independently", "[relay_chn][core]") // Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_REVERSE TEST_CASE("Forward to Reverse transition with opposite inertia", "[relay_chn][core][inertia]") { - uint8_t ch = 0; // Channel to test - // 1. Start in forward direction - relay_chn_run_forward(ch); // relay_chn_run_forward returns void + relay_chn_run_forward_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Short delay for state stabilization - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch)); + TEST_ESP_OK(relay_chn_get_state_all(states)); + test_set_expected_state_all(RELAY_CHN_STATE_FORWARD); + TEST_ASSERT_EQUAL_UINT_ARRAY(expect_states, states, CONFIG_RELAY_CHN_COUNT); // 2. Issue reverse command - relay_chn_run_reverse(ch); // relay_chn_run_reverse returns void + relay_chn_run_reverse_all(); // relay_chn_run_reverse returns void // Immediately after the command, the motor should be stopped vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE_PENDING, relay_chn_get_state(ch)); + TEST_ESP_OK(relay_chn_get_state_all(states)); + test_set_expected_state_all(RELAY_CHN_STATE_REVERSE_PENDING); + TEST_ASSERT_EQUAL_UINT_ARRAY(expect_states, states, CONFIG_RELAY_CHN_COUNT); // Wait for the inertia period (after which the reverse command will be dispatched) vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch)); // Should now be in reverse state + // Should now be in reverse state + TEST_ESP_OK(relay_chn_get_state_all(states)); + test_set_expected_state_all(RELAY_CHN_STATE_REVERSE); + TEST_ASSERT_EQUAL_UINT_ARRAY(expect_states, states, CONFIG_RELAY_CHN_COUNT); } // TEST_CASE: Test transition from reverse to forward with inertia and state checks // Scenario: RELAY_CHN_STATE_REVERSE -> (relay_chn_run_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_FORWARD TEST_CASE("Reverse to Forward transition with opposite inertia", "[relay_chn][core][inertia]") { - uint8_t ch = 0; - // 1. Start in reverse direction - relay_chn_run_reverse(ch); // relay_chn_run_reverse returns void + relay_chn_run_reverse_all(); // relay_chn_run_reverse returns void vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch)); + TEST_ESP_OK(relay_chn_get_state_all(states)); + test_set_expected_state_all(RELAY_CHN_STATE_REVERSE); + TEST_ASSERT_EQUAL_UINT_ARRAY(expect_states, states, CONFIG_RELAY_CHN_COUNT); // 2. Issue forward command - relay_chn_run_forward(ch); // relay_chn_run_forward returns void + relay_chn_run_forward_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD_PENDING, relay_chn_get_state(ch)); + TEST_ESP_OK(relay_chn_get_state_all(states)); + test_set_expected_state_all(RELAY_CHN_STATE_FORWARD_PENDING); + TEST_ASSERT_EQUAL_UINT_ARRAY(expect_states, states, CONFIG_RELAY_CHN_COUNT); // Wait for inertia vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch)); + TEST_ESP_OK(relay_chn_get_state_all(states)); + test_set_expected_state_all(RELAY_CHN_STATE_FORWARD); + TEST_ASSERT_EQUAL_UINT_ARRAY(expect_states, states, CONFIG_RELAY_CHN_COUNT); } // TEST_CASE: Test issuing the same run command while already running (no inertia expected) // Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD TEST_CASE("Running in same direction does not incur inertia", "[relay_chn][core][inertia]") { - uint8_t ch = 0; - // 1. Start in forward direction - relay_chn_run_forward(ch); // relay_chn_run_forward returns void + relay_chn_run_forward_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch)); + TEST_ESP_OK(relay_chn_get_state_all(states)); + test_set_expected_state_all(RELAY_CHN_STATE_FORWARD); + TEST_ASSERT_EQUAL_UINT_ARRAY(expect_states, states, CONFIG_RELAY_CHN_COUNT); // 2. Issue the same forward command again - relay_chn_run_forward(ch); // relay_chn_run_forward returns void + relay_chn_run_forward_all(); // As per the code, is_direction_opposite_to_current_motion should return false, so no inertia. // Just a short delay to check state remains the same. - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch)); -} - -// TEST_CASE: Test transition from FREE state to running (no inertia expected) -// Scenario: RELAY_CHN_STATE_IDLE -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD -TEST_CASE("FREE to Running transition without inertia", "[relay_chn][core][inertia]") -{ - uint8_t ch = 0; - - // setUp() should have already brought the channel to FREE state - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(ch)); - - // Start in forward direction - relay_chn_run_forward(ch); // relay_chn_run_forward returns void - // No inertia is expected when starting from FREE state. vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch)); + TEST_ESP_OK(relay_chn_get_state_all(states)); + test_set_expected_state_all(RELAY_CHN_STATE_FORWARD); + TEST_ASSERT_EQUAL_UINT_ARRAY(expect_states, states, CONFIG_RELAY_CHN_COUNT); } // ### Direction Flipping Tests -TEST_CASE("Single channel direction can be flipped", "[relay_chn][core][direction]") +TEST_CASE("Direction can be flipped for each channel independently", "[relay_chn][core][direction]") { - const uint8_t ch = 0; - // 1. Initial direction should be default - TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(ch)); - + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(i)); + } + // 2. Flip the direction - relay_chn_flip_direction(ch); + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + relay_chn_flip_direction(i); + } vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); // Wait for flip inertia // 3. Verify direction is flipped - TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction(ch)); + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction(i)); + } // 4. Flip back - relay_chn_flip_direction(ch); + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + relay_chn_flip_direction(i); + } vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); // Wait for flip inertia // 5. Verify direction is back to default - TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(ch)); + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(i)); + } } TEST_CASE("All channels direction can be flipped simultaneously", "[relay_chn][core][direction][batch]") @@ -314,38 +330,44 @@ TEST_CASE("All channels direction can be flipped simultaneously", "[relay_chn][c vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); // 2. Verify all channels are flipped - for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { - TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction(i)); - } - + TEST_ESP_OK(relay_chn_get_direction_all(directions)); + test_set_expected_direction_all(RELAY_CHN_DIRECTION_FLIPPED); + TEST_ASSERT_EQUAL_UINT_ARRAY(expect_directions, directions, CONFIG_RELAY_CHN_COUNT); + // 3. Flip all back relay_chn_flip_direction_all(); vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); // 4. Verify all channels are back to default - for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { - TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(i)); - } + TEST_ESP_OK(relay_chn_get_direction_all(directions)); + test_set_expected_direction_all(RELAY_CHN_DIRECTION_DEFAULT); + TEST_ASSERT_EQUAL_UINT_ARRAY(expect_directions, directions, CONFIG_RELAY_CHN_COUNT); } TEST_CASE("Flipping a running channel stops it and flips direction", "[relay_chn][core][direction]") { - const uint8_t ch = 0; - // 1. Start channel running and verify state - relay_chn_run_forward(ch); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch)); - + relay_chn_run_forward_all(); + TEST_ESP_OK(relay_chn_get_state_all(states)); + test_set_expected_state_all(RELAY_CHN_STATE_FORWARD); + TEST_ASSERT_EQUAL_UINT_ARRAY(expect_states, states, CONFIG_RELAY_CHN_COUNT); // 2. Flip the direction while running - relay_chn_flip_direction(ch); - + relay_chn_flip_direction_all(); // 3. The channel should stop as part of the flip process - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(ch)); + TEST_ESP_OK(relay_chn_get_state_all(states)); + test_set_expected_state_all(RELAY_CHN_STATE_STOPPED); + TEST_ASSERT_EQUAL_UINT_ARRAY(expect_states, states, CONFIG_RELAY_CHN_COUNT); - // 4. Wait for the flip inertia to pass, after which it should be FREE and FLIPPED + // 4. Wait for the flip inertia to pass, after which it should be idle and FLIPPED vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(ch)); - TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction(ch)); + + TEST_ESP_OK(relay_chn_get_state_all(states)); + test_set_expected_state_all(RELAY_CHN_STATE_IDLE); + TEST_ASSERT_EQUAL_UINT_ARRAY(expect_states, states, CONFIG_RELAY_CHN_COUNT); + + TEST_ESP_OK(relay_chn_get_direction_all(directions)); + test_set_expected_direction_all(RELAY_CHN_DIRECTION_FLIPPED); + TEST_ASSERT_EQUAL_UINT_ARRAY(expect_directions, directions, CONFIG_RELAY_CHN_COUNT); } TEST_CASE("Direction flip handles invalid channel ID gracefully", "[relay_chn][core][direction]") @@ -355,43 +377,40 @@ TEST_CASE("Direction flip handles invalid channel ID gracefully", "[relay_chn][c relay_chn_flip_direction(invalid_ch); // Call with an invalid ID TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(invalid_ch)); } + TEST_CASE("get_state_all retrieves all channel states", "[relay_chn][core][batch]") { - relay_chn_state_t states[CONFIG_RELAY_CHN_COUNT]; - // 1. All should be IDLE initially TEST_ESP_OK(relay_chn_get_state_all(states)); - for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, states[i]); - } + test_set_expected_state_all(RELAY_CHN_STATE_IDLE); // 2. Set some states - if (CONFIG_RELAY_CHN_COUNT >= 2) { - relay_chn_run_forward(0); - relay_chn_run_reverse(1); - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - } else { - relay_chn_run_forward(0); - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + if (i % 2 == 0) { + relay_chn_run_forward(i); + } else { + relay_chn_run_reverse(i); + } } + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // 3. Get all states and verify - TEST_ESP_OK(relay_chn_get_state_all(states)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, states[0]); - if (CONFIG_RELAY_CHN_COUNT >= 2) { - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, states[1]); + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + if (i % 2 == 0) { + expect_states[i] = RELAY_CHN_STATE_FORWARD; + } else { + expect_states[i] = RELAY_CHN_STATE_REVERSE; + } } + TEST_ASSERT_EQUAL_UINT_ARRAY(expect_states, states, CONFIG_RELAY_CHN_COUNT); } TEST_CASE("get_direction_all retrieves all channel directions", "[relay_chn][core][direction][batch]") { - relay_chn_direction_t directions[CONFIG_RELAY_CHN_COUNT]; - - // 1. All should be default initially + // 1. All should be default initially TEST_ESP_OK(relay_chn_get_direction_all(directions)); - for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { - TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, directions[i]); - } + test_set_expected_direction_all(RELAY_CHN_DIRECTION_DEFAULT); + TEST_ASSERT_EQUAL_UINT_ARRAY(expect_directions, directions, CONFIG_RELAY_CHN_COUNT); // 2. Flip all relay_chn_flip_direction_all(); @@ -399,9 +418,8 @@ TEST_CASE("get_direction_all retrieves all channel directions", "[relay_chn][cor // 3. Get all directions and verify TEST_ESP_OK(relay_chn_get_direction_all(directions)); - for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { - TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, directions[i]); - } + test_set_expected_direction_all(RELAY_CHN_DIRECTION_FLIPPED); + TEST_ASSERT_EQUAL_UINT_ARRAY(expect_directions, directions, CONFIG_RELAY_CHN_COUNT); } TEST_CASE("get_all functions handle NULL arguments", "[relay_chn][core][batch]") From d2b38a5b4e3ff95a1768fb2af370ea498a1357e1 Mon Sep 17 00:00:00 2001 From: ismail Date: Tue, 2 Sep 2025 15:30:51 +0300 Subject: [PATCH 42/69] Ignore autogenerated sdkconfig file --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index a5217f8..3b5303e 100644 --- a/.gitignore +++ b/.gitignore @@ -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 From 5e8e5a4cabc97f979cfee3a4b1eff620a6ddcdba Mon Sep 17 00:00:00 2001 From: ismail Date: Tue, 2 Sep 2025 15:46:48 +0300 Subject: [PATCH 43/69] Add notification system for relay channel state changes - Introduced a new notification module to handle state change listeners. - Added functions to register and unregister listeners for relay channel state changes. - Implemented a queue-based system to manage notifications and listener callbacks. - Updated core relay channel logic to utilize the new notification system. - Removed old listener management code from relay channel core. - Refactored the former listener tests to notify tests and added tests for the notification system, including handling of multiple listeners and queue overflow scenarios. - Updated CMakeLists.txt to include new source files and headers for the notification module. - Revised README.md to include warnings about callback execution context and performance considerations. Refs #1096, #1085 and closes #1097 --- CMakeLists.txt | 3 +- README.md | 3 + include/relay_chn_adapter.h | 30 ++ private_include/relay_chn_notify.h | 51 ++++ scripts/run_tests.sh | 4 +- src/relay_chn_core.c | 110 +------- src/relay_chn_notify.c | 258 ++++++++++++++++++ test_apps/main/CMakeLists.txt | 5 +- .../main/test_relay_chn_listener_multi.c | 127 --------- test_apps/main/test_relay_chn_notify_common.c | 49 ++++ test_apps/main/test_relay_chn_notify_common.h | 48 ++++ test_apps/main/test_relay_chn_notify_multi.c | 166 +++++++++++ ...ingle.c => test_relay_chn_notify_single.c} | 96 ++++--- 13 files changed, 678 insertions(+), 272 deletions(-) create mode 100644 private_include/relay_chn_notify.h create mode 100644 src/relay_chn_notify.c delete mode 100644 test_apps/main/test_relay_chn_listener_multi.c create mode 100644 test_apps/main/test_relay_chn_notify_common.c create mode 100644 test_apps/main/test_relay_chn_notify_common.h create mode 100644 test_apps/main/test_relay_chn_notify_multi.c rename test_apps/main/{test_relay_chn_listener_single.c => test_relay_chn_notify_single.c} (51%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f103e7..cdc9e67 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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") diff --git a/README.md b/README.md index 0814795..4bf96a0 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/include/relay_chn_adapter.h b/include/relay_chn_adapter.h index 85b0093..a6d27c2 100644 --- a/include/relay_chn_adapter.h +++ b/include/relay_chn_adapter.h @@ -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. diff --git a/private_include/relay_chn_notify.h b/private_include/relay_chn_notify.h new file mode 100644 index 0000000..d862acd --- /dev/null +++ b/private_include/relay_chn_notify.h @@ -0,0 +1,51 @@ +/* + * SPDX-FileCopyrightText: 2025 Kozmotronik Tech + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include "esp_err.h" +#include +#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 + diff --git a/scripts/run_tests.sh b/scripts/run_tests.sh index 89fb816..459c81d 100755 --- a/scripts/run_tests.sh +++ b/scripts/run_tests.sh @@ -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 "" diff --git a/src/relay_chn_core.c b/src/relay_chn_core.c index a8c7d08..64e44ca 100644 --- a/src/relay_chn_core.c +++ b/src/relay_chn_core.c @@ -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); diff --git a/src/relay_chn_notify.c b/src/relay_chn_notify.c new file mode 100644 index 0000000..0051d2a --- /dev/null +++ b/src/relay_chn_notify.c @@ -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; + } + } + } +} \ No newline at end of file diff --git a/test_apps/main/CMakeLists.txt b/test_apps/main/CMakeLists.txt index f148b59..3b8ff4d 100644 --- a/test_apps/main/CMakeLists.txt +++ b/test_apps/main/CMakeLists.txt @@ -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) diff --git a/test_apps/main/test_relay_chn_listener_multi.c b/test_apps/main/test_relay_chn_listener_multi.c deleted file mode 100644 index aca0ea0..0000000 --- a/test_apps/main/test_relay_chn_listener_multi.c +++ /dev/null @@ -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); -} diff --git a/test_apps/main/test_relay_chn_notify_common.c b/test_apps/main/test_relay_chn_notify_common.c new file mode 100644 index 0000000..f033d16 --- /dev/null +++ b/test_apps/main/test_relay_chn_notify_common.c @@ -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); +} \ No newline at end of file diff --git a/test_apps/main/test_relay_chn_notify_common.h b/test_apps/main/test_relay_chn_notify_common.h new file mode 100644 index 0000000..436f4af --- /dev/null +++ b/test_apps/main/test_relay_chn_notify_common.h @@ -0,0 +1,48 @@ +#pragma once + +#include "test_common.h" +#include "freertos/semphr.h" +#include + +#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 diff --git a/test_apps/main/test_relay_chn_notify_multi.c b/test_apps/main/test_relay_chn_notify_multi.c new file mode 100644 index 0000000..35844bc --- /dev/null +++ b/test_apps/main/test_relay_chn_notify_multi.c @@ -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; +} diff --git a/test_apps/main/test_relay_chn_listener_single.c b/test_apps/main/test_relay_chn_notify_single.c similarity index 51% rename from test_apps/main/test_relay_chn_listener_single.c rename to test_apps/main/test_relay_chn_notify_single.c index 155972c..66e2a92 100644 --- a/test_apps/main/test_relay_chn_listener_single.c +++ b/test_apps/main/test_relay_chn_notify_single.c @@ -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; +} From 0122ef0803afb7f861bfe9b44e8b335a9f05e863 Mon Sep 17 00:00:00 2001 From: ismail Date: Tue, 2 Sep 2025 17:02:05 +0300 Subject: [PATCH 44/69] Fix NVS key bug and optimize for single mode - Fixed a critical NVS key generation bug that would cause overwriting the values for all channels. - Optimized the code for single channel mode since no formatting required. - Improved multi-channel test coverage to cover that each value for each channel stored correctly. Refs #1096, #1098 and fixes #1100 --- src/relay_chn_nvs.c | 77 +++++++++++++++---- test_apps/main/test_relay_chn_nvs_multi.c | 91 ++++++++++++++++------- 2 files changed, 124 insertions(+), 44 deletions(-) diff --git a/src/relay_chn_nvs.c b/src/relay_chn_nvs.c index 5ba4fe0..7ddd5ae 100644 --- a/src/relay_chn_nvs.c +++ b/src/relay_chn_nvs.c @@ -5,15 +5,25 @@ */ #include "esp_check.h" +#include "nvs.h" #include "relay_chn_nvs.h" #define RELAY_CHN_KEY_DIR "dir" /*!< Direction key */ #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT -#define RELAY_CHN_KEY_RLIM(ch) "rlim_%d" /*!< Run limit key */ +#if CONFIG_RELAY_CHN_COUNT > 1 +#define RELAY_CHN_KEY_RLIM_FMT "rlim_%d" /*!< Run limit key format for multi-channel */ +#else +#define RELAY_CHN_KEY_RLIM "rlim_0" /*!< Run limit key for single-channel */ +#endif #endif #if CONFIG_RELAY_CHN_ENABLE_TILTING -#define RELAY_CHN_KEY_TSENS(ch) "tsens_%d" /*!< Tilt sensitivity key */ -#define RELAY_CHN_KEY_TCNT(ch) "tcnt_%d" /*!< Tilt count key */ +#if CONFIG_RELAY_CHN_COUNT > 1 +#define RELAY_CHN_KEY_TSENS_FMT "tsens_%d" /*!< Tilt sensitivity key format for multi-channel */ +#define RELAY_CHN_KEY_TCNT_FMT "tcnt_%d" /*!< Tilt count key format for multi-channel */ +#else +#define RELAY_CHN_KEY_TSENS "tsens_0" /*!< Tilt sensitivity key for single-channel */ +#define RELAY_CHN_KEY_TCNT "tcnt_0" /*!< Tilt count key for single-channel */ +#endif #endif static const char *TAG = "RELAY_CHN_NVS"; @@ -44,12 +54,9 @@ esp_err_t relay_chn_nvs_init() esp_err_t relay_chn_nvs_set_direction(uint8_t ch, relay_chn_direction_t direction) { - uint8_t direction_val; + uint8_t direction_val = 0; esp_err_t ret = nvs_get_u8(relay_chn_nvs, RELAY_CHN_KEY_DIR, &direction_val); - if (ret == ESP_ERR_NVS_NOT_FOUND) { - // The key does not exist yet, set it to zero which is the default direction - direction_val = RELAY_CHN_DIRECTION_DEFAULT; - } else if (ret != ESP_OK) { + if (ret != ESP_OK && ret != ESP_ERR_NVS_NOT_FOUND) { ESP_RETURN_ON_ERROR(ret, TAG, "Failed to get direction from NVS with error: %s", esp_err_to_name(ret)); } direction_val &= ~(1 << ch); // Clear the bit for the channel @@ -75,7 +82,14 @@ esp_err_t relay_chn_nvs_get_direction(uint8_t ch, relay_chn_direction_t *directi #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT esp_err_t relay_chn_nvs_set_run_limit(uint8_t ch, uint16_t limit_sec) { - esp_err_t ret = nvs_set_u16(relay_chn_nvs, RELAY_CHN_KEY_RLIM(ch), limit_sec); + esp_err_t ret; +#if CONFIG_RELAY_CHN_COUNT > 1 + char key[NVS_KEY_NAME_MAX_SIZE]; + snprintf(key, sizeof(key), RELAY_CHN_KEY_RLIM_FMT, ch); + ret = nvs_set_u16(relay_chn_nvs, key, limit_sec); +#else + ret = nvs_set_u16(relay_chn_nvs, RELAY_CHN_KEY_RLIM, limit_sec); +#endif ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set run limit for channel %d", ch); return nvs_commit(relay_chn_nvs); } @@ -84,14 +98,27 @@ esp_err_t relay_chn_nvs_get_run_limit(uint8_t ch, uint16_t *limit_sec) { ESP_RETURN_ON_FALSE(limit_sec != NULL, ESP_ERR_INVALID_ARG, TAG, "Run limit value pointer is NULL"); - return nvs_get_u16(relay_chn_nvs, RELAY_CHN_KEY_RLIM(ch), limit_sec); +#if CONFIG_RELAY_CHN_COUNT > 1 + char key[NVS_KEY_NAME_MAX_SIZE]; + snprintf(key, sizeof(key), RELAY_CHN_KEY_RLIM_FMT, ch); + return nvs_get_u16(relay_chn_nvs, key, limit_sec); +#else + return nvs_get_u16(relay_chn_nvs, RELAY_CHN_KEY_RLIM, limit_sec); +#endif } #endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 #if CONFIG_RELAY_CHN_ENABLE_TILTING esp_err_t relay_chn_nvs_set_tilt_sensitivity(uint8_t ch, uint8_t sensitivity) { - esp_err_t ret = nvs_set_u8(relay_chn_nvs, RELAY_CHN_KEY_TSENS(ch), sensitivity); + esp_err_t ret; +#if CONFIG_RELAY_CHN_COUNT > 1 + char key[NVS_KEY_NAME_MAX_SIZE]; + snprintf(key, sizeof(key), RELAY_CHN_KEY_TSENS_FMT, ch); + ret = nvs_set_u8(relay_chn_nvs, key, sensitivity); +#else + ret = nvs_set_u8(relay_chn_nvs, RELAY_CHN_KEY_TSENS, sensitivity); +#endif ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set tilt sensitivity for channel %d", ch); return nvs_commit(relay_chn_nvs); } @@ -100,22 +127,40 @@ esp_err_t relay_chn_nvs_get_tilt_sensitivity(uint8_t ch, uint8_t *sensitivity) { ESP_RETURN_ON_FALSE(sensitivity != NULL, ESP_ERR_INVALID_ARG, TAG, "Sensitivity pointer is NULL"); - return nvs_get_u8(relay_chn_nvs, RELAY_CHN_KEY_TSENS(ch), sensitivity); +#if CONFIG_RELAY_CHN_COUNT > 1 + char key[NVS_KEY_NAME_MAX_SIZE]; + snprintf(key, sizeof(key), RELAY_CHN_KEY_TSENS_FMT, ch); + return nvs_get_u8(relay_chn_nvs, key, sensitivity); +#else + return nvs_get_u8(relay_chn_nvs, RELAY_CHN_KEY_TSENS, sensitivity); +#endif } esp_err_t relay_chn_nvs_set_tilt_count(uint8_t ch, uint16_t tilt_count) { esp_err_t ret; - ret = nvs_set_u16(relay_chn_nvs, RELAY_CHN_KEY_TCNT(ch), tilt_count); +#if CONFIG_RELAY_CHN_COUNT > 1 + char key[NVS_KEY_NAME_MAX_SIZE]; + snprintf(key, sizeof(key), RELAY_CHN_KEY_TCNT_FMT, ch); + ret = nvs_set_u16(relay_chn_nvs, key, tilt_count); +#else + ret = nvs_set_u16(relay_chn_nvs, RELAY_CHN_KEY_TCNT, tilt_count); +#endif ESP_RETURN_ON_ERROR(ret, TAG, "Failed to save tilt_count tilt counter"); return nvs_commit(relay_chn_nvs); } esp_err_t relay_chn_nvs_get_tilt_count(uint8_t ch, uint16_t *tilt_count) { - ESP_RETURN_ON_FALSE(tilt_count != NULL, - ESP_ERR_INVALID_ARG, TAG, "Counter pointers are NULL"); - return nvs_get_u16(relay_chn_nvs, RELAY_CHN_KEY_TCNT(ch), tilt_count); + ESP_RETURN_ON_FALSE(tilt_count != NULL, ESP_ERR_INVALID_ARG, TAG, "Counter pointers are NULL"); + +#if CONFIG_RELAY_CHN_COUNT > 1 + char key[NVS_KEY_NAME_MAX_SIZE]; + snprintf(key, sizeof(key), RELAY_CHN_KEY_TCNT_FMT, ch); + return nvs_get_u16(relay_chn_nvs, key, tilt_count); +#else + return nvs_get_u16(relay_chn_nvs, RELAY_CHN_KEY_TCNT, tilt_count); +#endif } #endif // CONFIG_RELAY_CHN_ENABLE_TILTING diff --git a/test_apps/main/test_relay_chn_nvs_multi.c b/test_apps/main/test_relay_chn_nvs_multi.c index 1c9b243..4270d7d 100644 --- a/test_apps/main/test_relay_chn_nvs_multi.c +++ b/test_apps/main/test_relay_chn_nvs_multi.c @@ -54,15 +54,15 @@ TEST_CASE("Test relay_chn_nvs_erase_all", "[relay_chn][nvs]") // Store some test data first relay_chn_direction_t direction = RELAY_CHN_DIRECTION_FLIPPED; - for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) { - TEST_ESP_OK(relay_chn_nvs_set_direction(0, direction)); - } + // Set direction for all channels + TEST_ESP_OK(relay_chn_nvs_set_direction(0, direction)); + TEST_ESP_OK(relay_chn_nvs_set_direction(1, direction)); #if CONFIG_RELAY_CHN_ENABLE_TILTING uint8_t sensitivity = 50; for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) { - TEST_ESP_OK(relay_chn_nvs_set_tilt_sensitivity(0, sensitivity)); - TEST_ESP_OK(relay_chn_nvs_set_tilt_count(0, 100)); + TEST_ESP_OK(relay_chn_nvs_set_tilt_sensitivity(channel, sensitivity)); + TEST_ESP_OK(relay_chn_nvs_set_tilt_count(channel, 100 + channel)); } #endif @@ -74,11 +74,13 @@ TEST_CASE("Test relay_chn_nvs_erase_all", "[relay_chn][nvs]") TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_direction(0, &read_direction)); #if CONFIG_RELAY_CHN_ENABLE_TILTING - uint8_t read_sensitivity; - TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_tilt_sensitivity(0, &read_sensitivity)); + for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) { + uint8_t read_sensitivity; + TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_tilt_sensitivity(channel, &read_sensitivity)); - uint16_t tilt_count; - TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_tilt_count(0, &tilt_count)); + uint16_t tilt_count; + TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_tilt_count(channel, &tilt_count)); + } #endif TEST_ESP_OK(relay_chn_nvs_deinit()); @@ -89,14 +91,34 @@ TEST_CASE("Test run limit setting and getting", "[relay_chn][nvs][run_limit]") { TEST_ESP_OK(relay_chn_nvs_init()); - const uint16_t run_limit_sec = 32; + // Use different values for each channel to detect overwrites + uint16_t test_limits[CONFIG_RELAY_CHN_COUNT]; + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + test_limits[i] = 30 + i; // e.g., 30, 31, 32... + } + + // 1. Set all values first + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + TEST_ESP_OK(relay_chn_nvs_set_run_limit(i, test_limits[i])); + } + + // 2. Then, read them all back and verify for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { - TEST_ESP_OK(relay_chn_nvs_set_run_limit(i, run_limit_sec)); - uint16_t run_limit_read; TEST_ESP_OK(relay_chn_nvs_get_run_limit(i, &run_limit_read)); - TEST_ASSERT_EQUAL(run_limit_sec, run_limit_read); + TEST_ASSERT_EQUAL_UINT16(test_limits[i], run_limit_read); } + + // 3. Verify that changing one channel doesn't affect another + uint16_t new_limit_ch0 = 99; + TEST_ESP_OK(relay_chn_nvs_set_run_limit(0, new_limit_ch0)); + + uint16_t read_val_ch0, read_val_ch1; + TEST_ESP_OK(relay_chn_nvs_get_run_limit(0, &read_val_ch0)); + TEST_ESP_OK(relay_chn_nvs_get_run_limit(1, &read_val_ch1)); + TEST_ASSERT_EQUAL_UINT16(new_limit_ch0, read_val_ch0); + TEST_ASSERT_EQUAL_UINT16(test_limits[1], read_val_ch1); // Should still be the old value + TEST_ESP_OK(relay_chn_nvs_deinit()); } #endif @@ -106,14 +128,21 @@ TEST_CASE("Test sensitivity setting and getting", "[relay_chn][nvs][tilt]") { TEST_ESP_OK(relay_chn_nvs_init()); - const uint8_t test_sensitivity = 75; - uint8_t sensitivity; + uint8_t test_sensitivities[CONFIG_RELAY_CHN_COUNT]; + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + test_sensitivities[i] = 70 + i; // e.g., 70, 71, 72... + } - // Test all channels - for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) { - TEST_ESP_OK(relay_chn_nvs_set_tilt_sensitivity(channel, test_sensitivity)); - TEST_ESP_OK(relay_chn_nvs_get_tilt_sensitivity(channel, &sensitivity)); - TEST_ASSERT_EQUAL(test_sensitivity, sensitivity); + // 1. Set all values first + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + TEST_ESP_OK(relay_chn_nvs_set_tilt_sensitivity(i, test_sensitivities[i])); + } + + // 2. Then, read them all back and verify + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + uint8_t sensitivity_read; + TEST_ESP_OK(relay_chn_nvs_get_tilt_sensitivity(i, &sensitivity_read)); + TEST_ASSERT_EQUAL_UINT8(test_sensitivities[i], sensitivity_read); } TEST_ESP_OK(relay_chn_nvs_deinit()); @@ -123,15 +152,21 @@ TEST_CASE("Test tilt counter operations", "[relay_chn][nvs][tilt]") { TEST_ESP_OK(relay_chn_nvs_init()); - const uint16_t tilt_count = 100; - uint16_t tilt_count_read; + uint16_t test_counts[CONFIG_RELAY_CHN_COUNT]; + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + test_counts[i] = 100 + i; // e.g., 100, 101, 102... + } - // Test all channels - for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) { - // Test setting counters - TEST_ESP_OK(relay_chn_nvs_set_tilt_count(channel, tilt_count)); - TEST_ESP_OK(relay_chn_nvs_get_tilt_count(channel, &tilt_count_read)); - TEST_ASSERT_EQUAL(tilt_count, tilt_count_read); + // 1. Set all values first + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + TEST_ESP_OK(relay_chn_nvs_set_tilt_count(i, test_counts[i])); + } + + // 2. Then, read them all back and verify + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + uint16_t count_read; + TEST_ESP_OK(relay_chn_nvs_get_tilt_count(i, &count_read)); + TEST_ASSERT_EQUAL_UINT16(test_counts[i], count_read); } TEST_ESP_OK(relay_chn_nvs_deinit()); From 2c9ee40ff4721d2645c13e9ce92f15851579451e Mon Sep 17 00:00:00 2001 From: ismail Date: Thu, 4 Sep 2025 14:50:38 +0300 Subject: [PATCH 45/69] Enhance NVS module with a dedicated background task - Implemented a dedicated background task to decouple long-running code from the main application task. - Improved the NVS commit code logic, especially for batch writes to minimize flash wear. - Updated NVS functions to support asynchronous writes and synchronous reads. - Added default value parameters to `get` functions for better usability. - Improved error handling and logging in NVS operations. - Refactored related code in multiple source files to accommodate these changes. Refs #1085, #1096 and closes #1098 --- README.md | 10 + private_include/relay_chn_nvs.h | 37 +++- src/relay_chn_ctl_multi.c | 6 +- src/relay_chn_ctl_single.c | 6 +- src/relay_chn_nvs.c | 323 +++++++++++++++++++++++++++++--- src/relay_chn_output.c | 10 +- src/relay_chn_tilt.c | 18 +- 7 files changed, 347 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index 4bf96a0..87ad40c 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,16 @@ Another optional feature is NVS storage, which saves the configuration permanent - Tilt sensitivity - Last tilt position +### NVS Operation Details +When NVS storage is enabled (CONFIG_RELAY_CHN_ENABLE_NVS=y), the component creates a dedicated background task to manage all NVS write operations. This design has important implications for how you use the NVS-related functions: +- **Asynchronous Writes:** All `set` operations (e.g., `relay_chn_flip_direction()`, `relay_chn_set_run_limit()`) are asynchronous. They add the write request to a queue and return immediately, preventing the calling task from being blocked. +- **Synchronous Reads:** All get operations (e.g., `relay_chn_get_direction()`) are synchronous. They read the value directly from the NVS storage and will block the calling task until the read is complete. +- **Batched Commits:** To optimize performance and minimize flash wear, the NVS task uses a batching mechanism for writes. It collects multiple write requests and commits them to the NVS flash in a single operation after a short period of inactivity (typically 200ms). + +> [!IMPORTANT] +> Due to the asynchronous and batched nature of write operations, a call to a get function may not immediately reflect a value that was just written by a set function. Your application should account for this small delay. + + ## Configuration Configure the component through menuconfig under "Relay Channel Driver Configuration": diff --git a/private_include/relay_chn_nvs.h b/private_include/relay_chn_nvs.h index 6dab6a3..1f5bed3 100644 --- a/private_include/relay_chn_nvs.h +++ b/private_include/relay_chn_nvs.h @@ -32,6 +32,10 @@ esp_err_t relay_chn_nvs_init(void); * * @param[in] ch Channel number. * @param[in] direction Direction to store. + * + * @note This operation is asynchronous. The value is queued to be written + * by a background task. A subsequent `get` call may not immediately + * reflect the new value. * @return ESP_OK on success, error code otherwise. */ esp_err_t relay_chn_nvs_set_direction(uint8_t ch, relay_chn_direction_t direction); @@ -41,16 +45,21 @@ esp_err_t relay_chn_nvs_set_direction(uint8_t ch, relay_chn_direction_t directio * * @param[in] ch Channel number. * @param[out] direction Pointer to store retrieved direction. + * @param[in] default_val Default value to use if not found in NVS. * @return ESP_OK on success, error code otherwise. */ -esp_err_t relay_chn_nvs_get_direction(uint8_t ch, relay_chn_direction_t *direction); +esp_err_t relay_chn_nvs_get_direction(uint8_t ch, relay_chn_direction_t *direction, relay_chn_direction_t default_val); #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT /** * @brief Store relay channel run limit in NVS. * * @param[in] ch Channel number. - * @param[in] direction Run limit value to store. + * @param[in] limit_sec Run limit value to store. + * + * @note This operation is asynchronous. The value is queued to be written + * by a background task. A subsequent `get` call may not immediately + * reflect the new value. * @return ESP_OK on success, error code otherwise. */ esp_err_t relay_chn_nvs_set_run_limit(uint8_t ch, uint16_t limit_sec); @@ -59,10 +68,11 @@ esp_err_t relay_chn_nvs_set_run_limit(uint8_t ch, uint16_t limit_sec); * @brief Retrieve relay channel run limit from NVS. * * @param[in] ch Channel number. - * @param[out] direction Pointer to store retrieved run limit value. + * @param[out] limit_sec Pointer to store retrieved run limit value. + * @param[in] default_val Default value to use if not found in NVS. * @return ESP_OK on success, error code otherwise. */ -esp_err_t relay_chn_nvs_get_run_limit(uint8_t ch, uint16_t *limit_sec); +esp_err_t relay_chn_nvs_get_run_limit(uint8_t ch, uint16_t *limit_sec, uint16_t default_val); #endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 #if CONFIG_RELAY_CHN_ENABLE_TILTING @@ -71,6 +81,10 @@ esp_err_t relay_chn_nvs_get_run_limit(uint8_t ch, uint16_t *limit_sec); * * @param[in] ch Channel number. * @param[in] sensitivity Sensitivity value to store. + * + * @note This operation is asynchronous. The value is queued to be written + * by a background task. A subsequent `get` call may not immediately + * reflect the new value. * @return ESP_OK on success, error code otherwise. */ esp_err_t relay_chn_nvs_set_tilt_sensitivity(uint8_t ch, uint8_t sensitivity); @@ -80,15 +94,20 @@ esp_err_t relay_chn_nvs_set_tilt_sensitivity(uint8_t ch, uint8_t sensitivity); * * @param[in] ch Channel number. * @param[out] sensitivity Pointer to store retrieved sensitivity. + * @param[in] default_val Default value to use if not found in NVS. * @return ESP_OK on success, error code otherwise. */ -esp_err_t relay_chn_nvs_get_tilt_sensitivity(uint8_t ch, uint8_t *sensitivity); +esp_err_t relay_chn_nvs_get_tilt_sensitivity(uint8_t ch, uint8_t *sensitivity, uint8_t default_val); /** * @brief Store tilt counters in NVS. * * @param[in] ch Channel number. * @param[in] tilt_count Tilt count value. + * + * @note This operation is asynchronous. The value is queued to be written + * by a background task. A subsequent `get` call may not immediately + * reflect the new value. * @return ESP_OK on success, error code otherwise. */ esp_err_t relay_chn_nvs_set_tilt_count(uint8_t ch, uint16_t tilt_count); @@ -98,15 +117,17 @@ esp_err_t relay_chn_nvs_set_tilt_count(uint8_t ch, uint16_t tilt_count); * * @param[in] ch Channel number. * @param[out] tilt_count Pointer to store tilt count. + * @param[in] default_val Default value to use if not found in NVS. * @return ESP_OK on success, error code otherwise. */ -esp_err_t relay_chn_nvs_get_tilt_count(uint8_t ch, uint16_t *tilt_count); +esp_err_t relay_chn_nvs_get_tilt_count(uint8_t ch, uint16_t *tilt_count, uint16_t default_val); #endif // CONFIG_RELAY_CHN_ENABLE_TILTING /** * @brief Erase all keys in the NVS namespace. * * This function will erase all key-value pairs in the NVS namespace used by relay channels. + * It will also flush all pending operations in the queue. * * @return ESP_OK on success, error code otherwise. */ @@ -114,10 +135,8 @@ esp_err_t relay_chn_nvs_erase_all(void); /** * @brief Deinitialize NVS storage for relay channels. - * - * @return ESP_OK on success, error code otherwise. */ -esp_err_t relay_chn_nvs_deinit(void); +void relay_chn_nvs_deinit(void); #ifdef __cplusplus } diff --git a/src/relay_chn_ctl_multi.c b/src/relay_chn_ctl_multi.c index 14bf3a4..31122f9 100644 --- a/src/relay_chn_ctl_multi.c +++ b/src/relay_chn_ctl_multi.c @@ -38,10 +38,8 @@ esp_err_t relay_chn_ctl_init(relay_chn_output_t *outputs, relay_chn_run_info_t * uint16_t run_limit_sec = CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC; #if CONFIG_RELAY_CHN_ENABLE_NVS // Load run limit value from NVS - ret = relay_chn_nvs_get_run_limit(chn_ctl->id, &run_limit_sec); - if (ret != ESP_OK && ret != ESP_ERR_NVS_NOT_FOUND) { - ESP_LOGE(TAG, "Failed to load run limit from NVS for channel %d with error: %s", i, esp_err_to_name(ret)); - } + ret = relay_chn_nvs_get_run_limit(chn_ctl->id, &run_limit_sec, CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load run limit from NVS for #%d with error: %s", i, esp_err_to_name(ret)); #endif chn_ctl->run_limit_sec = run_limit_sec; ret = relay_chn_init_run_limit_timer(chn_ctl); diff --git a/src/relay_chn_ctl_single.c b/src/relay_chn_ctl_single.c index af7aba1..c95af90 100644 --- a/src/relay_chn_ctl_single.c +++ b/src/relay_chn_ctl_single.c @@ -32,10 +32,8 @@ esp_err_t relay_chn_ctl_init(relay_chn_output_t *output, relay_chn_run_info_t *r esp_err_t ret; #if CONFIG_RELAY_CHN_ENABLE_NVS // Load run limit value from NVS - ret = relay_chn_nvs_get_run_limit(chn_ctl.id, &run_limit_sec); - if (ret != ESP_OK && ret != ESP_ERR_NVS_NOT_FOUND) { - ESP_LOGE(TAG, "Failed to load run limit from NVS with error: %s", esp_err_to_name(ret)); - } + ret = relay_chn_nvs_get_run_limit(chn_ctl.id, &run_limit_sec, CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load run limit from NVS with error: %s", esp_err_to_name(ret)); #endif chn_ctl.run_limit_sec = run_limit_sec; ret = relay_chn_init_run_limit_timer(&chn_ctl); diff --git a/src/relay_chn_nvs.c b/src/relay_chn_nvs.c index 7ddd5ae..9f80f10 100644 --- a/src/relay_chn_nvs.c +++ b/src/relay_chn_nvs.c @@ -4,7 +4,12 @@ * SPDX-License-Identifier: MIT */ +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/task.h" +#include "freertos/queue.h" #include "esp_check.h" +#include "esp_log.h" #include "nvs.h" #include "relay_chn_nvs.h" @@ -26,12 +31,71 @@ #endif #endif +// --- Task and message queue config --- +#define RELAY_CHN_NVS_QUEUE_LEN (8 + CONFIG_RELAY_CHN_COUNT * 8) +#define RELAY_CHN_NVS_TASK_STACK 2048 +#define RELAY_CHN_NVS_COMMIT_TIMEOUT_MS 200 +#define RELAY_CHN_NVS_TASK_PRIO (tskIDLE_PRIORITY + 4) + +typedef enum { + RELAY_CHN_NVS_OP_ERASE_ALL, + RELAY_CHN_NVS_OP_SET_DIRECTION, +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT + RELAY_CHN_NVS_OP_SET_RUN_LIMIT, +#endif +#if CONFIG_RELAY_CHN_ENABLE_TILTING + RELAY_CHN_NVS_OP_SET_TILT_SENSITIVITY, + RELAY_CHN_NVS_OP_SET_TILT_COUNT, +#endif + RELAY_CHN_NVS_OP_DEINIT, +} relay_chn_nvs_op_t; + +typedef struct { + relay_chn_nvs_op_t op; + uint8_t ch; + union { + uint16_t data_u16; + uint8_t data_u8; + } data; +} relay_chn_nvs_msg_t; + static const char *TAG = "RELAY_CHN_NVS"; static nvs_handle_t relay_chn_nvs; +static QueueHandle_t nvs_queue_handle = NULL; +static TaskHandle_t nvs_task_handle = NULL; +static SemaphoreHandle_t deinit_sem = NULL; + + +static void relay_chn_nvs_task(void *arg); esp_err_t relay_chn_nvs_init() { + // Already initialized? + if (nvs_queue_handle != NULL) { + return ESP_OK; + } + + deinit_sem = xSemaphoreCreateBinary(); + if (!deinit_sem) { + ESP_LOGE(TAG, "Failed to create deinit semaphore"); + return ESP_ERR_NO_MEM; + } + + nvs_queue_handle = xQueueCreate(RELAY_CHN_NVS_QUEUE_LEN, sizeof(relay_chn_nvs_msg_t)); + if (!nvs_queue_handle) { + ESP_LOGE(TAG, "Failed to create NVS queue"); + return ESP_ERR_NO_MEM; + } + + BaseType_t res = xTaskCreate(relay_chn_nvs_task, "task_rlch_nvs", + RELAY_CHN_NVS_TASK_STACK, NULL, + RELAY_CHN_NVS_TASK_PRIO, &nvs_task_handle); + if (res != pdPASS) { + ESP_LOGE(TAG, "Failed to create NVS task"); + return ESP_ERR_NO_MEM; + } + esp_err_t ret; #if CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION ret = nvs_open_from_partition(CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME, @@ -52,7 +116,39 @@ esp_err_t relay_chn_nvs_init() return ESP_OK; } +static esp_err_t relay_chn_nvs_enqueue(relay_chn_nvs_msg_t *msg, const char *op_name) +{ + if (!nvs_queue_handle) { + return ESP_ERR_INVALID_STATE; + } + + if (msg->op == RELAY_CHN_NVS_OP_DEINIT || msg->op == RELAY_CHN_NVS_OP_ERASE_ALL) { + // Send DEINIT or ERASE_ALL to the front and wait up to 1 sec if needed + if (xQueueSendToFront(nvs_queue_handle, msg, pdMS_TO_TICKS(1000)) != pdTRUE) { + ESP_LOGW(TAG, "NVS queue is full, dropping %s for #%d", op_name, msg->ch); + return ESP_FAIL; + } + } else { + // Send async + if (xQueueSend(nvs_queue_handle, msg, 0) != pdTRUE) { + ESP_LOGW(TAG, "NVS queue is full, dropping %s for #%d", op_name, msg->ch); + return ESP_FAIL; + } + } + return ESP_OK; +} + esp_err_t relay_chn_nvs_set_direction(uint8_t ch, relay_chn_direction_t direction) +{ + relay_chn_nvs_msg_t msg = { + .op = RELAY_CHN_NVS_OP_SET_DIRECTION, + .ch = ch, + .data.data_u8 = (uint8_t) direction, + }; + return relay_chn_nvs_enqueue(&msg, "SET_DIRECTION"); +} + +static esp_err_t relay_chn_nvs_task_set_direction(uint8_t ch, uint8_t direction) { uint8_t direction_val = 0; esp_err_t ret = nvs_get_u8(relay_chn_nvs, RELAY_CHN_KEY_DIR, &direction_val); @@ -63,24 +159,38 @@ esp_err_t relay_chn_nvs_set_direction(uint8_t ch, relay_chn_direction_t directio direction_val |= (((uint8_t) direction) << ch); // Set the new direction bit ret = nvs_set_u8(relay_chn_nvs, RELAY_CHN_KEY_DIR, direction_val); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set direction for channel %d", ch); - return nvs_commit(relay_chn_nvs); + return ESP_OK; } -esp_err_t relay_chn_nvs_get_direction(uint8_t ch, relay_chn_direction_t *direction) +esp_err_t relay_chn_nvs_get_direction(uint8_t ch, relay_chn_direction_t *direction, relay_chn_direction_t default_val) { ESP_RETURN_ON_FALSE(direction != NULL, ESP_ERR_INVALID_ARG, TAG, "Direction pointer is NULL"); - + uint8_t direction_val; esp_err_t ret = nvs_get_u8(relay_chn_nvs, RELAY_CHN_KEY_DIR, &direction_val); - if (ret != ESP_OK) { - return ret; // Return error if the key does not exist + if (ret == ESP_ERR_NVS_NOT_FOUND) { + *direction = default_val; + return ESP_OK; + } else if (ret != ESP_OK) { + return ret; // A real error occurred, return it } + // If ret is ESP_OK, direction_val has the stored value. *direction = (relay_chn_direction_t)((direction_val >> ch) & 0x01); return ESP_OK; } #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT esp_err_t relay_chn_nvs_set_run_limit(uint8_t ch, uint16_t limit_sec) +{ + relay_chn_nvs_msg_t msg = { + .op = RELAY_CHN_NVS_OP_SET_RUN_LIMIT, + .ch = ch, + .data.data_u16 = limit_sec, + }; + return relay_chn_nvs_enqueue(&msg, "SET_RUN_LIMIT"); +} + +static esp_err_t relay_chn_nvs_task_set_run_limit(uint8_t ch, uint16_t limit_sec) { esp_err_t ret; #if CONFIG_RELAY_CHN_COUNT > 1 @@ -91,25 +201,41 @@ esp_err_t relay_chn_nvs_set_run_limit(uint8_t ch, uint16_t limit_sec) ret = nvs_set_u16(relay_chn_nvs, RELAY_CHN_KEY_RLIM, limit_sec); #endif ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set run limit for channel %d", ch); - return nvs_commit(relay_chn_nvs); + return ESP_OK; } -esp_err_t relay_chn_nvs_get_run_limit(uint8_t ch, uint16_t *limit_sec) +esp_err_t relay_chn_nvs_get_run_limit(uint8_t ch, uint16_t *limit_sec, uint16_t default_val) { ESP_RETURN_ON_FALSE(limit_sec != NULL, ESP_ERR_INVALID_ARG, TAG, "Run limit value pointer is NULL"); + esp_err_t ret; #if CONFIG_RELAY_CHN_COUNT > 1 char key[NVS_KEY_NAME_MAX_SIZE]; snprintf(key, sizeof(key), RELAY_CHN_KEY_RLIM_FMT, ch); - return nvs_get_u16(relay_chn_nvs, key, limit_sec); + ret = nvs_get_u16(relay_chn_nvs, key, limit_sec); #else - return nvs_get_u16(relay_chn_nvs, RELAY_CHN_KEY_RLIM, limit_sec); + ret = nvs_get_u16(relay_chn_nvs, RELAY_CHN_KEY_RLIM, limit_sec); #endif + if (ret == ESP_ERR_NVS_NOT_FOUND) { + *limit_sec = default_val; + return ESP_OK; + } + return ret; } #endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 #if CONFIG_RELAY_CHN_ENABLE_TILTING esp_err_t relay_chn_nvs_set_tilt_sensitivity(uint8_t ch, uint8_t sensitivity) +{ + relay_chn_nvs_msg_t msg = { + .op = RELAY_CHN_NVS_OP_SET_TILT_SENSITIVITY, + .ch = ch, + .data.data_u8 = sensitivity, + }; + return relay_chn_nvs_enqueue(&msg, "SET_TILT_SENSITIVITY"); +} + +static esp_err_t relay_chn_nvs_task_set_tilt_sensitivity(uint8_t ch, uint8_t sensitivity) { esp_err_t ret; #if CONFIG_RELAY_CHN_COUNT > 1 @@ -120,23 +246,39 @@ esp_err_t relay_chn_nvs_set_tilt_sensitivity(uint8_t ch, uint8_t sensitivity) ret = nvs_set_u8(relay_chn_nvs, RELAY_CHN_KEY_TSENS, sensitivity); #endif ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set tilt sensitivity for channel %d", ch); - return nvs_commit(relay_chn_nvs); + return ESP_OK; } -esp_err_t relay_chn_nvs_get_tilt_sensitivity(uint8_t ch, uint8_t *sensitivity) +esp_err_t relay_chn_nvs_get_tilt_sensitivity(uint8_t ch, uint8_t *sensitivity, uint8_t default_val) { ESP_RETURN_ON_FALSE(sensitivity != NULL, ESP_ERR_INVALID_ARG, TAG, "Sensitivity pointer is NULL"); + esp_err_t ret; #if CONFIG_RELAY_CHN_COUNT > 1 char key[NVS_KEY_NAME_MAX_SIZE]; snprintf(key, sizeof(key), RELAY_CHN_KEY_TSENS_FMT, ch); - return nvs_get_u8(relay_chn_nvs, key, sensitivity); + ret = nvs_get_u8(relay_chn_nvs, key, sensitivity); #else - return nvs_get_u8(relay_chn_nvs, RELAY_CHN_KEY_TSENS, sensitivity); + ret = nvs_get_u8(relay_chn_nvs, RELAY_CHN_KEY_TSENS, sensitivity); #endif + if (ret == ESP_ERR_NVS_NOT_FOUND) { + *sensitivity = default_val; + return ESP_OK; + } + return ret; } esp_err_t relay_chn_nvs_set_tilt_count(uint8_t ch, uint16_t tilt_count) +{ + relay_chn_nvs_msg_t msg = { + .op = RELAY_CHN_NVS_OP_SET_TILT_COUNT, + .ch = ch, + .data.data_u16 = tilt_count, + }; + return relay_chn_nvs_enqueue(&msg, "SET_TILT_COUNT"); +} + +static esp_err_t relay_chn_nvs_task_set_tilt_count(uint8_t ch, uint16_t tilt_count) { esp_err_t ret; #if CONFIG_RELAY_CHN_COUNT > 1 @@ -147,35 +289,164 @@ esp_err_t relay_chn_nvs_set_tilt_count(uint8_t ch, uint16_t tilt_count) ret = nvs_set_u16(relay_chn_nvs, RELAY_CHN_KEY_TCNT, tilt_count); #endif ESP_RETURN_ON_ERROR(ret, TAG, "Failed to save tilt_count tilt counter"); - return nvs_commit(relay_chn_nvs); + return ESP_OK; } -esp_err_t relay_chn_nvs_get_tilt_count(uint8_t ch, uint16_t *tilt_count) +esp_err_t relay_chn_nvs_get_tilt_count(uint8_t ch, uint16_t *tilt_count, uint16_t default_val) { ESP_RETURN_ON_FALSE(tilt_count != NULL, ESP_ERR_INVALID_ARG, TAG, "Counter pointers are NULL"); - + + esp_err_t ret; #if CONFIG_RELAY_CHN_COUNT > 1 char key[NVS_KEY_NAME_MAX_SIZE]; snprintf(key, sizeof(key), RELAY_CHN_KEY_TCNT_FMT, ch); - return nvs_get_u16(relay_chn_nvs, key, tilt_count); + ret = nvs_get_u16(relay_chn_nvs, key, tilt_count); #else - return nvs_get_u16(relay_chn_nvs, RELAY_CHN_KEY_TCNT, tilt_count); + ret = nvs_get_u16(relay_chn_nvs, RELAY_CHN_KEY_TCNT, tilt_count); #endif + if (ret == ESP_ERR_NVS_NOT_FOUND) { + *tilt_count = default_val; + return ESP_OK; + } + return ret; } #endif // CONFIG_RELAY_CHN_ENABLE_TILTING esp_err_t relay_chn_nvs_erase_all() { + relay_chn_nvs_msg_t msg = { + .op = RELAY_CHN_NVS_OP_ERASE_ALL, + }; + return relay_chn_nvs_enqueue(&msg, "ERASE_ALL"); +} + +static esp_err_t do_nvs_deinit() +{ + relay_chn_nvs_msg_t msg = { + .op = RELAY_CHN_NVS_OP_DEINIT, + }; + return relay_chn_nvs_enqueue(&msg, "DEINIT"); +} + +static esp_err_t do_nvs_erase_all() +{ + // Flush all pending SET operations since ERASE_ALL requested + xQueueReset(nvs_queue_handle); // Erase all key-value pairs in the relay_chn NVS namespace esp_err_t ret = nvs_erase_all(relay_chn_nvs); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to erase all keys in NVS namespace '%s'", CONFIG_RELAY_CHN_NVS_NAMESPACE); - - // Commit the changes - return nvs_commit(relay_chn_nvs); -} - -esp_err_t relay_chn_nvs_deinit() -{ - nvs_close(relay_chn_nvs); return ESP_OK; } + +void relay_chn_nvs_deinit() +{ + if (nvs_task_handle) { + if (do_nvs_deinit() == ESP_OK) { + if (deinit_sem && xSemaphoreTake(deinit_sem, pdMS_TO_TICKS(2000)) != pdTRUE) { + ESP_LOGE(TAG, "Failed to get deinit confirmation from NVS task. Forcing deletion."); + vTaskDelete(nvs_task_handle); // Last resort + } + } else { + ESP_LOGE(TAG, "Failed to send deinit message to NVS task. Forcing deletion."); + vTaskDelete(nvs_task_handle); + } + } + if (nvs_queue_handle) { + vQueueDelete(nvs_queue_handle); + nvs_queue_handle = NULL; + } + if (deinit_sem) { + vSemaphoreDelete(deinit_sem); + deinit_sem = NULL; + } + + // Close NVS handle here, after task has stopped and queue is deleted. + nvs_close(relay_chn_nvs); + nvs_task_handle = NULL; +} + +static esp_err_t relay_chn_nvs_task_process_message(const relay_chn_nvs_msg_t *msg, bool *running, bool *dirty) +{ + esp_err_t ret = ESP_OK; + switch (msg->op) { + case RELAY_CHN_NVS_OP_SET_DIRECTION: + ret = relay_chn_nvs_task_set_direction(msg->ch, msg->data.data_u8); + if (ret == ESP_OK) *dirty = true; + break; +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT + case RELAY_CHN_NVS_OP_SET_RUN_LIMIT: + ret = relay_chn_nvs_task_set_run_limit(msg->ch, msg->data.data_u16); + if (ret == ESP_OK) *dirty = true; + break; +#endif +#if CONFIG_RELAY_CHN_ENABLE_TILTING + case RELAY_CHN_NVS_OP_SET_TILT_SENSITIVITY: + ret = relay_chn_nvs_task_set_tilt_sensitivity(msg->ch, msg->data.data_u8); + if (ret == ESP_OK) *dirty = true; + break; + case RELAY_CHN_NVS_OP_SET_TILT_COUNT: + ret = relay_chn_nvs_task_set_tilt_count(msg->ch, msg->data.data_u16); + if (ret == ESP_OK) *dirty = true; + break; +#endif + case RELAY_CHN_NVS_OP_ERASE_ALL: + ret = do_nvs_erase_all(); + if (ret == ESP_OK) *dirty = true; + break; + case RELAY_CHN_NVS_OP_DEINIT: + *running = false; + break; + default: + ESP_LOGE(TAG, "Unknown operation in NVS queue: %d", msg->op); + ret = ESP_ERR_INVALID_ARG; + break; + } + return ret; +} + +/* + * The ESP-IDF NVS functions are protected by an internal mutex. If this task is killed + * while it's holding that mutex, the mutex is never released, which may result in + * deadlocks. This is why this task must be terminated gracefully. + */ +static void relay_chn_nvs_task(void *arg) +{ + relay_chn_nvs_msg_t msg; + bool dirty = false; + bool running = true; + + while (running) { + // Block indefinitely waiting for the first message of a potential batch. + if (xQueueReceive(nvs_queue_handle, &msg, portMAX_DELAY) == pdTRUE) { + // A batch of operations has started. Use a do-while to process the first message + // and any subsequent messages that arrive within the timeout. + do { + esp_err_t ret = relay_chn_nvs_task_process_message(&msg, &running, &dirty); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to process operation %d for #%d with error %s", msg.op, msg.ch, esp_err_to_name(ret)); + } + } while (running && xQueueReceive(nvs_queue_handle, &msg, pdMS_TO_TICKS(RELAY_CHN_NVS_COMMIT_TIMEOUT_MS)) == pdTRUE); + + // The burst of messages is over (timeout occurred). Commit if anything changed. + if (dirty) { + esp_err_t commit_ret = nvs_commit(relay_chn_nvs); + if (commit_ret == ESP_OK) { + dirty = false; + } else { + ESP_LOGE(TAG, "NVS batch commit failed"); + // Don't reset dirty flag, so we can try to commit again later. + } + } + } + } + + // Before exiting, do one final commit if there are pending changes. + if (dirty) { + if (nvs_commit(relay_chn_nvs) != ESP_OK) { + ESP_LOGE(TAG, "Final NVS commit failed on deinit"); + } + } + xSemaphoreGive(deinit_sem); + nvs_task_handle = NULL; + vTaskDelete(NULL); +} \ No newline at end of file diff --git a/src/relay_chn_output.c b/src/relay_chn_output.c index b0d2c0f..3a608a7 100644 --- a/src/relay_chn_output.c +++ b/src/relay_chn_output.c @@ -75,13 +75,9 @@ static esp_err_t relay_chn_output_ctl_init(relay_chn_output_t *output, #if CONFIG_RELAY_CHN_ENABLE_NVS static esp_err_t relay_chn_output_load_direction(uint8_t ch, relay_chn_direction_t *direction) { - esp_err_t ret = relay_chn_nvs_get_direction(ch, direction); - if (ret == ESP_ERR_NVS_NOT_FOUND) { - // If the key does not exist, use the default direction - *direction = RELAY_CHN_DIRECTION_DEFAULT; - } else if (ret != ESP_OK) { - ESP_RETURN_ON_ERROR(ret, TAG, "Failed to get direction from storage for channel %d: %s", ch, esp_err_to_name(ret)); - } + esp_err_t ret = relay_chn_nvs_get_direction(ch, direction, RELAY_CHN_DIRECTION_DEFAULT); + // relay_chn_nvs_get_direction now handles the NOT_FOUND case and returns a default value. + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to get direction from storage for channel %d: %s", ch, esp_err_to_name(ret)); return ESP_OK; } #endif diff --git a/src/relay_chn_tilt.c b/src/relay_chn_tilt.c index 98c7c7c..6a2da91 100644 --- a/src/relay_chn_tilt.c +++ b/src/relay_chn_tilt.c @@ -636,24 +636,16 @@ static void relay_chn_tilt_timer_cb(void *arg) #if CONFIG_RELAY_CHN_ENABLE_NVS static esp_err_t relay_chn_tilt_load_sensitivity(uint8_t ch, uint8_t *sensitivity) { - esp_err_t ret = relay_chn_nvs_get_tilt_sensitivity(ch, sensitivity); - if (ret == ESP_ERR_NVS_NOT_FOUND) { - *sensitivity = RELAY_CHN_TILT_DEFAULT_SENSITIVITY; - return ESP_OK; - } - ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load tilt sensitivity for channel %d", ch); + ESP_RETURN_ON_ERROR(relay_chn_nvs_get_tilt_sensitivity(ch, sensitivity, RELAY_CHN_TILT_DEFAULT_SENSITIVITY), + TAG, "Failed to load tilt sensitivity for channel %d", ch); return ESP_OK; } static esp_err_t relay_chn_tilt_load_tilt_count(uint8_t ch, uint16_t *tilt_count) { - esp_err_t ret = relay_chn_nvs_get_tilt_count(ch, tilt_count); - if (ret == ESP_ERR_NVS_NOT_FOUND) { - ESP_LOGD(TAG, "relay_chn_tilt_load_tilt_count: No tilt count found in NVS for channel %d, initializing to zero", ch); - tilt_count = 0; - return ESP_OK; - } - ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load tilt counters for channel %d", ch); + ESP_RETURN_ON_ERROR(relay_chn_nvs_get_tilt_count(ch, tilt_count, 0), + TAG, "Failed to load tilt counters for channel %d", ch); + ESP_LOGD(TAG, "Loaded tilt count for channel %d: %d", ch, *tilt_count); return ESP_OK; } #endif // CONFIG_RELAY_CHN_ENABLE_NVS From 639533cbb6248351b5e5b7ce606b107f222a373f Mon Sep 17 00:00:00 2001 From: ismail Date: Thu, 4 Sep 2025 15:06:17 +0300 Subject: [PATCH 46/69] Refactor and improve unit tests - Refactored and improved NVS tests to accomodate latest changes in NVS module. See #1098 - Improved channel reset function that is called from `tearDown` to minimize public API calls that trigger a chain of internal calls that involve NVS and timer operations. - Fixed test case bugs that make some test cases to fail. Refs #1096, #1098 --- test_apps/main/test_common.c | 57 +++++---- test_apps/main/test_relay_chn_core_multi.c | 17 +-- test_apps/main/test_relay_chn_nvs_multi.c | 130 ++++++++++---------- test_apps/main/test_relay_chn_nvs_single.c | 84 ++++++------- test_apps/main/test_relay_chn_tilt_multi.c | 31 ++--- test_apps/main/test_relay_chn_tilt_single.c | 4 +- 6 files changed, 164 insertions(+), 159 deletions(-) diff --git a/test_apps/main/test_common.c b/test_apps/main/test_common.c index d8fdb4d..87d1811 100644 --- a/test_apps/main/test_common.c +++ b/test_apps/main/test_common.c @@ -1,4 +1,6 @@ #include "test_common.h" +#include "relay_chn_ctl.h" // For resetting the channels +#include "relay_chn_tilt.h" // For resetting tilt count const char *TEST_TAG = "RELAY_CHN_TEST"; @@ -32,36 +34,45 @@ const uint8_t gpio_map[] = {4, 5}; const uint8_t gpio_count = sizeof(gpio_map) / sizeof(gpio_map[0]); +static void reset_channel(relay_chn_ctl_t *ctl) +{ + ctl->pending_cmd = RELAY_CHN_CMD_NONE; + ctl->state = RELAY_CHN_STATE_IDLE; + ctl->output->direction = RELAY_CHN_DIRECTION_DEFAULT; + ctl->run_info->last_run_cmd = RELAY_CHN_CMD_NONE; + ctl->run_info->last_run_cmd_time_ms = 0; + esp_timer_stop(ctl->inertia_timer); +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT + esp_timer_stop(ctl->run_limit_timer); + ctl->run_limit_sec = CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC; +#endif +#if CONFIG_RELAY_CHN_ENABLE_TILTING + relay_chn_tilt_reset_count(ctl->tilt_ctl); +#endif +#if CONFIG_RELAY_CHN_COUNT > 1 + #else +#endif +} + void reset_channels_to_defaults() { #if CONFIG_RELAY_CHN_COUNT > 1 - relay_chn_stop_all(); -#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT - relay_chn_set_run_limit_all_with(CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC); -#endif - vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); - for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i)); + relay_chn_ctl_t *ctls = relay_chn_ctl_get_all(); + TEST_ASSERT_NOT_NULL_MESSAGE(ctls, "reset_channels_to_defaults: relay_chn_ctl_get_all() returned NULL"); - // Reset directions - if (relay_chn_get_direction(i) != RELAY_CHN_DIRECTION_DEFAULT) { - relay_chn_flip_direction(i); - TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(i)); - } + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + relay_chn_ctl_t *ctl = &ctls[i]; + TEST_ASSERT_NOT_NULL_MESSAGE(ctl, "ctl is NULL"); + reset_channel(ctl); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i)); + TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(i)); } #else - relay_chn_stop(); -#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT - relay_chn_set_run_limit(CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC); -#endif - - // Reset direction - if (relay_chn_get_direction() != RELAY_CHN_DIRECTION_DEFAULT) { - relay_chn_flip_direction(); - TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction()); - } - vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); + relay_chn_ctl_t *ctl = relay_chn_ctl_get(); + TEST_ASSERT_NOT_NULL_MESSAGE(ctl, "reset_channels_to_defaults: relay_chn_ctl_get() returned NULL"); + reset_channel(ctl); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); + TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction()); #endif } diff --git a/test_apps/main/test_relay_chn_core_multi.c b/test_apps/main/test_relay_chn_core_multi.c index 7796039..080bd5f 100644 --- a/test_apps/main/test_relay_chn_core_multi.c +++ b/test_apps/main/test_relay_chn_core_multi.c @@ -402,6 +402,7 @@ TEST_CASE("get_state_all retrieves all channel states", "[relay_chn][core][batch expect_states[i] = RELAY_CHN_STATE_REVERSE; } } + TEST_ESP_OK(relay_chn_get_state_all(states)); TEST_ASSERT_EQUAL_UINT_ARRAY(expect_states, states, CONFIG_RELAY_CHN_COUNT); } @@ -485,13 +486,15 @@ TEST_CASE("Test run limit stops channel after timeout", "[relay_chn][run_limit]" TEST_CASE("Test run limit reset on direction change and time out finally", "[relay_chn][run_limit]") { - for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { - // Set a short run limit - relay_chn_set_run_limit(i, TEST_SHORT_RUN_LIMIT_SEC); - - // Start running forward - relay_chn_run_forward(i); - } + relay_chn_set_run_limit_all_with(TEST_SHORT_RUN_LIMIT_SEC); + +#if CONFIG_RELAY_CHN_ENABLE_NVS + // Wait for the NVS module task to process operations + vTaskDelay(300 / portTICK_PERIOD_MS); // Wait 1 second +#endif + + // Start running forward + relay_chn_run_forward_all(); vTaskDelay(1000 / portTICK_PERIOD_MS); // Wait 1 second diff --git a/test_apps/main/test_relay_chn_nvs_multi.c b/test_apps/main/test_relay_chn_nvs_multi.c index 4270d7d..6ac3bb1 100644 --- a/test_apps/main/test_relay_chn_nvs_multi.c +++ b/test_apps/main/test_relay_chn_nvs_multi.c @@ -9,88 +9,95 @@ #include "esp_system.h" #include "nvs_flash.h" #include "relay_chn_nvs.h" +#include "test_common.h" -TEST_CASE("Test relay storage init/deinit", "[relay_chn][nvs]") -{ - TEST_ESP_OK(relay_chn_nvs_init()); - TEST_ESP_OK(relay_chn_nvs_deinit()); -} +#define TEST_NVS_TASK_TIME_OUT_MS 300 TEST_CASE("Test direction setting and getting", "[relay_chn][nvs]") { - TEST_ESP_OK(relay_chn_nvs_init()); - // Test all channels - relay_chn_direction_t dir; - relay_chn_direction_t test_directions[] = { - RELAY_CHN_DIRECTION_DEFAULT, - RELAY_CHN_DIRECTION_FLIPPED - }; + relay_chn_direction_t dir, expect; - for (int channel = 0; channel < 2; channel++) { - TEST_ESP_OK(relay_chn_nvs_set_direction(channel, test_directions[channel])); - TEST_ESP_OK(relay_chn_nvs_get_direction(channel, &dir)); - TEST_ASSERT_EQUAL(test_directions[channel], dir); + for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) { + dir = channel % 2 == 0 ? RELAY_CHN_DIRECTION_DEFAULT : RELAY_CHN_DIRECTION_FLIPPED; + TEST_ESP_OK(relay_chn_nvs_set_direction(channel, dir)); } + + // Wait for the batch commit timeout to ensure the value is written + vTaskDelay(pdMS_TO_TICKS(TEST_NVS_TASK_TIME_OUT_MS)); - TEST_ESP_OK(relay_chn_nvs_deinit()); + for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) { + expect = channel % 2 == 0 ? RELAY_CHN_DIRECTION_DEFAULT : RELAY_CHN_DIRECTION_FLIPPED; + TEST_ESP_OK(relay_chn_nvs_get_direction(channel, &dir, RELAY_CHN_DIRECTION_DEFAULT)); + TEST_ASSERT_EQUAL(expect, dir); + } } TEST_CASE("Test invalid parameters", "[relay_chn][nvs]") { - TEST_ESP_OK(relay_chn_nvs_init()); - // Test NULL pointer for all channels for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) { - TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_direction(channel, NULL)); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_direction(channel, NULL, RELAY_CHN_DIRECTION_DEFAULT)); } - - TEST_ESP_OK(relay_chn_nvs_deinit()); } TEST_CASE("Test relay_chn_nvs_erase_all", "[relay_chn][nvs]") { - TEST_ESP_OK(relay_chn_nvs_init()); - // Store some test data first - relay_chn_direction_t direction = RELAY_CHN_DIRECTION_FLIPPED; - // Set direction for all channels - TEST_ESP_OK(relay_chn_nvs_set_direction(0, direction)); - TEST_ESP_OK(relay_chn_nvs_set_direction(1, direction)); + for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) { + // Set direction for all channels + TEST_ESP_OK(relay_chn_nvs_set_direction(channel, RELAY_CHN_DIRECTION_FLIPPED)); + +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT + TEST_ESP_OK(relay_chn_nvs_set_run_limit(channel, 100 + channel)); +#endif #if CONFIG_RELAY_CHN_ENABLE_TILTING - uint8_t sensitivity = 50; - for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) { - TEST_ESP_OK(relay_chn_nvs_set_tilt_sensitivity(channel, sensitivity)); + TEST_ESP_OK(relay_chn_nvs_set_tilt_sensitivity(channel, 50)); TEST_ESP_OK(relay_chn_nvs_set_tilt_count(channel, 100 + channel)); } #endif + // Wait for the set operations and subsequent commits to complete + // Wait 4 times more since 4 x 8 = 32 operations to process + vTaskDelay(pdMS_TO_TICKS(TEST_NVS_TASK_TIME_OUT_MS * 8)); + // Test erase all TEST_ESP_OK(relay_chn_nvs_erase_all()); + // Wait for the erase operation and subsequent commit to complete + vTaskDelay(pdMS_TO_TICKS(TEST_NVS_TASK_TIME_OUT_MS)); // Verify data was erased by trying to read it back - relay_chn_direction_t read_direction; - TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_direction(0, &read_direction)); - -#if CONFIG_RELAY_CHN_ENABLE_TILTING for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) { - uint8_t read_sensitivity; - TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_tilt_sensitivity(channel, &read_sensitivity)); + relay_chn_direction_t read_direction; + TEST_ESP_OK(relay_chn_nvs_get_direction(0, &read_direction, RELAY_CHN_DIRECTION_DEFAULT)); + TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, read_direction); + } - uint16_t tilt_count; - TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_tilt_count(channel, &tilt_count)); +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT + for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) { + uint16_t read_run_limit; + TEST_ESP_OK(relay_chn_nvs_get_run_limit(channel, &read_run_limit, CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC)); + TEST_ASSERT_EQUAL(CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC, read_run_limit); } #endif - TEST_ESP_OK(relay_chn_nvs_deinit()); +#if CONFIG_RELAY_CHN_ENABLE_TILTING + const uint8_t default_sensitivity_for_test = 42; + for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) { + uint8_t read_sensitivity; + TEST_ESP_OK(relay_chn_nvs_get_tilt_sensitivity(channel, &read_sensitivity, default_sensitivity_for_test)); + TEST_ASSERT_EQUAL(default_sensitivity_for_test, read_sensitivity); + uint16_t tilt_count; + TEST_ESP_OK(relay_chn_nvs_get_tilt_count(channel, &tilt_count, 0)); + TEST_ASSERT_EQUAL(0, tilt_count); + } +#endif } #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT TEST_CASE("Test run limit setting and getting", "[relay_chn][nvs][run_limit]") { - TEST_ESP_OK(relay_chn_nvs_init()); - // Use different values for each channel to detect overwrites uint16_t test_limits[CONFIG_RELAY_CHN_COUNT]; for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { @@ -102,32 +109,31 @@ TEST_CASE("Test run limit setting and getting", "[relay_chn][nvs][run_limit]") TEST_ESP_OK(relay_chn_nvs_set_run_limit(i, test_limits[i])); } + // Allow the NVS task to process the batch and commit + vTaskDelay(pdMS_TO_TICKS(TEST_NVS_TASK_TIME_OUT_MS)); + // 2. Then, read them all back and verify for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { uint16_t run_limit_read; - TEST_ESP_OK(relay_chn_nvs_get_run_limit(i, &run_limit_read)); + TEST_ESP_OK(relay_chn_nvs_get_run_limit(i, &run_limit_read, CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC)); TEST_ASSERT_EQUAL_UINT16(test_limits[i], run_limit_read); } // 3. Verify that changing one channel doesn't affect another uint16_t new_limit_ch0 = 99; TEST_ESP_OK(relay_chn_nvs_set_run_limit(0, new_limit_ch0)); - + vTaskDelay(pdMS_TO_TICKS(TEST_NVS_TASK_TIME_OUT_MS)); // Allow NVS task to write uint16_t read_val_ch0, read_val_ch1; - TEST_ESP_OK(relay_chn_nvs_get_run_limit(0, &read_val_ch0)); - TEST_ESP_OK(relay_chn_nvs_get_run_limit(1, &read_val_ch1)); + TEST_ESP_OK(relay_chn_nvs_get_run_limit(0, &read_val_ch0, CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC)); + TEST_ESP_OK(relay_chn_nvs_get_run_limit(1, &read_val_ch1, CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC)); TEST_ASSERT_EQUAL_UINT16(new_limit_ch0, read_val_ch0); TEST_ASSERT_EQUAL_UINT16(test_limits[1], read_val_ch1); // Should still be the old value - - TEST_ESP_OK(relay_chn_nvs_deinit()); } #endif #if CONFIG_RELAY_CHN_ENABLE_TILTING TEST_CASE("Test sensitivity setting and getting", "[relay_chn][nvs][tilt]") { - TEST_ESP_OK(relay_chn_nvs_init()); - uint8_t test_sensitivities[CONFIG_RELAY_CHN_COUNT]; for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { test_sensitivities[i] = 70 + i; // e.g., 70, 71, 72... @@ -138,20 +144,19 @@ TEST_CASE("Test sensitivity setting and getting", "[relay_chn][nvs][tilt]") TEST_ESP_OK(relay_chn_nvs_set_tilt_sensitivity(i, test_sensitivities[i])); } + // Allow the NVS task to process the batch and commit + vTaskDelay(pdMS_TO_TICKS(TEST_NVS_TASK_TIME_OUT_MS)); + // 2. Then, read them all back and verify for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { uint8_t sensitivity_read; - TEST_ESP_OK(relay_chn_nvs_get_tilt_sensitivity(i, &sensitivity_read)); + TEST_ESP_OK(relay_chn_nvs_get_tilt_sensitivity(i, &sensitivity_read, 0)); TEST_ASSERT_EQUAL_UINT8(test_sensitivities[i], sensitivity_read); } - - TEST_ESP_OK(relay_chn_nvs_deinit()); } TEST_CASE("Test tilt counter operations", "[relay_chn][nvs][tilt]") { - TEST_ESP_OK(relay_chn_nvs_init()); - uint16_t test_counts[CONFIG_RELAY_CHN_COUNT]; for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { test_counts[i] = 100 + i; // e.g., 100, 101, 102... @@ -162,26 +167,23 @@ TEST_CASE("Test tilt counter operations", "[relay_chn][nvs][tilt]") TEST_ESP_OK(relay_chn_nvs_set_tilt_count(i, test_counts[i])); } + // Allow the NVS task to process the batch and commit + vTaskDelay(pdMS_TO_TICKS(TEST_NVS_TASK_TIME_OUT_MS)); + // 2. Then, read them all back and verify for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { uint16_t count_read; - TEST_ESP_OK(relay_chn_nvs_get_tilt_count(i, &count_read)); + TEST_ESP_OK(relay_chn_nvs_get_tilt_count(i, &count_read, 0)); TEST_ASSERT_EQUAL_UINT16(test_counts[i], count_read); } - - TEST_ESP_OK(relay_chn_nvs_deinit()); } TEST_CASE("Test tilting invalid parameters", "[relay_chn][nvs][tilt]") { - TEST_ESP_OK(relay_chn_nvs_init()); - // Test NULL pointers for all channels for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) { - TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_tilt_sensitivity(channel, NULL)); - TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_tilt_count(channel, NULL)); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_tilt_sensitivity(channel, NULL, 0)); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_tilt_count(channel, NULL, 0)); } - - TEST_ESP_OK(relay_chn_nvs_deinit()); } #endif // CONFIG_RELAY_CHN_ENABLE_TILTING \ No newline at end of file diff --git a/test_apps/main/test_relay_chn_nvs_single.c b/test_apps/main/test_relay_chn_nvs_single.c index 06240a2..135d1d2 100644 --- a/test_apps/main/test_relay_chn_nvs_single.c +++ b/test_apps/main/test_relay_chn_nvs_single.c @@ -9,50 +9,43 @@ #include "esp_system.h" #include "nvs_flash.h" #include "relay_chn_nvs.h" +#include "test_common.h" - -TEST_CASE("Test relay storage init/deinit", "[relay_chn][nvs]") -{ - TEST_ESP_OK(relay_chn_nvs_init()); - TEST_ESP_OK(relay_chn_nvs_deinit()); -} +#define TEST_NVS_TASK_TIME_OUT_MS 300 TEST_CASE("Test direction setting and getting", "[relay_chn][nvs]") { - TEST_ESP_OK(relay_chn_nvs_init()); - // Test channel 0 TEST_ESP_OK(relay_chn_nvs_set_direction(0, RELAY_CHN_DIRECTION_DEFAULT)); relay_chn_direction_t dir; - TEST_ESP_OK(relay_chn_nvs_get_direction(0, &dir)); + vTaskDelay(pdMS_TO_TICKS(TEST_NVS_TASK_TIME_OUT_MS)); // Allow NVS task to write and commit + TEST_ESP_OK(relay_chn_nvs_get_direction(0, &dir, RELAY_CHN_DIRECTION_DEFAULT)); TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, dir); // Test channel 1 TEST_ESP_OK(relay_chn_nvs_set_direction(0, RELAY_CHN_DIRECTION_FLIPPED)); - TEST_ESP_OK(relay_chn_nvs_get_direction(0, &dir)); + vTaskDelay(pdMS_TO_TICKS(TEST_NVS_TASK_TIME_OUT_MS)); // Allow NVS task to write and commit + TEST_ESP_OK(relay_chn_nvs_get_direction(0, &dir, RELAY_CHN_DIRECTION_DEFAULT)); TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, dir); - - TEST_ESP_OK(relay_chn_nvs_deinit()); } TEST_CASE("Test invalid parameters", "[relay_chn][nvs]") { - TEST_ESP_OK(relay_chn_nvs_init()); - // Test NULL pointer - TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_direction(0, NULL)); - - TEST_ESP_OK(relay_chn_nvs_deinit()); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_direction(0, NULL, RELAY_CHN_DIRECTION_DEFAULT)); } TEST_CASE("Test relay_chn_nvs_erase_all", "[relay_chn][nvs]") { - TEST_ESP_OK(relay_chn_nvs_init()); - // Store some test data first relay_chn_direction_t direction = RELAY_CHN_DIRECTION_FLIPPED; TEST_ESP_OK(relay_chn_nvs_set_direction(0, direction)); +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT + uint16_t run_limit = 123; + TEST_ESP_OK(relay_chn_nvs_set_run_limit(0, run_limit)); +#endif + #if CONFIG_RELAY_CHN_ENABLE_TILTING uint8_t sensitivity = 50; TEST_ESP_OK(relay_chn_nvs_set_tilt_sensitivity(0, sensitivity)); @@ -62,76 +55,71 @@ TEST_CASE("Test relay_chn_nvs_erase_all", "[relay_chn][nvs]") // Test erase all TEST_ESP_OK(relay_chn_nvs_erase_all()); + vTaskDelay(pdMS_TO_TICKS(TEST_NVS_TASK_TIME_OUT_MS)); // Allow NVS task to write and commit // Verify data was erased by trying to read it back relay_chn_direction_t read_direction; - TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_direction(0, &read_direction)); + TEST_ESP_OK(relay_chn_nvs_get_direction(0, &read_direction, RELAY_CHN_DIRECTION_DEFAULT)); + TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, read_direction); -#if CONFIG_RELAY_CHN_ENABLE_TILTING - uint8_t read_sensitivity; - TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_tilt_sensitivity(0, &read_sensitivity)); - - uint16_t tilt_count; - TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_tilt_count(0, &tilt_count)); +#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT + uint16_t read_run_limit; + TEST_ESP_OK(relay_chn_nvs_get_run_limit(0, &read_run_limit, CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC)); + TEST_ASSERT_EQUAL(CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC, read_run_limit); #endif - TEST_ESP_OK(relay_chn_nvs_deinit()); +#if CONFIG_RELAY_CHN_ENABLE_TILTING + const uint8_t default_sensitivity_for_test = 42; + uint8_t read_sensitivity; + TEST_ESP_OK(relay_chn_nvs_get_tilt_sensitivity(0, &read_sensitivity, default_sensitivity_for_test)); + TEST_ASSERT_EQUAL(default_sensitivity_for_test, read_sensitivity); + uint16_t tilt_count; + TEST_ESP_OK(relay_chn_nvs_get_tilt_count(0, &tilt_count, 0)); + TEST_ASSERT_EQUAL(0, tilt_count); +#endif } #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT TEST_CASE("Test run limit setting and getting", "[relay_chn][nvs][run_limit]") { - TEST_ESP_OK(relay_chn_nvs_init()); - const uint16_t run_limit_sec = 32; TEST_ESP_OK(relay_chn_nvs_set_run_limit(0, run_limit_sec)); + vTaskDelay(pdMS_TO_TICKS(TEST_NVS_TASK_TIME_OUT_MS)); // Allow NVS task to write and commit uint16_t run_limit_read; - TEST_ESP_OK(relay_chn_nvs_get_run_limit(0, &run_limit_read)); + TEST_ESP_OK(relay_chn_nvs_get_run_limit(0, &run_limit_read, CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC)); TEST_ASSERT_EQUAL(run_limit_sec, run_limit_read); - - TEST_ESP_OK(relay_chn_nvs_deinit()); } #endif #if CONFIG_RELAY_CHN_ENABLE_TILTING TEST_CASE("Test sensitivity setting and getting", "[relay_chn][nvs][tilt]") { - TEST_ESP_OK(relay_chn_nvs_init()); - const uint8_t test_sensitivity = 75; TEST_ESP_OK(relay_chn_nvs_set_tilt_sensitivity(0, test_sensitivity)); + vTaskDelay(pdMS_TO_TICKS(TEST_NVS_TASK_TIME_OUT_MS)); // Allow NVS task to write and commit uint8_t sensitivity; - TEST_ESP_OK(relay_chn_nvs_get_tilt_sensitivity(0, &sensitivity)); + TEST_ESP_OK(relay_chn_nvs_get_tilt_sensitivity(0, &sensitivity, 0)); TEST_ASSERT_EQUAL(test_sensitivity, sensitivity); - - TEST_ESP_OK(relay_chn_nvs_deinit()); } TEST_CASE("Test tilt counter operations", "[relay_chn][nvs][tilt]") { - TEST_ESP_OK(relay_chn_nvs_init()); - const uint16_t tilt_count = 100; // Test setting counters TEST_ESP_OK(relay_chn_nvs_set_tilt_count(0, tilt_count)); + vTaskDelay(pdMS_TO_TICKS(TEST_NVS_TASK_TIME_OUT_MS)); // Allow NVS task to write and commit uint16_t tilt_count_read; - TEST_ESP_OK(relay_chn_nvs_get_tilt_count(0, &tilt_count_read)); + TEST_ESP_OK(relay_chn_nvs_get_tilt_count(0, &tilt_count_read, 0)); TEST_ASSERT_EQUAL(tilt_count, tilt_count_read); - - TEST_ESP_OK(relay_chn_nvs_deinit()); } TEST_CASE("Test tilting invalid parameters", "[relay_chn][nvs][tilt]") { - TEST_ESP_OK(relay_chn_nvs_init()); - // Test NULL pointers - TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_tilt_sensitivity(0, NULL)); - TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_tilt_count(0, NULL)); - - TEST_ESP_OK(relay_chn_nvs_deinit()); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_tilt_sensitivity(0, NULL, 0)); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_tilt_count(0, NULL, 0)); } #endif // CONFIG_RELAY_CHN_ENABLE_TILTING \ No newline at end of file diff --git a/test_apps/main/test_relay_chn_tilt_multi.c b/test_apps/main/test_relay_chn_tilt_multi.c index 1de119c..87b8d79 100644 --- a/test_apps/main/test_relay_chn_tilt_multi.c +++ b/test_apps/main/test_relay_chn_tilt_multi.c @@ -362,25 +362,26 @@ TEST_CASE("tilt counter logic: forward and reverse consumption", "[relay_chn][ti relay_chn_tilt_set_sensitivity_all_with(100); // Set sentivity to max for fastest execution // Tilt forward 3 times - for (int i = 0; i < 3; ++i) { - relay_chn_tilt_forward_all(); - vTaskDelay(pdMS_TO_TICKS(TEST_TILT_EXECUTION_TIME_MS)); - check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD); - relay_chn_tilt_stop_all(); - } + relay_chn_tilt_forward_all(); + vTaskDelay(pdMS_TO_TICKS(TEST_TILT_EXECUTION_TIME_MS * 3 + TEST_DELAY_MARGIN_MS)); + check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD); + + // Stop tilt on all channels + relay_chn_tilt_stop_all(); +#if CONFIG_RELAY_CHN_ENABLE_NVS + // Tilt stop should save the latest tilt count to the NVS + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS + 300)); +#else vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); +#endif // Now tilt reverse 3 times (should succeed) - for (int i = 0; i < 3; ++i) { - relay_chn_tilt_reverse_all(); - vTaskDelay(pdMS_TO_TICKS(TEST_TILT_EXECUTION_TIME_MS)); - check_all_channels_for_state(RELAY_CHN_STATE_TILT_REVERSE); - relay_chn_tilt_stop_all(); - } - - // Extra reverse tilt should fail (counter exhausted) relay_chn_tilt_reverse_all(); - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + vTaskDelay(pdMS_TO_TICKS(TEST_TILT_EXECUTION_TIME_MS * 3 + TEST_DELAY_MARGIN_MS)); + check_all_channels_for_state(RELAY_CHN_STATE_TILT_REVERSE); + + // One more reverse tilt should fail (counter exhausted) + vTaskDelay(pdMS_TO_TICKS(TEST_TILT_EXECUTION_TIME_MS * 3)); // Should not enter TILT_REVERSE, should remain IDLE check_all_channels_for_state(RELAY_CHN_STATE_IDLE); } diff --git a/test_apps/main/test_relay_chn_tilt_single.c b/test_apps/main/test_relay_chn_tilt_single.c index 5f8b36b..a2d6deb 100644 --- a/test_apps/main/test_relay_chn_tilt_single.c +++ b/test_apps/main/test_relay_chn_tilt_single.c @@ -155,7 +155,7 @@ TEST_CASE("Tilt Forward to Run Reverse transition without inertia", "[relay_chn] } // Test stopping from a tilt state (no inertia for stop command itself) -// Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_stop) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_IDLE +// Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_tilt_stop) -> RELAY_CHN_STATE_IDLE TEST_CASE("Tilt to Stop transition without immediate inertia for stop", "[relay_chn][tilt][inertia]") { // Prepare channel by running forward first to set last_run_cmd, then tilt @@ -165,7 +165,7 @@ TEST_CASE("Tilt to Stop transition without immediate inertia for stop", "[relay_ TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state()); // 2. Issue stop command - relay_chn_stop(); + relay_chn_tilt_stop(); // Stop command should apply immediately, setting state to FREE since last state was tilt. vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); From a1a54e2ca0e3a2c9ac4c8084340d6a5072be86b0 Mon Sep 17 00:00:00 2001 From: ismail Date: Thu, 4 Sep 2025 15:44:26 +0300 Subject: [PATCH 47/69] Stop tracking auto generated sdkconfig file --- test_apps/sdkconfig | 1414 ------------------------------------------- 1 file changed, 1414 deletions(-) delete mode 100644 test_apps/sdkconfig diff --git a/test_apps/sdkconfig b/test_apps/sdkconfig deleted file mode 100644 index e935f96..0000000 --- a/test_apps/sdkconfig +++ /dev/null @@ -1,1414 +0,0 @@ -# -# Automatically generated file. DO NOT EDIT. -# Espressif IoT Development Framework (ESP-IDF) 5.4.2 Project Configuration -# -CONFIG_SOC_BROWNOUT_RESET_SUPPORTED="Not determined" -CONFIG_SOC_TWAI_BRP_DIV_SUPPORTED="Not determined" -CONFIG_SOC_DPORT_WORKAROUND="Not determined" -CONFIG_SOC_CAPS_ECO_VER_MAX=301 -CONFIG_SOC_ADC_SUPPORTED=y -CONFIG_SOC_DAC_SUPPORTED=y -CONFIG_SOC_UART_SUPPORTED=y -CONFIG_SOC_MCPWM_SUPPORTED=y -CONFIG_SOC_GPTIMER_SUPPORTED=y -CONFIG_SOC_SDMMC_HOST_SUPPORTED=y -CONFIG_SOC_BT_SUPPORTED=y -CONFIG_SOC_PCNT_SUPPORTED=y -CONFIG_SOC_PHY_SUPPORTED=y -CONFIG_SOC_WIFI_SUPPORTED=y -CONFIG_SOC_SDIO_SLAVE_SUPPORTED=y -CONFIG_SOC_TWAI_SUPPORTED=y -CONFIG_SOC_EFUSE_SUPPORTED=y -CONFIG_SOC_EMAC_SUPPORTED=y -CONFIG_SOC_ULP_SUPPORTED=y -CONFIG_SOC_CCOMP_TIMER_SUPPORTED=y -CONFIG_SOC_RTC_FAST_MEM_SUPPORTED=y -CONFIG_SOC_RTC_SLOW_MEM_SUPPORTED=y -CONFIG_SOC_RTC_MEM_SUPPORTED=y -CONFIG_SOC_I2S_SUPPORTED=y -CONFIG_SOC_RMT_SUPPORTED=y -CONFIG_SOC_SDM_SUPPORTED=y -CONFIG_SOC_GPSPI_SUPPORTED=y -CONFIG_SOC_LEDC_SUPPORTED=y -CONFIG_SOC_I2C_SUPPORTED=y -CONFIG_SOC_SUPPORT_COEXISTENCE=y -CONFIG_SOC_AES_SUPPORTED=y -CONFIG_SOC_MPI_SUPPORTED=y -CONFIG_SOC_SHA_SUPPORTED=y -CONFIG_SOC_FLASH_ENC_SUPPORTED=y -CONFIG_SOC_SECURE_BOOT_SUPPORTED=y -CONFIG_SOC_TOUCH_SENSOR_SUPPORTED=y -CONFIG_SOC_BOD_SUPPORTED=y -CONFIG_SOC_ULP_FSM_SUPPORTED=y -CONFIG_SOC_CLK_TREE_SUPPORTED=y -CONFIG_SOC_MPU_SUPPORTED=y -CONFIG_SOC_WDT_SUPPORTED=y -CONFIG_SOC_SPI_FLASH_SUPPORTED=y -CONFIG_SOC_RNG_SUPPORTED=y -CONFIG_SOC_LIGHT_SLEEP_SUPPORTED=y -CONFIG_SOC_DEEP_SLEEP_SUPPORTED=y -CONFIG_SOC_LP_PERIPH_SHARE_INTERRUPT=y -CONFIG_SOC_PM_SUPPORTED=y -CONFIG_SOC_DPORT_WORKAROUND_DIS_INTERRUPT_LVL=5 -CONFIG_SOC_XTAL_SUPPORT_26M=y -CONFIG_SOC_XTAL_SUPPORT_40M=y -CONFIG_SOC_XTAL_SUPPORT_AUTO_DETECT=y -CONFIG_SOC_ADC_RTC_CTRL_SUPPORTED=y -CONFIG_SOC_ADC_DIG_CTRL_SUPPORTED=y -CONFIG_SOC_ADC_DMA_SUPPORTED=y -CONFIG_SOC_ADC_PERIPH_NUM=2 -CONFIG_SOC_ADC_MAX_CHANNEL_NUM=10 -CONFIG_SOC_ADC_ATTEN_NUM=4 -CONFIG_SOC_ADC_DIGI_CONTROLLER_NUM=2 -CONFIG_SOC_ADC_PATT_LEN_MAX=16 -CONFIG_SOC_ADC_DIGI_MIN_BITWIDTH=9 -CONFIG_SOC_ADC_DIGI_MAX_BITWIDTH=12 -CONFIG_SOC_ADC_DIGI_RESULT_BYTES=2 -CONFIG_SOC_ADC_DIGI_DATA_BYTES_PER_CONV=4 -CONFIG_SOC_ADC_DIGI_MONITOR_NUM=0 -CONFIG_SOC_ADC_SAMPLE_FREQ_THRES_HIGH=2 -CONFIG_SOC_ADC_SAMPLE_FREQ_THRES_LOW=20 -CONFIG_SOC_ADC_RTC_MIN_BITWIDTH=9 -CONFIG_SOC_ADC_RTC_MAX_BITWIDTH=12 -CONFIG_SOC_ADC_SHARED_POWER=y -CONFIG_SOC_SHARED_IDCACHE_SUPPORTED=y -CONFIG_SOC_IDCACHE_PER_CORE=y -CONFIG_SOC_CPU_CORES_NUM=2 -CONFIG_SOC_CPU_INTR_NUM=32 -CONFIG_SOC_CPU_HAS_FPU=y -CONFIG_SOC_HP_CPU_HAS_MULTIPLE_CORES=y -CONFIG_SOC_CPU_BREAKPOINTS_NUM=2 -CONFIG_SOC_CPU_WATCHPOINTS_NUM=2 -CONFIG_SOC_CPU_WATCHPOINT_MAX_REGION_SIZE=64 -CONFIG_SOC_DAC_CHAN_NUM=2 -CONFIG_SOC_DAC_RESOLUTION=8 -CONFIG_SOC_DAC_DMA_16BIT_ALIGN=y -CONFIG_SOC_GPIO_PORT=1 -CONFIG_SOC_GPIO_PIN_COUNT=40 -CONFIG_SOC_GPIO_VALID_GPIO_MASK=0xFFFFFFFFFF -CONFIG_SOC_GPIO_IN_RANGE_MAX=39 -CONFIG_SOC_GPIO_OUT_RANGE_MAX=33 -CONFIG_SOC_GPIO_VALID_DIGITAL_IO_PAD_MASK=0xEF0FEA -CONFIG_SOC_GPIO_CLOCKOUT_BY_IO_MUX=y -CONFIG_SOC_GPIO_CLOCKOUT_CHANNEL_NUM=3 -CONFIG_SOC_GPIO_SUPPORT_HOLD_IO_IN_DSLP=y -CONFIG_SOC_I2C_NUM=2 -CONFIG_SOC_HP_I2C_NUM=2 -CONFIG_SOC_I2C_FIFO_LEN=32 -CONFIG_SOC_I2C_CMD_REG_NUM=16 -CONFIG_SOC_I2C_SUPPORT_SLAVE=y -CONFIG_SOC_I2C_SUPPORT_APB=y -CONFIG_SOC_I2C_SUPPORT_10BIT_ADDR=y -CONFIG_SOC_I2C_STOP_INDEPENDENT=y -CONFIG_SOC_I2S_NUM=2 -CONFIG_SOC_I2S_HW_VERSION_1=y -CONFIG_SOC_I2S_SUPPORTS_APLL=y -CONFIG_SOC_I2S_SUPPORTS_PLL_F160M=y -CONFIG_SOC_I2S_SUPPORTS_PDM=y -CONFIG_SOC_I2S_SUPPORTS_PDM_TX=y -CONFIG_SOC_I2S_PDM_MAX_TX_LINES=1 -CONFIG_SOC_I2S_SUPPORTS_PDM_RX=y -CONFIG_SOC_I2S_PDM_MAX_RX_LINES=1 -CONFIG_SOC_I2S_SUPPORTS_ADC_DAC=y -CONFIG_SOC_I2S_SUPPORTS_ADC=y -CONFIG_SOC_I2S_SUPPORTS_DAC=y -CONFIG_SOC_I2S_SUPPORTS_LCD_CAMERA=y -CONFIG_SOC_I2S_MAX_DATA_WIDTH=24 -CONFIG_SOC_I2S_TRANS_SIZE_ALIGN_WORD=y -CONFIG_SOC_I2S_LCD_I80_VARIANT=y -CONFIG_SOC_LCD_I80_SUPPORTED=y -CONFIG_SOC_LCD_I80_BUSES=2 -CONFIG_SOC_LCD_I80_BUS_WIDTH=24 -CONFIG_SOC_LEDC_HAS_TIMER_SPECIFIC_MUX=y -CONFIG_SOC_LEDC_SUPPORT_APB_CLOCK=y -CONFIG_SOC_LEDC_SUPPORT_REF_TICK=y -CONFIG_SOC_LEDC_SUPPORT_HS_MODE=y -CONFIG_SOC_LEDC_TIMER_NUM=4 -CONFIG_SOC_LEDC_CHANNEL_NUM=8 -CONFIG_SOC_LEDC_TIMER_BIT_WIDTH=20 -CONFIG_SOC_MCPWM_GROUPS=2 -CONFIG_SOC_MCPWM_TIMERS_PER_GROUP=3 -CONFIG_SOC_MCPWM_OPERATORS_PER_GROUP=3 -CONFIG_SOC_MCPWM_COMPARATORS_PER_OPERATOR=2 -CONFIG_SOC_MCPWM_GENERATORS_PER_OPERATOR=2 -CONFIG_SOC_MCPWM_TRIGGERS_PER_OPERATOR=2 -CONFIG_SOC_MCPWM_GPIO_FAULTS_PER_GROUP=3 -CONFIG_SOC_MCPWM_CAPTURE_TIMERS_PER_GROUP=y -CONFIG_SOC_MCPWM_CAPTURE_CHANNELS_PER_TIMER=3 -CONFIG_SOC_MCPWM_GPIO_SYNCHROS_PER_GROUP=3 -CONFIG_SOC_MMU_PERIPH_NUM=2 -CONFIG_SOC_MMU_LINEAR_ADDRESS_REGION_NUM=3 -CONFIG_SOC_MPU_MIN_REGION_SIZE=0x20000000 -CONFIG_SOC_MPU_REGIONS_MAX_NUM=8 -CONFIG_SOC_PCNT_GROUPS=1 -CONFIG_SOC_PCNT_UNITS_PER_GROUP=8 -CONFIG_SOC_PCNT_CHANNELS_PER_UNIT=2 -CONFIG_SOC_PCNT_THRES_POINT_PER_UNIT=2 -CONFIG_SOC_RMT_GROUPS=1 -CONFIG_SOC_RMT_TX_CANDIDATES_PER_GROUP=8 -CONFIG_SOC_RMT_RX_CANDIDATES_PER_GROUP=8 -CONFIG_SOC_RMT_CHANNELS_PER_GROUP=8 -CONFIG_SOC_RMT_MEM_WORDS_PER_CHANNEL=64 -CONFIG_SOC_RMT_SUPPORT_REF_TICK=y -CONFIG_SOC_RMT_SUPPORT_APB=y -CONFIG_SOC_RMT_CHANNEL_CLK_INDEPENDENT=y -CONFIG_SOC_RTCIO_PIN_COUNT=18 -CONFIG_SOC_RTCIO_INPUT_OUTPUT_SUPPORTED=y -CONFIG_SOC_RTCIO_HOLD_SUPPORTED=y -CONFIG_SOC_RTCIO_WAKE_SUPPORTED=y -CONFIG_SOC_SDM_GROUPS=1 -CONFIG_SOC_SDM_CHANNELS_PER_GROUP=8 -CONFIG_SOC_SDM_CLK_SUPPORT_APB=y -CONFIG_SOC_SPI_HD_BOTH_INOUT_SUPPORTED=y -CONFIG_SOC_SPI_AS_CS_SUPPORTED=y -CONFIG_SOC_SPI_PERIPH_NUM=3 -CONFIG_SOC_SPI_DMA_CHAN_NUM=2 -CONFIG_SOC_SPI_MAX_CS_NUM=3 -CONFIG_SOC_SPI_SUPPORT_CLK_APB=y -CONFIG_SOC_SPI_MAXIMUM_BUFFER_SIZE=64 -CONFIG_SOC_SPI_MAX_PRE_DIVIDER=8192 -CONFIG_SOC_MEMSPI_SRC_FREQ_80M_SUPPORTED=y -CONFIG_SOC_MEMSPI_SRC_FREQ_40M_SUPPORTED=y -CONFIG_SOC_MEMSPI_SRC_FREQ_26M_SUPPORTED=y -CONFIG_SOC_MEMSPI_SRC_FREQ_20M_SUPPORTED=y -CONFIG_SOC_TIMER_GROUPS=2 -CONFIG_SOC_TIMER_GROUP_TIMERS_PER_GROUP=2 -CONFIG_SOC_TIMER_GROUP_COUNTER_BIT_WIDTH=64 -CONFIG_SOC_TIMER_GROUP_TOTAL_TIMERS=4 -CONFIG_SOC_TIMER_GROUP_SUPPORT_APB=y -CONFIG_SOC_LP_TIMER_BIT_WIDTH_LO=32 -CONFIG_SOC_LP_TIMER_BIT_WIDTH_HI=16 -CONFIG_SOC_TOUCH_SENSOR_VERSION=1 -CONFIG_SOC_TOUCH_SENSOR_NUM=10 -CONFIG_SOC_TOUCH_SAMPLE_CFG_NUM=1 -CONFIG_SOC_TWAI_CONTROLLER_NUM=1 -CONFIG_SOC_TWAI_BRP_MIN=2 -CONFIG_SOC_TWAI_CLK_SUPPORT_APB=y -CONFIG_SOC_TWAI_SUPPORT_MULTI_ADDRESS_LAYOUT=y -CONFIG_SOC_UART_NUM=3 -CONFIG_SOC_UART_HP_NUM=3 -CONFIG_SOC_UART_SUPPORT_APB_CLK=y -CONFIG_SOC_UART_SUPPORT_REF_TICK=y -CONFIG_SOC_UART_FIFO_LEN=128 -CONFIG_SOC_UART_BITRATE_MAX=5000000 -CONFIG_SOC_SPIRAM_SUPPORTED=y -CONFIG_SOC_SPI_MEM_SUPPORT_CONFIG_GPIO_BY_EFUSE=y -CONFIG_SOC_SHA_SUPPORT_PARALLEL_ENG=y -CONFIG_SOC_SHA_ENDIANNESS_BE=y -CONFIG_SOC_SHA_SUPPORT_SHA1=y -CONFIG_SOC_SHA_SUPPORT_SHA256=y -CONFIG_SOC_SHA_SUPPORT_SHA384=y -CONFIG_SOC_SHA_SUPPORT_SHA512=y -CONFIG_SOC_MPI_MEM_BLOCKS_NUM=4 -CONFIG_SOC_MPI_OPERATIONS_NUM=y -CONFIG_SOC_RSA_MAX_BIT_LEN=4096 -CONFIG_SOC_AES_SUPPORT_AES_128=y -CONFIG_SOC_AES_SUPPORT_AES_192=y -CONFIG_SOC_AES_SUPPORT_AES_256=y -CONFIG_SOC_SECURE_BOOT_V1=y -CONFIG_SOC_EFUSE_SECURE_BOOT_KEY_DIGESTS=y -CONFIG_SOC_FLASH_ENCRYPTED_XTS_AES_BLOCK_MAX=32 -CONFIG_SOC_PHY_DIG_REGS_MEM_SIZE=21 -CONFIG_SOC_PM_SUPPORT_EXT0_WAKEUP=y -CONFIG_SOC_PM_SUPPORT_EXT1_WAKEUP=y -CONFIG_SOC_PM_SUPPORT_EXT_WAKEUP=y -CONFIG_SOC_PM_SUPPORT_TOUCH_SENSOR_WAKEUP=y -CONFIG_SOC_PM_SUPPORT_RTC_PERIPH_PD=y -CONFIG_SOC_PM_SUPPORT_RTC_FAST_MEM_PD=y -CONFIG_SOC_PM_SUPPORT_RTC_SLOW_MEM_PD=y -CONFIG_SOC_PM_SUPPORT_RC_FAST_PD=y -CONFIG_SOC_PM_SUPPORT_VDDSDIO_PD=y -CONFIG_SOC_PM_SUPPORT_MODEM_PD=y -CONFIG_SOC_CONFIGURABLE_VDDSDIO_SUPPORTED=y -CONFIG_SOC_PM_MODEM_PD_BY_SW=y -CONFIG_SOC_CLK_APLL_SUPPORTED=y -CONFIG_SOC_CLK_RC_FAST_D256_SUPPORTED=y -CONFIG_SOC_RTC_SLOW_CLK_SUPPORT_RC_FAST_D256=y -CONFIG_SOC_CLK_RC_FAST_SUPPORT_CALIBRATION=y -CONFIG_SOC_CLK_XTAL32K_SUPPORTED=y -CONFIG_SOC_SDMMC_USE_IOMUX=y -CONFIG_SOC_SDMMC_NUM_SLOTS=2 -CONFIG_SOC_WIFI_WAPI_SUPPORT=y -CONFIG_SOC_WIFI_CSI_SUPPORT=y -CONFIG_SOC_WIFI_MESH_SUPPORT=y -CONFIG_SOC_WIFI_SUPPORT_VARIABLE_BEACON_WINDOW=y -CONFIG_SOC_WIFI_NAN_SUPPORT=y -CONFIG_SOC_BLE_SUPPORTED=y -CONFIG_SOC_BLE_MESH_SUPPORTED=y -CONFIG_SOC_BT_CLASSIC_SUPPORTED=y -CONFIG_SOC_BLUFI_SUPPORTED=y -CONFIG_SOC_BT_H2C_ENC_KEY_CTRL_ENH_VSC_SUPPORTED=y -CONFIG_SOC_ULP_HAS_ADC=y -CONFIG_SOC_PHY_COMBO_MODULE=y -CONFIG_SOC_EMAC_RMII_CLK_OUT_INTERNAL_LOOPBACK=y -CONFIG_IDF_CMAKE=y -CONFIG_IDF_TOOLCHAIN="gcc" -CONFIG_IDF_TOOLCHAIN_GCC=y -CONFIG_IDF_TARGET_ARCH_XTENSA=y -CONFIG_IDF_TARGET_ARCH="xtensa" -CONFIG_IDF_TARGET="esp32" -CONFIG_IDF_INIT_VERSION="5.4.2" -CONFIG_IDF_TARGET_ESP32=y -CONFIG_IDF_FIRMWARE_CHIP_ID=0x0000 - -# -# Build type -# -CONFIG_APP_BUILD_TYPE_APP_2NDBOOT=y -# CONFIG_APP_BUILD_TYPE_RAM is not set -CONFIG_APP_BUILD_GENERATE_BINARIES=y -CONFIG_APP_BUILD_BOOTLOADER=y -CONFIG_APP_BUILD_USE_FLASH_SECTIONS=y -# CONFIG_APP_REPRODUCIBLE_BUILD is not set -# CONFIG_APP_NO_BLOBS is not set -# CONFIG_APP_COMPATIBLE_PRE_V2_1_BOOTLOADERS is not set -# CONFIG_APP_COMPATIBLE_PRE_V3_1_BOOTLOADERS is not set -# end of Build type - -# -# Bootloader config -# - -# -# Bootloader manager -# -CONFIG_BOOTLOADER_COMPILE_TIME_DATE=y -CONFIG_BOOTLOADER_PROJECT_VER=1 -# end of Bootloader manager - -CONFIG_BOOTLOADER_OFFSET_IN_FLASH=0x1000 -CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y -# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_DEBUG is not set -# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_PERF is not set -# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_NONE is not set - -# -# Log -# -# CONFIG_BOOTLOADER_LOG_LEVEL_NONE is not set -# CONFIG_BOOTLOADER_LOG_LEVEL_ERROR is not set -# CONFIG_BOOTLOADER_LOG_LEVEL_WARN is not set -CONFIG_BOOTLOADER_LOG_LEVEL_INFO=y -# CONFIG_BOOTLOADER_LOG_LEVEL_DEBUG is not set -# CONFIG_BOOTLOADER_LOG_LEVEL_VERBOSE is not set -CONFIG_BOOTLOADER_LOG_LEVEL=3 - -# -# Format -# -# CONFIG_BOOTLOADER_LOG_COLORS is not set -CONFIG_BOOTLOADER_LOG_TIMESTAMP_SOURCE_CPU_TICKS=y -# end of Format -# end of Log - -# -# Serial Flash Configurations -# -# CONFIG_BOOTLOADER_FLASH_DC_AWARE is not set -CONFIG_BOOTLOADER_FLASH_XMC_SUPPORT=y -# end of Serial Flash Configurations - -# CONFIG_BOOTLOADER_VDDSDIO_BOOST_1_8V is not set -CONFIG_BOOTLOADER_VDDSDIO_BOOST_1_9V=y -# CONFIG_BOOTLOADER_FACTORY_RESET is not set -# CONFIG_BOOTLOADER_APP_TEST is not set -CONFIG_BOOTLOADER_REGION_PROTECTION_ENABLE=y -CONFIG_BOOTLOADER_WDT_ENABLE=y -# CONFIG_BOOTLOADER_WDT_DISABLE_IN_USER_CODE is not set -CONFIG_BOOTLOADER_WDT_TIME_MS=9000 -# CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE is not set -# CONFIG_BOOTLOADER_SKIP_VALIDATE_IN_DEEP_SLEEP is not set -# CONFIG_BOOTLOADER_SKIP_VALIDATE_ON_POWER_ON is not set -# CONFIG_BOOTLOADER_SKIP_VALIDATE_ALWAYS is not set -CONFIG_BOOTLOADER_RESERVE_RTC_SIZE=0 -# CONFIG_BOOTLOADER_CUSTOM_RESERVE_RTC is not set -# end of Bootloader config - -# -# Security features -# -CONFIG_SECURE_BOOT_V1_SUPPORTED=y -# CONFIG_SECURE_SIGNED_APPS_NO_SECURE_BOOT is not set -# CONFIG_SECURE_BOOT is not set -# CONFIG_SECURE_FLASH_ENC_ENABLED is not set -# end of Security features - -# -# Application manager -# -CONFIG_APP_COMPILE_TIME_DATE=y -# CONFIG_APP_EXCLUDE_PROJECT_VER_VAR is not set -# CONFIG_APP_EXCLUDE_PROJECT_NAME_VAR is not set -# CONFIG_APP_PROJECT_VER_FROM_CONFIG is not set -CONFIG_APP_RETRIEVE_LEN_ELF_SHA=9 -# end of Application manager - -CONFIG_ESP_ROM_HAS_CRC_LE=y -CONFIG_ESP_ROM_HAS_CRC_BE=y -CONFIG_ESP_ROM_HAS_MZ_CRC32=y -CONFIG_ESP_ROM_HAS_JPEG_DECODE=y -CONFIG_ESP_ROM_HAS_UART_BUF_SWITCH=y -CONFIG_ESP_ROM_NEEDS_SWSETUP_WORKAROUND=y -CONFIG_ESP_ROM_HAS_NEWLIB=y -CONFIG_ESP_ROM_HAS_NEWLIB_NANO_FORMAT=y -CONFIG_ESP_ROM_HAS_NEWLIB_32BIT_TIME=y -CONFIG_ESP_ROM_HAS_SW_FLOAT=y -CONFIG_ESP_ROM_USB_OTG_NUM=-1 -CONFIG_ESP_ROM_USB_SERIAL_DEVICE_NUM=-1 -CONFIG_ESP_ROM_SUPPORT_DEEP_SLEEP_WAKEUP_STUB=y -CONFIG_ESP_ROM_HAS_OUTPUT_PUTC_FUNC=y - -# -# Serial flasher config -# -# CONFIG_ESPTOOLPY_NO_STUB is not set -# CONFIG_ESPTOOLPY_FLASHMODE_QIO is not set -# CONFIG_ESPTOOLPY_FLASHMODE_QOUT is not set -CONFIG_ESPTOOLPY_FLASHMODE_DIO=y -# CONFIG_ESPTOOLPY_FLASHMODE_DOUT is not set -CONFIG_ESPTOOLPY_FLASH_SAMPLE_MODE_STR=y -CONFIG_ESPTOOLPY_FLASHMODE="dio" -# CONFIG_ESPTOOLPY_FLASHFREQ_80M is not set -CONFIG_ESPTOOLPY_FLASHFREQ_40M=y -# CONFIG_ESPTOOLPY_FLASHFREQ_26M is not set -# CONFIG_ESPTOOLPY_FLASHFREQ_20M is not set -CONFIG_ESPTOOLPY_FLASHFREQ="40m" -# CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set -CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y -# CONFIG_ESPTOOLPY_FLASHSIZE_4MB is not set -# CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set -# CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set -# CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set -# CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set -# CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set -CONFIG_ESPTOOLPY_FLASHSIZE="2MB" -# CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE is not set -CONFIG_ESPTOOLPY_BEFORE_RESET=y -# CONFIG_ESPTOOLPY_BEFORE_NORESET is not set -CONFIG_ESPTOOLPY_BEFORE="default_reset" -CONFIG_ESPTOOLPY_AFTER_RESET=y -# CONFIG_ESPTOOLPY_AFTER_NORESET is not set -CONFIG_ESPTOOLPY_AFTER="hard_reset" -CONFIG_ESPTOOLPY_MONITOR_BAUD=115200 -# end of Serial flasher config - -# -# Partition Table -# -# CONFIG_PARTITION_TABLE_SINGLE_APP is not set -# CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE is not set -# CONFIG_PARTITION_TABLE_TWO_OTA is not set -# CONFIG_PARTITION_TABLE_TWO_OTA_LARGE is not set -CONFIG_PARTITION_TABLE_CUSTOM=y -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/part_nvs.csv" -CONFIG_PARTITION_TABLE_FILENAME="partitions/part_nvs.csv" -CONFIG_PARTITION_TABLE_OFFSET=0x8000 -CONFIG_PARTITION_TABLE_MD5=y -# end of Partition Table - -# -# Compiler options -# -CONFIG_COMPILER_OPTIMIZATION_DEBUG=y -# CONFIG_COMPILER_OPTIMIZATION_SIZE is not set -# CONFIG_COMPILER_OPTIMIZATION_PERF is not set -# CONFIG_COMPILER_OPTIMIZATION_NONE is not set -CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y -# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT is not set -# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE is not set -CONFIG_COMPILER_ASSERT_NDEBUG_EVALUATE=y -CONFIG_COMPILER_FLOAT_LIB_FROM_GCCLIB=y -CONFIG_COMPILER_OPTIMIZATION_ASSERTION_LEVEL=2 -# CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT is not set -CONFIG_COMPILER_HIDE_PATHS_MACROS=y -# CONFIG_COMPILER_CXX_EXCEPTIONS is not set -# CONFIG_COMPILER_CXX_RTTI is not set -CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y -# CONFIG_COMPILER_STACK_CHECK_MODE_NORM is not set -# CONFIG_COMPILER_STACK_CHECK_MODE_STRONG is not set -# CONFIG_COMPILER_STACK_CHECK_MODE_ALL is not set -# CONFIG_COMPILER_NO_MERGE_CONSTANTS is not set -# CONFIG_COMPILER_WARN_WRITE_STRINGS is not set -CONFIG_COMPILER_DISABLE_DEFAULT_ERRORS=y -# CONFIG_COMPILER_DISABLE_GCC12_WARNINGS is not set -# CONFIG_COMPILER_DISABLE_GCC13_WARNINGS is not set -# CONFIG_COMPILER_DISABLE_GCC14_WARNINGS is not set -# CONFIG_COMPILER_DUMP_RTL_FILES is not set -CONFIG_COMPILER_RT_LIB_GCCLIB=y -CONFIG_COMPILER_RT_LIB_NAME="gcc" -CONFIG_COMPILER_ORPHAN_SECTIONS_WARNING=y -# CONFIG_COMPILER_ORPHAN_SECTIONS_PLACE is not set -# CONFIG_COMPILER_STATIC_ANALYZER is not set -# end of Compiler options - -# -# Component config -# - -# -# Driver Configurations -# - -# -# TWAI Configuration -# -# CONFIG_TWAI_ISR_IN_IRAM is not set -CONFIG_TWAI_ERRATA_FIX_BUS_OFF_REC=y -CONFIG_TWAI_ERRATA_FIX_TX_INTR_LOST=y -CONFIG_TWAI_ERRATA_FIX_RX_FRAME_INVALID=y -CONFIG_TWAI_ERRATA_FIX_RX_FIFO_CORRUPT=y -CONFIG_TWAI_ERRATA_FIX_LISTEN_ONLY_DOM=y -# end of TWAI Configuration - -# -# Legacy ADC Driver Configuration -# -CONFIG_ADC_DISABLE_DAC=y -# CONFIG_ADC_SUPPRESS_DEPRECATE_WARN is not set -# CONFIG_ADC_SKIP_LEGACY_CONFLICT_CHECK is not set - -# -# Legacy ADC Calibration Configuration -# -CONFIG_ADC_CAL_EFUSE_TP_ENABLE=y -CONFIG_ADC_CAL_EFUSE_VREF_ENABLE=y -CONFIG_ADC_CAL_LUT_ENABLE=y -# CONFIG_ADC_CALI_SUPPRESS_DEPRECATE_WARN is not set -# end of Legacy ADC Calibration Configuration -# end of Legacy ADC Driver Configuration - -# -# Legacy DAC Driver Configurations -# -# CONFIG_DAC_SUPPRESS_DEPRECATE_WARN is not set -# CONFIG_DAC_SKIP_LEGACY_CONFLICT_CHECK is not set -# end of Legacy DAC Driver Configurations - -# -# Legacy MCPWM Driver Configurations -# -# CONFIG_MCPWM_SUPPRESS_DEPRECATE_WARN is not set -# CONFIG_MCPWM_SKIP_LEGACY_CONFLICT_CHECK is not set -# end of Legacy MCPWM Driver Configurations - -# -# Legacy Timer Group Driver Configurations -# -# CONFIG_GPTIMER_SUPPRESS_DEPRECATE_WARN is not set -# CONFIG_GPTIMER_SKIP_LEGACY_CONFLICT_CHECK is not set -# end of Legacy Timer Group Driver Configurations - -# -# Legacy RMT Driver Configurations -# -# CONFIG_RMT_SUPPRESS_DEPRECATE_WARN is not set -# CONFIG_RMT_SKIP_LEGACY_CONFLICT_CHECK is not set -# end of Legacy RMT Driver Configurations - -# -# Legacy I2S Driver Configurations -# -# CONFIG_I2S_SUPPRESS_DEPRECATE_WARN is not set -# CONFIG_I2S_SKIP_LEGACY_CONFLICT_CHECK is not set -# end of Legacy I2S Driver Configurations - -# -# Legacy I2C Driver Configurations -# -# CONFIG_I2C_SKIP_LEGACY_CONFLICT_CHECK is not set -# end of Legacy I2C Driver Configurations - -# -# Legacy PCNT Driver Configurations -# -# CONFIG_PCNT_SUPPRESS_DEPRECATE_WARN is not set -# CONFIG_PCNT_SKIP_LEGACY_CONFLICT_CHECK is not set -# end of Legacy PCNT Driver Configurations - -# -# Legacy SDM Driver Configurations -# -# CONFIG_SDM_SUPPRESS_DEPRECATE_WARN is not set -# CONFIG_SDM_SKIP_LEGACY_CONFLICT_CHECK is not set -# end of Legacy SDM Driver Configurations -# end of Driver Configurations - -# -# eFuse Bit Manager -# -# CONFIG_EFUSE_CUSTOM_TABLE is not set -# CONFIG_EFUSE_VIRTUAL is not set -# CONFIG_EFUSE_CODE_SCHEME_COMPAT_NONE is not set -CONFIG_EFUSE_CODE_SCHEME_COMPAT_3_4=y -# CONFIG_EFUSE_CODE_SCHEME_COMPAT_REPEAT is not set -CONFIG_EFUSE_MAX_BLK_LEN=192 -# end of eFuse Bit Manager - -# -# Common ESP-related -# -CONFIG_ESP_ERR_TO_NAME_LOOKUP=y -# end of Common ESP-related - -# -# ESP-Driver:DAC Configurations -# -# CONFIG_DAC_CTRL_FUNC_IN_IRAM is not set -# CONFIG_DAC_ISR_IRAM_SAFE is not set -# CONFIG_DAC_ENABLE_DEBUG_LOG is not set -CONFIG_DAC_DMA_AUTO_16BIT_ALIGN=y -# end of ESP-Driver:DAC Configurations - -# -# ESP-Driver:GPIO Configurations -# -# CONFIG_GPIO_ESP32_SUPPORT_SWITCH_SLP_PULL is not set -# CONFIG_GPIO_CTRL_FUNC_IN_IRAM is not set -# end of ESP-Driver:GPIO Configurations - -# -# ESP-Driver:GPTimer Configurations -# -CONFIG_GPTIMER_ISR_HANDLER_IN_IRAM=y -# CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM is not set -# CONFIG_GPTIMER_ISR_IRAM_SAFE is not set -CONFIG_GPTIMER_OBJ_CACHE_SAFE=y -# CONFIG_GPTIMER_ENABLE_DEBUG_LOG is not set -# end of ESP-Driver:GPTimer Configurations - -# -# ESP-Driver:I2C Configurations -# -# CONFIG_I2C_ISR_IRAM_SAFE is not set -# CONFIG_I2C_ENABLE_DEBUG_LOG is not set -# CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2 is not set -# end of ESP-Driver:I2C Configurations - -# -# ESP-Driver:I2S Configurations -# -# CONFIG_I2S_ISR_IRAM_SAFE is not set -# CONFIG_I2S_ENABLE_DEBUG_LOG is not set -# end of ESP-Driver:I2S Configurations - -# -# ESP-Driver:LEDC Configurations -# -# CONFIG_LEDC_CTRL_FUNC_IN_IRAM is not set -# end of ESP-Driver:LEDC Configurations - -# -# ESP-Driver:MCPWM Configurations -# -# CONFIG_MCPWM_ISR_IRAM_SAFE is not set -# CONFIG_MCPWM_CTRL_FUNC_IN_IRAM is not set -# CONFIG_MCPWM_ENABLE_DEBUG_LOG is not set -# end of ESP-Driver:MCPWM Configurations - -# -# ESP-Driver:PCNT Configurations -# -# CONFIG_PCNT_CTRL_FUNC_IN_IRAM is not set -# CONFIG_PCNT_ISR_IRAM_SAFE is not set -# CONFIG_PCNT_ENABLE_DEBUG_LOG is not set -# end of ESP-Driver:PCNT Configurations - -# -# ESP-Driver:RMT Configurations -# -# CONFIG_RMT_ISR_IRAM_SAFE is not set -# CONFIG_RMT_RECV_FUNC_IN_IRAM is not set -# CONFIG_RMT_ENABLE_DEBUG_LOG is not set -# end of ESP-Driver:RMT Configurations - -# -# ESP-Driver:Sigma Delta Modulator Configurations -# -# CONFIG_SDM_CTRL_FUNC_IN_IRAM is not set -# CONFIG_SDM_ENABLE_DEBUG_LOG is not set -# end of ESP-Driver:Sigma Delta Modulator Configurations - -# -# ESP-Driver:SPI Configurations -# -# CONFIG_SPI_MASTER_IN_IRAM is not set -CONFIG_SPI_MASTER_ISR_IN_IRAM=y -# CONFIG_SPI_SLAVE_IN_IRAM is not set -CONFIG_SPI_SLAVE_ISR_IN_IRAM=y -# end of ESP-Driver:SPI Configurations - -# -# ESP-Driver:UART Configurations -# -# CONFIG_UART_ISR_IN_IRAM is not set -# end of ESP-Driver:UART Configurations - -# -# Hardware Settings -# - -# -# Chip revision -# -CONFIG_ESP32_REV_MIN_0=y -# CONFIG_ESP32_REV_MIN_1 is not set -# CONFIG_ESP32_REV_MIN_1_1 is not set -# CONFIG_ESP32_REV_MIN_2 is not set -# CONFIG_ESP32_REV_MIN_3 is not set -# CONFIG_ESP32_REV_MIN_3_1 is not set -CONFIG_ESP32_REV_MIN=0 -CONFIG_ESP32_REV_MIN_FULL=0 -CONFIG_ESP_REV_MIN_FULL=0 - -# -# Maximum Supported ESP32 Revision (Rev v3.99) -# -CONFIG_ESP32_REV_MAX_FULL=399 -CONFIG_ESP_REV_MAX_FULL=399 -CONFIG_ESP_EFUSE_BLOCK_REV_MIN_FULL=0 -CONFIG_ESP_EFUSE_BLOCK_REV_MAX_FULL=99 - -# -# Maximum Supported ESP32 eFuse Block Revision (eFuse Block Rev v0.99) -# -# end of Chip revision - -# -# MAC Config -# -CONFIG_ESP_MAC_ADDR_UNIVERSE_WIFI_STA=y -CONFIG_ESP_MAC_ADDR_UNIVERSE_WIFI_AP=y -CONFIG_ESP_MAC_ADDR_UNIVERSE_BT=y -CONFIG_ESP_MAC_ADDR_UNIVERSE_ETH=y -CONFIG_ESP_MAC_UNIVERSAL_MAC_ADDRESSES_FOUR=y -CONFIG_ESP_MAC_UNIVERSAL_MAC_ADDRESSES=4 -# CONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES_TWO is not set -CONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES_FOUR=y -CONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES=4 -# CONFIG_ESP_MAC_IGNORE_MAC_CRC_ERROR is not set -# CONFIG_ESP_MAC_USE_CUSTOM_MAC_AS_BASE_MAC is not set -# end of MAC Config - -# -# Sleep Config -# -# CONFIG_ESP_SLEEP_POWER_DOWN_FLASH is not set -CONFIG_ESP_SLEEP_FLASH_LEAKAGE_WORKAROUND=y -# CONFIG_ESP_SLEEP_MSPI_NEED_ALL_IO_PU is not set -CONFIG_ESP_SLEEP_RTC_BUS_ISO_WORKAROUND=y -# CONFIG_ESP_SLEEP_GPIO_RESET_WORKAROUND is not set -CONFIG_ESP_SLEEP_WAIT_FLASH_READY_EXTRA_DELAY=2000 -# CONFIG_ESP_SLEEP_CACHE_SAFE_ASSERTION is not set -# CONFIG_ESP_SLEEP_DEBUG is not set -CONFIG_ESP_SLEEP_GPIO_ENABLE_INTERNAL_RESISTORS=y -# end of Sleep Config - -# -# RTC Clock Config -# -CONFIG_RTC_CLK_SRC_INT_RC=y -# CONFIG_RTC_CLK_SRC_EXT_CRYS is not set -# CONFIG_RTC_CLK_SRC_EXT_OSC is not set -# CONFIG_RTC_CLK_SRC_INT_8MD256 is not set -CONFIG_RTC_CLK_CAL_CYCLES=1024 -# end of RTC Clock Config - -# -# Peripheral Control -# -# CONFIG_PERIPH_CTRL_FUNC_IN_IRAM is not set -# end of Peripheral Control - -# -# Main XTAL Config -# -# CONFIG_XTAL_FREQ_26 is not set -# CONFIG_XTAL_FREQ_32 is not set -CONFIG_XTAL_FREQ_40=y -# CONFIG_XTAL_FREQ_AUTO is not set -CONFIG_XTAL_FREQ=40 -# end of Main XTAL Config - -CONFIG_ESP_SPI_BUS_LOCK_ISR_FUNCS_IN_IRAM=y -# end of Hardware Settings - -# -# ESP-MM: Memory Management Configurations -# -# end of ESP-MM: Memory Management Configurations - -# -# Partition API Configuration -# -# end of Partition API Configuration - -# -# Power Management -# -# CONFIG_PM_ENABLE is not set -# CONFIG_PM_SLP_IRAM_OPT is not set -# end of Power Management - -# -# ESP Ringbuf -# -# CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH is not set -# end of ESP Ringbuf - -# -# ESP Security Specific -# -# end of ESP Security Specific - -# -# ESP System Settings -# -# CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_80 is not set -CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_160=y -# CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240 is not set -CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=160 - -# -# Memory -# -# CONFIG_ESP32_USE_FIXED_STATIC_RAM_SIZE is not set - -# -# Non-backward compatible options -# -# CONFIG_ESP_SYSTEM_ESP32_SRAM1_REGION_AS_IRAM is not set -# end of Non-backward compatible options -# end of Memory - -# -# Trace memory -# -# CONFIG_ESP32_TRAX is not set -CONFIG_ESP32_TRACEMEM_RESERVE_DRAM=0x0 -# end of Trace memory - -# CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT is not set -CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT=y -# CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT is not set -CONFIG_ESP_SYSTEM_PANIC_REBOOT_DELAY_SECONDS=0 - -# -# Memory protection -# -# end of Memory protection - -CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=32 -CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=2304 -CONFIG_ESP_MAIN_TASK_STACK_SIZE=3584 -CONFIG_ESP_MAIN_TASK_AFFINITY_CPU0=y -# CONFIG_ESP_MAIN_TASK_AFFINITY_CPU1 is not set -# CONFIG_ESP_MAIN_TASK_AFFINITY_NO_AFFINITY is not set -CONFIG_ESP_MAIN_TASK_AFFINITY=0x0 -CONFIG_ESP_MINIMAL_SHARED_STACK_SIZE=2048 -CONFIG_ESP_CONSOLE_UART_DEFAULT=y -# CONFIG_ESP_CONSOLE_UART_CUSTOM is not set -# CONFIG_ESP_CONSOLE_NONE is not set -CONFIG_ESP_CONSOLE_UART=y -CONFIG_ESP_CONSOLE_UART_NUM=0 -CONFIG_ESP_CONSOLE_ROM_SERIAL_PORT_NUM=0 -CONFIG_ESP_CONSOLE_UART_BAUDRATE=115200 -CONFIG_ESP_INT_WDT=y -CONFIG_ESP_INT_WDT_TIMEOUT_MS=300 -CONFIG_ESP_INT_WDT_CHECK_CPU1=y -CONFIG_ESP_TASK_WDT_EN=y -# CONFIG_ESP_TASK_WDT_INIT is not set -# CONFIG_ESP_PANIC_HANDLER_IRAM is not set -# CONFIG_ESP_DEBUG_STUBS_ENABLE is not set -CONFIG_ESP_DEBUG_OCDAWARE=y -# CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5 is not set -CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_4=y - -# -# Brownout Detector -# -CONFIG_ESP_BROWNOUT_DET=y -CONFIG_ESP_BROWNOUT_DET_LVL_SEL_0=y -# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_1 is not set -# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_2 is not set -# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_3 is not set -# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_4 is not set -# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_5 is not set -# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_6 is not set -# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_7 is not set -CONFIG_ESP_BROWNOUT_DET_LVL=0 -# end of Brownout Detector - -# CONFIG_ESP32_DISABLE_BASIC_ROM_CONSOLE is not set -CONFIG_ESP_SYSTEM_BROWNOUT_INTR=y -# end of ESP System Settings - -# -# IPC (Inter-Processor Call) -# -CONFIG_ESP_IPC_TASK_STACK_SIZE=1024 -CONFIG_ESP_IPC_USES_CALLERS_PRIORITY=y -CONFIG_ESP_IPC_ISR_ENABLE=y -# end of IPC (Inter-Processor Call) - -# -# ESP Timer (High Resolution Timer) -# -# CONFIG_ESP_TIMER_PROFILING is not set -CONFIG_ESP_TIME_FUNCS_USE_RTC_TIMER=y -CONFIG_ESP_TIME_FUNCS_USE_ESP_TIMER=y -CONFIG_ESP_TIMER_TASK_STACK_SIZE=3584 -CONFIG_ESP_TIMER_INTERRUPT_LEVEL=1 -# CONFIG_ESP_TIMER_SHOW_EXPERIMENTAL is not set -CONFIG_ESP_TIMER_TASK_AFFINITY=0x0 -CONFIG_ESP_TIMER_TASK_AFFINITY_CPU0=y -CONFIG_ESP_TIMER_ISR_AFFINITY_CPU0=y -# CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD is not set -CONFIG_ESP_TIMER_IMPL_TG0_LAC=y -# end of ESP Timer (High Resolution Timer) - -# -# FreeRTOS -# - -# -# Kernel -# -# CONFIG_FREERTOS_SMP is not set -# CONFIG_FREERTOS_UNICORE is not set -CONFIG_FREERTOS_HZ=100 -# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_NONE is not set -# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_PTRVAL is not set -CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY=y -CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=1 -CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=1536 -# CONFIG_FREERTOS_USE_IDLE_HOOK is not set -# CONFIG_FREERTOS_USE_TICK_HOOK is not set -CONFIG_FREERTOS_MAX_TASK_NAME_LEN=16 -# CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY is not set -CONFIG_FREERTOS_USE_TIMERS=y -CONFIG_FREERTOS_TIMER_SERVICE_TASK_NAME="Tmr Svc" -# CONFIG_FREERTOS_TIMER_TASK_AFFINITY_CPU0 is not set -# CONFIG_FREERTOS_TIMER_TASK_AFFINITY_CPU1 is not set -CONFIG_FREERTOS_TIMER_TASK_NO_AFFINITY=y -CONFIG_FREERTOS_TIMER_SERVICE_TASK_CORE_AFFINITY=0x7FFFFFFF -CONFIG_FREERTOS_TIMER_TASK_PRIORITY=1 -CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=2048 -CONFIG_FREERTOS_TIMER_QUEUE_LENGTH=10 -CONFIG_FREERTOS_QUEUE_REGISTRY_SIZE=0 -CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=1 -# CONFIG_FREERTOS_USE_TRACE_FACILITY is not set -# CONFIG_FREERTOS_USE_LIST_DATA_INTEGRITY_CHECK_BYTES is not set -# CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS is not set -# CONFIG_FREERTOS_USE_APPLICATION_TASK_TAG is not set -# end of Kernel - -# -# Port -# -CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=y -# CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK is not set -CONFIG_FREERTOS_TLSP_DELETION_CALLBACKS=y -# CONFIG_FREERTOS_TASK_PRE_DELETION_HOOK is not set -# CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP is not set -CONFIG_FREERTOS_CHECK_MUTEX_GIVEN_BY_OWNER=y -CONFIG_FREERTOS_ISR_STACKSIZE=1536 -CONFIG_FREERTOS_INTERRUPT_BACKTRACE=y -# CONFIG_FREERTOS_FPU_IN_ISR is not set -CONFIG_FREERTOS_TICK_SUPPORT_CORETIMER=y -CONFIG_FREERTOS_CORETIMER_0=y -# CONFIG_FREERTOS_CORETIMER_1 is not set -CONFIG_FREERTOS_SYSTICK_USES_CCOUNT=y -# CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH is not set -# CONFIG_FREERTOS_CHECK_PORT_CRITICAL_COMPLIANCE is not set -# end of Port - -# -# Extra -# -# end of Extra - -CONFIG_FREERTOS_PORT=y -CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF -CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION=y -CONFIG_FREERTOS_DEBUG_OCDAWARE=y -CONFIG_FREERTOS_ENABLE_TASK_SNAPSHOT=y -CONFIG_FREERTOS_PLACE_SNAPSHOT_FUNS_INTO_FLASH=y -CONFIG_FREERTOS_NUMBER_OF_CORES=2 -# end of FreeRTOS - -# -# Hardware Abstraction Layer (HAL) and Low Level (LL) -# -CONFIG_HAL_ASSERTION_EQUALS_SYSTEM=y -# CONFIG_HAL_ASSERTION_DISABLE is not set -# CONFIG_HAL_ASSERTION_SILENT is not set -# CONFIG_HAL_ASSERTION_ENABLE is not set -CONFIG_HAL_DEFAULT_ASSERTION_LEVEL=2 -CONFIG_HAL_SPI_MASTER_FUNC_IN_IRAM=y -CONFIG_HAL_SPI_SLAVE_FUNC_IN_IRAM=y -# end of Hardware Abstraction Layer (HAL) and Low Level (LL) - -# -# Heap memory debugging -# -CONFIG_HEAP_POISONING_DISABLED=y -# CONFIG_HEAP_POISONING_LIGHT is not set -# CONFIG_HEAP_POISONING_COMPREHENSIVE is not set -CONFIG_HEAP_TRACING_OFF=y -# CONFIG_HEAP_TRACING_STANDALONE is not set -# CONFIG_HEAP_TRACING_TOHOST is not set -# CONFIG_HEAP_USE_HOOKS is not set -# CONFIG_HEAP_TASK_TRACKING is not set -# CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS is not set -# CONFIG_HEAP_PLACE_FUNCTION_INTO_FLASH is not set -# end of Heap memory debugging - -# -# Log -# - -# -# Log Level -# -# CONFIG_LOG_DEFAULT_LEVEL_NONE is not set -# CONFIG_LOG_DEFAULT_LEVEL_ERROR is not set -# CONFIG_LOG_DEFAULT_LEVEL_WARN is not set -CONFIG_LOG_DEFAULT_LEVEL_INFO=y -# CONFIG_LOG_DEFAULT_LEVEL_DEBUG is not set -# CONFIG_LOG_DEFAULT_LEVEL_VERBOSE is not set -CONFIG_LOG_DEFAULT_LEVEL=3 -CONFIG_LOG_MAXIMUM_EQUALS_DEFAULT=y -# CONFIG_LOG_MAXIMUM_LEVEL_DEBUG is not set -# CONFIG_LOG_MAXIMUM_LEVEL_VERBOSE is not set -CONFIG_LOG_MAXIMUM_LEVEL=3 - -# -# Level Settings -# -# CONFIG_LOG_MASTER_LEVEL is not set -CONFIG_LOG_DYNAMIC_LEVEL_CONTROL=y -# CONFIG_LOG_TAG_LEVEL_IMPL_NONE is not set -# CONFIG_LOG_TAG_LEVEL_IMPL_LINKED_LIST is not set -CONFIG_LOG_TAG_LEVEL_IMPL_CACHE_AND_LINKED_LIST=y -# CONFIG_LOG_TAG_LEVEL_CACHE_ARRAY is not set -CONFIG_LOG_TAG_LEVEL_CACHE_BINARY_MIN_HEAP=y -CONFIG_LOG_TAG_LEVEL_IMPL_CACHE_SIZE=31 -# end of Level Settings -# end of Log Level - -# -# Format -# -# CONFIG_LOG_COLORS is not set -CONFIG_LOG_TIMESTAMP_SOURCE_RTOS=y -# CONFIG_LOG_TIMESTAMP_SOURCE_SYSTEM is not set -# end of Format -# end of Log - -# -# mbedTLS -# -CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y -# CONFIG_MBEDTLS_DEFAULT_MEM_ALLOC is not set -# CONFIG_MBEDTLS_CUSTOM_MEM_ALLOC is not set -CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN=y -CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=16384 -CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=4096 -# CONFIG_MBEDTLS_DYNAMIC_BUFFER is not set -# CONFIG_MBEDTLS_DEBUG is not set - -# -# mbedTLS v3.x related -# -# CONFIG_MBEDTLS_SSL_PROTO_TLS1_3 is not set -# CONFIG_MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH is not set -# CONFIG_MBEDTLS_X509_TRUSTED_CERT_CALLBACK is not set -# CONFIG_MBEDTLS_SSL_CONTEXT_SERIALIZATION is not set -CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE=y -CONFIG_MBEDTLS_PKCS7_C=y -# end of mbedTLS v3.x related - -# -# Certificate Bundle -# -CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y -CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=y -# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN is not set -# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_NONE is not set -# CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE is not set -# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEPRECATED_LIST is not set -CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_MAX_CERTS=200 -# end of Certificate Bundle - -# CONFIG_MBEDTLS_ECP_RESTARTABLE is not set -# CONFIG_MBEDTLS_CMAC_C is not set -CONFIG_MBEDTLS_HARDWARE_AES=y -CONFIG_MBEDTLS_GCM_SUPPORT_NON_AES_CIPHER=y -CONFIG_MBEDTLS_HARDWARE_MPI=y -# CONFIG_MBEDTLS_LARGE_KEY_SOFTWARE_MPI is not set -CONFIG_MBEDTLS_HARDWARE_SHA=y -CONFIG_MBEDTLS_ROM_MD5=y -# CONFIG_MBEDTLS_ATCA_HW_ECDSA_SIGN is not set -# CONFIG_MBEDTLS_ATCA_HW_ECDSA_VERIFY is not set -CONFIG_MBEDTLS_HAVE_TIME=y -# CONFIG_MBEDTLS_PLATFORM_TIME_ALT is not set -# CONFIG_MBEDTLS_HAVE_TIME_DATE is not set -CONFIG_MBEDTLS_ECDSA_DETERMINISTIC=y -CONFIG_MBEDTLS_SHA1_C=y -CONFIG_MBEDTLS_SHA512_C=y -# CONFIG_MBEDTLS_SHA3_C is not set -CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT=y -# CONFIG_MBEDTLS_TLS_SERVER_ONLY is not set -# CONFIG_MBEDTLS_TLS_CLIENT_ONLY is not set -# CONFIG_MBEDTLS_TLS_DISABLED is not set -CONFIG_MBEDTLS_TLS_SERVER=y -CONFIG_MBEDTLS_TLS_CLIENT=y -CONFIG_MBEDTLS_TLS_ENABLED=y - -# -# TLS Key Exchange Methods -# -# CONFIG_MBEDTLS_PSK_MODES is not set -CONFIG_MBEDTLS_KEY_EXCHANGE_RSA=y -CONFIG_MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE=y -CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_RSA=y -CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA=y -CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA=y -CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_RSA=y -# end of TLS Key Exchange Methods - -CONFIG_MBEDTLS_SSL_RENEGOTIATION=y -CONFIG_MBEDTLS_SSL_PROTO_TLS1_2=y -# CONFIG_MBEDTLS_SSL_PROTO_GMTSSL1_1 is not set -# CONFIG_MBEDTLS_SSL_PROTO_DTLS is not set -CONFIG_MBEDTLS_SSL_ALPN=y -CONFIG_MBEDTLS_CLIENT_SSL_SESSION_TICKETS=y -CONFIG_MBEDTLS_SERVER_SSL_SESSION_TICKETS=y - -# -# Symmetric Ciphers -# -CONFIG_MBEDTLS_AES_C=y -# CONFIG_MBEDTLS_CAMELLIA_C is not set -# CONFIG_MBEDTLS_DES_C is not set -# CONFIG_MBEDTLS_BLOWFISH_C is not set -# CONFIG_MBEDTLS_XTEA_C is not set -CONFIG_MBEDTLS_CCM_C=y -CONFIG_MBEDTLS_GCM_C=y -# CONFIG_MBEDTLS_NIST_KW_C is not set -# end of Symmetric Ciphers - -# CONFIG_MBEDTLS_RIPEMD160_C is not set - -# -# Certificates -# -CONFIG_MBEDTLS_PEM_PARSE_C=y -CONFIG_MBEDTLS_PEM_WRITE_C=y -CONFIG_MBEDTLS_X509_CRL_PARSE_C=y -CONFIG_MBEDTLS_X509_CSR_PARSE_C=y -# end of Certificates - -CONFIG_MBEDTLS_ECP_C=y -CONFIG_MBEDTLS_PK_PARSE_EC_EXTENDED=y -CONFIG_MBEDTLS_PK_PARSE_EC_COMPRESSED=y -# CONFIG_MBEDTLS_DHM_C is not set -CONFIG_MBEDTLS_ECDH_C=y -CONFIG_MBEDTLS_ECDSA_C=y -# CONFIG_MBEDTLS_ECJPAKE_C is not set -CONFIG_MBEDTLS_ECP_DP_SECP192R1_ENABLED=y -CONFIG_MBEDTLS_ECP_DP_SECP224R1_ENABLED=y -CONFIG_MBEDTLS_ECP_DP_SECP256R1_ENABLED=y -CONFIG_MBEDTLS_ECP_DP_SECP384R1_ENABLED=y -CONFIG_MBEDTLS_ECP_DP_SECP521R1_ENABLED=y -CONFIG_MBEDTLS_ECP_DP_SECP192K1_ENABLED=y -CONFIG_MBEDTLS_ECP_DP_SECP224K1_ENABLED=y -CONFIG_MBEDTLS_ECP_DP_SECP256K1_ENABLED=y -CONFIG_MBEDTLS_ECP_DP_BP256R1_ENABLED=y -CONFIG_MBEDTLS_ECP_DP_BP384R1_ENABLED=y -CONFIG_MBEDTLS_ECP_DP_BP512R1_ENABLED=y -CONFIG_MBEDTLS_ECP_DP_CURVE25519_ENABLED=y -CONFIG_MBEDTLS_ECP_NIST_OPTIM=y -# CONFIG_MBEDTLS_ECP_FIXED_POINT_OPTIM is not set -# CONFIG_MBEDTLS_POLY1305_C is not set -# CONFIG_MBEDTLS_CHACHA20_C is not set -# CONFIG_MBEDTLS_HKDF_C is not set -# CONFIG_MBEDTLS_THREADING_C is not set -CONFIG_MBEDTLS_ERROR_STRINGS=y -# CONFIG_MBEDTLS_ALLOW_WEAK_CERTIFICATE_VERIFICATION is not set -# end of mbedTLS - -# -# Newlib -# -CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF=y -# CONFIG_NEWLIB_STDOUT_LINE_ENDING_LF is not set -# CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR is not set -# CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF is not set -# CONFIG_NEWLIB_STDIN_LINE_ENDING_LF is not set -CONFIG_NEWLIB_STDIN_LINE_ENDING_CR=y -# CONFIG_NEWLIB_NANO_FORMAT is not set -CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC_HRT=y -# CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC is not set -# CONFIG_NEWLIB_TIME_SYSCALL_USE_HRT is not set -# CONFIG_NEWLIB_TIME_SYSCALL_USE_NONE is not set -# end of Newlib - -# -# NVS -# -# CONFIG_NVS_ASSERT_ERROR_CHECK is not set -# CONFIG_NVS_LEGACY_DUP_KEYS_COMPATIBILITY is not set -# end of NVS - -# -# PThreads -# -CONFIG_PTHREAD_TASK_PRIO_DEFAULT=5 -CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072 -CONFIG_PTHREAD_STACK_MIN=768 -CONFIG_PTHREAD_DEFAULT_CORE_NO_AFFINITY=y -# CONFIG_PTHREAD_DEFAULT_CORE_0 is not set -# CONFIG_PTHREAD_DEFAULT_CORE_1 is not set -CONFIG_PTHREAD_TASK_CORE_DEFAULT=-1 -CONFIG_PTHREAD_TASK_NAME_DEFAULT="pthread" -# end of PThreads - -# -# MMU Config -# -CONFIG_MMU_PAGE_SIZE_64KB=y -CONFIG_MMU_PAGE_MODE="64KB" -CONFIG_MMU_PAGE_SIZE=0x10000 -# end of MMU Config - -# -# Main Flash configuration -# - -# -# SPI Flash behavior when brownout -# -CONFIG_SPI_FLASH_BROWNOUT_RESET_XMC=y -CONFIG_SPI_FLASH_BROWNOUT_RESET=y -# end of SPI Flash behavior when brownout - -# -# Optional and Experimental Features (READ DOCS FIRST) -# - -# -# Features here require specific hardware (READ DOCS FIRST!) -# -CONFIG_SPI_FLASH_SUSPEND_TSUS_VAL_US=50 -# CONFIG_SPI_FLASH_FORCE_ENABLE_XMC_C_SUSPEND is not set -# CONFIG_SPI_FLASH_FORCE_ENABLE_C6_H2_SUSPEND is not set -# end of Optional and Experimental Features (READ DOCS FIRST) -# end of Main Flash configuration - -# -# SPI Flash driver -# -# CONFIG_SPI_FLASH_VERIFY_WRITE is not set -# CONFIG_SPI_FLASH_ENABLE_COUNTERS is not set -CONFIG_SPI_FLASH_ROM_DRIVER_PATCH=y -CONFIG_SPI_FLASH_DANGEROUS_WRITE_ABORTS=y -# CONFIG_SPI_FLASH_DANGEROUS_WRITE_FAILS is not set -# CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWED is not set -# CONFIG_SPI_FLASH_SHARE_SPI1_BUS is not set -# CONFIG_SPI_FLASH_BYPASS_BLOCK_ERASE is not set -CONFIG_SPI_FLASH_YIELD_DURING_ERASE=y -CONFIG_SPI_FLASH_ERASE_YIELD_DURATION_MS=20 -CONFIG_SPI_FLASH_ERASE_YIELD_TICKS=1 -CONFIG_SPI_FLASH_WRITE_CHUNK_SIZE=8192 -# CONFIG_SPI_FLASH_SIZE_OVERRIDE is not set -# CONFIG_SPI_FLASH_CHECK_ERASE_TIMEOUT_DISABLED is not set -# CONFIG_SPI_FLASH_OVERRIDE_CHIP_DRIVER_LIST is not set - -# -# Auto-detect flash chips -# -CONFIG_SPI_FLASH_VENDOR_XMC_SUPPORTED=y -CONFIG_SPI_FLASH_VENDOR_GD_SUPPORTED=y -CONFIG_SPI_FLASH_VENDOR_ISSI_SUPPORTED=y -CONFIG_SPI_FLASH_VENDOR_MXIC_SUPPORTED=y -CONFIG_SPI_FLASH_VENDOR_WINBOND_SUPPORTED=y -CONFIG_SPI_FLASH_SUPPORT_ISSI_CHIP=y -CONFIG_SPI_FLASH_SUPPORT_MXIC_CHIP=y -CONFIG_SPI_FLASH_SUPPORT_GD_CHIP=y -CONFIG_SPI_FLASH_SUPPORT_WINBOND_CHIP=y -# CONFIG_SPI_FLASH_SUPPORT_BOYA_CHIP is not set -# CONFIG_SPI_FLASH_SUPPORT_TH_CHIP is not set -# end of Auto-detect flash chips - -CONFIG_SPI_FLASH_ENABLE_ENCRYPTED_READ_WRITE=y -# end of SPI Flash driver - -# -# Unity unit testing library -# -CONFIG_UNITY_ENABLE_FLOAT=y -CONFIG_UNITY_ENABLE_DOUBLE=y -# CONFIG_UNITY_ENABLE_64BIT is not set -# CONFIG_UNITY_ENABLE_COLOR is not set -CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=y -# CONFIG_UNITY_ENABLE_FIXTURE is not set -# CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL is not set -# end of Unity unit testing library - -# -# Relay Channel Driver Configuration -# -CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 -CONFIG_RELAY_CHN_COUNT=1 -CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT=y -CONFIG_RELAY_CHN_ENABLE_TILTING=y -CONFIG_RELAY_CHN_ENABLE_NVS=y -# end of Relay Channel Driver Configuration - -# -# Relay Channel NVS Storage Configuration -# -CONFIG_RELAY_CHN_NVS_NAMESPACE="relay_chn" -CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION=y -CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME="app_data" -# end of Relay Channel NVS Storage Configuration - -# -# Relay Channel Run Limit Configuration -# -CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC=1 -CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC=600 -CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC=60 -# end of Relay Channel Run Limit Configuration -# end of Component config - -# CONFIG_IDF_EXPERIMENTAL_FEATURES is not set - -# Deprecated options for backward compatibility -# CONFIG_APP_BUILD_TYPE_ELF_RAM is not set -# CONFIG_NO_BLOBS is not set -# CONFIG_ESP32_NO_BLOBS is not set -# CONFIG_ESP32_COMPATIBLE_PRE_V2_1_BOOTLOADERS is not set -# CONFIG_ESP32_COMPATIBLE_PRE_V3_1_BOOTLOADERS is not set -# CONFIG_LOG_BOOTLOADER_LEVEL_NONE is not set -# CONFIG_LOG_BOOTLOADER_LEVEL_ERROR is not set -# CONFIG_LOG_BOOTLOADER_LEVEL_WARN is not set -CONFIG_LOG_BOOTLOADER_LEVEL_INFO=y -# CONFIG_LOG_BOOTLOADER_LEVEL_DEBUG is not set -# CONFIG_LOG_BOOTLOADER_LEVEL_VERBOSE is not set -CONFIG_LOG_BOOTLOADER_LEVEL=3 -# CONFIG_APP_ROLLBACK_ENABLE is not set -# CONFIG_FLASH_ENCRYPTION_ENABLED is not set -# CONFIG_FLASHMODE_QIO is not set -# CONFIG_FLASHMODE_QOUT is not set -CONFIG_FLASHMODE_DIO=y -# CONFIG_FLASHMODE_DOUT is not set -CONFIG_MONITOR_BAUD=115200 -CONFIG_OPTIMIZATION_LEVEL_DEBUG=y -CONFIG_COMPILER_OPTIMIZATION_LEVEL_DEBUG=y -CONFIG_COMPILER_OPTIMIZATION_DEFAULT=y -# CONFIG_OPTIMIZATION_LEVEL_RELEASE is not set -# CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE is not set -CONFIG_OPTIMIZATION_ASSERTIONS_ENABLED=y -# CONFIG_OPTIMIZATION_ASSERTIONS_SILENT is not set -# CONFIG_OPTIMIZATION_ASSERTIONS_DISABLED is not set -CONFIG_OPTIMIZATION_ASSERTION_LEVEL=2 -# CONFIG_CXX_EXCEPTIONS is not set -CONFIG_STACK_CHECK_NONE=y -# CONFIG_STACK_CHECK_NORM is not set -# CONFIG_STACK_CHECK_STRONG is not set -# CONFIG_STACK_CHECK_ALL is not set -# CONFIG_WARN_WRITE_STRINGS is not set -CONFIG_ADC2_DISABLE_DAC=y -# CONFIG_MCPWM_ISR_IN_IRAM is not set -# CONFIG_TWO_UNIVERSAL_MAC_ADDRESS is not set -CONFIG_FOUR_UNIVERSAL_MAC_ADDRESS=y -CONFIG_NUMBER_OF_UNIVERSAL_MAC_ADDRESS=4 -# CONFIG_ESP_SYSTEM_PD_FLASH is not set -CONFIG_ESP32_DEEP_SLEEP_WAKEUP_DELAY=2000 -CONFIG_ESP_SLEEP_DEEP_SLEEP_WAKEUP_DELAY=2000 -CONFIG_ESP32_RTC_CLK_SRC_INT_RC=y -CONFIG_ESP32_RTC_CLOCK_SOURCE_INTERNAL_RC=y -# CONFIG_ESP32_RTC_CLK_SRC_EXT_CRYS is not set -# CONFIG_ESP32_RTC_CLOCK_SOURCE_EXTERNAL_CRYSTAL is not set -# CONFIG_ESP32_RTC_CLK_SRC_EXT_OSC is not set -# CONFIG_ESP32_RTC_CLOCK_SOURCE_EXTERNAL_OSC is not set -# CONFIG_ESP32_RTC_CLK_SRC_INT_8MD256 is not set -# CONFIG_ESP32_RTC_CLOCK_SOURCE_INTERNAL_8MD256 is not set -CONFIG_ESP32_RTC_CLK_CAL_CYCLES=1024 -# CONFIG_ESP32_XTAL_FREQ_26 is not set -CONFIG_ESP32_XTAL_FREQ_40=y -# CONFIG_ESP32_XTAL_FREQ_AUTO is not set -CONFIG_ESP32_XTAL_FREQ=40 -# CONFIG_ESP32_DEFAULT_CPU_FREQ_80 is not set -CONFIG_ESP32_DEFAULT_CPU_FREQ_160=y -# CONFIG_ESP32_DEFAULT_CPU_FREQ_240 is not set -CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ=160 -CONFIG_TRACEMEM_RESERVE_DRAM=0x0 -# CONFIG_ESP32_PANIC_PRINT_HALT is not set -CONFIG_ESP32_PANIC_PRINT_REBOOT=y -# CONFIG_ESP32_PANIC_SILENT_REBOOT is not set -CONFIG_SYSTEM_EVENT_QUEUE_SIZE=32 -CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=2304 -CONFIG_MAIN_TASK_STACK_SIZE=3584 -CONFIG_CONSOLE_UART_DEFAULT=y -# CONFIG_CONSOLE_UART_CUSTOM is not set -# CONFIG_CONSOLE_UART_NONE is not set -# CONFIG_ESP_CONSOLE_UART_NONE is not set -CONFIG_CONSOLE_UART=y -CONFIG_CONSOLE_UART_NUM=0 -CONFIG_CONSOLE_UART_BAUDRATE=115200 -CONFIG_INT_WDT=y -CONFIG_INT_WDT_TIMEOUT_MS=300 -CONFIG_INT_WDT_CHECK_CPU1=y -# CONFIG_TASK_WDT is not set -# CONFIG_ESP_TASK_WDT is not set -# CONFIG_ESP32_DEBUG_STUBS_ENABLE is not set -CONFIG_ESP32_DEBUG_OCDAWARE=y -CONFIG_BROWNOUT_DET=y -CONFIG_ESP32_BROWNOUT_DET=y -CONFIG_BROWNOUT_DET_LVL_SEL_0=y -CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_0=y -# CONFIG_BROWNOUT_DET_LVL_SEL_1 is not set -# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_1 is not set -# CONFIG_BROWNOUT_DET_LVL_SEL_2 is not set -# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_2 is not set -# CONFIG_BROWNOUT_DET_LVL_SEL_3 is not set -# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_3 is not set -# CONFIG_BROWNOUT_DET_LVL_SEL_4 is not set -# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_4 is not set -# CONFIG_BROWNOUT_DET_LVL_SEL_5 is not set -# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_5 is not set -# CONFIG_BROWNOUT_DET_LVL_SEL_6 is not set -# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_6 is not set -# CONFIG_BROWNOUT_DET_LVL_SEL_7 is not set -# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_7 is not set -CONFIG_BROWNOUT_DET_LVL=0 -CONFIG_ESP32_BROWNOUT_DET_LVL=0 -# CONFIG_DISABLE_BASIC_ROM_CONSOLE is not set -CONFIG_IPC_TASK_STACK_SIZE=1024 -CONFIG_TIMER_TASK_STACK_SIZE=3584 -CONFIG_TIMER_TASK_PRIORITY=1 -CONFIG_TIMER_TASK_STACK_DEPTH=2048 -CONFIG_TIMER_QUEUE_LENGTH=10 -# CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK is not set -# CONFIG_HAL_ASSERTION_SILIENT is not set -CONFIG_ESP32_TIME_SYSCALL_USE_RTC_HRT=y -CONFIG_ESP32_TIME_SYSCALL_USE_RTC_FRC1=y -# CONFIG_ESP32_TIME_SYSCALL_USE_RTC is not set -# CONFIG_ESP32_TIME_SYSCALL_USE_HRT is not set -# CONFIG_ESP32_TIME_SYSCALL_USE_FRC1 is not set -# CONFIG_ESP32_TIME_SYSCALL_USE_NONE is not set -CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT=5 -CONFIG_ESP32_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072 -CONFIG_ESP32_PTHREAD_STACK_MIN=768 -CONFIG_ESP32_DEFAULT_PTHREAD_CORE_NO_AFFINITY=y -# CONFIG_ESP32_DEFAULT_PTHREAD_CORE_0 is not set -# CONFIG_ESP32_DEFAULT_PTHREAD_CORE_1 is not set -CONFIG_ESP32_PTHREAD_TASK_CORE_DEFAULT=-1 -CONFIG_ESP32_PTHREAD_TASK_NAME_DEFAULT="pthread" -CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ABORTS=y -# CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_FAILS is not set -# CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ALLOWED is not set -# End of deprecated options From ad377ebfc8ff7f96ee004fb226d0814ed52c0915 Mon Sep 17 00:00:00 2001 From: ismail Date: Thu, 4 Sep 2025 15:46:57 +0300 Subject: [PATCH 48/69] Fix the operation of unregistering listener - Add check for empty listener list in find_listener_entry function. - Reset notification queue when all listeners are removed. - Change queue operation to send remove_listener message to the front of the queue. Refs #1085, #1096 and fixes #1102 --- src/relay_chn_notify.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/relay_chn_notify.c b/src/relay_chn_notify.c index 0051d2a..47e3b55 100644 --- a/src/relay_chn_notify.c +++ b/src/relay_chn_notify.c @@ -118,6 +118,11 @@ void relay_chn_notify_deinit(void) */ static relay_chn_listener_entry_t* find_listener_entry(relay_chn_state_listener_t listener) { + if (listLIST_IS_EMPTY(&listeners)) { + ESP_LOGD(TAG, "No listeners registered"); + return NULL; + } + // Iterate through the linked list of listeners for (ListItem_t *pxListItem = listGET_HEAD_ENTRY(&listeners); pxListItem != listGET_END_MARKER(&listeners); @@ -164,6 +169,11 @@ static void do_remove_listener(relay_chn_state_listener_t listener) } else { ESP_LOGD(TAG, "Listener %p not found for unregistration.", listener); } + + if (listLIST_IS_EMPTY(&listeners)) { + // Flush all pending notifications in the queue + xQueueReset(notify_msg_queue); + } } esp_err_t relay_chn_notify_add_listener(relay_chn_state_listener_t listener) @@ -191,7 +201,7 @@ void relay_chn_notify_remove_listener(relay_chn_state_listener_t listener) } relay_chn_notify_msg_t msg = { .cmd = RELAY_CHN_NOTIFY_CMD_REMOVE_LISTENER, .payload.listener = listener }; - if (xQueueSend(notify_msg_queue, &msg, 0) != pdTRUE) { + if (xQueueSendToFront(notify_msg_queue, &msg, 0) != pdTRUE) { ESP_LOGW(TAG, "Notify queue is full, failed to queue remove_listener"); } } From 7bafc4845f83a290152ab1081cc1b27524eb0fc7 Mon Sep 17 00:00:00 2001 From: ismail Date: Thu, 4 Sep 2025 16:16:10 +0300 Subject: [PATCH 49/69] Add default tilt sensitivity get function Added `relay_chn_tilt_get_default_sensitivity` function and test cases. Refs #1085 and fixes #1101 --- include/relay_chn.h | 12 ++++++++++++ src/relay_chn_tilt.c | 5 +++++ test_apps/main/test_relay_chn_tilt_multi.c | 11 +++++++++++ test_apps/main/test_relay_chn_tilt_single.c | 11 +++++++++++ 4 files changed, 39 insertions(+) diff --git a/include/relay_chn.h b/include/relay_chn.h index 424add7..43e15f3 100644 --- a/include/relay_chn.h +++ b/include/relay_chn.h @@ -63,6 +63,18 @@ esp_err_t relay_chn_register_listener(relay_chn_state_listener_t listener); */ void relay_chn_unregister_listener(relay_chn_state_listener_t listener); +#if CONFIG_RELAY_CHN_ENABLE_TILTING +/** + * @brief Get the default tilting sensitivity for the relay channel. + * + * This function retrieves the default sensitivity for the relay channel's automatic + * tilting mechanism. + * + * @return Sensitivity value in percentage: 0 - 100%. + */ +uint8_t relay_chn_tilt_get_default_sensitivity(void); +#endif + #if CONFIG_RELAY_CHN_COUNT > 1 /** * @brief Get the state of the specified relay channel. diff --git a/src/relay_chn_tilt.c b/src/relay_chn_tilt.c index 6a2da91..ac3d1ff 100644 --- a/src/relay_chn_tilt.c +++ b/src/relay_chn_tilt.c @@ -199,6 +199,11 @@ static void relay_chn_tilt_issue_auto(relay_chn_tilt_ctl_t *tilt_ctl) } } +uint8_t relay_chn_tilt_get_default_sensitivity() +{ + return RELAY_CHN_TILT_DEFAULT_SENSITIVITY; +} + #if CONFIG_RELAY_CHN_COUNT > 1 static void relay_chn_tilt_issue_cmd_on_all_channels(relay_chn_tilt_cmd_t cmd) diff --git a/test_apps/main/test_relay_chn_tilt_multi.c b/test_apps/main/test_relay_chn_tilt_multi.c index 87b8d79..b5d9913 100644 --- a/test_apps/main/test_relay_chn_tilt_multi.c +++ b/test_apps/main/test_relay_chn_tilt_multi.c @@ -322,6 +322,17 @@ TEST_CASE("relay_chn_tilt_set_sensitivity and get", "[relay_chn][tilt][sensitivi TEST_ASSERT_EQUAL_UINT8_ARRAY(expect, vals, CONFIG_RELAY_CHN_COUNT); } +TEST_CASE("relay_chn_tilt_get_default_sensitivity returns correct value", "[relay_chn][tilt][sensitivity]") +{ + // The default sensitivity is calculated from default timing values. + // Default run time: 15ms, Min run time: 50ms, Max run time: 10ms. + // Formula: ( (DEFAULT_RUN - MIN_RUN) * 100 ) / (MAX_RUN - MIN_RUN) + // ( (15 - 50) * 100 ) / (10 - 50) = (-35 * 100) / -40 = -3500 / -40 = 87.5 + // As integer arithmetic, this is 87. + uint8_t expected_sensitivity = 87; + TEST_ASSERT_EQUAL_UINT8(expected_sensitivity, relay_chn_tilt_get_default_sensitivity()); +} + // Test sensitivity upper boundary for all set functions TEST_CASE("relay_chn_tilt_set_sensitivity functions handle upper boundary", "[relay_chn][tilt][sensitivity]") { diff --git a/test_apps/main/test_relay_chn_tilt_single.c b/test_apps/main/test_relay_chn_tilt_single.c index a2d6deb..185e535 100644 --- a/test_apps/main/test_relay_chn_tilt_single.c +++ b/test_apps/main/test_relay_chn_tilt_single.c @@ -205,6 +205,17 @@ TEST_CASE("relay_chn_tilt_set_sensitivity and get", "[relay_chn][tilt][sensitivi TEST_ASSERT_EQUAL_UINT8(42, relay_chn_tilt_get_sensitivity()); } +TEST_CASE("relay_chn_tilt_get_default_sensitivity returns correct value", "[relay_chn][tilt][sensitivity]") +{ + // The default sensitivity is calculated from default timing values. + // Default run time: 15ms, Min run time: 50ms, Max run time: 10ms. + // Formula: ( (DEFAULT_RUN - MIN_RUN) * 100 ) / (MAX_RUN - MIN_RUN) + // ( (15 - 50) * 100 ) / (10 - 50) = (-35 * 100) / -40 = -3500 / -40 = 87.5 + // As integer arithmetic, this is 87. + uint8_t expected_sensitivity = 87; + TEST_ASSERT_EQUAL_UINT8(expected_sensitivity, relay_chn_tilt_get_default_sensitivity()); +} + // Test sensitivity upper boundary TEST_CASE("relay_chn_tilt_set_sensitivity handles upper boundary", "[relay_chn][tilt][sensitivity]") { From 7244b57061427c972c1099c73bb0b52f35f04cb8 Mon Sep 17 00:00:00 2001 From: ismail Date: Thu, 4 Sep 2025 16:23:13 +0300 Subject: [PATCH 50/69] Add SPDX license headers to multiple source files Refs #1085 and closes #1099 --- test_apps/main/test_app_main.c | 5 +++++ test_apps/main/test_common.c | 6 ++++++ test_apps/main/test_common.h | 6 ++++++ test_apps/main/test_relay_chn_core_multi.c | 6 ++++++ test_apps/main/test_relay_chn_core_single.c | 6 ++++++ test_apps/main/test_relay_chn_notify_common.c | 6 ++++++ test_apps/main/test_relay_chn_notify_common.h | 6 ++++++ test_apps/main/test_relay_chn_notify_multi.c | 6 ++++++ test_apps/main/test_relay_chn_notify_single.c | 6 ++++++ test_apps/main/test_relay_chn_tilt_multi.c | 6 ++++++ test_apps/main/test_relay_chn_tilt_single.c | 6 ++++++ 11 files changed, 65 insertions(+) diff --git a/test_apps/main/test_app_main.c b/test_apps/main/test_app_main.c index 1f8d4f9..b733c76 100644 --- a/test_apps/main/test_app_main.c +++ b/test_apps/main/test_app_main.c @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2025 Kozmotronik Tech + * + * SPDX-License-Identifier: MIT + */ #include #include "esp_log.h" diff --git a/test_apps/main/test_common.c b/test_apps/main/test_common.c index 87d1811..b15b6c9 100644 --- a/test_apps/main/test_common.c +++ b/test_apps/main/test_common.c @@ -1,3 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 Kozmotronik Tech + * + * SPDX-License-Identifier: MIT + */ + #include "test_common.h" #include "relay_chn_ctl.h" // For resetting the channels #include "relay_chn_tilt.h" // For resetting tilt count diff --git a/test_apps/main/test_common.h b/test_apps/main/test_common.h index b2c1c55..0930f17 100644 --- a/test_apps/main/test_common.h +++ b/test_apps/main/test_common.h @@ -1,3 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 Kozmotronik Tech + * + * SPDX-License-Identifier: MIT + */ + #pragma once #include // For memset diff --git a/test_apps/main/test_relay_chn_core_multi.c b/test_apps/main/test_relay_chn_core_multi.c index 080bd5f..d8287f3 100644 --- a/test_apps/main/test_relay_chn_core_multi.c +++ b/test_apps/main/test_relay_chn_core_multi.c @@ -1,3 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 Kozmotronik Tech + * + * SPDX-License-Identifier: MIT + */ + #include "test_common.h" relay_chn_state_t states[CONFIG_RELAY_CHN_COUNT], expect_states[CONFIG_RELAY_CHN_COUNT]; diff --git a/test_apps/main/test_relay_chn_core_single.c b/test_apps/main/test_relay_chn_core_single.c index 171f98a..c7d4aeb 100644 --- a/test_apps/main/test_relay_chn_core_single.c +++ b/test_apps/main/test_relay_chn_core_single.c @@ -1,3 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 Kozmotronik Tech + * + * SPDX-License-Identifier: MIT + */ + #include "test_common.h" diff --git a/test_apps/main/test_relay_chn_notify_common.c b/test_apps/main/test_relay_chn_notify_common.c index f033d16..33314e3 100644 --- a/test_apps/main/test_relay_chn_notify_common.c +++ b/test_apps/main/test_relay_chn_notify_common.c @@ -1,3 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 Kozmotronik Tech + * + * SPDX-License-Identifier: MIT + */ + #include "test_relay_chn_notify_common.h" listener_callback_info_t listener1_info; diff --git a/test_apps/main/test_relay_chn_notify_common.h b/test_apps/main/test_relay_chn_notify_common.h index 436f4af..cd45f36 100644 --- a/test_apps/main/test_relay_chn_notify_common.h +++ b/test_apps/main/test_relay_chn_notify_common.h @@ -1,3 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 Kozmotronik Tech + * + * SPDX-License-Identifier: MIT + */ + #pragma once #include "test_common.h" diff --git a/test_apps/main/test_relay_chn_notify_multi.c b/test_apps/main/test_relay_chn_notify_multi.c index 35844bc..bef58b0 100644 --- a/test_apps/main/test_relay_chn_notify_multi.c +++ b/test_apps/main/test_relay_chn_notify_multi.c @@ -1,3 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 Kozmotronik Tech + * + * SPDX-License-Identifier: MIT + */ + #include "test_relay_chn_notify_common.h" // This is a private header, but we need it for direct notification calls and queue length. diff --git a/test_apps/main/test_relay_chn_notify_single.c b/test_apps/main/test_relay_chn_notify_single.c index 66e2a92..8d29704 100644 --- a/test_apps/main/test_relay_chn_notify_single.c +++ b/test_apps/main/test_relay_chn_notify_single.c @@ -1,3 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 Kozmotronik Tech + * + * SPDX-License-Identifier: MIT + */ + #include "test_relay_chn_notify_common.h" // This is a private header, but we need it for direct notification calls and queue length. diff --git a/test_apps/main/test_relay_chn_tilt_multi.c b/test_apps/main/test_relay_chn_tilt_multi.c index b5d9913..07e102e 100644 --- a/test_apps/main/test_relay_chn_tilt_multi.c +++ b/test_apps/main/test_relay_chn_tilt_multi.c @@ -1,3 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 Kozmotronik Tech + * + * SPDX-License-Identifier: MIT + */ + #include "test_common.h" diff --git a/test_apps/main/test_relay_chn_tilt_single.c b/test_apps/main/test_relay_chn_tilt_single.c index 185e535..7137822 100644 --- a/test_apps/main/test_relay_chn_tilt_single.c +++ b/test_apps/main/test_relay_chn_tilt_single.c @@ -1,3 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2025 Kozmotronik Tech + * + * SPDX-License-Identifier: MIT + */ + #include "test_common.h" From bf5e3a44267cb95e0c3324e864e53966fbc58d9f Mon Sep 17 00:00:00 2001 From: ismail Date: Thu, 4 Sep 2025 16:59:00 +0300 Subject: [PATCH 51/69] General code and comment cleanup --- Kconfig | 10 ++--- README.md | 12 +++--- include/relay_chn.h | 32 +++++++-------- include/relay_chn_adapter.h | 14 +++---- private_include/relay_chn_core.h | 30 +++++++------- private_include/relay_chn_ctl.h | 12 +++--- private_include/relay_chn_notify.h | 4 +- private_include/relay_chn_nvs.h | 14 +++---- private_include/relay_chn_output.h | 12 +++--- private_include/relay_chn_run_info.h | 4 +- private_include/relay_chn_tilt.h | 6 +-- src/relay_chn_core.c | 36 ++++++++--------- src/relay_chn_ctl_multi.c | 2 +- src/relay_chn_ctl_single.c | 6 +-- src/relay_chn_notify.c | 2 +- src/relay_chn_nvs.c | 6 +-- src/relay_chn_output.c | 12 +++--- src/relay_chn_tilt.c | 42 ++++++++++---------- test_apps/main/test_app_main.c | 8 ++-- test_apps/main/test_relay_chn_core_multi.c | 42 ++++++++++---------- test_apps/main/test_relay_chn_core_single.c | 40 +++++++++---------- test_apps/main/test_relay_chn_nvs_multi.c | 8 ++-- test_apps/main/test_relay_chn_nvs_single.c | 10 ++--- test_apps/main/test_relay_chn_tilt_multi.c | 43 ++++++++++----------- test_apps/main/test_relay_chn_tilt_single.c | 24 ++++++------ 25 files changed, 213 insertions(+), 218 deletions(-) diff --git a/Kconfig b/Kconfig index 2d743d9..a40f3b4 100644 --- a/Kconfig +++ b/Kconfig @@ -9,14 +9,14 @@ menu "Relay Channel Driver Configuration" starting the output. This is useful for the motors or some other mechanical actuators to allow them to stop and settle before changing the direction. - + config RELAY_CHN_COUNT int "Number of relay channels" range 1 8 default 1 help Number of relay channels between 1 and 8. - + config RELAY_CHN_ENABLE_RUN_LIMIT bool "Enable run limit for channels" default n @@ -32,7 +32,7 @@ menu "Relay Channel Driver Configuration" at a time. Tilting is specifically designed for controlling some types of curtains that need to be adjusted to let enter specific amount of day light. - + config RELAY_CHN_ENABLE_NVS bool "Enable persistent NVS storage for relay channel" default n @@ -58,7 +58,7 @@ menu "Relay Channel NVS Storage Configuration" If enabled, a custom NVS partition will be used for storing relay channel configuration. If disabled, the default NVS partition will be used. - + config RELAY_CHN_NVS_CUSTOM_PARTITION_NAME string "Custom NVS partition name" depends on RELAY_CHN_NVS_CUSTOM_PARTITION @@ -92,5 +92,5 @@ menu "Relay Channel Run Limit Configuration" default 60 help Default run limit in seconds for channels. - + endmenu \ No newline at end of file diff --git a/README.md b/README.md index 87ad40c..3b40d17 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,8 @@ Each relay channel consists of 2 output relays controlled by 2 GPIO pins. The co The run limit feature provides an additional layer of protection by automatically stopping channels after a configurable time period. This is particularly useful for motor-driven applications where continuous operation beyond a certain duration could cause damage or safety issues. Each channel can have its own run limit setting, and when enabled, the component will automatically stop the channel once it has been running for the specified duration. -It also provides an optional tilting interface per channel base. Tilting makes a channel move with a specific pattern moving with small steps at a time. Tilting is specifically designed for controlling some types of curtains that need to be adjusted to let enter specific amount of day light. -Since it operates on relays, the switching frequency is limited to 10Hz which complies with the most of the general purpose relays' requirements. The minimum frequency is 2Hz and the duty cycle is about 10% in all ranges. +It also provides an optional tilting interface per channel base. Tilting makes a channel move with a specific pattern moving with small steps at a time. Tilting is specifically designed for controlling some types of curtains that need to be adjusted to let enter specific amount of day light. +Since it operates on relays, the switching frequency is limited to 10Hz which complies with the most of the general purpose relays' requirements. The minimum frequency is 2Hz and the duty cycle is about 10% in all ranges. Another optional feature is NVS storage, which saves the configuration permanently across reboots of the device. These configurations are: @@ -31,7 +31,7 @@ Another optional feature is NVS storage, which saves the configuration permanent - Tilt sensitivity - Last tilt position -### NVS Operation Details +### NVS Operation Details When NVS storage is enabled (CONFIG_RELAY_CHN_ENABLE_NVS=y), the component creates a dedicated background task to manage all NVS write operations. This design has important implications for how you use the NVS-related functions: - **Asynchronous Writes:** All `set` operations (e.g., `relay_chn_flip_direction()`, `relay_chn_set_run_limit()`) are asynchronous. They add the write request to a queue and return immediately, preventing the calling task from being blocked. - **Synchronous Reads:** All get operations (e.g., `relay_chn_get_direction()`) are synchronous. They read the value directly from the NVS storage and will block the calling task until the read is complete. @@ -145,7 +145,7 @@ Depending on the mode, the component will be built selectively, so the signature ```c relay_chn_run_forward(); // No channel ID parameter for single channel mode // or -relay_chn_run_forward(2); // Channel ID parameters will be needed in multi channel mode +relay_chn_run_forward(2); // Channel ID parameters will be needed in multi channel mode ``` See the examples for further reference @@ -197,9 +197,9 @@ relay_chn_run_reverse(1); // Run all channels reverse relay_chn_run_reverse_all(); -// Stop channel #1 +// Stop channel #1 relay_chn_stop(1); -// Stop all channels +// Stop all channels relay_chn_stop_all(); // Flip direction of channel #0 diff --git a/include/relay_chn.h b/include/relay_chn.h index 43e15f3..1ed03a1 100644 --- a/include/relay_chn.h +++ b/include/relay_chn.h @@ -6,7 +6,7 @@ * One relay channel consists of 2 output relays, hence 2 GPIO pins are required for each relay channel. * This module provides an API to control the relay channels, specifically to drive bipolar motors. * It also provides APIs to control the direction of the relay channel, bipolar motors in mind. - * To prevent mechanical strain on the motor, the component automatically manages direction changes + * To prevent mechanical strain on the motor, the component automatically manages direction changes * with a configurable inertia delay, protecting it from abrupt reversals. * The STOP command overrides any other command and clears the pending command if any. */ @@ -45,7 +45,7 @@ void relay_chn_destroy(void); /** * @brief Register a channel state change listener. - * + * * @param listener A function that implements relay_chn_state_listener_t interface. * * @return @@ -58,7 +58,7 @@ esp_err_t relay_chn_register_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. */ void relay_chn_unregister_listener(relay_chn_state_listener_t listener); @@ -151,8 +151,8 @@ void relay_chn_ctl_run_reverse_all(void); /** * @brief Stops the relay channel specified by the channel ID. * - * This function stops the operation of the relay channel identified by the - * provided channel ID. It is typically used to turn off or disable the relay + * This function stops the operation of the relay channel identified by the + * provided channel ID. It is typically used to turn off or disable the relay * channel. * * @param chn_id The ID of the relay channel to stop. @@ -162,7 +162,7 @@ void relay_chn_stop(uint8_t chn_id); /** * @brief Commands all configured relay channels to stop. * - * This function iterates through all configured relay channels and issues a command + * This function iterates through all configured relay channels and issues a command * to each to stop any ongoing movement. */ void relay_chn_ctl_stop_all(void); @@ -181,7 +181,7 @@ void relay_chn_flip_direction(uint8_t chn_id); /** * @brief Flips the logical direction of all configured relay channels. * - * This function iterates through all configured relay channels and swaps the + * This function iterates through all configured relay channels and swaps the * physical GPIO pins assigned to the forward and reverse directions for each. */ void relay_chn_ctl_flip_direction_all(void); @@ -215,7 +215,7 @@ esp_err_t relay_chn_ctl_get_direction_all(relay_chn_direction_t *directions); * @brief Get the run limit for the specified channel * * @param chn_id The ID of the relay channel to query. - * + * * @return The run limit value for the relevant channel if the channel ID is valid. * 0 if the channel ID is invalid. */ @@ -235,8 +235,8 @@ esp_err_t relay_chn_ctl_get_run_limit_all(uint16_t *limits_sec); /** * @brief Set the run limit for the specified channel - * - * Sets the time limit in seconds for the specified channel. It will not proceed + * + * Sets the time limit in seconds for the specified channel. It will not proceed * if the channel ID is invalid. * If the limit_sec value is lesser than the CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC, * the value will be set to CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC. @@ -358,8 +358,8 @@ void relay_chn_tilt_set_sensitivity(uint8_t chn_id, uint8_t sensitivity); * The new sensitivities are persisted in NVS if enabled. * * @param sensitivities Pointer to an array containing the desired tilt sensitivities. - * - * @return + * + * @return * - ESP_OK: Success * - ESP_ERR_INVALID_ARG: When sensitivities parameter is NULL */ @@ -459,7 +459,7 @@ void relay_chn_flip_direction(void); * @brief Get the direction of the relay channel. * * This function retrieves the direction configuration of a relay channel. - * + * * @return The direction of the relay channel as a value of type * relay_chn_direction_t. */ @@ -468,15 +468,15 @@ relay_chn_direction_t relay_chn_get_direction(void); #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT /** * @brief Get the run limit for the channel - * + * * @return The run limit value for the channel. */ uint16_t relay_chn_get_run_limit(void); /** * @brief Set the run limit for the channel - * - * Sets the time limit in seconds for the channel. It will not proceed + * + * Sets the time limit in seconds for the channel. It will not proceed * if the channel ID is invalid. * If the limit_sec value is lesser than the CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC, * the value will be set to CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC. diff --git a/include/relay_chn_adapter.h b/include/relay_chn_adapter.h index a6d27c2..a174780 100644 --- a/include/relay_chn_adapter.h +++ b/include/relay_chn_adapter.h @@ -2,7 +2,7 @@ * SPDX-FileCopyrightText: 2025 Kozmotronik Tech * * SPDX-License-Identifier: MIT - * + * * An adapter header to expose the appropriate API functions to the public API * depending on the CONFIG_RELAY_CHN_COUNT value which determines single or multi mode. */ @@ -15,7 +15,7 @@ extern "C" { /** * @brief Register a channel state change listener. - * + * * @param listener A function that implements relay_chn_state_listener_t interface. * * @return @@ -28,7 +28,7 @@ extern esp_err_t relay_chn_notify_add_listener(relay_chn_state_listener_t listen /** * @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); @@ -60,7 +60,7 @@ extern relay_chn_state_t relay_chn_ctl_get_state(uint8_t chn_id); * to hold `CONFIG_RELAY_CHN_COUNT` elements. * * @param states Pointer to an array where the states will be stored. - * + * * @return ESP_OK on success, ESP_ERR_INVALID_ARG if `states` is NULL. */ extern esp_err_t relay_chn_ctl_get_state_all(relay_chn_state_t *states); @@ -127,7 +127,7 @@ extern void relay_chn_ctl_flip_direction(uint8_t chn_id); /** * @brief Flips the logical direction of all configured relay channels. * - * This function iterates through all configured relay channels and swaps the + * This function iterates through all configured relay channels and swaps the * physical GPIO pins assigned to the forward and reverse directions for each. */ extern void relay_chn_ctl_flip_direction_all(void); @@ -222,7 +222,7 @@ static inline esp_err_t relay_chn_get_direction_all(relay_chn_direction_t *direc * @brief Get the run limit for the specified channel * * @param chn_id The ID of the relay channel to query. - * + * * @return The run limit value for the relevant channel if the channel ID is valid. * 0 if the channel ID is invalid. */ @@ -380,7 +380,7 @@ static inline relay_chn_direction_t relay_chn_get_direction(void) #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT /** * @brief Get the run limit for the channel - * + * * @return The run limit value for the channel. */ extern uint16_t relay_chn_ctl_get_run_limit(void); diff --git a/private_include/relay_chn_core.h b/private_include/relay_chn_core.h index c98bb33..1649099 100644 --- a/private_include/relay_chn_core.h +++ b/private_include/relay_chn_core.h @@ -18,10 +18,10 @@ extern "C" { /** * @brief Initializes the relay channel timer. - * + * * This function creates a timer for the relay channel to handle direction change inertia. * Required by *_ctl_* module. - * + * * @param chn_ctl Pointer to the relay channel control structure. * @return esp_err_t ESP_OK on success, or an error code on failure. */ @@ -30,12 +30,12 @@ esp_err_t relay_chn_init_timer(relay_chn_ctl_t *chn_ctl); #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT /** * @brief Initializes the relay channel run limit timer. - * + * * This function creates a timer for the relay channel to handle run time limit. * Required by *_ctl_* module. - * + * * @param chn_ctl Pointer to the relay channel control structure. - * + * * @return esp_err_t ESP_OK on success, or an error code on failure. */ esp_err_t relay_chn_init_run_limit_timer(relay_chn_ctl_t *chn_ctl); @@ -43,10 +43,10 @@ esp_err_t relay_chn_init_run_limit_timer(relay_chn_ctl_t *chn_ctl); /** * @brief Issues a command to the relay channel. - * + * * Evaluates the current state of the relay channel and issues the command accordingly. * Required by *_core, *_ctl_* and *_tilt modules. - * + * * @param chn_ctl Pointer to the relay channel control structure. * @param cmd The command to issue. */ @@ -54,7 +54,7 @@ void relay_chn_issue_cmd(relay_chn_ctl_t* chn_ctl, relay_chn_cmd_t cmd); /** * @brief Dispatches a relay channel command. - * + * * @param chn_ctl Pointer to the relay channel control structure. * @param cmd The command to dispatch. */ @@ -62,7 +62,7 @@ void relay_chn_dispatch_cmd(relay_chn_ctl_t *chn_ctl, relay_chn_cmd_t cmd); /** * @brief Returns the string representation of a relay channel command. - * + * * @param cmd The relay channel command. * @return char* The string representation of the command. */ @@ -70,11 +70,11 @@ char *relay_chn_cmd_str(relay_chn_cmd_t cmd); /** * @brief Starts the ESP timer once with the specified time in milliseconds. - * + * * Starts the ESP timer to run once after the specified time. * If the timer is already running, it stops it first and then starts it again. * Required by *_ctl_* and *_tilt modules. - * + * * @param esp_timer The ESP timer handle. * @param time_ms The time in milliseconds to wait before the timer expires. * @return esp_err_t ESP_OK on success, or an error code on failure. @@ -83,11 +83,11 @@ esp_err_t relay_chn_start_esp_timer_once(esp_timer_handle_t esp_timer, uint32_t /** * @brief Updates the state of the relay channel and notifies listeners. - * + * * This function updates the state of the relay channel and notifies all registered listeners * about the state change. * Required by *_ctl_* and *_tilt modules. - * + * * @param chn_ctl Pointer to the relay channel control structure. * @param new_state The new state to set for the relay channel. */ @@ -95,7 +95,7 @@ void relay_chn_update_state(relay_chn_ctl_t *chn_ctl, relay_chn_state_t new_stat /** * @brief Return the text presentation of an state. - * + * * @param state A state with type of relay_chn_state_t. * @return char* The text presentation of the state. "UNKNOWN" if the state is not known. */ @@ -104,7 +104,7 @@ char *relay_chn_state_str(relay_chn_state_t state); #if CONFIG_RELAY_CHN_COUNT > 1 /** * @brief Check if the provided channel ID is valid. - * + * * @param chn_id Channel ID to check. * @return true Channel ID is valid. * @return false Channel ID is invalid. diff --git a/private_include/relay_chn_ctl.h b/private_include/relay_chn_ctl.h index ba9b98c..ef5f1e7 100644 --- a/private_include/relay_chn_ctl.h +++ b/private_include/relay_chn_ctl.h @@ -16,17 +16,17 @@ extern "C" { /** * @brief Initialize the relay channel control. - * + * * @param output Pointer to the output object(s). * @param run_info Pointer to the runtime information object(s). - * + * * @return esp_err_t Returns ESP_OK on success, or an error code on failure. */ esp_err_t relay_chn_ctl_init(relay_chn_output_t *output, relay_chn_run_info_t *run_info); /** * @brief Deinitialize the relay channel control. - * + * * This function cleans up resources used by the relay channel control. */ void relay_chn_ctl_deinit(void); @@ -34,16 +34,16 @@ void relay_chn_ctl_deinit(void); #if CONFIG_RELAY_CHN_COUNT > 1 /** * @brief Get the control structure for a specific relay channel. - * + * * @param chn_id The ID of the relay channel to retrieve. - * + * * @return relay_chn_ctl_t* Pointer to the control structure for the specified channel, or NULL if not found. */ relay_chn_ctl_t *relay_chn_ctl_get(uint8_t chn_id); /** * @brief Get the control structures for all relay channels. - * + * * @return relay_chn_ctl_t* Pointer to the array of control structures for all channels. */ relay_chn_ctl_t *relay_chn_ctl_get_all(void); diff --git a/private_include/relay_chn_notify.h b/private_include/relay_chn_notify.h index d862acd..db561e5 100644 --- a/private_include/relay_chn_notify.h +++ b/private_include/relay_chn_notify.h @@ -16,7 +16,7 @@ extern "C" { /** * @brief Init the notify module. - * + * * @return * - ESP_OK: Success * - ESP_ERR_NO_MEM: Not enough memory to create notify queue @@ -25,7 +25,7 @@ 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); diff --git a/private_include/relay_chn_nvs.h b/private_include/relay_chn_nvs.h index 1f5bed3..705aee4 100644 --- a/private_include/relay_chn_nvs.h +++ b/private_include/relay_chn_nvs.h @@ -18,9 +18,9 @@ extern "C" { /** * @brief Initialize NVS storage for relay channels. - * - * @attention Before calling this function, make sure the NVS flash is initialised - * using either the nvs_flash_init() function for the default NVS partition or the + * + * @attention Before calling this function, make sure the NVS flash is initialised + * using either the nvs_flash_init() function for the default NVS partition or the * nvs_flash_init_partition() function for a custom partition. * * @return ESP_OK on success, error code otherwise. @@ -32,7 +32,7 @@ esp_err_t relay_chn_nvs_init(void); * * @param[in] ch Channel number. * @param[in] direction Direction to store. - * + * * @note This operation is asynchronous. The value is queued to be written * by a background task. A subsequent `get` call may not immediately * reflect the new value. @@ -56,7 +56,7 @@ esp_err_t relay_chn_nvs_get_direction(uint8_t ch, relay_chn_direction_t *directi * * @param[in] ch Channel number. * @param[in] limit_sec Run limit value to store. - * + * * @note This operation is asynchronous. The value is queued to be written * by a background task. A subsequent `get` call may not immediately * reflect the new value. @@ -81,7 +81,7 @@ esp_err_t relay_chn_nvs_get_run_limit(uint8_t ch, uint16_t *limit_sec, uint16_t * * @param[in] ch Channel number. * @param[in] sensitivity Sensitivity value to store. - * + * * @note This operation is asynchronous. The value is queued to be written * by a background task. A subsequent `get` call may not immediately * reflect the new value. @@ -104,7 +104,7 @@ esp_err_t relay_chn_nvs_get_tilt_sensitivity(uint8_t ch, uint8_t *sensitivity, u * * @param[in] ch Channel number. * @param[in] tilt_count Tilt count value. - * + * * @note This operation is asynchronous. The value is queued to be written * by a background task. A subsequent `get` call may not immediately * reflect the new value. diff --git a/private_include/relay_chn_output.h b/private_include/relay_chn_output.h index d504fbf..592ecf8 100644 --- a/private_include/relay_chn_output.h +++ b/private_include/relay_chn_output.h @@ -24,7 +24,7 @@ extern "C" { * * @param[in] gpio_map Array of GPIO pin numbers for each relay channel. * @param[in] gpio_count Number of GPIO pins (relay channels). - * + * * @return ESP_OK on success, error code otherwise. */ esp_err_t relay_chn_output_init(const uint8_t* gpio_map, uint8_t gpio_count); @@ -41,7 +41,7 @@ void relay_chn_output_deinit(void); * @brief Get the relay channel output object for a specific channel. * * @param[in] chn_id Channel ID. - * + * * @return Pointer to relay channel output object, or NULL if invalid. */ relay_chn_output_t *relay_chn_output_get(uint8_t chn_id); @@ -67,7 +67,7 @@ relay_chn_output_t *relay_chn_output_get(void); * Sets the relay channel to the stop state. * * @param[in] output Pointer to relay channel output object. - * + * * @return ESP_OK on success, error code otherwise. */ esp_err_t relay_chn_output_stop(relay_chn_output_t *output); @@ -76,7 +76,7 @@ esp_err_t relay_chn_output_stop(relay_chn_output_t *output); * @brief Set relay channel output to forward direction. * * @param[in] output Pointer to relay channel output object. - * + * * @return ESP_OK on success, error code otherwise. */ esp_err_t relay_chn_output_forward(relay_chn_output_t *output); @@ -85,7 +85,7 @@ esp_err_t relay_chn_output_forward(relay_chn_output_t *output); * @brief Set relay channel output to reverse direction. * * @param[in] output Pointer to relay channel output object. - * + * * @return ESP_OK on success, error code otherwise. */ esp_err_t relay_chn_output_reverse(relay_chn_output_t *output); @@ -103,7 +103,7 @@ void relay_chn_output_flip(relay_chn_output_t *output); * @brief Get the current direction of the relay channel output. * * @param[in] output Pointer to relay channel output object. - * + * * @return Current direction of the relay channel. */ relay_chn_direction_t relay_chn_output_get_direction(relay_chn_output_t *output); diff --git a/private_include/relay_chn_run_info.h b/private_include/relay_chn_run_info.h index b3f9188..0cc2726 100644 --- a/private_include/relay_chn_run_info.h +++ b/private_include/relay_chn_run_info.h @@ -49,7 +49,7 @@ relay_chn_run_info_t *relay_chn_run_info_get(void); * @brief Get the last run command for a relay channel. * * @param[in] run_info Pointer to run information structure. - * + * * @return Last command that was executed, or RELAY_CHN_CMD_NONE if invalid. */ relay_chn_cmd_t relay_chn_run_info_get_last_run_cmd(relay_chn_run_info_t *run_info); @@ -66,7 +66,7 @@ void relay_chn_run_info_set_last_run_cmd(relay_chn_run_info_t *run_info, relay_c * @brief Get the timestamp of the last run command. * * @param[in] run_info Pointer to run information structure. - * + * * @return Timestamp in milliseconds of last command, or 0 if invalid. */ uint32_t relay_chn_run_info_get_last_run_cmd_time_ms(relay_chn_run_info_t *run_info); diff --git a/private_include/relay_chn_tilt.h b/private_include/relay_chn_tilt.h index 30db7aa..d109a72 100644 --- a/private_include/relay_chn_tilt.h +++ b/private_include/relay_chn_tilt.h @@ -19,7 +19,7 @@ extern "C" { * Must be called before using any other tilt functions. * * @param[in] chn_ctls Array of relay channel control structures. - * + * * @return ESP_OK on success, error code otherwise. */ esp_err_t relay_chn_tilt_init(relay_chn_ctl_t *chn_ctls); @@ -34,12 +34,12 @@ void relay_chn_tilt_deinit(void); /** * @brief Dispatch a tilt command to a relay channel. - * + * * Queues a tilt command for execution on the specified channel. * * @param[in] tilt_ctl Pointer to tilt control structure. * @param[in] cmd Tilt command to execute. - * + * * @return ESP_OK on success, error code otherwise. */ esp_err_t relay_chn_tilt_dispatch_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_tilt_cmd_t cmd); diff --git a/src/relay_chn_core.c b/src/relay_chn_core.c index 64e44ca..d1347cc 100644 --- a/src/relay_chn_core.c +++ b/src/relay_chn_core.c @@ -28,8 +28,8 @@ static const char *TAG = "RELAY_CHN_CORE"; #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT -/* - * Run limit timer callback immediately dispatches a STOP command for the +/* + * Run limit timer callback immediately dispatches a STOP command for the * relevant channel as soon as the run limit time times out */ static void relay_chn_run_limit_timer_cb(void* arg) @@ -195,17 +195,17 @@ static void relay_chn_stop_prv(relay_chn_ctl_t *chn_ctl) /** * @brief The command issuer function. - * - * This function is the deciding logic for issuing a command to a relay channel. It evaluates + * + * This function is the deciding logic for issuing a command to a relay channel. It evaluates * the current state of the channel before issuing the command. Then it decides whether to run * the command immediately or wait for the opposite inertia time. - * + * * The STOP command is an exception, it is always run immediately since it is safe in any case. - * + * * Another special consideration is the FLIP command. If the channel is running, the FLIP command * is issued after the channel is stopped. If the channel is stopped, the FLIP command is issued * immediately. - * + * * @param chn_ctl The relay channel to issue the command to. * @param cmd The command to issue. */ @@ -214,7 +214,7 @@ void relay_chn_issue_cmd(relay_chn_ctl_t* chn_ctl, relay_chn_cmd_t cmd) if (cmd == RELAY_CHN_CMD_NONE) { return; } - + if (cmd == RELAY_CHN_CMD_STOP) { if (chn_ctl->state == RELAY_CHN_STATE_STOPPED) { return; // Do nothing if already stopped @@ -232,7 +232,7 @@ void relay_chn_issue_cmd(relay_chn_ctl_t* chn_ctl, relay_chn_cmd_t cmd) // If the channel is idle, run the command immediately relay_chn_dispatch_cmd(chn_ctl, cmd); break; - + case RELAY_CHN_STATE_FORWARD_PENDING: case RELAY_CHN_STATE_REVERSE_PENDING: // The channel is already waiting for the opposite inertia time, @@ -241,7 +241,7 @@ void relay_chn_issue_cmd(relay_chn_ctl_t* chn_ctl, relay_chn_cmd_t cmd) relay_chn_dispatch_cmd(chn_ctl, cmd); } break; - + case RELAY_CHN_STATE_STOPPED: if (last_run_cmd == cmd || last_run_cmd == RELAY_CHN_CMD_NONE) { // Since the state is STOPPED, the inertia timer should be running and must be invalidated @@ -254,7 +254,7 @@ void relay_chn_issue_cmd(relay_chn_ctl_t* chn_ctl, relay_chn_cmd_t cmd) relay_chn_dispatch_cmd(chn_ctl, cmd); } else { - // If the last run command is different from the current command, calculate the time passed + // If the last run command is different from the current command, calculate the time passed // since the last run command stopped and decide whether to run the command immediately or wait uint32_t last_run_cmd_time_ms = relay_chn_run_info_get_last_run_cmd_time_ms(chn_ctl->run_info); uint32_t current_time_ms = (uint32_t)(esp_timer_get_time() / 1000); @@ -281,7 +281,7 @@ void relay_chn_issue_cmd(relay_chn_ctl_t* chn_ctl, relay_chn_cmd_t cmd) } } break; - + case RELAY_CHN_STATE_FORWARD: case RELAY_CHN_STATE_REVERSE: if (cmd == RELAY_CHN_CMD_FLIP) { @@ -290,18 +290,18 @@ void relay_chn_issue_cmd(relay_chn_ctl_t* chn_ctl, relay_chn_cmd_t cmd) relay_chn_dispatch_cmd(chn_ctl, cmd); return; } - + if (last_run_cmd == cmd) { // If the last run command is the same as the current command, do nothing return; } - + // Stop the channel first before the schedule relay_chn_stop_prv(chn_ctl); // If the last run command is different from the current command, wait for the opposite inertia time chn_ctl->pending_cmd = cmd; - relay_chn_state_t new_state = cmd == RELAY_CHN_CMD_FORWARD + relay_chn_state_t new_state = cmd == RELAY_CHN_CMD_FORWARD ? RELAY_CHN_STATE_FORWARD_PENDING : RELAY_CHN_STATE_REVERSE_PENDING; relay_chn_update_state(chn_ctl, new_state); relay_chn_start_timer_or_idle(chn_ctl, chn_ctl->inertia_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS, "inertia"); @@ -366,9 +366,6 @@ static void relay_chn_execute_stop(relay_chn_ctl_t *chn_ctl) { relay_chn_stop_prv(chn_ctl); - // If there is any pending command, cancel it since the STOP command is issued right after it - // chn_ctl->pending_cmd = RELAY_CHN_CMD_NONE; - #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT esp_timer_stop(chn_ctl->run_limit_timer); #endif @@ -382,8 +379,7 @@ static void relay_chn_execute_stop(relay_chn_ctl_t *chn_ctl) chn_ctl->pending_cmd = RELAY_CHN_CMD_IDLE; relay_chn_start_timer_or_idle(chn_ctl, chn_ctl->inertia_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS, "idle"); } else { - // If the channel was not running one of the run or fwd, issue a free command immediately - // relay_chn_dispatch_cmd(chn_ctl, RELAY_CHN_CMD_IDLE); + // If the channel was not running forward or reverse, issue a free command immediately relay_chn_execute_idle(chn_ctl); } } diff --git a/src/relay_chn_ctl_multi.c b/src/relay_chn_ctl_multi.c index 31122f9..a581b79 100644 --- a/src/relay_chn_ctl_multi.c +++ b/src/relay_chn_ctl_multi.c @@ -140,7 +140,7 @@ void relay_chn_ctl_stop_all() void relay_chn_ctl_flip_direction(uint8_t chn_id) { if (relay_chn_is_channel_id_valid(chn_id)) - relay_chn_issue_cmd(&chn_ctls[chn_id], RELAY_CHN_CMD_FLIP); + relay_chn_issue_cmd(&chn_ctls[chn_id], RELAY_CHN_CMD_FLIP); } void relay_chn_ctl_flip_direction_all() diff --git a/src/relay_chn_ctl_single.c b/src/relay_chn_ctl_single.c index c95af90..6840d49 100644 --- a/src/relay_chn_ctl_single.c +++ b/src/relay_chn_ctl_single.c @@ -85,7 +85,7 @@ void relay_chn_ctl_stop() void relay_chn_ctl_flip_direction() { - relay_chn_issue_cmd(&chn_ctl, RELAY_CHN_CMD_FLIP); + relay_chn_issue_cmd(&chn_ctl, RELAY_CHN_CMD_FLIP); } relay_chn_direction_t relay_chn_ctl_get_direction() @@ -106,13 +106,13 @@ void relay_chn_ctl_set_run_limit(uint16_t limit_sec) limit_sec = CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC; else if (limit_sec < CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC) limit_sec = CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC; - + chn_ctl.run_limit_sec = limit_sec; #if CONFIG_RELAY_CHN_ENABLE_NVS relay_chn_nvs_set_run_limit(chn_ctl.id, limit_sec); #endif -} +} #endif /* relay_chn APIs */ diff --git a/src/relay_chn_notify.c b/src/relay_chn_notify.c index 47e3b55..50093e5 100644 --- a/src/relay_chn_notify.c +++ b/src/relay_chn_notify.c @@ -127,7 +127,7 @@ static relay_chn_listener_entry_t* find_listener_entry(relay_chn_state_listener_ 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 diff --git a/src/relay_chn_nvs.c b/src/relay_chn_nvs.c index 9f80f10..f1d0a9d 100644 --- a/src/relay_chn_nvs.c +++ b/src/relay_chn_nvs.c @@ -81,7 +81,7 @@ esp_err_t relay_chn_nvs_init() ESP_LOGE(TAG, "Failed to create deinit semaphore"); return ESP_ERR_NO_MEM; } - + nvs_queue_handle = xQueueCreate(RELAY_CHN_NVS_QUEUE_LEN, sizeof(relay_chn_nvs_msg_t)); if (!nvs_queue_handle) { ESP_LOGE(TAG, "Failed to create NVS queue"); @@ -165,7 +165,7 @@ static esp_err_t relay_chn_nvs_task_set_direction(uint8_t ch, uint8_t direction) esp_err_t relay_chn_nvs_get_direction(uint8_t ch, relay_chn_direction_t *direction, relay_chn_direction_t default_val) { ESP_RETURN_ON_FALSE(direction != NULL, ESP_ERR_INVALID_ARG, TAG, "Direction pointer is NULL"); - + uint8_t direction_val; esp_err_t ret = nvs_get_u8(relay_chn_nvs, RELAY_CHN_KEY_DIR, &direction_val); if (ret == ESP_ERR_NVS_NOT_FOUND) { @@ -295,7 +295,7 @@ static esp_err_t relay_chn_nvs_task_set_tilt_count(uint8_t ch, uint16_t tilt_cou esp_err_t relay_chn_nvs_get_tilt_count(uint8_t ch, uint16_t *tilt_count, uint16_t default_val) { ESP_RETURN_ON_FALSE(tilt_count != NULL, ESP_ERR_INVALID_ARG, TAG, "Counter pointers are NULL"); - + esp_err_t ret; #if CONFIG_RELAY_CHN_COUNT > 1 char key[NVS_KEY_NAME_MAX_SIZE]; diff --git a/src/relay_chn_output.c b/src/relay_chn_output.c index 3a608a7..2b1ce9d 100644 --- a/src/relay_chn_output.c +++ b/src/relay_chn_output.c @@ -50,7 +50,7 @@ static esp_err_t relay_chn_output_ctl_init(relay_chn_output_t *output, "Invalid GPIO pin number for forward_pin: %d", forward_pin); ESP_RETURN_ON_FALSE(GPIO_IS_VALID_OUTPUT_GPIO(reverse_pin), ESP_ERR_INVALID_ARG, TAG, "Invalid GPIO pin number for reverse_pin: %d", reverse_pin); - + // Check if the GPIOs are valid esp_err_t ret; // Initialize the GPIOs @@ -58,7 +58,7 @@ static esp_err_t relay_chn_output_ctl_init(relay_chn_output_t *output, ESP_RETURN_ON_ERROR(ret, TAG, "Failed to reset GPIO forward pin: %d", forward_pin); ret = gpio_set_direction(forward_pin, GPIO_MODE_OUTPUT); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set GPIO direction for forward pin: %d", forward_pin); - + ret = gpio_reset_pin(reverse_pin); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to reset GPIO reverse pin: %d", reverse_pin); ret = gpio_set_direction(reverse_pin, GPIO_MODE_OUTPUT); @@ -75,8 +75,8 @@ static esp_err_t relay_chn_output_ctl_init(relay_chn_output_t *output, #if CONFIG_RELAY_CHN_ENABLE_NVS static esp_err_t relay_chn_output_load_direction(uint8_t ch, relay_chn_direction_t *direction) { + // relay_chn_nvs_get_direction handles the NOT_FOUND case and returns the provided default value. esp_err_t ret = relay_chn_nvs_get_direction(ch, direction, RELAY_CHN_DIRECTION_DEFAULT); - // relay_chn_nvs_get_direction now handles the NOT_FOUND case and returns a default value. ESP_RETURN_ON_ERROR(ret, TAG, "Failed to get direction from storage for channel %d: %s", ch, esp_err_to_name(ret)); return ESP_OK; } @@ -87,7 +87,7 @@ esp_err_t relay_chn_output_init(const uint8_t* gpio_map, uint8_t gpio_count) esp_err_t ret; ret = relay_chn_output_check_gpio_capabilities(gpio_count); ESP_RETURN_ON_ERROR(ret, TAG, "Device does not support the provided GPIOs"); - + #if CONFIG_RELAY_CHN_COUNT > 1 for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { relay_chn_output_t* output = &outputs[i]; @@ -185,8 +185,8 @@ void relay_chn_output_flip(relay_chn_output_t *output) output->forward_pin = output->reverse_pin; output->reverse_pin = temp; // Flip the direction - output->direction = (output->direction == RELAY_CHN_DIRECTION_DEFAULT) - ? RELAY_CHN_DIRECTION_FLIPPED + output->direction = (output->direction == RELAY_CHN_DIRECTION_DEFAULT) + ? RELAY_CHN_DIRECTION_FLIPPED : RELAY_CHN_DIRECTION_DEFAULT; #if CONFIG_RELAY_CHN_ENABLE_NVS diff --git a/src/relay_chn_tilt.c b/src/relay_chn_tilt.c index ac3d1ff..b0529d3 100644 --- a/src/relay_chn_tilt.c +++ b/src/relay_chn_tilt.c @@ -22,7 +22,7 @@ static const char *TAG = "RELAY_CHN_TILT"; /**@{*/ /* * Tilt Pattern Timing Definitions - * + * * The min and max timing definitions as well as the default timing definitions. * These definitions are used to define and adjust the tilt sensitivity. */ @@ -85,7 +85,7 @@ static uint32_t relay_chn_tilt_get_required_timing_before_tilting(relay_chn_tilt return 0; else if (cmd == RELAY_CHN_TILT_CMD_REVERSE && last_run_cmd == RELAY_CHN_CMD_FORWARD) return 0; - + uint32_t last_run_cmd_time_ms = relay_chn_run_info_get_last_run_cmd_time_ms(tilt_ctl->chn_ctl->run_info); uint32_t inertia_time_passed_ms = (uint32_t) (esp_timer_get_time() / 1000) - last_run_cmd_time_ms; return CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS - inertia_time_passed_ms; @@ -115,13 +115,13 @@ static void relay_chn_tilt_issue_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_t relay_chn_tilt_dispatch_cmd(tilt_ctl, cmd); return; } - + if (relay_chn_run_info_get_last_run_cmd(tilt_ctl->chn_ctl->run_info) == RELAY_CHN_CMD_NONE) { // Do not tilt if the channel hasn't been run before ESP_LOGD(TAG, "relay_chn_tilt_issue_cmd: Tilt will not be executed since the channel hasn't been run yet"); return; } - + if (tilt_ctl->cmd == cmd) { ESP_LOGD(TAG, "relay_chn_tilt_issue_cmd: There is already a tilt command in progress!"); return; @@ -134,7 +134,7 @@ static void relay_chn_tilt_issue_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_t // Relay channel is free, tilt can be issued immediately relay_chn_tilt_dispatch_cmd(tilt_ctl, cmd); break; - + case RELAY_CHN_STATE_FORWARD_PENDING: case RELAY_CHN_STATE_REVERSE_PENDING: // Issue a stop command first so that the timer and pending cmd get cleared @@ -322,7 +322,7 @@ static void relay_chn_tilt_compute_set_sensitivity(relay_chn_tilt_ctl_t *tilt_ct uint32_t tilt_run_time_ms = 0, tilt_pause_time_ms = 0; tilt_run_time_ms = RELAY_CHN_TILT_RUN_MIN_MS + (sensitivity * (RELAY_CHN_TILT_RUN_MAX_MS - RELAY_CHN_TILT_RUN_MIN_MS) / 100); tilt_pause_time_ms = RELAY_CHN_TILT_PAUSE_MIN_MS + (sensitivity * (RELAY_CHN_TILT_PAUSE_MAX_MS - RELAY_CHN_TILT_PAUSE_MIN_MS) / 100); - + relay_chn_tilt_set_timing_values(&tilt_ctl->tilt_timing, sensitivity, tilt_run_time_ms, @@ -336,7 +336,7 @@ void relay_chn_tilt_set_sensitivity(uint8_t chn_id, uint8_t sensitivity) if (relay_chn_is_channel_id_valid(chn_id)) { ADJUST_TILT_SENS_BOUNDARIES(sensitivity); relay_chn_tilt_compute_set_sensitivity(&tilt_ctls[chn_id], sensitivity); - + #if CONFIG_RELAY_CHN_ENABLE_NVS relay_chn_nvs_set_tilt_sensitivity(chn_id, sensitivity); #endif // CONFIG_RELAY_CHN_ENABLE_NVS @@ -346,7 +346,7 @@ void relay_chn_tilt_set_sensitivity(uint8_t chn_id, uint8_t sensitivity) esp_err_t relay_chn_tilt_set_sensitivity_all(uint8_t *sensitivities) { ESP_RETURN_ON_FALSE(sensitivities != NULL, ESP_ERR_INVALID_ARG, TAG, "set_sensitivity_all: sensitivities is NULL"); - + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { uint8_t *src_sensitivity = &sensitivities[i]; if (src_sensitivity == NULL) { @@ -382,7 +382,7 @@ uint8_t relay_chn_tilt_get_sensitivity(uint8_t chn_id) esp_err_t relay_chn_tilt_get_sensitivity_all(uint8_t *sensitivities) { ESP_RETURN_ON_FALSE(sensitivities != NULL, ESP_ERR_INVALID_ARG, TAG, "get_sensitivity_all: sensitivities is NULL"); - + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { uint8_t *dest_sensitivity = &sensitivities[i]; if (dest_sensitivity == NULL) { @@ -400,7 +400,7 @@ void relay_chn_tilt_set_sensitivity(uint8_t sensitivity) { ADJUST_TILT_SENS_BOUNDARIES(sensitivity); relay_chn_tilt_compute_set_sensitivity(&tilt_ctl, sensitivity); - + #if CONFIG_RELAY_CHN_ENABLE_NVS relay_chn_nvs_set_tilt_sensitivity(0, sensitivity); #endif // CONFIG_RELAY_CHN_ENABLE_NVS @@ -423,25 +423,25 @@ void relay_chn_tilt_reset_count(relay_chn_tilt_ctl_t *tilt_ctl) /** * @brief Update tilt count automatically and return the current value. - * + * * This helper function updates the relevant tilt count depending on the * last run info and helps the tilt module in deciding whether the requested * tilt should execute or not. - * + * * This is useful to control reverse tilting for the same direction particularly. * For example: * - If the channel's last run was FORWARD and a TILT_FORWARD is requested, - * then the tilt count will count up on the relay_chn_tilt_ctl_t::tilt_count + * then the tilt count will count up on the relay_chn_tilt_ctl_t::tilt_count * and the function will return the actual count. * - If the channel's last run was FORWARD and a TILT_REVERSE is requested, - * then the relay_chn_tilt_ctl_t::tilt_count will be checked against zero first, - * and then it will count down and return the actual count if it is greater + * then the relay_chn_tilt_ctl_t::tilt_count will be checked against zero first, + * and then it will count down and return the actual count if it is greater * than 0, else the function will return 0. * - If the tilt command is irrelevant then the function will return 0. * - If the last run is irrelevant then the function will return 0. - * + * * @param tilt_ctl The relay channel handle. - * + * * @return The actual value of the relevant count. * @return 1 if the last tilt_count was 1 and decremented to 0. * @return 0 if: @@ -612,7 +612,7 @@ static void relay_chn_tilt_timer_cb(void *arg) { relay_chn_tilt_ctl_t* tilt_ctl = (relay_chn_tilt_ctl_t*) arg; ESP_RETURN_VOID_ON_FALSE(tilt_ctl != NULL, TAG, "relay_chn_tilt_timer_cb: timer arg is NULL"); - + switch (tilt_ctl->step) { case RELAY_CHN_TILT_STEP_MOVE: @@ -632,7 +632,7 @@ static void relay_chn_tilt_timer_cb(void *arg) // Just dispatch the pending tilt command relay_chn_tilt_dispatch_cmd(tilt_ctl, tilt_ctl->cmd); break; - + default: break; } @@ -667,7 +667,7 @@ static esp_err_t relay_chn_tilt_ctl_init(relay_chn_tilt_ctl_t *tilt_ctl, tilt_ctl->chn_ctl = chn_ctl; tilt_ctl->chn_ctl->tilt_ctl = tilt_ctl; - + // Create tilt timer for the channel char timer_name[32]; snprintf(timer_name, sizeof(timer_name), "relay_chn_%2d_tilt_timer", chn_ctl->id); @@ -694,7 +694,7 @@ esp_err_t relay_chn_tilt_init(relay_chn_ctl_t *chn_ctls) { uint8_t sensitivity; uint16_t tilt_count; - + #if CONFIG_RELAY_CHN_COUNT > 1 for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { esp_err_t ret; diff --git a/test_apps/main/test_app_main.c b/test_apps/main/test_app_main.c index b733c76..064b444 100644 --- a/test_apps/main/test_app_main.c +++ b/test_apps/main/test_app_main.c @@ -23,9 +23,9 @@ #define RELAY_CHN_UNITY_TEST_GROUP_TAG "relay_chn" #endif -void setUp() +void setUp() { - + } void tearDown() @@ -75,7 +75,7 @@ static void test_nvs_flash_deinit(void) } #endif -void app_main(void) +void app_main(void) { #if CONFIG_RELAY_CHN_ENABLE_NVS // Init NVS once for all tests @@ -109,6 +109,6 @@ void app_main(void) #endif ESP_LOGI(TEST_TAG, "All tests complete."); - + esp_restart(); // Restart to invoke qemu exit } diff --git a/test_apps/main/test_relay_chn_core_multi.c b/test_apps/main/test_relay_chn_core_multi.c index d8287f3..ecd6b4b 100644 --- a/test_apps/main/test_relay_chn_core_multi.c +++ b/test_apps/main/test_relay_chn_core_multi.c @@ -91,7 +91,7 @@ TEST_CASE("Run reverse does nothing if channel id is invalid", "[relay_chn][core TEST_CASE("Relay channels run reverse and update state", "[relay_chn][core]") { for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { - relay_chn_run_reverse(i); // relay_chn_run_reverse returns void + relay_chn_run_reverse(i); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(i)); } @@ -148,7 +148,7 @@ TEST_CASE("Relay channels stop and update to FREE state", "[relay_chn][core]") TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(i)); // Now, issue the stop command - relay_chn_stop(i); // relay_chn_stop returns void + relay_chn_stop(i); // Immediately after stop, state should be STOPPED vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(i)); @@ -201,19 +201,19 @@ TEST_CASE("Multiple channels can operate independently", "[relay_chn][core]") TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(1)); // Other channel should not be affected // Start Channel 1 in reverse direction - relay_chn_run_reverse(1); // relay_chn_run_reverse returns void + relay_chn_run_reverse(1); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(0)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(1)); // Stop Channel 0 and wait for it to become FREE - relay_chn_stop(0); // relay_chn_stop returns void + relay_chn_stop(0); vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(0)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(1)); // Other channel should continue running // Stop Channel 1 and wait for it to become FREE - relay_chn_stop(1); // relay_chn_stop returns void + relay_chn_stop(1); vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(0)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(1)); @@ -236,15 +236,15 @@ TEST_CASE("Forward to Reverse transition with opposite inertia", "[relay_chn][co TEST_ASSERT_EQUAL_UINT_ARRAY(expect_states, states, CONFIG_RELAY_CHN_COUNT); // 2. Issue reverse command - relay_chn_run_reverse_all(); // relay_chn_run_reverse returns void + relay_chn_run_reverse_all(); // Immediately after the command, the motor should be stopped - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ESP_OK(relay_chn_get_state_all(states)); test_set_expected_state_all(RELAY_CHN_STATE_REVERSE_PENDING); TEST_ASSERT_EQUAL_UINT_ARRAY(expect_states, states, CONFIG_RELAY_CHN_COUNT); // Wait for the inertia period (after which the reverse command will be dispatched) - vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); // Should now be in reverse state TEST_ESP_OK(relay_chn_get_state_all(states)); test_set_expected_state_all(RELAY_CHN_STATE_REVERSE); @@ -256,7 +256,7 @@ TEST_CASE("Forward to Reverse transition with opposite inertia", "[relay_chn][co TEST_CASE("Reverse to Forward transition with opposite inertia", "[relay_chn][core][inertia]") { // 1. Start in reverse direction - relay_chn_run_reverse_all(); // relay_chn_run_reverse returns void + relay_chn_run_reverse_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ESP_OK(relay_chn_get_state_all(states)); test_set_expected_state_all(RELAY_CHN_STATE_REVERSE); @@ -305,7 +305,7 @@ TEST_CASE("Direction can be flipped for each channel independently", "[relay_chn for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(i)); } - + // 2. Flip the direction for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { relay_chn_flip_direction(i); @@ -339,7 +339,7 @@ TEST_CASE("All channels direction can be flipped simultaneously", "[relay_chn][c TEST_ESP_OK(relay_chn_get_direction_all(directions)); test_set_expected_direction_all(RELAY_CHN_DIRECTION_FLIPPED); TEST_ASSERT_EQUAL_UINT_ARRAY(expect_directions, directions, CONFIG_RELAY_CHN_COUNT); - + // 3. Flip all back relay_chn_flip_direction_all(); vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); @@ -366,7 +366,7 @@ TEST_CASE("Flipping a running channel stops it and flips direction", "[relay_chn // 4. Wait for the flip inertia to pass, after which it should be idle and FLIPPED vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); - + TEST_ESP_OK(relay_chn_get_state_all(states)); test_set_expected_state_all(RELAY_CHN_STATE_IDLE); TEST_ASSERT_EQUAL_UINT_ARRAY(expect_states, states, CONFIG_RELAY_CHN_COUNT); @@ -452,7 +452,7 @@ TEST_CASE("Test run limit initialization", "[relay_chn][run_limit]") } } -TEST_CASE("Test run limit setting boundaries", "[relay_chn][run_limit]") +TEST_CASE("Test run limit setting boundaries", "[relay_chn][run_limit]") { for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { // Test minimum boundary @@ -482,7 +482,7 @@ TEST_CASE("Test run limit stops channel after timeout", "[relay_chn][run_limit]" // Check running forward TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(i)); } - + // Wait for run limit timeout vTaskDelay(pdMS_TO_TICKS(TEST_SHORT_RUN_LIMIT_SEC * 1000 + TEST_DELAY_MARGIN_MS)); for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { @@ -498,25 +498,25 @@ TEST_CASE("Test run limit reset on direction change and time out finally", "[rel // Wait for the NVS module task to process operations vTaskDelay(300 / portTICK_PERIOD_MS); // Wait 1 second #endif - + // Start running forward relay_chn_run_forward_all(); vTaskDelay(1000 / portTICK_PERIOD_MS); // Wait 1 second - + // Change direction before timeout relay_chn_run_reverse_all(); - + // Wait for the inertia period (after which the reverse command will be dispatched) vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(i)); } - + // Timer should time out and stop the channel after the run limit time vTaskDelay(pdMS_TO_TICKS(TEST_SHORT_RUN_LIMIT_SEC * 1000 + TEST_DELAY_MARGIN_MS)); - + for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(i)); } @@ -527,11 +527,11 @@ TEST_CASE("Test run limit persistence across stop/start", "[relay_chn][run_limit for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { // Set initial run limit relay_chn_set_run_limit(i, TEST_RUN_LIMIT_SEC); - + // Stop and start channel relay_chn_stop(i); relay_chn_run_forward(i); - + // Run limit should persist TEST_ASSERT_EQUAL(TEST_RUN_LIMIT_SEC, relay_chn_get_run_limit(i)); } diff --git a/test_apps/main/test_relay_chn_core_single.c b/test_apps/main/test_relay_chn_core_single.c index c7d4aeb..99e57be 100644 --- a/test_apps/main/test_relay_chn_core_single.c +++ b/test_apps/main/test_relay_chn_core_single.c @@ -44,7 +44,7 @@ TEST_CASE("Relay channels run forward and update state", "[relay_chn][core]") // TEST_CASE: Test that relays run in the reverse direction and update their state TEST_CASE("Relay channels run reverse and update state", "[relay_chn][core]") { - relay_chn_run_reverse(); // relay_chn_run_reverse returns void + relay_chn_run_reverse(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state()); } @@ -60,7 +60,7 @@ TEST_CASE("Relay channels stop and update to IDLE state", "[relay_chn][core]") TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); // Now, issue the stop command - relay_chn_stop(); // relay_chn_stop returns void + relay_chn_stop(); // Immediately after stop, state should be STOPPED vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state()); @@ -85,13 +85,13 @@ TEST_CASE("Forward to Reverse transition with opposite inertia", "[relay_chn][co TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); // 2. Issue reverse command - relay_chn_run_reverse(); // relay_chn_run_reverse returns void + relay_chn_run_reverse(); // Immediately after the command, the motor should be stopped - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE_PENDING, relay_chn_get_state()); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE_PENDING, relay_chn_get_state()); // Wait for the inertia period (after which the reverse command will be dispatched) - vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state()); // Should now be in reverse state } @@ -100,7 +100,7 @@ TEST_CASE("Forward to Reverse transition with opposite inertia", "[relay_chn][co TEST_CASE("Reverse to Forward transition with opposite inertia", "[relay_chn][core][inertia]") { // 1. Start in reverse direction - relay_chn_run_reverse(); // relay_chn_run_reverse returns void + relay_chn_run_reverse(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state()); @@ -127,7 +127,7 @@ TEST_CASE("Running in same direction does not incur inertia", "[relay_chn][core] relay_chn_run_forward(); // As per the code, is_direction_opposite_to_current_motion should return false, so no inertia. // Just a short delay to check state remains the same. - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); } @@ -177,7 +177,7 @@ TEST_CASE("Flipping a running channel stops it and flips direction", "[relay_chn // 2. Flip the direction while running relay_chn_flip_direction(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Give time for events to process - + // 3. The channel should stop as part of the flip process TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state()); @@ -197,7 +197,7 @@ TEST_CASE("Test run limit initialization", "[relay_chn][run_limit]") TEST_ASSERT_EQUAL(CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC, relay_chn_get_run_limit()); } -TEST_CASE("Test run limit setting boundaries", "[relay_chn][run_limit]") +TEST_CASE("Test run limit setting boundaries", "[relay_chn][run_limit]") { // Test minimum boundary relay_chn_set_run_limit(CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC - 1); @@ -216,11 +216,11 @@ TEST_CASE("Test run limit stops channel after timeout", "[relay_chn][run_limit]" { // Set a short run limit for testing relay_chn_set_run_limit(TEST_SHORT_RUN_LIMIT_SEC); - + // Start running forward relay_chn_run_forward(); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); - + // Wait for run limit timeout vTaskDelay(pdMS_TO_TICKS(TEST_SHORT_RUN_LIMIT_SEC * 1000 + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state()); @@ -230,22 +230,22 @@ TEST_CASE("Test run limit reset on direction change and time out finally", "[rel { // Set a short run limit relay_chn_set_run_limit(TEST_SHORT_RUN_LIMIT_SEC); - + // Start running forward relay_chn_run_forward(); vTaskDelay(1000 / portTICK_PERIOD_MS); // Wait 1 second - + // Change direction before timeout relay_chn_run_reverse(); - + // Wait for the inertia period (after which the reverse command will be dispatched) vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); - + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state()); - + // Timer should time out and stop the channel after the run limit time vTaskDelay(pdMS_TO_TICKS(TEST_SHORT_RUN_LIMIT_SEC * 1000 + TEST_DELAY_MARGIN_MS)); - + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state()); } @@ -253,11 +253,11 @@ TEST_CASE("Test run limit persistence across stop/start", "[relay_chn][run_limit { // Set initial run limit relay_chn_set_run_limit(TEST_RUN_LIMIT_SEC); - + // Stop and start channel relay_chn_stop(); relay_chn_run_forward(); - + // Run limit should persist TEST_ASSERT_EQUAL(TEST_RUN_LIMIT_SEC, relay_chn_get_run_limit()); } diff --git a/test_apps/main/test_relay_chn_nvs_multi.c b/test_apps/main/test_relay_chn_nvs_multi.c index 6ac3bb1..8a3a5be 100644 --- a/test_apps/main/test_relay_chn_nvs_multi.c +++ b/test_apps/main/test_relay_chn_nvs_multi.c @@ -17,7 +17,7 @@ TEST_CASE("Test direction setting and getting", "[relay_chn][nvs]") { // Test all channels relay_chn_direction_t dir, expect; - + for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) { dir = channel % 2 == 0 ? RELAY_CHN_DIRECTION_DEFAULT : RELAY_CHN_DIRECTION_FLIPPED; TEST_ESP_OK(relay_chn_nvs_set_direction(channel, dir)); @@ -25,7 +25,7 @@ TEST_CASE("Test direction setting and getting", "[relay_chn][nvs]") // Wait for the batch commit timeout to ensure the value is written vTaskDelay(pdMS_TO_TICKS(TEST_NVS_TASK_TIME_OUT_MS)); - + for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) { expect = channel % 2 == 0 ? RELAY_CHN_DIRECTION_DEFAULT : RELAY_CHN_DIRECTION_FLIPPED; TEST_ESP_OK(relay_chn_nvs_get_direction(channel, &dir, RELAY_CHN_DIRECTION_DEFAULT)); @@ -138,7 +138,7 @@ TEST_CASE("Test sensitivity setting and getting", "[relay_chn][nvs][tilt]") for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { test_sensitivities[i] = 70 + i; // e.g., 70, 71, 72... } - + // 1. Set all values first for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { TEST_ESP_OK(relay_chn_nvs_set_tilt_sensitivity(i, test_sensitivities[i])); @@ -161,7 +161,7 @@ TEST_CASE("Test tilt counter operations", "[relay_chn][nvs][tilt]") for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { test_counts[i] = 100 + i; // e.g., 100, 101, 102... } - + // 1. Set all values first for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { TEST_ESP_OK(relay_chn_nvs_set_tilt_count(i, test_counts[i])); diff --git a/test_apps/main/test_relay_chn_nvs_single.c b/test_apps/main/test_relay_chn_nvs_single.c index 135d1d2..1493a83 100644 --- a/test_apps/main/test_relay_chn_nvs_single.c +++ b/test_apps/main/test_relay_chn_nvs_single.c @@ -21,7 +21,7 @@ TEST_CASE("Test direction setting and getting", "[relay_chn][nvs]") vTaskDelay(pdMS_TO_TICKS(TEST_NVS_TASK_TIME_OUT_MS)); // Allow NVS task to write and commit TEST_ESP_OK(relay_chn_nvs_get_direction(0, &dir, RELAY_CHN_DIRECTION_DEFAULT)); TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, dir); - + // Test channel 1 TEST_ESP_OK(relay_chn_nvs_set_direction(0, RELAY_CHN_DIRECTION_FLIPPED)); vTaskDelay(pdMS_TO_TICKS(TEST_NVS_TASK_TIME_OUT_MS)); // Allow NVS task to write and commit @@ -83,7 +83,7 @@ TEST_CASE("Test run limit setting and getting", "[relay_chn][nvs][run_limit]") { const uint16_t run_limit_sec = 32; TEST_ESP_OK(relay_chn_nvs_set_run_limit(0, run_limit_sec)); - + vTaskDelay(pdMS_TO_TICKS(TEST_NVS_TASK_TIME_OUT_MS)); // Allow NVS task to write and commit uint16_t run_limit_read; TEST_ESP_OK(relay_chn_nvs_get_run_limit(0, &run_limit_read, CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC)); @@ -96,7 +96,7 @@ TEST_CASE("Test sensitivity setting and getting", "[relay_chn][nvs][tilt]") { const uint8_t test_sensitivity = 75; TEST_ESP_OK(relay_chn_nvs_set_tilt_sensitivity(0, test_sensitivity)); - + vTaskDelay(pdMS_TO_TICKS(TEST_NVS_TASK_TIME_OUT_MS)); // Allow NVS task to write and commit uint8_t sensitivity; TEST_ESP_OK(relay_chn_nvs_get_tilt_sensitivity(0, &sensitivity, 0)); @@ -106,10 +106,10 @@ TEST_CASE("Test sensitivity setting and getting", "[relay_chn][nvs][tilt]") TEST_CASE("Test tilt counter operations", "[relay_chn][nvs][tilt]") { const uint16_t tilt_count = 100; - + // Test setting counters TEST_ESP_OK(relay_chn_nvs_set_tilt_count(0, tilt_count)); - + vTaskDelay(pdMS_TO_TICKS(TEST_NVS_TASK_TIME_OUT_MS)); // Allow NVS task to write and commit uint16_t tilt_count_read; TEST_ESP_OK(relay_chn_nvs_get_tilt_count(0, &tilt_count_read, 0)); diff --git a/test_apps/main/test_relay_chn_tilt_multi.c b/test_apps/main/test_relay_chn_tilt_multi.c index 07e102e..0934444 100644 --- a/test_apps/main/test_relay_chn_tilt_multi.c +++ b/test_apps/main/test_relay_chn_tilt_multi.c @@ -30,7 +30,7 @@ void prepare_channels_for_tilt_with_mixed_runs() { // Ensure the channel reset tilt control relay_chn_tilt_stop_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - + // Ensure the channel has had a 'last_run_cmd' for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { if (i % 2 == 0) { @@ -39,7 +39,7 @@ void prepare_channels_for_tilt_with_mixed_runs() { relay_chn_run_reverse(i); } } - + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow command to process relay_chn_stop_all(); // Stop it to set last_run_cmd but return to FREE for next test vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); @@ -60,11 +60,11 @@ void prepare_all_channels_for_tilt(int initial_cmd) { break; } } - + if (not_idle) { vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS)); } - + // Ensure the channel has had a 'last_run_cmd' if (initial_cmd == RELAY_CHN_CMD_FORWARD) { relay_chn_run_forward_all(); @@ -74,7 +74,7 @@ void prepare_all_channels_for_tilt(int initial_cmd) { vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow command to process relay_chn_stop_all(); // Stop all to set last_run_cmd but return to FREE for next test vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); - + check_all_channels_for_state(RELAY_CHN_STATE_IDLE); } @@ -86,15 +86,15 @@ TEST_CASE("Run Forward to Tilt Forward transition with inertia", "[relay_chn][ti prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD); // 1. Start in forward direction - relay_chn_run_forward_all(); + relay_chn_run_forward_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); check_all_channels_for_state(RELAY_CHN_STATE_FORWARD); // 2. Issue tilt forward command - relay_chn_tilt_forward_all(); + relay_chn_tilt_forward_all(); // After tilt command, it should immediately stop and then trigger inertia. - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - check_all_channels_for_state(RELAY_CHN_STATE_STOPPED); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + check_all_channels_for_state(RELAY_CHN_STATE_STOPPED); // Wait for the inertia period (after which the tilt command will be dispatched) vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS)); @@ -109,12 +109,12 @@ TEST_CASE("Run Reverse to Tilt Reverse transition with inertia", "[relay_chn][ti prepare_all_channels_for_tilt(RELAY_CHN_CMD_REVERSE); // 1. Start in reverse direction - relay_chn_run_reverse_all(); + relay_chn_run_reverse_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); check_all_channels_for_state(RELAY_CHN_STATE_REVERSE); // 2. Issue tilt reverse command - relay_chn_tilt_reverse_all(); + relay_chn_tilt_reverse_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); check_all_channels_for_state(RELAY_CHN_STATE_STOPPED); @@ -130,9 +130,9 @@ TEST_CASE("FREE to Tilt Forward transition with inertia (prepared)", "[relay_chn prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD); // Issue tilt forward command - relay_chn_tilt_forward_all(); + relay_chn_tilt_forward_all(); // From FREE state, tilt command should still incur the inertia due to the internal timer logic - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD); } @@ -144,7 +144,7 @@ TEST_CASE("FREE to Tilt Reverse transition with inertia (prepared)", "[relay_chn prepare_all_channels_for_tilt(RELAY_CHN_CMD_REVERSE); // Issue tilt reverse command - relay_chn_tilt_reverse_all(); + relay_chn_tilt_reverse_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); check_all_channels_for_state(RELAY_CHN_STATE_TILT_REVERSE); } @@ -160,7 +160,7 @@ TEST_CASE("Tilt Forward to Run Forward transition with inertia", "[relay_chn][ti check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD); // 2. Issue run forward command - relay_chn_run_forward_all(); + relay_chn_run_forward_all(); // From Tilt to Run in the same logical name but in the opposite direction, inertia is expected. check_all_channels_for_state(RELAY_CHN_STATE_FORWARD_PENDING); vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); @@ -178,7 +178,7 @@ TEST_CASE("Tilt Reverse to Run Reverse transition with inertia", "[relay_chn][ti check_all_channels_for_state(RELAY_CHN_STATE_TILT_REVERSE); // 2. Issue run reverse command - relay_chn_run_reverse_all(); + relay_chn_run_reverse_all(); check_all_channels_for_state(RELAY_CHN_STATE_REVERSE_PENDING); vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); check_all_channels_for_state(RELAY_CHN_STATE_REVERSE); @@ -195,7 +195,7 @@ TEST_CASE("Tilt Forward to Run Reverse transition without inertia", "[relay_chn] check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD); // 2. Issue run reverse command (opposite direction) - relay_chn_run_reverse_all(); + relay_chn_run_reverse_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); check_all_channels_for_state(RELAY_CHN_STATE_REVERSE); } @@ -251,7 +251,7 @@ TEST_CASE("tilt_stop_all stops all tilting channels", "[relay_chn][tilt][batch]" { // 1. Prepare and start all channels tilting forward prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD); - + relay_chn_tilt_forward_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); @@ -279,7 +279,7 @@ TEST_CASE("tilt_auto_all tilts channels based on last run direction", "[relay_ch for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { relay_chn_state_t state = i % 2 == 0 ? RELAY_CHN_STATE_TILT_FORWARD : RELAY_CHN_STATE_TILT_REVERSE; - + TEST_ASSERT_EQUAL(state, relay_chn_get_state(i)); } } @@ -319,11 +319,11 @@ TEST_CASE("relay_chn_tilt_set_sensitivity and get", "[relay_chn][tilt][sensitivi TEST_ASSERT_EQUAL_UINT8(100, relay_chn_tilt_get_sensitivity(ch)); // Set all channels relay_chn_tilt_set_sensitivity_all_with(42); - + uint8_t vals[CONFIG_RELAY_CHN_COUNT] = {0}; uint8_t expect[CONFIG_RELAY_CHN_COUNT]; memset(expect, 42, CONFIG_RELAY_CHN_COUNT); - + TEST_ESP_OK(relay_chn_tilt_get_sensitivity_all(vals)); TEST_ASSERT_EQUAL_UINT8_ARRAY(expect, vals, CONFIG_RELAY_CHN_COUNT); } @@ -371,7 +371,6 @@ TEST_CASE("relay_chn_tilt_set_sensitivity functions handle upper boundary", "[re // Test tilt counter logic: forward x3, reverse x3, extra reverse fails TEST_CASE("tilt counter logic: forward and reverse consumption", "[relay_chn][tilt][counter]") { - // Tilt execution time at 100% sensitivity in milliseconds (10 + 90) #define TEST_TILT_EXECUTION_TIME_MS 100 diff --git a/test_apps/main/test_relay_chn_tilt_single.c b/test_apps/main/test_relay_chn_tilt_single.c index 7137822..481793b 100644 --- a/test_apps/main/test_relay_chn_tilt_single.c +++ b/test_apps/main/test_relay_chn_tilt_single.c @@ -44,15 +44,15 @@ TEST_CASE("Run Forward to Tilt Forward transition with inertia", "[relay_chn][ti prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); // 1. Start in forward direction - relay_chn_run_forward(); + relay_chn_run_forward(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); // 2. Issue tilt forward command - relay_chn_tilt_forward(); + relay_chn_tilt_forward(); // After tilt command, it should immediately stop and then trigger inertia. - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state()); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state()); // Wait for the inertia period (after which the tilt command will be dispatched) vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); @@ -67,12 +67,12 @@ TEST_CASE("Run Reverse to Tilt Reverse transition with inertia", "[relay_chn][ti prepare_channel_for_tilt(RELAY_CHN_CMD_REVERSE); // 1. Start in reverse direction - relay_chn_run_reverse(); + relay_chn_run_reverse(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state()); // 2. Issue tilt reverse command - relay_chn_tilt_reverse(); + relay_chn_tilt_reverse(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state()); @@ -89,9 +89,9 @@ TEST_CASE("FREE to Tilt Forward transition with inertia (prepared)", "[relay_chn TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); // Ensure we are back to FREE // Issue tilt forward command - relay_chn_tilt_forward(); + relay_chn_tilt_forward(); // From FREE state, tilt command should still incur the inertia due to the internal timer logic - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state()); } @@ -104,7 +104,7 @@ TEST_CASE("FREE to Tilt Reverse transition with inertia (prepared)", "[relay_chn TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); // Ensure we are back to FREE // Issue tilt reverse command - relay_chn_tilt_reverse(); + relay_chn_tilt_reverse(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state()); } @@ -120,7 +120,7 @@ TEST_CASE("Tilt Forward to Run Forward transition with inertia", "[relay_chn][ti TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state()); // 2. Issue run forward command - relay_chn_run_forward(); + relay_chn_run_forward(); // From Tilt to Run in the same logical name but in the opposite direction, inertia is expected. TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD_PENDING, relay_chn_get_state()); vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); @@ -138,7 +138,7 @@ TEST_CASE("Tilt Reverse to Run Reverse transition with inertia", "[relay_chn][ti TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state()); // 2. Issue run reverse command - relay_chn_run_reverse(); + relay_chn_run_reverse(); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE_PENDING, relay_chn_get_state()); vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state()); @@ -155,7 +155,7 @@ TEST_CASE("Tilt Forward to Run Reverse transition without inertia", "[relay_chn] TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state()); // 2. Issue run reverse command (opposite direction) - relay_chn_run_reverse(); + relay_chn_run_reverse(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state()); } From 86cc29a33b30e6989262bcdd2274895f827695b1 Mon Sep 17 00:00:00 2001 From: ismail Date: Thu, 4 Sep 2025 18:20:51 +0300 Subject: [PATCH 52/69] Fix static variable names Fixed static variable names according to the ESP-IDF C code formatting guide. Refs #1085 and fixes #1103 --- src/relay_chn_ctl_multi.c | 36 ++++---- src/relay_chn_ctl_single.c | 54 +++++------ src/relay_chn_notify.c | 64 ++++++------- src/relay_chn_nvs.c | 100 ++++++++++----------- src/relay_chn_output.c | 20 ++--- src/relay_chn_run_info.c | 18 ++-- src/relay_chn_tilt.c | 46 +++++----- test_apps/main/test_relay_chn_core_multi.c | 82 ++++++++--------- 8 files changed, 210 insertions(+), 210 deletions(-) diff --git a/src/relay_chn_ctl_multi.c b/src/relay_chn_ctl_multi.c index a581b79..d433fd5 100644 --- a/src/relay_chn_ctl_multi.c +++ b/src/relay_chn_ctl_multi.c @@ -16,7 +16,7 @@ static const char *TAG = "RELAY_CHN_CTL"; -static relay_chn_ctl_t chn_ctls[CONFIG_RELAY_CHN_COUNT]; +static relay_chn_ctl_t s_chn_ctls[CONFIG_RELAY_CHN_COUNT]; esp_err_t relay_chn_ctl_init(relay_chn_output_t *outputs, relay_chn_run_info_t *run_infos) @@ -24,7 +24,7 @@ esp_err_t relay_chn_ctl_init(relay_chn_output_t *outputs, relay_chn_run_info_t * // Initialize all relay channels esp_err_t ret; for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { - relay_chn_ctl_t* chn_ctl = &chn_ctls[i]; + relay_chn_ctl_t* chn_ctl = &s_chn_ctls[i]; relay_chn_output_t* output = &outputs[i]; relay_chn_run_info_t* run_info = &run_infos[i]; @@ -54,7 +54,7 @@ esp_err_t relay_chn_ctl_init(relay_chn_output_t *outputs, relay_chn_run_info_t * void relay_chn_ctl_deinit() { for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { - relay_chn_ctl_t* chn_ctl = &chn_ctls[i]; + relay_chn_ctl_t* chn_ctl = &s_chn_ctls[i]; if (chn_ctl->inertia_timer != NULL) { esp_timer_delete(chn_ctl->inertia_timer); chn_ctl->inertia_timer = NULL; @@ -71,7 +71,7 @@ void relay_chn_ctl_deinit() relay_chn_state_t relay_chn_ctl_get_state(uint8_t chn_id) { return relay_chn_is_channel_id_valid(chn_id) ? - chn_ctls[chn_id].state : RELAY_CHN_STATE_UNDEFINED; + s_chn_ctls[chn_id].state : RELAY_CHN_STATE_UNDEFINED; } esp_err_t relay_chn_ctl_get_state_all(relay_chn_state_t *states) @@ -84,7 +84,7 @@ esp_err_t relay_chn_ctl_get_state_all(relay_chn_state_t *states) ESP_LOGW(TAG, "get_state_all: States have been copied until channel %d since states[%d] is NULL", i, i); break; } - *dest_state = chn_ctls[i].state; + *dest_state = s_chn_ctls[i].state; } return ESP_OK; } @@ -92,7 +92,7 @@ esp_err_t relay_chn_ctl_get_state_all(relay_chn_state_t *states) char *relay_chn_ctl_get_state_str(uint8_t chn_id) { return relay_chn_is_channel_id_valid(chn_id) - ? relay_chn_state_str(chn_ctls[chn_id].state) + ? relay_chn_state_str(s_chn_ctls[chn_id].state) : relay_chn_state_str(RELAY_CHN_STATE_UNDEFINED); } @@ -100,14 +100,14 @@ char *relay_chn_ctl_get_state_str(uint8_t chn_id) static void relay_chn_ctl_issue_cmd_on_all_channels(relay_chn_cmd_t cmd) { for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { - relay_chn_issue_cmd(&chn_ctls[i], cmd); + relay_chn_issue_cmd(&s_chn_ctls[i], cmd); } } void relay_chn_ctl_run_forward(uint8_t chn_id) { if (relay_chn_is_channel_id_valid(chn_id)) - relay_chn_issue_cmd(&chn_ctls[chn_id], RELAY_CHN_CMD_FORWARD); + relay_chn_issue_cmd(&s_chn_ctls[chn_id], RELAY_CHN_CMD_FORWARD); } void relay_chn_ctl_run_forward_all() @@ -118,7 +118,7 @@ void relay_chn_ctl_run_forward_all() void relay_chn_ctl_run_reverse(uint8_t chn_id) { if (relay_chn_is_channel_id_valid(chn_id)) - relay_chn_issue_cmd(&chn_ctls[chn_id], RELAY_CHN_CMD_REVERSE); + relay_chn_issue_cmd(&s_chn_ctls[chn_id], RELAY_CHN_CMD_REVERSE); } void relay_chn_ctl_run_reverse_all() @@ -129,7 +129,7 @@ void relay_chn_ctl_run_reverse_all() void relay_chn_ctl_stop(uint8_t chn_id) { if (relay_chn_is_channel_id_valid(chn_id)) - relay_chn_issue_cmd(&chn_ctls[chn_id], RELAY_CHN_CMD_STOP); + relay_chn_issue_cmd(&s_chn_ctls[chn_id], RELAY_CHN_CMD_STOP); } void relay_chn_ctl_stop_all() @@ -140,7 +140,7 @@ void relay_chn_ctl_stop_all() void relay_chn_ctl_flip_direction(uint8_t chn_id) { if (relay_chn_is_channel_id_valid(chn_id)) - relay_chn_issue_cmd(&chn_ctls[chn_id], RELAY_CHN_CMD_FLIP); + relay_chn_issue_cmd(&s_chn_ctls[chn_id], RELAY_CHN_CMD_FLIP); } void relay_chn_ctl_flip_direction_all() @@ -151,7 +151,7 @@ void relay_chn_ctl_flip_direction_all() relay_chn_direction_t relay_chn_ctl_get_direction(uint8_t chn_id) { return relay_chn_is_channel_id_valid(chn_id) - ? relay_chn_output_get_direction(chn_ctls[chn_id].output) + ? relay_chn_output_get_direction(s_chn_ctls[chn_id].output) : RELAY_CHN_DIRECTION_DEFAULT; } @@ -165,7 +165,7 @@ esp_err_t relay_chn_ctl_get_direction_all(relay_chn_direction_t *directions) ESP_LOGW(TAG, "get_direction_all: Directions have been copied until channel %d since directions[%d] is NULL", i, i); break; } - *dest_direction = relay_chn_output_get_direction(chn_ctls[i].output); + *dest_direction = relay_chn_output_get_direction(s_chn_ctls[i].output); } return ESP_OK; } @@ -173,7 +173,7 @@ esp_err_t relay_chn_ctl_get_direction_all(relay_chn_direction_t *directions) #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT uint16_t relay_chn_ctl_get_run_limit(uint8_t chn_id) { - return relay_chn_is_channel_id_valid(chn_id) ? chn_ctls[chn_id].run_limit_sec : 0; + return relay_chn_is_channel_id_valid(chn_id) ? s_chn_ctls[chn_id].run_limit_sec : 0; } esp_err_t relay_chn_ctl_get_run_limit_all(uint16_t *limits_sec) @@ -186,7 +186,7 @@ esp_err_t relay_chn_ctl_get_run_limit_all(uint16_t *limits_sec) ESP_LOGW(TAG, "get_run_limit_all: Run limits have been copied until channel %d since limits_sec[%d] is NULL", i, i); break; } - *dest_limit_sec = chn_ctls[i].run_limit_sec; + *dest_limit_sec = s_chn_ctls[i].run_limit_sec; } return ESP_OK; } @@ -199,7 +199,7 @@ static void relay_chn_ctl_set_run_limit_common(uint8_t chn_id, uint16_t limit_se else if (limit_sec < CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC) limit_sec = CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC; - chn_ctls[chn_id].run_limit_sec = limit_sec; + s_chn_ctls[chn_id].run_limit_sec = limit_sec; #if CONFIG_RELAY_CHN_ENABLE_NVS relay_chn_nvs_set_run_limit(chn_id, limit_sec); @@ -241,10 +241,10 @@ esp_err_t relay_chn_ctl_set_run_limit_all_with(uint16_t limit_sec) relay_chn_ctl_t *relay_chn_ctl_get(uint8_t chn_id) { - return relay_chn_is_channel_id_valid(chn_id) ? &chn_ctls[chn_id] : NULL; + return relay_chn_is_channel_id_valid(chn_id) ? &s_chn_ctls[chn_id] : NULL; } relay_chn_ctl_t *relay_chn_ctl_get_all(void) { - return chn_ctls; + return s_chn_ctls; } \ No newline at end of file diff --git a/src/relay_chn_ctl_single.c b/src/relay_chn_ctl_single.c index 6840d49..e242030 100644 --- a/src/relay_chn_ctl_single.c +++ b/src/relay_chn_ctl_single.c @@ -16,43 +16,43 @@ static const char *TAG = "RELAY_CHN_CTL"; -static relay_chn_ctl_t chn_ctl; +static relay_chn_ctl_t s_chn_ctl; esp_err_t relay_chn_ctl_init(relay_chn_output_t *output, relay_chn_run_info_t *run_info) { // Initialize the relay channel - chn_ctl.id = 0; // Single channel, so ID is 0 - chn_ctl.state = RELAY_CHN_STATE_IDLE; - chn_ctl.pending_cmd = RELAY_CHN_CMD_NONE; - chn_ctl.output = output; - chn_ctl.run_info = run_info; + s_chn_ctl.id = 0; // Single channel, so ID is 0 + s_chn_ctl.state = RELAY_CHN_STATE_IDLE; + s_chn_ctl.pending_cmd = RELAY_CHN_CMD_NONE; + s_chn_ctl.output = output; + s_chn_ctl.run_info = run_info; #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT uint16_t run_limit_sec = CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC; esp_err_t ret; #if CONFIG_RELAY_CHN_ENABLE_NVS // Load run limit value from NVS - ret = relay_chn_nvs_get_run_limit(chn_ctl.id, &run_limit_sec, CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC); + ret = relay_chn_nvs_get_run_limit(s_chn_ctl.id, &run_limit_sec, CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load run limit from NVS with error: %s", esp_err_to_name(ret)); #endif - chn_ctl.run_limit_sec = run_limit_sec; - ret = relay_chn_init_run_limit_timer(&chn_ctl); + s_chn_ctl.run_limit_sec = run_limit_sec; + ret = relay_chn_init_run_limit_timer(&s_chn_ctl); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize run limit timer"); #endif - return relay_chn_init_timer(&chn_ctl); // Create direction change inertia timer + return relay_chn_init_timer(&s_chn_ctl); // Create direction change inertia timer } void relay_chn_ctl_deinit() { - if (chn_ctl.inertia_timer != NULL) { - esp_timer_delete(chn_ctl.inertia_timer); - chn_ctl.inertia_timer = NULL; + if (s_chn_ctl.inertia_timer != NULL) { + esp_timer_delete(s_chn_ctl.inertia_timer); + s_chn_ctl.inertia_timer = NULL; } #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT - if (chn_ctl.run_limit_timer != NULL) { - esp_timer_delete(chn_ctl.run_limit_timer); - chn_ctl.run_limit_timer = NULL; + if (s_chn_ctl.run_limit_timer != NULL) { + esp_timer_delete(s_chn_ctl.run_limit_timer); + s_chn_ctl.run_limit_timer = NULL; } #endif } @@ -60,43 +60,43 @@ void relay_chn_ctl_deinit() /* relay_chn APIs */ relay_chn_state_t relay_chn_ctl_get_state() { - return chn_ctl.state; + return s_chn_ctl.state; } char *relay_chn_ctl_get_state_str() { - return relay_chn_state_str(chn_ctl.state); + return relay_chn_state_str(s_chn_ctl.state); } void relay_chn_ctl_run_forward() { - relay_chn_issue_cmd(&chn_ctl, RELAY_CHN_CMD_FORWARD); + relay_chn_issue_cmd(&s_chn_ctl, RELAY_CHN_CMD_FORWARD); } void relay_chn_ctl_run_reverse() { - relay_chn_issue_cmd(&chn_ctl, RELAY_CHN_CMD_REVERSE); + relay_chn_issue_cmd(&s_chn_ctl, RELAY_CHN_CMD_REVERSE); } void relay_chn_ctl_stop() { - relay_chn_issue_cmd(&chn_ctl, RELAY_CHN_CMD_STOP); + relay_chn_issue_cmd(&s_chn_ctl, RELAY_CHN_CMD_STOP); } void relay_chn_ctl_flip_direction() { - relay_chn_issue_cmd(&chn_ctl, RELAY_CHN_CMD_FLIP); + relay_chn_issue_cmd(&s_chn_ctl, RELAY_CHN_CMD_FLIP); } relay_chn_direction_t relay_chn_ctl_get_direction() { - return relay_chn_output_get_direction(chn_ctl.output); + return relay_chn_output_get_direction(s_chn_ctl.output); } #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT uint16_t relay_chn_ctl_get_run_limit() { - return chn_ctl.run_limit_sec; +return s_chn_ctl.run_limit_sec; } void relay_chn_ctl_set_run_limit(uint16_t limit_sec) @@ -107,10 +107,10 @@ void relay_chn_ctl_set_run_limit(uint16_t limit_sec) else if (limit_sec < CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC) limit_sec = CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC; - chn_ctl.run_limit_sec = limit_sec; + s_chn_ctl.run_limit_sec = limit_sec; #if CONFIG_RELAY_CHN_ENABLE_NVS - relay_chn_nvs_set_run_limit(chn_ctl.id, limit_sec); + relay_chn_nvs_set_run_limit(s_chn_ctl.id, limit_sec); #endif } #endif @@ -118,5 +118,5 @@ void relay_chn_ctl_set_run_limit(uint16_t limit_sec) relay_chn_ctl_t *relay_chn_ctl_get() { - return &chn_ctl; + return &s_chn_ctl; } \ No newline at end of file diff --git a/src/relay_chn_notify.c b/src/relay_chn_notify.c index 50093e5..a4633eb 100644 --- a/src/relay_chn_notify.c +++ b/src/relay_chn_notify.c @@ -48,22 +48,22 @@ typedef struct { } relay_chn_notify_msg_t; // The list that holds references to the registered listeners. -static List_t listeners; +static List_t s_listeners; -static QueueHandle_t notify_msg_queue = NULL; -static TaskHandle_t notify_task_handle = NULL; +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 (notify_msg_queue != NULL) { + if (s_notify_queue != NULL) { return ESP_OK; } - notify_msg_queue = xQueueCreate(RELAY_CHN_NOTIFY_QUEUE_LEN, sizeof(relay_chn_notify_msg_t)); - if (!notify_msg_queue) { + 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; } @@ -71,34 +71,34 @@ esp_err_t relay_chn_notify_init(void) // 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); + 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(&listeners); + vListInitialise(&s_listeners); return ESP_OK; } void relay_chn_notify_deinit(void) { - if (notify_task_handle != NULL) { - vTaskDelete(notify_task_handle); - notify_task_handle = NULL; + if (s_notify_task != NULL) { + vTaskDelete(s_notify_task); + s_notify_task = NULL; } - if (notify_msg_queue != NULL) { - vQueueDelete(notify_msg_queue); - notify_msg_queue = NULL; + if (s_notify_queue != NULL) { + vQueueDelete(s_notify_queue); + s_notify_queue = NULL; } - if (!listLIST_IS_EMPTY(&listeners)) { + if (!listLIST_IS_EMPTY(&s_listeners)) { // Free the listeners - while (listCURRENT_LIST_LENGTH(&listeners) > 0) { - ListItem_t *pxItem = listGET_HEAD_ENTRY(&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); @@ -118,14 +118,14 @@ void relay_chn_notify_deinit(void) */ static relay_chn_listener_entry_t* find_listener_entry(relay_chn_state_listener_t listener) { - if (listLIST_IS_EMPTY(&listeners)) { + 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(&listeners); - pxListItem != listGET_END_MARKER(&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); @@ -154,7 +154,7 @@ static void do_add_listener(relay_chn_state_listener_t listener) entry->listener = listener; vListInitialiseItem(&(entry->list_item)); listSET_LIST_ITEM_OWNER(&(entry->list_item), (void *)entry); - vListInsertEnd(&listeners, &(entry->list_item)); + vListInsertEnd(&s_listeners, &(entry->list_item)); ESP_LOGD(TAG, "Registered listener %p", listener); } @@ -170,19 +170,19 @@ static void do_remove_listener(relay_chn_state_listener_t listener) ESP_LOGD(TAG, "Listener %p not found for unregistration.", listener); } - if (listLIST_IS_EMPTY(&listeners)) { + if (listLIST_IS_EMPTY(&s_listeners)) { // Flush all pending notifications in the queue - xQueueReset(notify_msg_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(notify_msg_queue, ESP_ERR_INVALID_STATE, TAG, "Notify module not initialized"); + 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(notify_msg_queue, &msg, 0) != pdTRUE) { + if (xQueueSend(s_notify_queue, &msg, 0) != pdTRUE) { ESP_LOGE(TAG, "Notify queue is full, failed to queue add_listener"); return ESP_FAIL; } @@ -195,20 +195,20 @@ void relay_chn_notify_remove_listener(relay_chn_state_listener_t listener) ESP_LOGD(TAG, "Cannot unregister a NULL listener."); return; } - if (!notify_msg_queue) { + 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(notify_msg_queue, &msg, 0) != pdTRUE) { + 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 (!notify_msg_queue) { + if (!s_notify_queue) { return ESP_ERR_INVALID_STATE; } @@ -220,7 +220,7 @@ esp_err_t relay_chn_notify_state_change(uint8_t chn_id, relay_chn_state_t old_st }; // Try to send, do not wait if the queue is full - if (xQueueSend(notify_msg_queue, &msg, 0) != pdTRUE) { + 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; } @@ -231,8 +231,8 @@ 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); + 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) { @@ -247,7 +247,7 @@ static void relay_chn_notify_task(void *arg) { relay_chn_notify_msg_t msg; for (;;) { - if (xQueueReceive(notify_msg_queue, &msg, portMAX_DELAY) == pdTRUE) { + if (xQueueReceive(s_notify_queue, &msg, portMAX_DELAY) == pdTRUE) { switch (msg.cmd) { case RELAY_CHN_NOTIFY_CMD_BROADCAST: { do_notify(&msg.payload.event_data); diff --git a/src/relay_chn_nvs.c b/src/relay_chn_nvs.c index f1d0a9d..c120041 100644 --- a/src/relay_chn_nvs.c +++ b/src/relay_chn_nvs.c @@ -61,10 +61,10 @@ typedef struct { static const char *TAG = "RELAY_CHN_NVS"; -static nvs_handle_t relay_chn_nvs; -static QueueHandle_t nvs_queue_handle = NULL; -static TaskHandle_t nvs_task_handle = NULL; -static SemaphoreHandle_t deinit_sem = NULL; +static nvs_handle_t s_relay_chn_nvs; +static QueueHandle_t s_nvs_ops_queue = NULL; +static TaskHandle_t s_nvs_ops_task = NULL; +static SemaphoreHandle_t s_nvs_deinit_sem = NULL; static void relay_chn_nvs_task(void *arg); @@ -72,25 +72,25 @@ static void relay_chn_nvs_task(void *arg); esp_err_t relay_chn_nvs_init() { // Already initialized? - if (nvs_queue_handle != NULL) { + if (s_nvs_ops_queue != NULL) { return ESP_OK; } - deinit_sem = xSemaphoreCreateBinary(); - if (!deinit_sem) { + s_nvs_deinit_sem = xSemaphoreCreateBinary(); + if (!s_nvs_deinit_sem) { ESP_LOGE(TAG, "Failed to create deinit semaphore"); return ESP_ERR_NO_MEM; } - nvs_queue_handle = xQueueCreate(RELAY_CHN_NVS_QUEUE_LEN, sizeof(relay_chn_nvs_msg_t)); - if (!nvs_queue_handle) { + s_nvs_ops_queue = xQueueCreate(RELAY_CHN_NVS_QUEUE_LEN, sizeof(relay_chn_nvs_msg_t)); + if (!s_nvs_ops_queue) { ESP_LOGE(TAG, "Failed to create NVS queue"); return ESP_ERR_NO_MEM; } BaseType_t res = xTaskCreate(relay_chn_nvs_task, "task_rlch_nvs", RELAY_CHN_NVS_TASK_STACK, NULL, - RELAY_CHN_NVS_TASK_PRIO, &nvs_task_handle); + RELAY_CHN_NVS_TASK_PRIO, &s_nvs_ops_task); if (res != pdPASS) { ESP_LOGE(TAG, "Failed to create NVS task"); return ESP_ERR_NO_MEM; @@ -101,7 +101,7 @@ esp_err_t relay_chn_nvs_init() ret = nvs_open_from_partition(CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME, CONFIG_RELAY_CHN_NVS_NAMESPACE, NVS_READWRITE, - &relay_chn_nvs); + &s_relay_chn_nvs); ESP_RETURN_ON_ERROR(ret, TAG, @@ -110,7 +110,7 @@ esp_err_t relay_chn_nvs_init() CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME, esp_err_to_name(ret)); #else - ret = nvs_open(CONFIG_RELAY_CHN_NVS_NAMESPACE, NVS_READWRITE, &relay_chn_nvs); + ret = nvs_open(CONFIG_RELAY_CHN_NVS_NAMESPACE, NVS_READWRITE, &s_relay_chn_nvs); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to open NVS namespace '%s'", CONFIG_RELAY_CHN_NVS_NAMESPACE); #endif // CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION return ESP_OK; @@ -118,19 +118,19 @@ esp_err_t relay_chn_nvs_init() static esp_err_t relay_chn_nvs_enqueue(relay_chn_nvs_msg_t *msg, const char *op_name) { - if (!nvs_queue_handle) { + if (!s_nvs_ops_queue) { return ESP_ERR_INVALID_STATE; } if (msg->op == RELAY_CHN_NVS_OP_DEINIT || msg->op == RELAY_CHN_NVS_OP_ERASE_ALL) { // Send DEINIT or ERASE_ALL to the front and wait up to 1 sec if needed - if (xQueueSendToFront(nvs_queue_handle, msg, pdMS_TO_TICKS(1000)) != pdTRUE) { + if (xQueueSendToFront(s_nvs_ops_queue, msg, pdMS_TO_TICKS(1000)) != pdTRUE) { ESP_LOGW(TAG, "NVS queue is full, dropping %s for #%d", op_name, msg->ch); return ESP_FAIL; } } else { // Send async - if (xQueueSend(nvs_queue_handle, msg, 0) != pdTRUE) { + if (xQueueSend(s_nvs_ops_queue, msg, 0) != pdTRUE) { ESP_LOGW(TAG, "NVS queue is full, dropping %s for #%d", op_name, msg->ch); return ESP_FAIL; } @@ -151,13 +151,13 @@ esp_err_t relay_chn_nvs_set_direction(uint8_t ch, relay_chn_direction_t directio static esp_err_t relay_chn_nvs_task_set_direction(uint8_t ch, uint8_t direction) { uint8_t direction_val = 0; - esp_err_t ret = nvs_get_u8(relay_chn_nvs, RELAY_CHN_KEY_DIR, &direction_val); + esp_err_t ret = nvs_get_u8(s_relay_chn_nvs, RELAY_CHN_KEY_DIR, &direction_val); if (ret != ESP_OK && ret != ESP_ERR_NVS_NOT_FOUND) { ESP_RETURN_ON_ERROR(ret, TAG, "Failed to get direction from NVS with error: %s", esp_err_to_name(ret)); } direction_val &= ~(1 << ch); // Clear the bit for the channel direction_val |= (((uint8_t) direction) << ch); // Set the new direction bit - ret = nvs_set_u8(relay_chn_nvs, RELAY_CHN_KEY_DIR, direction_val); + ret = nvs_set_u8(s_relay_chn_nvs, RELAY_CHN_KEY_DIR, direction_val); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set direction for channel %d", ch); return ESP_OK; } @@ -167,7 +167,7 @@ esp_err_t relay_chn_nvs_get_direction(uint8_t ch, relay_chn_direction_t *directi ESP_RETURN_ON_FALSE(direction != NULL, ESP_ERR_INVALID_ARG, TAG, "Direction pointer is NULL"); uint8_t direction_val; - esp_err_t ret = nvs_get_u8(relay_chn_nvs, RELAY_CHN_KEY_DIR, &direction_val); + esp_err_t ret = nvs_get_u8(s_relay_chn_nvs, RELAY_CHN_KEY_DIR, &direction_val); if (ret == ESP_ERR_NVS_NOT_FOUND) { *direction = default_val; return ESP_OK; @@ -196,9 +196,9 @@ static esp_err_t relay_chn_nvs_task_set_run_limit(uint8_t ch, uint16_t limit_sec #if CONFIG_RELAY_CHN_COUNT > 1 char key[NVS_KEY_NAME_MAX_SIZE]; snprintf(key, sizeof(key), RELAY_CHN_KEY_RLIM_FMT, ch); - ret = nvs_set_u16(relay_chn_nvs, key, limit_sec); + ret = nvs_set_u16(s_relay_chn_nvs, key, limit_sec); #else - ret = nvs_set_u16(relay_chn_nvs, RELAY_CHN_KEY_RLIM, limit_sec); + ret = nvs_set_u16(s_relay_chn_nvs, RELAY_CHN_KEY_RLIM, limit_sec); #endif ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set run limit for channel %d", ch); return ESP_OK; @@ -212,9 +212,9 @@ esp_err_t relay_chn_nvs_get_run_limit(uint8_t ch, uint16_t *limit_sec, uint16_t #if CONFIG_RELAY_CHN_COUNT > 1 char key[NVS_KEY_NAME_MAX_SIZE]; snprintf(key, sizeof(key), RELAY_CHN_KEY_RLIM_FMT, ch); - ret = nvs_get_u16(relay_chn_nvs, key, limit_sec); + ret = nvs_get_u16(s_relay_chn_nvs, key, limit_sec); #else - ret = nvs_get_u16(relay_chn_nvs, RELAY_CHN_KEY_RLIM, limit_sec); + ret = nvs_get_u16(s_relay_chn_nvs, RELAY_CHN_KEY_RLIM, limit_sec); #endif if (ret == ESP_ERR_NVS_NOT_FOUND) { *limit_sec = default_val; @@ -241,9 +241,9 @@ static esp_err_t relay_chn_nvs_task_set_tilt_sensitivity(uint8_t ch, uint8_t sen #if CONFIG_RELAY_CHN_COUNT > 1 char key[NVS_KEY_NAME_MAX_SIZE]; snprintf(key, sizeof(key), RELAY_CHN_KEY_TSENS_FMT, ch); - ret = nvs_set_u8(relay_chn_nvs, key, sensitivity); + ret = nvs_set_u8(s_relay_chn_nvs, key, sensitivity); #else - ret = nvs_set_u8(relay_chn_nvs, RELAY_CHN_KEY_TSENS, sensitivity); + ret = nvs_set_u8(s_relay_chn_nvs, RELAY_CHN_KEY_TSENS, sensitivity); #endif ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set tilt sensitivity for channel %d", ch); return ESP_OK; @@ -257,9 +257,9 @@ esp_err_t relay_chn_nvs_get_tilt_sensitivity(uint8_t ch, uint8_t *sensitivity, u #if CONFIG_RELAY_CHN_COUNT > 1 char key[NVS_KEY_NAME_MAX_SIZE]; snprintf(key, sizeof(key), RELAY_CHN_KEY_TSENS_FMT, ch); - ret = nvs_get_u8(relay_chn_nvs, key, sensitivity); + ret = nvs_get_u8(s_relay_chn_nvs, key, sensitivity); #else - ret = nvs_get_u8(relay_chn_nvs, RELAY_CHN_KEY_TSENS, sensitivity); + ret = nvs_get_u8(s_relay_chn_nvs, RELAY_CHN_KEY_TSENS, sensitivity); #endif if (ret == ESP_ERR_NVS_NOT_FOUND) { *sensitivity = default_val; @@ -284,9 +284,9 @@ static esp_err_t relay_chn_nvs_task_set_tilt_count(uint8_t ch, uint16_t tilt_cou #if CONFIG_RELAY_CHN_COUNT > 1 char key[NVS_KEY_NAME_MAX_SIZE]; snprintf(key, sizeof(key), RELAY_CHN_KEY_TCNT_FMT, ch); - ret = nvs_set_u16(relay_chn_nvs, key, tilt_count); + ret = nvs_set_u16(s_relay_chn_nvs, key, tilt_count); #else - ret = nvs_set_u16(relay_chn_nvs, RELAY_CHN_KEY_TCNT, tilt_count); + ret = nvs_set_u16(s_relay_chn_nvs, RELAY_CHN_KEY_TCNT, tilt_count); #endif ESP_RETURN_ON_ERROR(ret, TAG, "Failed to save tilt_count tilt counter"); return ESP_OK; @@ -300,9 +300,9 @@ esp_err_t relay_chn_nvs_get_tilt_count(uint8_t ch, uint16_t *tilt_count, uint16_ #if CONFIG_RELAY_CHN_COUNT > 1 char key[NVS_KEY_NAME_MAX_SIZE]; snprintf(key, sizeof(key), RELAY_CHN_KEY_TCNT_FMT, ch); - ret = nvs_get_u16(relay_chn_nvs, key, tilt_count); + ret = nvs_get_u16(s_relay_chn_nvs, key, tilt_count); #else - ret = nvs_get_u16(relay_chn_nvs, RELAY_CHN_KEY_TCNT, tilt_count); + ret = nvs_get_u16(s_relay_chn_nvs, RELAY_CHN_KEY_TCNT, tilt_count); #endif if (ret == ESP_ERR_NVS_NOT_FOUND) { *tilt_count = default_val; @@ -331,38 +331,38 @@ static esp_err_t do_nvs_deinit() static esp_err_t do_nvs_erase_all() { // Flush all pending SET operations since ERASE_ALL requested - xQueueReset(nvs_queue_handle); + xQueueReset(s_nvs_ops_queue); // Erase all key-value pairs in the relay_chn NVS namespace - esp_err_t ret = nvs_erase_all(relay_chn_nvs); + esp_err_t ret = nvs_erase_all(s_relay_chn_nvs); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to erase all keys in NVS namespace '%s'", CONFIG_RELAY_CHN_NVS_NAMESPACE); return ESP_OK; } void relay_chn_nvs_deinit() { - if (nvs_task_handle) { + if (s_nvs_ops_task) { if (do_nvs_deinit() == ESP_OK) { - if (deinit_sem && xSemaphoreTake(deinit_sem, pdMS_TO_TICKS(2000)) != pdTRUE) { + if (s_nvs_deinit_sem && xSemaphoreTake(s_nvs_deinit_sem, pdMS_TO_TICKS(2000)) != pdTRUE) { ESP_LOGE(TAG, "Failed to get deinit confirmation from NVS task. Forcing deletion."); - vTaskDelete(nvs_task_handle); // Last resort + vTaskDelete(s_nvs_ops_task); // Last resort } } else { ESP_LOGE(TAG, "Failed to send deinit message to NVS task. Forcing deletion."); - vTaskDelete(nvs_task_handle); + vTaskDelete(s_nvs_ops_task); } } - if (nvs_queue_handle) { - vQueueDelete(nvs_queue_handle); - nvs_queue_handle = NULL; + if (s_nvs_ops_queue) { + vQueueDelete(s_nvs_ops_queue); + s_nvs_ops_queue = NULL; } - if (deinit_sem) { - vSemaphoreDelete(deinit_sem); - deinit_sem = NULL; + if (s_nvs_deinit_sem) { + vSemaphoreDelete(s_nvs_deinit_sem); + s_nvs_deinit_sem = NULL; } // Close NVS handle here, after task has stopped and queue is deleted. - nvs_close(relay_chn_nvs); - nvs_task_handle = NULL; + nvs_close(s_relay_chn_nvs); + s_nvs_ops_task = NULL; } static esp_err_t relay_chn_nvs_task_process_message(const relay_chn_nvs_msg_t *msg, bool *running, bool *dirty) @@ -417,7 +417,7 @@ static void relay_chn_nvs_task(void *arg) while (running) { // Block indefinitely waiting for the first message of a potential batch. - if (xQueueReceive(nvs_queue_handle, &msg, portMAX_DELAY) == pdTRUE) { + if (xQueueReceive(s_nvs_ops_queue, &msg, portMAX_DELAY) == pdTRUE) { // A batch of operations has started. Use a do-while to process the first message // and any subsequent messages that arrive within the timeout. do { @@ -425,11 +425,11 @@ static void relay_chn_nvs_task(void *arg) if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to process operation %d for #%d with error %s", msg.op, msg.ch, esp_err_to_name(ret)); } - } while (running && xQueueReceive(nvs_queue_handle, &msg, pdMS_TO_TICKS(RELAY_CHN_NVS_COMMIT_TIMEOUT_MS)) == pdTRUE); + } while (running && xQueueReceive(s_nvs_ops_queue, &msg, pdMS_TO_TICKS(RELAY_CHN_NVS_COMMIT_TIMEOUT_MS)) == pdTRUE); // The burst of messages is over (timeout occurred). Commit if anything changed. if (dirty) { - esp_err_t commit_ret = nvs_commit(relay_chn_nvs); + esp_err_t commit_ret = nvs_commit(s_relay_chn_nvs); if (commit_ret == ESP_OK) { dirty = false; } else { @@ -442,11 +442,11 @@ static void relay_chn_nvs_task(void *arg) // Before exiting, do one final commit if there are pending changes. if (dirty) { - if (nvs_commit(relay_chn_nvs) != ESP_OK) { + if (nvs_commit(s_relay_chn_nvs) != ESP_OK) { ESP_LOGE(TAG, "Final NVS commit failed on deinit"); } } - xSemaphoreGive(deinit_sem); - nvs_task_handle = NULL; + xSemaphoreGive(s_nvs_deinit_sem); + s_nvs_ops_task = NULL; vTaskDelete(NULL); } \ No newline at end of file diff --git a/src/relay_chn_output.c b/src/relay_chn_output.c index 2b1ce9d..100b9f8 100644 --- a/src/relay_chn_output.c +++ b/src/relay_chn_output.c @@ -17,9 +17,9 @@ static const char *TAG = "RELAY_CHN_OUTPUT"; #if CONFIG_RELAY_CHN_COUNT > 1 -static relay_chn_output_t outputs[CONFIG_RELAY_CHN_COUNT]; +static relay_chn_output_t s_outputs[CONFIG_RELAY_CHN_COUNT]; #else -static relay_chn_output_t output; +static relay_chn_output_t s_output; #endif @@ -90,7 +90,7 @@ esp_err_t relay_chn_output_init(const uint8_t* gpio_map, uint8_t gpio_count) #if CONFIG_RELAY_CHN_COUNT > 1 for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { - relay_chn_output_t* output = &outputs[i]; + relay_chn_output_t* output = &s_outputs[i]; int gpio_index = i << 1; // gpio_index = i * 2 gpio_num_t forward_pin = (gpio_num_t) gpio_map[gpio_index]; gpio_num_t reverse_pin = (gpio_num_t) gpio_map[gpio_index + 1]; @@ -111,7 +111,7 @@ esp_err_t relay_chn_output_init(const uint8_t* gpio_map, uint8_t gpio_count) ret = relay_chn_output_load_direction(0, &direction); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load direction from storage for channel %d", 0); #endif - ret = relay_chn_output_ctl_init(&output, gpio_map[0], gpio_map[1], direction); + ret = relay_chn_output_ctl_init(&s_output, gpio_map[0], gpio_map[1], direction); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize relay channel"); #endif return ESP_OK; @@ -127,10 +127,10 @@ void relay_chn_output_deinit() { #if CONFIG_RELAY_CHN_COUNT > 1 for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { - relay_chn_output_ctl_deinit(&outputs[i]); + relay_chn_output_ctl_deinit(&s_outputs[i]); } #else - relay_chn_output_ctl_deinit(&output); + relay_chn_output_ctl_deinit(&s_output); #endif // CONFIG_RELAY_CHN_COUNT > 1 } @@ -140,17 +140,17 @@ relay_chn_output_t *relay_chn_output_get(uint8_t chn_id) if (!relay_chn_is_channel_id_valid(chn_id)) { return NULL; } - return &outputs[chn_id]; + return &s_outputs[chn_id]; } relay_chn_output_t *relay_chn_output_get_all(void) { - return outputs; + return s_outputs; } #else relay_chn_output_t *relay_chn_output_get(void) { - return &output; + return &s_output; } #endif // CONFIG_RELAY_CHN_COUNT > 1 @@ -193,7 +193,7 @@ void relay_chn_output_flip(relay_chn_output_t *output) uint8_t ch = 0; #if CONFIG_RELAY_CHN_COUNT > 1 for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { - if (output == &outputs[i]) { + if (output == &s_outputs[i]) { ch = i; break; } diff --git a/src/relay_chn_run_info.c b/src/relay_chn_run_info.c index 9a21f47..172cc36 100644 --- a/src/relay_chn_run_info.c +++ b/src/relay_chn_run_info.c @@ -9,21 +9,21 @@ #if CONFIG_RELAY_CHN_COUNT > 1 -static relay_chn_run_info_t run_infos[CONFIG_RELAY_CHN_COUNT]; +static relay_chn_run_info_t s_run_infos[CONFIG_RELAY_CHN_COUNT]; #else -static relay_chn_run_info_t run_info; +static relay_chn_run_info_t s_run_info; #endif void relay_chn_run_info_init() { #if CONFIG_RELAY_CHN_COUNT > 1 for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { - run_infos[i].last_run_cmd = RELAY_CHN_CMD_NONE; - run_infos[i].last_run_cmd_time_ms = 0; + s_run_infos[i].last_run_cmd = RELAY_CHN_CMD_NONE; + s_run_infos[i].last_run_cmd_time_ms = 0; } #else - run_info.last_run_cmd = RELAY_CHN_CMD_NONE; - run_info.last_run_cmd_time_ms = 0; + s_run_info.last_run_cmd = RELAY_CHN_CMD_NONE; + s_run_info.last_run_cmd_time_ms = 0; #endif } @@ -33,17 +33,17 @@ relay_chn_run_info_t *relay_chn_run_info_get(uint8_t chn_id) if (!relay_chn_is_channel_id_valid(chn_id)) { return NULL; } - return &run_infos[chn_id]; + return &s_run_infos[chn_id]; } relay_chn_run_info_t *relay_chn_run_info_get_all() { - return run_infos; + return s_run_infos; } #else relay_chn_run_info_t *relay_chn_run_info_get() { - return &run_info; + return &s_run_info; } #endif // CONFIG_RELAY_CHN_COUNT > 1 diff --git a/src/relay_chn_tilt.c b/src/relay_chn_tilt.c index b0529d3..c9552ad 100644 --- a/src/relay_chn_tilt.c +++ b/src/relay_chn_tilt.c @@ -71,9 +71,9 @@ typedef struct relay_chn_tilt_ctl { #if CONFIG_RELAY_CHN_COUNT > 1 -static relay_chn_tilt_ctl_t tilt_ctls[CONFIG_RELAY_CHN_COUNT]; +static relay_chn_tilt_ctl_t s_tilt_ctls[CONFIG_RELAY_CHN_COUNT]; #else -static relay_chn_tilt_ctl_t tilt_ctl; +static relay_chn_tilt_ctl_t s_tilt_ctl; #endif @@ -209,7 +209,7 @@ uint8_t relay_chn_tilt_get_default_sensitivity() static void relay_chn_tilt_issue_cmd_on_all_channels(relay_chn_tilt_cmd_t cmd) { for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { - relay_chn_tilt_ctl_t* tilt_ctl = &tilt_ctls[i]; + relay_chn_tilt_ctl_t* tilt_ctl = &s_tilt_ctls[i]; relay_chn_tilt_issue_cmd(tilt_ctl, cmd); } } @@ -217,21 +217,21 @@ static void relay_chn_tilt_issue_cmd_on_all_channels(relay_chn_tilt_cmd_t cmd) void relay_chn_tilt_auto(uint8_t chn_id) { if (relay_chn_is_channel_id_valid(chn_id)) { - relay_chn_tilt_issue_auto(&tilt_ctls[chn_id]); + relay_chn_tilt_issue_auto(&s_tilt_ctls[chn_id]); } } void relay_chn_tilt_auto_all() { for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { - relay_chn_tilt_issue_auto(&tilt_ctls[i]); + relay_chn_tilt_issue_auto(&s_tilt_ctls[i]); } } void relay_chn_tilt_forward(uint8_t chn_id) { if (relay_chn_is_channel_id_valid(chn_id)) { - relay_chn_tilt_issue_cmd(&tilt_ctls[chn_id], RELAY_CHN_TILT_CMD_FORWARD); + relay_chn_tilt_issue_cmd(&s_tilt_ctls[chn_id], RELAY_CHN_TILT_CMD_FORWARD); } } @@ -243,7 +243,7 @@ void relay_chn_tilt_forward_all() void relay_chn_tilt_reverse(uint8_t chn_id) { if (relay_chn_is_channel_id_valid(chn_id)) { - relay_chn_tilt_issue_cmd(&tilt_ctls[chn_id], RELAY_CHN_TILT_CMD_REVERSE); + relay_chn_tilt_issue_cmd(&s_tilt_ctls[chn_id], RELAY_CHN_TILT_CMD_REVERSE); } } @@ -255,7 +255,7 @@ void relay_chn_tilt_reverse_all() void relay_chn_tilt_stop(uint8_t chn_id) { if (!relay_chn_is_channel_id_valid(chn_id)) { - relay_chn_tilt_dispatch_cmd(&tilt_ctls[chn_id], RELAY_CHN_TILT_CMD_STOP); + relay_chn_tilt_dispatch_cmd(&s_tilt_ctls[chn_id], RELAY_CHN_TILT_CMD_STOP); } } @@ -268,22 +268,22 @@ void relay_chn_tilt_stop_all() void relay_chn_tilt_auto() { - relay_chn_tilt_issue_auto(&tilt_ctl); + relay_chn_tilt_issue_auto(&s_tilt_ctl); } void relay_chn_tilt_forward() { - relay_chn_tilt_issue_cmd(&tilt_ctl, RELAY_CHN_TILT_CMD_FORWARD); + relay_chn_tilt_issue_cmd(&s_tilt_ctl, RELAY_CHN_TILT_CMD_FORWARD); } void relay_chn_tilt_reverse() { - relay_chn_tilt_issue_cmd(&tilt_ctl, RELAY_CHN_TILT_CMD_REVERSE); + relay_chn_tilt_issue_cmd(&s_tilt_ctl, RELAY_CHN_TILT_CMD_REVERSE); } void relay_chn_tilt_stop() { - relay_chn_tilt_dispatch_cmd(&tilt_ctl, RELAY_CHN_TILT_CMD_STOP); + relay_chn_tilt_dispatch_cmd(&s_tilt_ctl, RELAY_CHN_TILT_CMD_STOP); } #endif // CONFIG_RELAY_CHN_COUNT > 1 @@ -335,7 +335,7 @@ void relay_chn_tilt_set_sensitivity(uint8_t chn_id, uint8_t sensitivity) { if (relay_chn_is_channel_id_valid(chn_id)) { ADJUST_TILT_SENS_BOUNDARIES(sensitivity); - relay_chn_tilt_compute_set_sensitivity(&tilt_ctls[chn_id], sensitivity); + relay_chn_tilt_compute_set_sensitivity(&s_tilt_ctls[chn_id], sensitivity); #if CONFIG_RELAY_CHN_ENABLE_NVS relay_chn_nvs_set_tilt_sensitivity(chn_id, sensitivity); @@ -354,7 +354,7 @@ esp_err_t relay_chn_tilt_set_sensitivity_all(uint8_t *sensitivities) break; } ADJUST_TILT_SENS_BOUNDARIES(*src_sensitivity); - relay_chn_tilt_compute_set_sensitivity(&tilt_ctls[i], *src_sensitivity); + relay_chn_tilt_compute_set_sensitivity(&s_tilt_ctls[i], *src_sensitivity); #if CONFIG_RELAY_CHN_ENABLE_NVS relay_chn_nvs_set_tilt_sensitivity(i, *src_sensitivity); #endif // CONFIG_RELAY_CHN_ENABLE_NVS @@ -366,7 +366,7 @@ void relay_chn_tilt_set_sensitivity_all_with(uint8_t sensitivity) { for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { ADJUST_TILT_SENS_BOUNDARIES(sensitivity); - relay_chn_tilt_compute_set_sensitivity(&tilt_ctls[i], sensitivity); + relay_chn_tilt_compute_set_sensitivity(&s_tilt_ctls[i], sensitivity); #if CONFIG_RELAY_CHN_ENABLE_NVS relay_chn_nvs_set_tilt_sensitivity(i, sensitivity); #endif // CONFIG_RELAY_CHN_ENABLE_NVS @@ -376,7 +376,7 @@ void relay_chn_tilt_set_sensitivity_all_with(uint8_t sensitivity) uint8_t relay_chn_tilt_get_sensitivity(uint8_t chn_id) { return relay_chn_is_channel_id_valid(chn_id) ? - tilt_ctls[chn_id].tilt_timing.sensitivity : 0; + s_tilt_ctls[chn_id].tilt_timing.sensitivity : 0; } esp_err_t relay_chn_tilt_get_sensitivity_all(uint8_t *sensitivities) @@ -389,7 +389,7 @@ esp_err_t relay_chn_tilt_get_sensitivity_all(uint8_t *sensitivities) ESP_LOGW(TAG, "get_sensitivity_all: Sensitivites have been copied until channel %d since sensitivities[%d] is NULL", i, i); break; } - *dest_sensitivity = tilt_ctls[i].tilt_timing.sensitivity; + *dest_sensitivity = s_tilt_ctls[i].tilt_timing.sensitivity; } return ESP_OK; } @@ -399,7 +399,7 @@ esp_err_t relay_chn_tilt_get_sensitivity_all(uint8_t *sensitivities) void relay_chn_tilt_set_sensitivity(uint8_t sensitivity) { ADJUST_TILT_SENS_BOUNDARIES(sensitivity); - relay_chn_tilt_compute_set_sensitivity(&tilt_ctl, sensitivity); + relay_chn_tilt_compute_set_sensitivity(&s_tilt_ctl, sensitivity); #if CONFIG_RELAY_CHN_ENABLE_NVS relay_chn_nvs_set_tilt_sensitivity(0, sensitivity); @@ -408,7 +408,7 @@ void relay_chn_tilt_set_sensitivity(uint8_t sensitivity) uint8_t relay_chn_tilt_get_sensitivity() { - return tilt_ctl.tilt_timing.sensitivity; + return s_tilt_ctl.tilt_timing.sensitivity; } #endif // CONFIG_RELAY_CHN_COUNT > 1 @@ -707,7 +707,7 @@ esp_err_t relay_chn_tilt_init(relay_chn_ctl_t *chn_ctls) sensitivity = RELAY_CHN_TILT_DEFAULT_SENSITIVITY; tilt_count = 0; #endif // CONFIG_RELAY_CHN_ENABLE_NVS == 1 - ret = relay_chn_tilt_ctl_init(&tilt_ctls[i], &chn_ctls[i], tilt_count, sensitivity); + ret = relay_chn_tilt_ctl_init(&s_tilt_ctls[i], &chn_ctls[i], tilt_count, sensitivity); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to init tilt control for channel %d", i); } return ESP_OK; @@ -720,7 +720,7 @@ esp_err_t relay_chn_tilt_init(relay_chn_ctl_t *chn_ctls) ret = relay_chn_tilt_load_tilt_count(0, &tilt_count); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load tilt count for channel %d", 0); #endif // CONFIG_RELAY_CHN_ENABLE_NVS == 1 - return relay_chn_tilt_ctl_init(&tilt_ctl, chn_ctls, tilt_count, sensitivity); + return relay_chn_tilt_ctl_init(&s_tilt_ctl, chn_ctls, tilt_count, sensitivity); #endif // CONFIG_RELAY_CHN_COUNT > 1 } @@ -742,9 +742,9 @@ void relay_chn_tilt_deinit() { #if CONFIG_RELAY_CHN_COUNT > 1 for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { - relay_chn_tilt_ctl_deinit(&tilt_ctls[i]); + relay_chn_tilt_ctl_deinit(&s_tilt_ctls[i]); } #else - relay_chn_tilt_ctl_deinit(&tilt_ctl); + relay_chn_tilt_ctl_deinit(&s_tilt_ctl); #endif // CONFIG_RELAY_CHN_COUNT > 1 } \ No newline at end of file diff --git a/test_apps/main/test_relay_chn_core_multi.c b/test_apps/main/test_relay_chn_core_multi.c index ecd6b4b..7da4008 100644 --- a/test_apps/main/test_relay_chn_core_multi.c +++ b/test_apps/main/test_relay_chn_core_multi.c @@ -6,20 +6,20 @@ #include "test_common.h" -relay_chn_state_t states[CONFIG_RELAY_CHN_COUNT], expect_states[CONFIG_RELAY_CHN_COUNT]; -relay_chn_direction_t directions[CONFIG_RELAY_CHN_COUNT], expect_directions[CONFIG_RELAY_CHN_COUNT]; +static relay_chn_state_t s_states[CONFIG_RELAY_CHN_COUNT], s_expect_states[CONFIG_RELAY_CHN_COUNT]; +static relay_chn_direction_t s_directions[CONFIG_RELAY_CHN_COUNT], s_expect_directions[CONFIG_RELAY_CHN_COUNT]; static void test_set_expected_state_all(relay_chn_state_t state) { for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { - expect_states[i] = state; + s_expect_states[i] = state; } } static void test_set_expected_direction_all(relay_chn_direction_t direction) { for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { - expect_directions[i] = direction; + s_expect_directions[i] = direction; } } @@ -231,24 +231,24 @@ TEST_CASE("Forward to Reverse transition with opposite inertia", "[relay_chn][co // 1. Start in forward direction relay_chn_run_forward_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Short delay for state stabilization - TEST_ESP_OK(relay_chn_get_state_all(states)); + TEST_ESP_OK(relay_chn_get_state_all(s_states)); test_set_expected_state_all(RELAY_CHN_STATE_FORWARD); - TEST_ASSERT_EQUAL_UINT_ARRAY(expect_states, states, CONFIG_RELAY_CHN_COUNT); + TEST_ASSERT_EQUAL_UINT_ARRAY(s_expect_states, s_states, CONFIG_RELAY_CHN_COUNT); // 2. Issue reverse command relay_chn_run_reverse_all(); // Immediately after the command, the motor should be stopped vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - TEST_ESP_OK(relay_chn_get_state_all(states)); + TEST_ESP_OK(relay_chn_get_state_all(s_states)); test_set_expected_state_all(RELAY_CHN_STATE_REVERSE_PENDING); - TEST_ASSERT_EQUAL_UINT_ARRAY(expect_states, states, CONFIG_RELAY_CHN_COUNT); + TEST_ASSERT_EQUAL_UINT_ARRAY(s_expect_states, s_states, CONFIG_RELAY_CHN_COUNT); // Wait for the inertia period (after which the reverse command will be dispatched) vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); // Should now be in reverse state - TEST_ESP_OK(relay_chn_get_state_all(states)); + TEST_ESP_OK(relay_chn_get_state_all(s_states)); test_set_expected_state_all(RELAY_CHN_STATE_REVERSE); - TEST_ASSERT_EQUAL_UINT_ARRAY(expect_states, states, CONFIG_RELAY_CHN_COUNT); + TEST_ASSERT_EQUAL_UINT_ARRAY(s_expect_states, s_states, CONFIG_RELAY_CHN_COUNT); } // TEST_CASE: Test transition from reverse to forward with inertia and state checks @@ -258,22 +258,22 @@ TEST_CASE("Reverse to Forward transition with opposite inertia", "[relay_chn][co // 1. Start in reverse direction relay_chn_run_reverse_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - TEST_ESP_OK(relay_chn_get_state_all(states)); + TEST_ESP_OK(relay_chn_get_state_all(s_states)); test_set_expected_state_all(RELAY_CHN_STATE_REVERSE); - TEST_ASSERT_EQUAL_UINT_ARRAY(expect_states, states, CONFIG_RELAY_CHN_COUNT); + TEST_ASSERT_EQUAL_UINT_ARRAY(s_expect_states, s_states, CONFIG_RELAY_CHN_COUNT); // 2. Issue forward command relay_chn_run_forward_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - TEST_ESP_OK(relay_chn_get_state_all(states)); + TEST_ESP_OK(relay_chn_get_state_all(s_states)); test_set_expected_state_all(RELAY_CHN_STATE_FORWARD_PENDING); - TEST_ASSERT_EQUAL_UINT_ARRAY(expect_states, states, CONFIG_RELAY_CHN_COUNT); + TEST_ASSERT_EQUAL_UINT_ARRAY(s_expect_states, s_states, CONFIG_RELAY_CHN_COUNT); // Wait for inertia vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); - TEST_ESP_OK(relay_chn_get_state_all(states)); + TEST_ESP_OK(relay_chn_get_state_all(s_states)); test_set_expected_state_all(RELAY_CHN_STATE_FORWARD); - TEST_ASSERT_EQUAL_UINT_ARRAY(expect_states, states, CONFIG_RELAY_CHN_COUNT); + TEST_ASSERT_EQUAL_UINT_ARRAY(s_expect_states, s_states, CONFIG_RELAY_CHN_COUNT); } // TEST_CASE: Test issuing the same run command while already running (no inertia expected) @@ -283,18 +283,18 @@ TEST_CASE("Running in same direction does not incur inertia", "[relay_chn][core] // 1. Start in forward direction relay_chn_run_forward_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - TEST_ESP_OK(relay_chn_get_state_all(states)); + TEST_ESP_OK(relay_chn_get_state_all(s_states)); test_set_expected_state_all(RELAY_CHN_STATE_FORWARD); - TEST_ASSERT_EQUAL_UINT_ARRAY(expect_states, states, CONFIG_RELAY_CHN_COUNT); + TEST_ASSERT_EQUAL_UINT_ARRAY(s_expect_states, s_states, CONFIG_RELAY_CHN_COUNT); // 2. Issue the same forward command again relay_chn_run_forward_all(); // As per the code, is_direction_opposite_to_current_motion should return false, so no inertia. // Just a short delay to check state remains the same. vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - TEST_ESP_OK(relay_chn_get_state_all(states)); + TEST_ESP_OK(relay_chn_get_state_all(s_states)); test_set_expected_state_all(RELAY_CHN_STATE_FORWARD); - TEST_ASSERT_EQUAL_UINT_ARRAY(expect_states, states, CONFIG_RELAY_CHN_COUNT); + TEST_ASSERT_EQUAL_UINT_ARRAY(s_expect_states, s_states, CONFIG_RELAY_CHN_COUNT); } // ### Direction Flipping Tests @@ -336,44 +336,44 @@ TEST_CASE("All channels direction can be flipped simultaneously", "[relay_chn][c vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); // 2. Verify all channels are flipped - TEST_ESP_OK(relay_chn_get_direction_all(directions)); + TEST_ESP_OK(relay_chn_get_direction_all(s_directions)); test_set_expected_direction_all(RELAY_CHN_DIRECTION_FLIPPED); - TEST_ASSERT_EQUAL_UINT_ARRAY(expect_directions, directions, CONFIG_RELAY_CHN_COUNT); + TEST_ASSERT_EQUAL_UINT_ARRAY(s_expect_directions, s_directions, CONFIG_RELAY_CHN_COUNT); // 3. Flip all back relay_chn_flip_direction_all(); vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); // 4. Verify all channels are back to default - TEST_ESP_OK(relay_chn_get_direction_all(directions)); + TEST_ESP_OK(relay_chn_get_direction_all(s_directions)); test_set_expected_direction_all(RELAY_CHN_DIRECTION_DEFAULT); - TEST_ASSERT_EQUAL_UINT_ARRAY(expect_directions, directions, CONFIG_RELAY_CHN_COUNT); + TEST_ASSERT_EQUAL_UINT_ARRAY(s_expect_directions, s_directions, CONFIG_RELAY_CHN_COUNT); } TEST_CASE("Flipping a running channel stops it and flips direction", "[relay_chn][core][direction]") { // 1. Start channel running and verify state relay_chn_run_forward_all(); - TEST_ESP_OK(relay_chn_get_state_all(states)); + TEST_ESP_OK(relay_chn_get_state_all(s_states)); test_set_expected_state_all(RELAY_CHN_STATE_FORWARD); - TEST_ASSERT_EQUAL_UINT_ARRAY(expect_states, states, CONFIG_RELAY_CHN_COUNT); + TEST_ASSERT_EQUAL_UINT_ARRAY(s_expect_states, s_states, CONFIG_RELAY_CHN_COUNT); // 2. Flip the direction while running relay_chn_flip_direction_all(); // 3. The channel should stop as part of the flip process - TEST_ESP_OK(relay_chn_get_state_all(states)); + TEST_ESP_OK(relay_chn_get_state_all(s_states)); test_set_expected_state_all(RELAY_CHN_STATE_STOPPED); - TEST_ASSERT_EQUAL_UINT_ARRAY(expect_states, states, CONFIG_RELAY_CHN_COUNT); + TEST_ASSERT_EQUAL_UINT_ARRAY(s_expect_states, s_states, CONFIG_RELAY_CHN_COUNT); // 4. Wait for the flip inertia to pass, after which it should be idle and FLIPPED vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); - TEST_ESP_OK(relay_chn_get_state_all(states)); + TEST_ESP_OK(relay_chn_get_state_all(s_states)); test_set_expected_state_all(RELAY_CHN_STATE_IDLE); - TEST_ASSERT_EQUAL_UINT_ARRAY(expect_states, states, CONFIG_RELAY_CHN_COUNT); + TEST_ASSERT_EQUAL_UINT_ARRAY(s_expect_states, s_states, CONFIG_RELAY_CHN_COUNT); - TEST_ESP_OK(relay_chn_get_direction_all(directions)); + TEST_ESP_OK(relay_chn_get_direction_all(s_directions)); test_set_expected_direction_all(RELAY_CHN_DIRECTION_FLIPPED); - TEST_ASSERT_EQUAL_UINT_ARRAY(expect_directions, directions, CONFIG_RELAY_CHN_COUNT); + TEST_ASSERT_EQUAL_UINT_ARRAY(s_expect_directions, s_directions, CONFIG_RELAY_CHN_COUNT); } TEST_CASE("Direction flip handles invalid channel ID gracefully", "[relay_chn][core][direction]") @@ -387,7 +387,7 @@ TEST_CASE("Direction flip handles invalid channel ID gracefully", "[relay_chn][c TEST_CASE("get_state_all retrieves all channel states", "[relay_chn][core][batch]") { // 1. All should be IDLE initially - TEST_ESP_OK(relay_chn_get_state_all(states)); + TEST_ESP_OK(relay_chn_get_state_all(s_states)); test_set_expected_state_all(RELAY_CHN_STATE_IDLE); // 2. Set some states @@ -403,30 +403,30 @@ TEST_CASE("get_state_all retrieves all channel states", "[relay_chn][core][batch // 3. Get all states and verify for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { if (i % 2 == 0) { - expect_states[i] = RELAY_CHN_STATE_FORWARD; + s_expect_states[i] = RELAY_CHN_STATE_FORWARD; } else { - expect_states[i] = RELAY_CHN_STATE_REVERSE; + s_expect_states[i] = RELAY_CHN_STATE_REVERSE; } } - TEST_ESP_OK(relay_chn_get_state_all(states)); - TEST_ASSERT_EQUAL_UINT_ARRAY(expect_states, states, CONFIG_RELAY_CHN_COUNT); + TEST_ESP_OK(relay_chn_get_state_all(s_states)); + TEST_ASSERT_EQUAL_UINT_ARRAY(s_expect_states, s_states, CONFIG_RELAY_CHN_COUNT); } TEST_CASE("get_direction_all retrieves all channel directions", "[relay_chn][core][direction][batch]") { // 1. All should be default initially - TEST_ESP_OK(relay_chn_get_direction_all(directions)); + TEST_ESP_OK(relay_chn_get_direction_all(s_directions)); test_set_expected_direction_all(RELAY_CHN_DIRECTION_DEFAULT); - TEST_ASSERT_EQUAL_UINT_ARRAY(expect_directions, directions, CONFIG_RELAY_CHN_COUNT); + TEST_ASSERT_EQUAL_UINT_ARRAY(s_expect_directions, s_directions, CONFIG_RELAY_CHN_COUNT); // 2. Flip all relay_chn_flip_direction_all(); vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); // 3. Get all directions and verify - TEST_ESP_OK(relay_chn_get_direction_all(directions)); + TEST_ESP_OK(relay_chn_get_direction_all(s_directions)); test_set_expected_direction_all(RELAY_CHN_DIRECTION_FLIPPED); - TEST_ASSERT_EQUAL_UINT_ARRAY(expect_directions, directions, CONFIG_RELAY_CHN_COUNT); + TEST_ASSERT_EQUAL_UINT_ARRAY(s_expect_directions, s_directions, CONFIG_RELAY_CHN_COUNT); } TEST_CASE("get_all functions handle NULL arguments", "[relay_chn][core][batch]") From 61ca2197e1cb64e8a3df4729bd2ff3ba93cb8014 Mon Sep 17 00:00:00 2001 From: ismail Date: Thu, 4 Sep 2025 18:23:05 +0300 Subject: [PATCH 53/69] Fix conditional compilation issues. Fixed include directory settings and conditional includes in CMakeLists and test_common.c. Refs #1085. --- test_apps/main/CMakeLists.txt | 4 ++-- test_apps/main/test_common.c | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/test_apps/main/CMakeLists.txt b/test_apps/main/CMakeLists.txt index 3b8ff4d..fc44d68 100644 --- a/test_apps/main/CMakeLists.txt +++ b/test_apps/main/CMakeLists.txt @@ -3,7 +3,8 @@ set(srcs "test_common.c" "test_relay_chn_notify_common.c" "test_app_main.c") -set(incdirs ".") +set(incdirs "." + "../../private_include") # === Selective compilation based on channel count === if(CONFIG_RELAY_CHN_COUNT GREATER 1) @@ -23,7 +24,6 @@ if(CONFIG_RELAY_CHN_ENABLE_TILTING) endif() if(CONFIG_RELAY_CHN_ENABLE_NVS) - list(APPEND incdirs "../../private_include") list(APPEND srcs "../../src/relay_chn_nvs.c") if(CONFIG_RELAY_CHN_COUNT GREATER 1) list(APPEND srcs "test_relay_chn_nvs_multi.c") diff --git a/test_apps/main/test_common.c b/test_apps/main/test_common.c index b15b6c9..3c6786a 100644 --- a/test_apps/main/test_common.c +++ b/test_apps/main/test_common.c @@ -6,7 +6,10 @@ #include "test_common.h" #include "relay_chn_ctl.h" // For resetting the channels + +#if CONFIG_RELAY_CHN_ENABLE_TILTING #include "relay_chn_tilt.h" // For resetting tilt count +#endif const char *TEST_TAG = "RELAY_CHN_TEST"; From fbf8b5dfc8d496c9b918e5227ff383b990c62e61 Mon Sep 17 00:00:00 2001 From: ismail Date: Tue, 9 Sep 2025 09:14:28 +0300 Subject: [PATCH 54/69] Fix mispelled config parameter Fixed a mispelled configuration parameter. Refs #1105 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cdc9e67..01a511d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,7 @@ else() list(APPEND srcs "src/relay_chn_ctl_single.c") endif() -if(CONFIG_RELAY_CHN_NVS) +if(CONFIG_RELAY_CHN_ENABLE_NVS) list(APPEND srcs "src/relay_chn_nvs.c") endif() From 087deb338e909ff57f3655e2c0ada3f09eb55283 Mon Sep 17 00:00:00 2001 From: ismail Date: Tue, 9 Sep 2025 09:26:24 +0300 Subject: [PATCH 55/69] Fix STOP command issuing when idle Fixed almost unconditional STOP command issuing when the channel is idle. Refs #1104, #1105 and closes #1107. --- src/relay_chn_core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/relay_chn_core.c b/src/relay_chn_core.c index d1347cc..2d8fba8 100644 --- a/src/relay_chn_core.c +++ b/src/relay_chn_core.c @@ -216,8 +216,8 @@ void relay_chn_issue_cmd(relay_chn_ctl_t* chn_ctl, relay_chn_cmd_t cmd) } if (cmd == RELAY_CHN_CMD_STOP) { - if (chn_ctl->state == RELAY_CHN_STATE_STOPPED) { - return; // Do nothing if already stopped + if (chn_ctl->state == RELAY_CHN_STATE_STOPPED || chn_ctl->state == RELAY_CHN_STATE_IDLE) { + return; // Do nothing if already stopped or idle } // If the command is STOP, issue it immediately relay_chn_dispatch_cmd(chn_ctl, cmd); From a5b320c152d5224056557534e344f8e6ae3bb7d6 Mon Sep 17 00:00:00 2001 From: ismail Date: Tue, 9 Sep 2025 10:53:56 +0300 Subject: [PATCH 56/69] Add state to string API function Added a state to string public API function. There was already a private function (`relay_chn_state_str`) that provides this functionality. So this function has been renamed to `relay_chn_state_to_str` and made publicly available. Refs #1104, #1105 and closes #1108. --- include/relay_chn.h | 8 ++++++++ private_include/relay_chn_core.h | 8 -------- src/relay_chn_core.c | 2 +- src/relay_chn_ctl_multi.c | 5 +++-- src/relay_chn_ctl_single.c | 3 ++- src/relay_chn_tilt.c | 3 ++- 6 files changed, 16 insertions(+), 13 deletions(-) diff --git a/include/relay_chn.h b/include/relay_chn.h index 1ed03a1..d69f1f7 100644 --- a/include/relay_chn.h +++ b/include/relay_chn.h @@ -63,6 +63,14 @@ esp_err_t relay_chn_register_listener(relay_chn_state_listener_t listener); */ void relay_chn_unregister_listener(relay_chn_state_listener_t listener); +/** + * @brief Return the text presentation of an state. + * + * @param state A state with type of relay_chn_state_t. + * @return char* The text presentation of the state. "UNKNOWN" if the state is not known. + */ +char *relay_chn_state_to_str(relay_chn_state_t state); + #if CONFIG_RELAY_CHN_ENABLE_TILTING /** * @brief Get the default tilting sensitivity for the relay channel. diff --git a/private_include/relay_chn_core.h b/private_include/relay_chn_core.h index 1649099..86c704a 100644 --- a/private_include/relay_chn_core.h +++ b/private_include/relay_chn_core.h @@ -93,14 +93,6 @@ esp_err_t relay_chn_start_esp_timer_once(esp_timer_handle_t esp_timer, uint32_t */ void relay_chn_update_state(relay_chn_ctl_t *chn_ctl, relay_chn_state_t new_state); -/** - * @brief Return the text presentation of an state. - * - * @param state A state with type of relay_chn_state_t. - * @return char* The text presentation of the state. "UNKNOWN" if the state is not known. - */ -char *relay_chn_state_str(relay_chn_state_t state); - #if CONFIG_RELAY_CHN_COUNT > 1 /** * @brief Check if the provided channel ID is valid. diff --git a/src/relay_chn_core.c b/src/relay_chn_core.c index 2d8fba8..8d3aeb9 100644 --- a/src/relay_chn_core.c +++ b/src/relay_chn_core.c @@ -471,7 +471,7 @@ char *relay_chn_cmd_str(relay_chn_cmd_t cmd) } } -char *relay_chn_state_str(relay_chn_state_t state) +char *relay_chn_state_to_str(relay_chn_state_t state) { switch (state) { case RELAY_CHN_STATE_IDLE: diff --git a/src/relay_chn_ctl_multi.c b/src/relay_chn_ctl_multi.c index d433fd5..4289836 100644 --- a/src/relay_chn_ctl_multi.c +++ b/src/relay_chn_ctl_multi.c @@ -5,6 +5,7 @@ */ #include "esp_check.h" +#include "relay_chn.h" #include "relay_chn_priv_types.h" #include "relay_chn_core.h" #include "relay_chn_ctl.h" @@ -92,8 +93,8 @@ esp_err_t relay_chn_ctl_get_state_all(relay_chn_state_t *states) char *relay_chn_ctl_get_state_str(uint8_t chn_id) { return relay_chn_is_channel_id_valid(chn_id) - ? relay_chn_state_str(s_chn_ctls[chn_id].state) - : relay_chn_state_str(RELAY_CHN_STATE_UNDEFINED); + ? relay_chn_state_to_str(s_chn_ctls[chn_id].state) + : relay_chn_state_to_str(RELAY_CHN_STATE_UNDEFINED); } diff --git a/src/relay_chn_ctl_single.c b/src/relay_chn_ctl_single.c index e242030..dcb3ac7 100644 --- a/src/relay_chn_ctl_single.c +++ b/src/relay_chn_ctl_single.c @@ -5,6 +5,7 @@ */ #include "esp_check.h" +#include "relay_chn.h" #include "relay_chn_priv_types.h" #include "relay_chn_core.h" #include "relay_chn_ctl.h" @@ -65,7 +66,7 @@ relay_chn_state_t relay_chn_ctl_get_state() char *relay_chn_ctl_get_state_str() { - return relay_chn_state_str(s_chn_ctl.state); + return relay_chn_state_to_str(s_chn_ctl.state); } void relay_chn_ctl_run_forward() diff --git a/src/relay_chn_tilt.c b/src/relay_chn_tilt.c index c9552ad..acb6c47 100644 --- a/src/relay_chn_tilt.c +++ b/src/relay_chn_tilt.c @@ -5,6 +5,7 @@ */ #include "esp_check.h" +#include "relay_chn.h" #include "relay_chn_core.h" #include "relay_chn_output.h" #include "relay_chn_run_info.h" @@ -184,7 +185,7 @@ static void relay_chn_tilt_issue_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_t break; default: - ESP_LOGD(TAG, "relay_chn_tilt_issue_cmd: Unexpected relay channel state: %s!", relay_chn_state_str(tilt_ctl->chn_ctl->state)); + ESP_LOGD(TAG, "relay_chn_tilt_issue_cmd: Unexpected relay channel state: %s!", relay_chn_state_to_str(tilt_ctl->chn_ctl->state)); } } From 3ce079c2e80d1f9a368149d6da59c0d6af844095 Mon Sep 17 00:00:00 2001 From: ismail Date: Tue, 9 Sep 2025 17:09:08 +0300 Subject: [PATCH 57/69] Fix unwanted reverse tilts Added a helper function to determine if the channel may perform the requested tilt command. Refs #1105 and fixes #1109. --- src/relay_chn_tilt.c | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/relay_chn_tilt.c b/src/relay_chn_tilt.c index acb6c47..9162258 100644 --- a/src/relay_chn_tilt.c +++ b/src/relay_chn_tilt.c @@ -104,6 +104,37 @@ static void relay_chn_tilt_start_timer_or_stop(relay_chn_tilt_ctl_t *tilt_ctl, e } } +/** + * @brief Checks if the relay channel can perform the current tilt command. + * + * This function evaluates whether a tilt command can be executed based on the + * channel's history. The rules are as follows: + * - Tilting in the same direction as the last full run command (e.g., TILT_FORWARD + * after a FORWARD run) is always allowed. + * - Tilting in the opposite direction of the last full run (e.g., TILT_REVERSE + * after a FORWARD run) is only allowed if the tilt counter is greater than zero, + * which indicates that the channel has previously tilted in the primary direction. + * - If the channel has not been run before, tilting is not allowed. + * + * @param tilt_ctl Pointer to the tilt control structure for the channel. + * @param tilt_cmd The tilt command to check against. + * + * @return true if the tilt command is allowed, false otherwise. + */ +static bool relay_chn_can_perform_tilt_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_tilt_cmd_t tilt_cmd) +{ + relay_chn_cmd_t last_run_cmd = relay_chn_run_info_get_last_run_cmd(tilt_ctl->chn_ctl->run_info); + if (last_run_cmd == RELAY_CHN_CMD_FORWARD) { + return (tilt_cmd == RELAY_CHN_TILT_CMD_FORWARD) || + (tilt_cmd == RELAY_CHN_TILT_CMD_REVERSE && tilt_ctl->tilt_count > 0); + } else if (last_run_cmd == RELAY_CHN_CMD_REVERSE) { + return (tilt_cmd == RELAY_CHN_TILT_CMD_REVERSE) || + (tilt_cmd == RELAY_CHN_TILT_CMD_FORWARD && tilt_ctl->tilt_count > 0); + } + + return false; +} + // Issue a tilt command to a specific relay channel. static void relay_chn_tilt_issue_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_tilt_cmd_t cmd) { @@ -128,6 +159,11 @@ static void relay_chn_tilt_issue_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_t return; } + if (!relay_chn_can_perform_tilt_cmd(tilt_ctl, cmd)) { + ESP_LOGD(TAG, "Cannot perform tilt command: %d for #%d", cmd, tilt_ctl->chn_ctl->id); + return; + } + // Set the command that will be processed tilt_ctl->cmd = cmd; switch (tilt_ctl->chn_ctl->state) { From 31e351a129d180c598d8e4a40a4a3795c893a41f Mon Sep 17 00:00:00 2001 From: ismail Date: Tue, 9 Sep 2025 17:50:02 +0300 Subject: [PATCH 58/69] Fix tilting opposite direction when running Fixed TILT_STOP command issuing chain that was causing a running channel to be stopped when an opposite direction tilting requested. For exeample: RUN_FORWARD > TILT_REVERSE. Refs #1105 and fixes #1110. --- src/relay_chn_tilt.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/relay_chn_tilt.c b/src/relay_chn_tilt.c index 9162258..4fab83c 100644 --- a/src/relay_chn_tilt.c +++ b/src/relay_chn_tilt.c @@ -140,11 +140,11 @@ static void relay_chn_tilt_issue_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_t { // TILT_STOP is safe and high priority if (cmd == RELAY_CHN_TILT_CMD_STOP) { - if (tilt_ctl->chn_ctl->state == RELAY_CHN_STATE_STOPPED) { - return; // Do nothing if already stopped + relay_chn_state_t state = tilt_ctl->chn_ctl->state; + if (state == RELAY_CHN_STATE_TILT_FORWARD || state == RELAY_CHN_STATE_TILT_REVERSE) { + // If the command is TILT_STOP, issue it immediately + relay_chn_tilt_dispatch_cmd(tilt_ctl, cmd); } - // If the command is TILT_STOP, issue it immediately - relay_chn_tilt_dispatch_cmd(tilt_ctl, cmd); return; } @@ -320,7 +320,7 @@ void relay_chn_tilt_reverse() void relay_chn_tilt_stop() { - relay_chn_tilt_dispatch_cmd(&s_tilt_ctl, RELAY_CHN_TILT_CMD_STOP); + relay_chn_tilt_issue_cmd(&s_tilt_ctl, RELAY_CHN_TILT_CMD_STOP); } #endif // CONFIG_RELAY_CHN_COUNT > 1 From 9ee974e677a7c799c948024c0d32647c5ad06106 Mon Sep 17 00:00:00 2001 From: ismail Date: Tue, 9 Sep 2025 18:15:32 +0300 Subject: [PATCH 59/69] Add a single channel example Added a single channel example with run limit and tilting features. Refs #1104 and closes #1105. --- examples/relay_chn_single/.gitignore | 3 + .../.vscode/c_cpp_properties.json | 23 ++ examples/relay_chn_single/.vscode/launch.json | 15 + .../relay_chn_single/.vscode/settings.json | 17 + examples/relay_chn_single/CMakeLists.txt | 10 + examples/relay_chn_single/README.md | 126 ++++++++ .../relay_chn_single/example_schematic.png | Bin 0 -> 134744 bytes examples/relay_chn_single/main/CMakeLists.txt | 2 + .../relay_chn_single/main/Kconfig.projbuild | 40 +++ .../relay_chn_single/main/idf_component.yml | 8 + .../main/relay_chn_single_main.c | 290 ++++++++++++++++++ examples/relay_chn_single/sdkconfig.defaults | 9 + 12 files changed, 543 insertions(+) create mode 100644 examples/relay_chn_single/.gitignore create mode 100644 examples/relay_chn_single/.vscode/c_cpp_properties.json create mode 100644 examples/relay_chn_single/.vscode/launch.json create mode 100644 examples/relay_chn_single/.vscode/settings.json create mode 100644 examples/relay_chn_single/CMakeLists.txt create mode 100644 examples/relay_chn_single/README.md create mode 100644 examples/relay_chn_single/example_schematic.png create mode 100644 examples/relay_chn_single/main/CMakeLists.txt create mode 100644 examples/relay_chn_single/main/Kconfig.projbuild create mode 100644 examples/relay_chn_single/main/idf_component.yml create mode 100644 examples/relay_chn_single/main/relay_chn_single_main.c create mode 100644 examples/relay_chn_single/sdkconfig.defaults diff --git a/examples/relay_chn_single/.gitignore b/examples/relay_chn_single/.gitignore new file mode 100644 index 0000000..51c9513 --- /dev/null +++ b/examples/relay_chn_single/.gitignore @@ -0,0 +1,3 @@ +build/ +sdkconfig +sdkconfig.old \ No newline at end of file diff --git a/examples/relay_chn_single/.vscode/c_cpp_properties.json b/examples/relay_chn_single/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..badb83d --- /dev/null +++ b/examples/relay_chn_single/.vscode/c_cpp_properties.json @@ -0,0 +1,23 @@ +{ + "configurations": [ + { + "name": "ESP-IDF", + "compilerPath": "${config:idf.toolsPath}/tools/riscv32-esp-elf/esp-14.2.0_20241119/riscv32-esp-elf/bin/riscv32-esp-elf-gcc", + "compileCommands": "/disk/Projeler/ESP-Components/relay_chn/examples/relay_chn_single/build/compile_commands.json", + "includePath": [ + "${config:idf.espIdfPath}/components/**", + "${config:idf.espIdfPathWin}/components/**", + "${workspaceFolder}/**" + ], + "browse": { + "path": [ + "${config:idf.espIdfPath}/components", + "${config:idf.espIdfPathWin}/components", + "${workspaceFolder}" + ], + "limitSymbolsToIncludedHeaders": true + } + } + ], + "version": 4 +} diff --git a/examples/relay_chn_single/.vscode/launch.json b/examples/relay_chn_single/.vscode/launch.json new file mode 100644 index 0000000..2511a38 --- /dev/null +++ b/examples/relay_chn_single/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "gdbtarget", + "request": "attach", + "name": "Eclipse CDT GDB Adapter" + }, + { + "type": "espidf", + "name": "Launch", + "request": "launch" + } + ] +} \ No newline at end of file diff --git a/examples/relay_chn_single/.vscode/settings.json b/examples/relay_chn_single/.vscode/settings.json new file mode 100644 index 0000000..81b74eb --- /dev/null +++ b/examples/relay_chn_single/.vscode/settings.json @@ -0,0 +1,17 @@ +{ + "C_Cpp.intelliSenseEngine": "default", + "idf.espIdfPath": "/disk/Depo/Developer/SDK/esp-idf/v5.4/esp-idf", + "idf.pythonInstallPath": "/usr/bin/python3", + "idf.openOcdConfigs": [ + "board/esp32c3-builtin.cfg" + ], + "idf.port": "/dev/ttyUSB0", + "idf.toolsPath": "/home/ismail/.espressif", + "idf.customExtraVars": { + "IDF_TARGET": "esp32c3" + }, + "idf.flashType": "UART", + "files.associations": { + "led_indicator_blink_default.h": "c" + } +} diff --git a/examples/relay_chn_single/CMakeLists.txt b/examples/relay_chn_single/CMakeLists.txt new file mode 100644 index 0000000..c64f60a --- /dev/null +++ b/examples/relay_chn_single/CMakeLists.txt @@ -0,0 +1,10 @@ +# For more information about build system see +# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +set(COMPONENTS main) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(relay_chn_single) \ No newline at end of file diff --git a/examples/relay_chn_single/README.md b/examples/relay_chn_single/README.md new file mode 100644 index 0000000..c39c97b --- /dev/null +++ b/examples/relay_chn_single/README.md @@ -0,0 +1,126 @@ +# Relay Channel Single Example + +## Introduction + +This example demonstrates how to use the relay channel component to control a 2-relay setup with button inputs and LED status indication. It showcases: + +- Basic relay channel operations (forward/reverse running, stopping) +- Secondary operations (tilting, direction flipping) +- State change event handling with multiple listeners +- Relay channel run limit +- Button event handling using esp-iot-solution's button component +- Visual feedback using esp-iot-solution's LED indicator component + +## How to Use Example + +This example has been tested on an `ESP32-C3-DevKitM-1U` board. However, it can be adapted to any ESP32-based board with at least six available GPIO pins by adjusting the configuration options. + +### Hardware Required + +* An ESP32-based development board +* 2 relays connected to GPIO pins (default: GPIO4, GPIO5) +* 3 buttons connected to GPIO pins: + - UP button (default: GPIO0) + - DOWN button (default: GPIO1) + - STOP button (default: GPIO2) +* 1 LED for status indication (default: GPIO3) + +#### Hardware Schematic + +![Hardware Schematic](example_schematic.png) + +### Configuration + +The example can be configured through `menuconfig` under "Relay Channel Single Example Configuration": + +1. Button active level (`EXAMPLE_RLCHN_BTN_ACTIVE_LEVEL`) + - Select between active LOW or HIGH logic level for buttons + +2. GPIO assignments: + - UP button pin (`EXAMPLE_RLCHN_BTN_UP_IO_NUM`, default: 0) + - DOWN button pin (`EXAMPLE_RLCHN_BTN_DOWN_IO_NUM`, default: 1) + - STOP button pin (`EXAMPLE_RLCHN_BTN_STOP_IO_NUM`, default: 2) + - LED indicator pin (`EXAMPLE_RLCHN_LED_INDICATOR_IO_NUM`, default: 3) + +3. Long press timing: + - `EXAMPLE_RLCHN_BTN_LONG_PRESS_TIME_MS`: Duration for long press actions (1500-3000ms, default: 2000ms) + +### Button Operations + +The example uses esp-iot-solution's `button` component to handle the following operations: + +- **UP button**: + * Short press: Start forward movement + * Long press: Start forward tilt (stops on release) + +- **DOWN button**: + * Short press: Start reverse movement + * Long press: Start reverse tilt (stops on release) + +- **STOP button**: + * Short press: Stop movement + * Long press: Flip movement direction + +### LED Indicator States + +The example uses esp-iot-solution's `led_indicator` component to show different states: + +- **Running**: LED blinks at 300ms on, 100ms off +- **Tilting**: Fast blink at 100ms on, 50ms off +- **Operation Success**: Two quick blinks +- **Operation Fail**: One long blink + +### Dependencies + +This example requires: +- ESP-IDF v4.1 or later +- esp-iot-solution components: + * button v4.1.1 or later + * led_indicator v1.1.1 or later +- relay_chn component + +## Example Output +When the application boots, it will wait for a button event. Then the two state listeners will print state changes. + +```log +I (273) main_task: Calling app_main() +I (273) RELAY_CHN_SINGLE_EXAMPLE: Initializing default NVS storage +I (283) RELAY_CHN_SINGLE_EXAMPLE: nvs_flash_init: NVS flash init return: ESP_OK +I (283) RELAY_CHN_SINGLE_EXAMPLE: Initializing relay channel +I (293) gpio: GPIO[4]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (303) gpio: GPIO[5]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (313) RELAY_CHN_SINGLE_EXAMPLE: Initializing buttons +I (313) RELAY_CHN_SINGLE_EXAMPLE: Initializing buttons with active level: 0 +I (323) gpio: GPIO[0]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (323) button: IoT Button Version: 4.1.3 +I (333) gpio: GPIO[1]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (343) gpio: GPIO[2]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (343) RELAY_CHN_SINGLE_EXAMPLE: Setting up button callbacks. Configured long press time: 2000 ms +I (353) RELAY_CHN_SINGLE_EXAMPLE: Initializing LED indicator +I (363) gpio: GPIO[3]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (373) led_indicator: LED Indicator Version: 1.1.1 +I (373) gpio: GPIO[3]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0 +I (383) led_indicator: Indicator create successfully. type:GPIO mode, hardware_data:0x3fc97be4, blink_lists:custom +I (393) RELAY_CHN_SINGLE_EXAMPLE: Relay Channel Single Example is ready to operate +I (403) main_task: Returned from app_main() +I (3683) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_1: Defining new indicator mode for #0 and state change from 1 to 3 +I (3683) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_2: State change for #0, from IDLE to FORWARD +I (9513) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_1: Defining new indicator mode for #0 and state change from 3 to 2 +I (9513) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_2: State change for #0, from FORWARD to STOPPED +I (9523) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_1: Defining new indicator mode for #0 and state change from 2 to 6 +I (9533) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_2: State change for #0, from STOPPED to REVERSE_PENDING +I (10313) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_1: Defining new indicator mode for #0 and state change from 6 to 4 +I (10313) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_2: State change for #0, from REVERSE_PENDING to REVERSE +I (32173) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_1: Defining new indicator mode for #0 and state change from 4 to 2 +I (32173) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_2: State change for #0, from REVERSE to STOPPED +I (32973) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_1: Defining new indicator mode for #0 and state change from 2 to 1 +I (32973) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_2: State change for #0, from STOPPED to IDLE +I (36423) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_1: Defining new indicator mode for #0 and state change from 1 to 8 +I (36423) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_2: State change for #0, from IDLE to TILT_REVERSE +I (41153) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_1: Defining new indicator mode for #0 and state change from 8 to 1 +I (41153) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_2: State change for #0, from TILT_REVERSE to IDLE +I (47113) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_1: Defining new indicator mode for #0 and state change from 1 to 7 +I (47113) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_2: State change for #0, from IDLE to TILT_FORWARD +I (51913) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_1: Defining new indicator mode for #0 and state change from 7 to 1 +I (51913) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_2: State change for #0, from TILT_FORWARD to IDLE +``` \ No newline at end of file diff --git a/examples/relay_chn_single/example_schematic.png b/examples/relay_chn_single/example_schematic.png new file mode 100644 index 0000000000000000000000000000000000000000..2c05916ae323e00130a9f1e5a2fd3c007021b0b2 GIT binary patch literal 134744 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%0Amvy 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≦?_M!kxV0Goz`SE#4)ArEKBp7`+>UH(^W))n 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 XzjnJdL_#fc=V47vEUr`<-;Vts&^eRe literal 0 HcmV?d00001 diff --git a/examples/relay_chn_single/main/CMakeLists.txt b/examples/relay_chn_single/main/CMakeLists.txt new file mode 100644 index 0000000..3dcf1b0 --- /dev/null +++ b/examples/relay_chn_single/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "relay_chn_single_main.c" + PRIV_REQUIRES button led_indicator relay_chn) \ No newline at end of file diff --git a/examples/relay_chn_single/main/Kconfig.projbuild b/examples/relay_chn_single/main/Kconfig.projbuild new file mode 100644 index 0000000..e3a6992 --- /dev/null +++ b/examples/relay_chn_single/main/Kconfig.projbuild @@ -0,0 +1,40 @@ +menu "Relay Channel Single Example Configuration" + + choice EXAMPLE_RLCHN_BTN_ACTIVE_LEVEL + prompt "Choose an active level for buttons" + default EXAMPLE_RLCHN_BTN_ACTIVE_LEVEL_LOW + help + Specify the active level for buttons. + + config EXAMPLE_RLCHN_BTN_ACTIVE_LEVEL_LOW + bool "Active level LOW" + + config EXAMPLE_RLCHN_BTN_ACTIVE_LEVEL_HIGH + bool "Active level HIGH" + endchoice + + config EXAMPLE_RLCHN_BTN_UP_IO_NUM + int "GPIO number for UP button" + default 0 + + config EXAMPLE_RLCHN_BTN_DOWN_IO_NUM + int "GPIO number for DOWN button" + default 1 + + config EXAMPLE_RLCHN_BTN_STOP_IO_NUM + int "GPIO number for STOP button" + default 2 + + config EXAMPLE_RLCHN_LED_INDICATOR_IO_NUM + int "GPIO number for LED indicator output" + default 3 + + config EXAMPLE_RLCHN_BTN_LONG_PRESS_TIME_MS + int "Long press time in ms to start secondary actions" + range 1500 3000 + default 2000 + help + Long press time in milliseconds is required to start secondary actions + like tilting and flipping. + +endmenu \ No newline at end of file diff --git a/examples/relay_chn_single/main/idf_component.yml b/examples/relay_chn_single/main/idf_component.yml new file mode 100644 index 0000000..60c7cac --- /dev/null +++ b/examples/relay_chn_single/main/idf_component.yml @@ -0,0 +1,8 @@ +dependencies: + idf: + version: '>=4.1.0' + espressif/button: ^4.1.1 + espressif/led_indicator: ^1.1.1 + relay_chn: + version: '*' + override_path: ../../../ diff --git a/examples/relay_chn_single/main/relay_chn_single_main.c b/examples/relay_chn_single/main/relay_chn_single_main.c new file mode 100644 index 0000000..4e9db7d --- /dev/null +++ b/examples/relay_chn_single/main/relay_chn_single_main.c @@ -0,0 +1,290 @@ +/* + * SPDX-FileCopyrightText: 2025 Kozmotronik Tech + * + * SPDX-License-Identifier: MIT + */ + +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_err.h" +#include "esp_log.h" +#include "esp_check.h" +#include "driver/gpio.h" +#include "nvs.h" +#include "nvs_flash.h" +#include "button_gpio.h" +#include "iot_button.h" +#include "led_indicator.h" +#include "relay_chn.h" + +static const char *TAG = "RELAY_CHN_SINGLE_EXAMPLE"; + +/** + * @brief LED indicator modes for different states. + */ +typedef enum { + INDICATOR_MODE_OK, /*!< OK/Success indication */ + INDICATOR_MODE_FAIL, /*!< Fail/Error indication */ + INDICATOR_MODE_TILTING, /*!< Tilting operation in progress */ + INDICATOR_MODE_RUNNING, /*!< Full run operation in progress */ + INDICATOR_MODE_MAX /*!< Maximum number of indicator modes */ +} indicator_mode_t; + +/** @brief Blink pattern for OK/Success indication. */ +static const blink_step_t indc_mode_ok[] = { + {LED_BLINK_HOLD, LED_STATE_ON, 100}, // step1: turn on LED 100 ms + {LED_BLINK_HOLD, LED_STATE_OFF, 50}, // step2: turn off LED 50 ms + {LED_BLINK_HOLD, LED_STATE_ON, 100}, // step3: turn on LED 100 ms + {LED_BLINK_HOLD, LED_STATE_OFF, 50}, // step4: turn off LED 50 ms + {LED_BLINK_STOP, 0, 0}, // step5: stop blink (off) +}; + +/** @brief Blink pattern for Fail/Error indication. */ +static const blink_step_t indc_mode_fail[] = { + {LED_BLINK_HOLD, LED_STATE_ON, 1000}, // step1: turn on LED 1000 ms + {LED_BLINK_HOLD, LED_STATE_OFF, 500}, // step2: turn off LED 500 ms + {LED_BLINK_STOP, 0, 0}, // step4: stop blink (off) +}; + +/** @brief Blink pattern for full run operation. */ +static const blink_step_t indc_mode_running[] = { + {LED_BLINK_HOLD, LED_STATE_ON, 300}, // step1: turn on LED 300 ms + {LED_BLINK_HOLD, LED_STATE_OFF, 100}, // step2: turn off LED 100 ms + {LED_BLINK_LOOP, 0, 0}, // step3: loop from step1 +}; + +/** @brief Blink pattern for tilting operation. */ +static const blink_step_t indc_mode_tilting[] = { + {LED_BLINK_HOLD, LED_STATE_ON, 100}, // step1: turn on LED 100 ms + {LED_BLINK_HOLD, LED_STATE_OFF, 50}, // step2: turn off LED 50 ms + {LED_BLINK_LOOP, 0, 0}, // step3: loop from step1 +}; + +/** @brief Array of LED indicator blink patterns. */ +blink_step_t const *led_indicator_modes[] = { + [INDICATOR_MODE_OK] = indc_mode_ok, + [INDICATOR_MODE_FAIL] = indc_mode_fail, + [INDICATOR_MODE_RUNNING] = indc_mode_running, + [INDICATOR_MODE_TILTING] = indc_mode_tilting, + [INDICATOR_MODE_MAX] = NULL, +}; + +/** @brief Handle for the LED indicator. */ +static led_indicator_handle_t indicator = NULL; + +/** + * @brief Initializes the buttons for user interaction. + * + * This function configures and creates GPIO buttons for UP, DOWN, and STOP + * operations. It also registers callbacks for single-click and long-press + * events to control the relay channel. + * + * @return esp_err_t + * - ESP_OK: Success + * - Others: Fail + */ +static esp_err_t init_buttons(void); + +/** + * @brief Initializes the LED indicator. + * + * This function configures and creates the LED indicator used to provide + * visual feedback on the relay channel's status. + * + * @return esp_err_t + * - ESP_OK: Success + * - ESP_FAIL: Fail + */ +static esp_err_t init_led_indicator(void); + +/** + * @brief Event listener for relay channel state changes to control the LED indicator. + */ +static void example_event_listener_1(uint8_t ch, relay_chn_state_t old_state, relay_chn_state_t new_state); + +/** + * @brief Event listener for relay channel state changes to log the state transition. + */ +static void example_event_listener_2(uint8_t ch, relay_chn_state_t old_state, relay_chn_state_t new_state); + +void app_main(void) +{ + const uint8_t gpio_map[] = { 4, 5 }; + const uint8_t gpio_count = sizeof(gpio_map) / sizeof(gpio_map[0]); + + ESP_LOGI(TAG, "Initializing relay channel"); + ESP_ERROR_CHECK(relay_chn_create(gpio_map, gpio_count)); + ESP_ERROR_CHECK(relay_chn_register_listener(example_event_listener_1)); + ESP_ERROR_CHECK(relay_chn_register_listener(example_event_listener_2)); + + ESP_LOGI(TAG, "Initializing buttons"); + ESP_ERROR_CHECK(init_buttons()); + ESP_LOGI(TAG, "Initializing LED indicator"); + ESP_ERROR_CHECK(init_led_indicator()); + + ESP_LOGI(TAG, "Relay Channel Single Example is ready to operate"); + + // Indicate init was successful + led_indicator_start(indicator, INDICATOR_MODE_OK); +} + +static void on_click_up(void *arg, void *data) +{ + relay_chn_run_forward(); +} + +static void on_click_down(void *arg, void *data) +{ + relay_chn_run_reverse(); +} + +static void on_click_stop(void *arg, void *data) +{ + relay_chn_stop(); +} + +static void on_click_flip(void *arg, void *data) +{ + relay_chn_flip_direction(); + led_indicator_start(indicator, INDICATOR_MODE_OK); +} + +static void on_click_tilt_up(void *arg, void *data) +{ + relay_chn_tilt_forward(); +} + +static void on_click_tilt_down(void *arg, void *data) +{ + relay_chn_tilt_reverse(); +} + +static void on_release_tilt(void *arg, void *data) +{ + relay_chn_tilt_stop(); +} + +static void example_event_listener_1(uint8_t ch, relay_chn_state_t old_state, relay_chn_state_t new_state) +{ + ESP_LOGI(TAG, "example_event_listener_1: Defining new indicator mode for #%d and state change from %d to %d", ch, old_state, new_state); + switch (new_state) { + case RELAY_CHN_STATE_FORWARD: + case RELAY_CHN_STATE_REVERSE: + led_indicator_start(indicator, INDICATOR_MODE_RUNNING); + break; + + case RELAY_CHN_STATE_STOPPED: + case RELAY_CHN_STATE_IDLE: + if (old_state == RELAY_CHN_STATE_FORWARD || old_state == RELAY_CHN_STATE_REVERSE) { + led_indicator_stop(indicator, INDICATOR_MODE_RUNNING); + // Make sure the indicator turned off + led_indicator_set_on_off(indicator, false); + } + else if (old_state == RELAY_CHN_STATE_TILT_FORWARD || old_state == RELAY_CHN_STATE_TILT_REVERSE) { + led_indicator_stop(indicator, INDICATOR_MODE_TILTING); + // Make sure the indicator turned off + led_indicator_set_on_off(indicator, false); + } + break; + + case RELAY_CHN_STATE_TILT_FORWARD: + case RELAY_CHN_STATE_TILT_REVERSE: + led_indicator_start(indicator, INDICATOR_MODE_TILTING); + break; + + default: // No-op + } +} + +static void example_event_listener_2(uint8_t ch, relay_chn_state_t old_state, relay_chn_state_t new_state) +{ + ESP_LOGI(TAG, "example_event_listener_2: State change for #%d, from %s to %s", + ch, relay_chn_state_to_str(old_state), relay_chn_state_to_str(new_state)); +} + +static esp_err_t init_buttons() +{ + esp_err_t ret; + button_config_t btn_cfg = {0}; + + uint8_t active_level = CONFIG_EXAMPLE_RLCHN_BTN_ACTIVE_LEVEL_LOW ? 0 : 1; + + button_gpio_config_t btn_gpio_ccfg = { + .gpio_num = (gpio_num_t) CONFIG_EXAMPLE_RLCHN_BTN_UP_IO_NUM, + .active_level = active_level + }; + + ESP_LOGI(TAG, "Initializing buttons with active level: %u", active_level); + button_handle_t btn_up = NULL, btn_down = NULL, btn_stop = NULL; + // --- Create buttons --- + ret = iot_button_new_gpio_device(&btn_cfg, &btn_gpio_ccfg, &btn_up); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create UP button"); + ESP_RETURN_ON_FALSE(btn_up != NULL, ret, TAG, "Failed to create UP button"); + + btn_gpio_ccfg.gpio_num = (gpio_num_t) CONFIG_EXAMPLE_RLCHN_BTN_DOWN_IO_NUM; + ret = iot_button_new_gpio_device(&btn_cfg, &btn_gpio_ccfg, &btn_down); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create DOWN button"); + ESP_RETURN_ON_FALSE(btn_down != NULL, ret, TAG, "Failed to create DOWN button"); + + btn_gpio_ccfg.gpio_num = (gpio_num_t) CONFIG_EXAMPLE_RLCHN_BTN_STOP_IO_NUM; + ret = iot_button_new_gpio_device(&btn_cfg, &btn_gpio_ccfg, &btn_stop); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create STOP button"); + ESP_RETURN_ON_FALSE(btn_stop != NULL, ret, TAG, "Failed to create STOP button"); + // --- Create buttons --- + + // --- Register button callbacks --- + ESP_LOGI(TAG, "Setting up button callbacks. Configured long press time: %d ms", CONFIG_EXAMPLE_RLCHN_BTN_LONG_PRESS_TIME_MS); + button_event_args_t btn_event_args = { + .long_press.press_time = CONFIG_EXAMPLE_RLCHN_BTN_LONG_PRESS_TIME_MS + }; + // --- Register UP and TILT_UP operations on UP button --- + ret = iot_button_register_cb(btn_up, BUTTON_SINGLE_CLICK, NULL, on_click_up, NULL); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register UP button click callback"); + ret = iot_button_register_cb(btn_up, BUTTON_LONG_PRESS_START, &btn_event_args, on_click_tilt_up, NULL); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register TILT_UP button press callback"); + ret = iot_button_register_cb(btn_up, BUTTON_LONG_PRESS_UP, NULL, on_release_tilt, NULL); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register TILT_UP button release callback"); + + // --- Register DOWN and TILT_DOWN operations on DOWN button --- + ret = iot_button_register_cb(btn_down, BUTTON_SINGLE_CLICK, NULL, on_click_down, NULL); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register DOWN button click callback"); + ret = iot_button_register_cb(btn_down, BUTTON_LONG_PRESS_START, &btn_event_args, on_click_tilt_down, NULL); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register TILT_DOWN button press callback"); + ret = iot_button_register_cb(btn_down, BUTTON_LONG_PRESS_UP, NULL, on_release_tilt, NULL); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register TILT_DOWN button release callback"); + + // --- Register STOP and FLIP operations on STOP --- + ret = iot_button_register_cb(btn_stop, BUTTON_SINGLE_CLICK, NULL, on_click_stop, NULL); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register STOP button click callback"); + ret = iot_button_register_cb(btn_stop, BUTTON_LONG_PRESS_START, &btn_event_args, on_click_flip, NULL); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register FLIP button press callback"); + + return ESP_OK; +} + +static esp_err_t init_led_indicator() +{ + const gpio_num_t indicator_io_num = (gpio_num_t) CONFIG_EXAMPLE_RLCHN_LED_INDICATOR_IO_NUM; + gpio_reset_pin(indicator_io_num); // Clear the output buffers + + led_indicator_gpio_config_t led_indicator_gpio_cfg = { + .gpio_num = indicator_io_num, + .is_active_level_high = true + }; + + led_indicator_config_t led_indicator_cfg = { + .mode = LED_GPIO_MODE, + .led_indicator_gpio_config = &led_indicator_gpio_cfg, + .blink_lists = led_indicator_modes, + .blink_list_num = INDICATOR_MODE_MAX + }; + + indicator = led_indicator_create(&led_indicator_cfg); + if (!indicator) { + ESP_LOGE(TAG, "Failed to create LED indicator"); + return ESP_FAIL; + } + + return ESP_OK; +} \ No newline at end of file diff --git a/examples/relay_chn_single/sdkconfig.defaults b/examples/relay_chn_single/sdkconfig.defaults new file mode 100644 index 0000000..faefa95 --- /dev/null +++ b/examples/relay_chn_single/sdkconfig.defaults @@ -0,0 +1,9 @@ +# Halt on panic +CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT=y + +# Relay Channel Configs +CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT=y +# Keep this as short as possible for example purposes +CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC=5 +CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC=20 +CONFIG_RELAY_CHN_ENABLE_TILTING=y From e30b445b9123e84e289689fa5eaf88c421201d6e Mon Sep 17 00:00:00 2001 From: ismail Date: Thu, 11 Sep 2025 14:21:24 +0300 Subject: [PATCH 60/69] Fix channel validity check Fixed mistaken channel validity check in `tilt_stop` funciton in multi mode. Refs #1111 and fixes #1114. --- src/relay_chn_tilt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/relay_chn_tilt.c b/src/relay_chn_tilt.c index 4fab83c..a9c270b 100644 --- a/src/relay_chn_tilt.c +++ b/src/relay_chn_tilt.c @@ -291,7 +291,7 @@ void relay_chn_tilt_reverse_all() void relay_chn_tilt_stop(uint8_t chn_id) { - if (!relay_chn_is_channel_id_valid(chn_id)) { + if (relay_chn_is_channel_id_valid(chn_id)) { relay_chn_tilt_dispatch_cmd(&s_tilt_ctls[chn_id], RELAY_CHN_TILT_CMD_STOP); } } From 4edebf206e65bda841eef0b9b90148c0dfd0c284 Mon Sep 17 00:00:00 2001 From: ismail Date: Thu, 11 Sep 2025 15:50:34 +0300 Subject: [PATCH 61/69] Add a multi channel example Added a multi channel example with run limit, tilting and channel selection features. Refs #1104 and closes #1111. --- examples/relay_chn_multi/.gitignore | 3 + .../.vscode/c_cpp_properties.json | 23 + examples/relay_chn_multi/.vscode/launch.json | 15 + .../relay_chn_multi/.vscode/settings.json | 6 + examples/relay_chn_multi/CMakeLists.txt | 10 + examples/relay_chn_multi/README.md | 176 ++++++++ .../relay_chn_multi/example_schematic.png | Bin 0 -> 135707 bytes examples/relay_chn_multi/main/CMakeLists.txt | 2 + .../relay_chn_multi/main/Kconfig.projbuild | 52 +++ .../relay_chn_multi/main/idf_component.yml | 8 + .../main/relay_chn_multi_main.c | 427 ++++++++++++++++++ examples/relay_chn_multi/sdkconfig.defaults | 10 + 12 files changed, 732 insertions(+) create mode 100644 examples/relay_chn_multi/.gitignore create mode 100644 examples/relay_chn_multi/.vscode/c_cpp_properties.json create mode 100644 examples/relay_chn_multi/.vscode/launch.json create mode 100644 examples/relay_chn_multi/.vscode/settings.json create mode 100644 examples/relay_chn_multi/CMakeLists.txt create mode 100644 examples/relay_chn_multi/README.md create mode 100644 examples/relay_chn_multi/example_schematic.png create mode 100644 examples/relay_chn_multi/main/CMakeLists.txt create mode 100644 examples/relay_chn_multi/main/Kconfig.projbuild create mode 100644 examples/relay_chn_multi/main/idf_component.yml create mode 100644 examples/relay_chn_multi/main/relay_chn_multi_main.c create mode 100644 examples/relay_chn_multi/sdkconfig.defaults diff --git a/examples/relay_chn_multi/.gitignore b/examples/relay_chn_multi/.gitignore new file mode 100644 index 0000000..51c9513 --- /dev/null +++ b/examples/relay_chn_multi/.gitignore @@ -0,0 +1,3 @@ +build/ +sdkconfig +sdkconfig.old \ No newline at end of file diff --git a/examples/relay_chn_multi/.vscode/c_cpp_properties.json b/examples/relay_chn_multi/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..badb83d --- /dev/null +++ b/examples/relay_chn_multi/.vscode/c_cpp_properties.json @@ -0,0 +1,23 @@ +{ + "configurations": [ + { + "name": "ESP-IDF", + "compilerPath": "${config:idf.toolsPath}/tools/riscv32-esp-elf/esp-14.2.0_20241119/riscv32-esp-elf/bin/riscv32-esp-elf-gcc", + "compileCommands": "/disk/Projeler/ESP-Components/relay_chn/examples/relay_chn_single/build/compile_commands.json", + "includePath": [ + "${config:idf.espIdfPath}/components/**", + "${config:idf.espIdfPathWin}/components/**", + "${workspaceFolder}/**" + ], + "browse": { + "path": [ + "${config:idf.espIdfPath}/components", + "${config:idf.espIdfPathWin}/components", + "${workspaceFolder}" + ], + "limitSymbolsToIncludedHeaders": true + } + } + ], + "version": 4 +} diff --git a/examples/relay_chn_multi/.vscode/launch.json b/examples/relay_chn_multi/.vscode/launch.json new file mode 100644 index 0000000..2511a38 --- /dev/null +++ b/examples/relay_chn_multi/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "gdbtarget", + "request": "attach", + "name": "Eclipse CDT GDB Adapter" + }, + { + "type": "espidf", + "name": "Launch", + "request": "launch" + } + ] +} \ No newline at end of file diff --git a/examples/relay_chn_multi/.vscode/settings.json b/examples/relay_chn_multi/.vscode/settings.json new file mode 100644 index 0000000..9fa2d52 --- /dev/null +++ b/examples/relay_chn_multi/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "C_Cpp.intelliSenseEngine": "default", + "files.associations": { + "led_indicator_blink_default.h": "c" + } +} diff --git a/examples/relay_chn_multi/CMakeLists.txt b/examples/relay_chn_multi/CMakeLists.txt new file mode 100644 index 0000000..c64f60a --- /dev/null +++ b/examples/relay_chn_multi/CMakeLists.txt @@ -0,0 +1,10 @@ +# For more information about build system see +# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +set(COMPONENTS main) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(relay_chn_single) \ No newline at end of file diff --git a/examples/relay_chn_multi/README.md b/examples/relay_chn_multi/README.md new file mode 100644 index 0000000..54b6f47 --- /dev/null +++ b/examples/relay_chn_multi/README.md @@ -0,0 +1,176 @@ +# Relay Channel Multi Example + +## Introduction + +This example demonstrates how to use the relay channel component to control a 3-channel setup with button inputs and LED status indication. It showcases: + +- Basic relay channel operations (forward/reverse running, stopping) +- Secondary operations (tilting, direction flipping) +- State change event handling with a listener +- Relay channel run limit +- Button event handling using esp-iot-solution's button component +- Visual feedback using esp-iot-solution's LED indicator component + +## How to Use Example + +This example has been tested on an `ESP32-C3-DevKitM-1U` board. However, it can be adapted to any ESP32-based board with at least six available GPIO pins by adjusting the configuration options. + +### Hardware Required + +* An ESP32-based development board +* 2 relays connected to GPIO pins (default: GPIO4, GPIO5) +* 3 buttons connected to GPIO pins: + - UP button (default: GPIO0) + - DOWN button (default: GPIO1) + - STOP button (default: GPIO2) + - SELECT button (default: GPIO3) +* LED indicators for status indication of each channel (default: GPIO4, GPIO10 and GPIO9 respectively) + +#### Hardware Schematic + +Relay blocks are ommitted for simplicity. You can refer to schematic of the [Single-channel example](/examples/relay_chn_single/README.md) for a fully implemented relay block. + +> [!NOTE] +> A single relay channel consists of two relay block and two GPIO pins. + +![Hardware Schematic](example_schematic.png) + +### Configuration + +The example can be configured through `menuconfig` under "Relay Channel Multi Example Configuration": + +1. Button active level (`EXAMPLE_RLCHN_BTN_ACTIVE_LEVEL`) + - Select between active LOW or HIGH logic level for buttons + +2. GPIO assignments: + - UP button pin (`EXAMPLE_RLCHN_BTN_UP_IO_NUM`, default: 0) + - DOWN button pin (`EXAMPLE_RLCHN_BTN_DOWN_IO_NUM`, default: 1) + - STOP button pin (`EXAMPLE_RLCHN_BTN_STOP_IO_NUM`, default: 2) + - SELECT button pin (`EXAMPLE_RLCHN_BTN_SELECT_IO_NUM`, default: 3) + - LED indicator pin (`EXAMPLE_RLCHN_LED_INDICATOR1_IO_NUM`, default: 4) + - LED indicator pin (`EXAMPLE_RLCHN_LED_INDICATOR2_IO_NUM`, default: 10) + - LED indicator pin (`EXAMPLE_RLCHN_LED_INDICATOR3_IO_NUM`, default: 9) + +3. Long press timing: + - `EXAMPLE_RLCHN_BTN_LONG_PRESS_TIME_MS`: Duration for long press actions (1500-3000ms, default: 2000ms) + +### Button Operations + +The example uses esp-iot-solution's `button` component to handle the following operations: + +- **UP button**: + * Short press: Start forward movement + * Long press: Start forward tilt (stops on release) + +- **DOWN button**: + * Short press: Start reverse movement + * Long press: Start reverse tilt (stops on release) + +- **STOP button**: + * Short press: Stop movement + * Long press: Flip movement direction + +- **SELECT button**: + * Short press: Selects a channel to operate + * Long press: Selects all channels to operate in batch + +### LED Indicator States + +The example uses esp-iot-solution's `led_indicator` component to show different states: + +- **Running**: LED blinks at 300ms on, 100ms off +- **Tilting**: Fast blink at 100ms on, 50ms off +- **Operation Success**: Two quick blinks +- **Operation Fail**: One long blink + +### Dependencies + +This example requires: +- ESP-IDF v4.1 or later +- esp-iot-solution components: + * button v4.1.1 or later + * led_indicator v1.1.1 or later +- relay_chn component + +## Example Output + +When the application boots, it will wait for a button event. Then ta state listener will print state changes. + +```log +I (269) main_task: Calling app_main() +I (269) RELAY_CHN_MULTI_EXAMPLE: Initializing relay channel +I (279) gpio: GPIO[18]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (279) gpio: GPIO[19]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (289) gpio: GPIO[5]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (299) gpio: GPIO[6]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (309) gpio: GPIO[7]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (319) gpio: GPIO[8]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (329) RELAY_CHN_MULTI_EXAMPLE: Initializing buttons +I (329) RELAY_CHN_MULTI_EXAMPLE: Initializing buttons with active level: 0 +I (339) gpio: GPIO[0]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (339) button: IoT Button Version: 4.1.3 +I (349) gpio: GPIO[1]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (359) gpio: GPIO[2]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (359) gpio: GPIO[3]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (369) RELAY_CHN_MULTI_EXAMPLE: Setting up button callbacks. Configured long press time: 2000 ms +I (379) RELAY_CHN_MULTI_EXAMPLE: Initializing LED indicator +I (389) gpio: GPIO[4]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (389) led_indicator: LED Indicator Version: 1.1.1 +I (399) gpio: GPIO[4]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0 +I (409) led_indicator: Indicator create successfully. type:GPIO mode, hardware_data:0x3fc96498, blink_lists:custom +I (419) gpio: GPIO[10]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (429) led_indicator: LED Indicator Version: 1.1.1 +I (429) gpio: GPIO[10]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0 +I (439) led_indicator: Indicator create successfully. type:GPIO mode, hardware_data:0x3fc965a4, blink_lists:custom +I (449) gpio: GPIO[9]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (459) led_indicator: LED Indicator Version: 1.1.1 +I (459) gpio: GPIO[9]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0 +I (469) led_indicator: Indicator create successfully. type:GPIO mode, hardware_data:0x3fc966b0, blink_lists:custom +I (479) RELAY_CHN_MULTI_EXAMPLE: Relay Channel Multi Example is ready to operate +I (489) main_task: Returned from app_main() +I (3759) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from IDLE to FORWARD +I (6639) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from FORWARD to STOPPED +I (7439) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from STOPPED to IDLE +I (7439) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from IDLE to TILT_FORWARD +I (8119) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from TILT_FORWARD to IDLE +I (13579) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from IDLE to TILT_REVERSE +I (14419) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from TILT_REVERSE to IDLE +I (20319) RELAY_CHN_MULTI_EXAMPLE: Selected channel: 1 +I (21479) RELAY_CHN_MULTI_EXAMPLE: Selected channel: 2 +I (22229) RELAY_CHN_MULTI_EXAMPLE: Selected channel: 0 +I (23169) RELAY_CHN_MULTI_EXAMPLE: Selected channel: 1 +I (29039) RELAY_CHN_MULTI_EXAMPLE: Selected all channels +I (35419) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from IDLE to FORWARD +I (35419) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #1, from IDLE to FORWARD +I (35419) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #2, from IDLE to FORWARD +I (39349) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from FORWARD to STOPPED +I (39349) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #1, from FORWARD to STOPPED +I (39359) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #2, from FORWARD to STOPPED +I (40149) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from STOPPED to IDLE +I (40149) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from IDLE to TILT_FORWARD +I (40159) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #1, from STOPPED to IDLE +I (40169) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #1, from IDLE to TILT_FORWARD +I (40179) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #2, from STOPPED to IDLE +I (40189) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #2, from IDLE to TILT_FORWARD +I (41699) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from TILT_FORWARD to IDLE +I (41699) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #1, from TILT_FORWARD to IDLE +I (41709) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #2, from TILT_FORWARD to IDLE +I (64129) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from IDLE to TILT_REVERSE +I (64129) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #1, from IDLE to TILT_REVERSE +I (64139) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #2, from IDLE to TILT_REVERSE +I (68929) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from TILT_REVERSE to IDLE +I (68929) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #1, from TILT_REVERSE to IDLE +I (68939) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #2, from TILT_REVERSE to IDLE +I (280109) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from IDLE to FORWARD +I (280109) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #1, from IDLE to FORWARD +I (280109) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #2, from IDLE to FORWARD +I (283219) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from FORWARD to STOPPED +I (283219) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from STOPPED to REVERSE_PENDING +I (283229) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #1, from FORWARD to STOPPED +I (283239) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #1, from STOPPED to REVERSE_PENDING +I (283249) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #2, from FORWARD to STOPPED +I (283259) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #2, from STOPPED to REVERSE_PENDING +I (284019) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from REVERSE_PENDING to REVERSE +I (284019) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #1, from REVERSE_PENDING to REVERSE +I (284029) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #2, from REVERSE_PENDING to REVERSE +``` \ No newline at end of file diff --git a/examples/relay_chn_multi/example_schematic.png b/examples/relay_chn_multi/example_schematic.png new file mode 100644 index 0000000000000000000000000000000000000000..45c4aff4e41f91a5fb94fe57873d51c7f962a88a GIT binary patch literal 135707 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�(n=JIL-=?z>p){K>x?jC188fV%uae@A{s!Vp1 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 m>)HSR2-v9qH}?h6y~}x~sImF8`=4.1.0' + espressif/button: ^4.1.1 + espressif/led_indicator: ^1.1.1 + relay_chn: + version: '*' + override_path: ../../../ diff --git a/examples/relay_chn_multi/main/relay_chn_multi_main.c b/examples/relay_chn_multi/main/relay_chn_multi_main.c new file mode 100644 index 0000000..e209ed2 --- /dev/null +++ b/examples/relay_chn_multi/main/relay_chn_multi_main.c @@ -0,0 +1,427 @@ +/* + * SPDX-FileCopyrightText: 2025 Kozmotronik Tech + * + * SPDX-License-Identifier: MIT + */ + +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_err.h" +#include "esp_log.h" +#include "esp_check.h" +#include "driver/gpio.h" +#include "nvs.h" +#include "nvs_flash.h" +#include "esp_timer.h" +#include "button_gpio.h" +#include "iot_button.h" +#include "led_indicator.h" +#include "relay_chn.h" + +static const char *TAG = "RELAY_CHN_MULTI_EXAMPLE"; + +#define EXAMPLE_CHN_INDICATOR_ON_TIME_MS 3000 +#define EXAMPLE_ALL_CHANNELS CONFIG_RELAY_CHN_COUNT + + +/** + * @brief LED indicator modes for different states. + */ +typedef enum { + INDICATOR_MODE_SELECT, /*!< Channel select indication */ + INDICATOR_MODE_OK, /*!< OK/Success indication */ + INDICATOR_MODE_FAIL, /*!< Fail/Error indication */ + INDICATOR_MODE_TILTING, /*!< Tilting operation in progress */ + INDICATOR_MODE_RUNNING, /*!< Full run operation in progress */ + INDICATOR_MODE_MAX /*!< Maximum number of indicator modes */ +} indicator_mode_t; + +/** @brief Blink pattern for channel select indication. */ +static const blink_step_t indc_mode_select[] = { + {LED_BLINK_HOLD, LED_STATE_ON, EXAMPLE_CHN_INDICATOR_ON_TIME_MS}, // step1: turn on LED 3000 ms + {LED_BLINK_HOLD, LED_STATE_OFF, 10}, // step2: turn off LED 500 ms + {LED_BLINK_STOP, 0, 0}, // step4: stop blink (off) +}; + +/** @brief Blink pattern for OK/Success indication. */ +static const blink_step_t indc_mode_ok[] = { + {LED_BLINK_HOLD, LED_STATE_ON, 100}, // step1: turn on LED 100 ms + {LED_BLINK_HOLD, LED_STATE_OFF, 50}, // step2: turn off LED 50 ms + {LED_BLINK_HOLD, LED_STATE_ON, 100}, // step3: turn on LED 100 ms + {LED_BLINK_HOLD, LED_STATE_OFF, 50}, // step4: turn off LED 50 ms + {LED_BLINK_STOP, 0, 0}, // step5: stop blink (off) +}; + +/** @brief Blink pattern for Fail/Error indication. */ +static const blink_step_t indc_mode_fail[] = { + {LED_BLINK_HOLD, LED_STATE_ON, 1000}, // step1: turn on LED 1000 ms + {LED_BLINK_HOLD, LED_STATE_OFF, 500}, // step2: turn off LED 500 ms + {LED_BLINK_STOP, 0, 0}, // step4: stop blink (off) +}; + +/** @brief Blink pattern for full run operation. */ +static const blink_step_t indc_mode_running[] = { + {LED_BLINK_HOLD, LED_STATE_ON, 300}, // step1: turn on LED 300 ms + {LED_BLINK_HOLD, LED_STATE_OFF, 100}, // step2: turn off LED 100 ms + {LED_BLINK_LOOP, 0, 0}, // step3: loop from step1 +}; + +/** @brief Blink pattern for tilting operation. */ +static const blink_step_t indc_mode_tilting[] = { + {LED_BLINK_HOLD, LED_STATE_ON, 100}, // step1: turn on LED 100 ms + {LED_BLINK_HOLD, LED_STATE_OFF, 50}, // step2: turn off LED 50 ms + {LED_BLINK_LOOP, 0, 0}, // step3: loop from step1 +}; + +/** @brief Array of LED indicator blink patterns. */ +blink_step_t const *led_indicator_modes[] = { + [INDICATOR_MODE_SELECT] = indc_mode_select, + [INDICATOR_MODE_OK] = indc_mode_ok, + [INDICATOR_MODE_FAIL] = indc_mode_fail, + [INDICATOR_MODE_RUNNING] = indc_mode_running, + [INDICATOR_MODE_TILTING] = indc_mode_tilting, + [INDICATOR_MODE_MAX] = NULL, +}; + +/** @brief Handle for the LED indicator. */ +static led_indicator_handle_t indicators[CONFIG_RELAY_CHN_COUNT]; +static uint8_t selected_channel = 0; /*!< Currently selected channel */ +static uint8_t selected_channel_backup = 0; /*!< Backup for last selected channel */ +static int64_t last_indc_update_us = 0; /*!< Timestamp of the last channel selection */ + + +/** + * @brief Initializes the buttons for user interaction. + * + * This function configures and creates GPIO buttons for UP, DOWN, and STOP + * operations. It also registers callbacks for single-click and long-press + * events to control the relay channel. + * + * @return esp_err_t + * - ESP_OK: Success + * - Others: Fail + */ +static esp_err_t init_buttons(void); + +/** + * @brief Initializes the LED indicator. + * + * This function configures and creates the LED indicator used to provide + * visual feedback on the relay channel's status. + * + * @return esp_err_t + * - ESP_OK: Success + * - ESP_FAIL: Fail + */ +static esp_err_t init_led_indicators(void); + +/** + * @brief Starts all indicators with the specified mode. + * + * @param mode One of the indicator modes defined with indicator_mode_t. + */ +static void start_all_indicators_for_mode(indicator_mode_t mode); + +/** + * @brief Stops all indicators with the specified mode. + * + * @param mode One of the indicator modes defined with indicator_mode_t. + */ +static void stop_all_indicators_for_mode(indicator_mode_t mode); + +/** + * @brief Event listener for relay channel state changes to log the state transition. + */ +static void example_event_listener(uint8_t ch, relay_chn_state_t old_state, relay_chn_state_t new_state); + +void app_main(void) +{ + const uint8_t gpio_map[] = { 18, 19, 5, 6, 7, 8 }; + const uint8_t gpio_count = sizeof(gpio_map) / sizeof(gpio_map[0]); + + ESP_LOGI(TAG, "Initializing relay channel"); + ESP_ERROR_CHECK(relay_chn_create(gpio_map, gpio_count)); + ESP_ERROR_CHECK(relay_chn_register_listener(example_event_listener)); + + ESP_LOGI(TAG, "Initializing buttons"); + ESP_ERROR_CHECK(init_buttons()); + ESP_LOGI(TAG, "Initializing LED indicator"); + ESP_ERROR_CHECK(init_led_indicators()); + + ESP_LOGI(TAG, "Relay Channel Multi Example is ready to operate"); + + // Indicate init was successful + start_all_indicators_for_mode(INDICATOR_MODE_OK); +} + +static void on_click_up(void *arg, void *data) +{ + if (selected_channel == EXAMPLE_ALL_CHANNELS) { + relay_chn_run_forward_all(); + } else { + relay_chn_run_forward(selected_channel); + } +} + +static void on_click_down(void *arg, void *data) +{ + if (selected_channel == EXAMPLE_ALL_CHANNELS) { + relay_chn_run_reverse_all(); + } else { + relay_chn_run_reverse(selected_channel); + } +} + +static void on_click_stop(void *arg, void *data) +{ + if (selected_channel == EXAMPLE_ALL_CHANNELS) { + relay_chn_stop_all(); + } else { + relay_chn_stop(selected_channel); + } +} + +static void on_click_flip(void *arg, void *data) +{ + if (selected_channel == EXAMPLE_ALL_CHANNELS) { + relay_chn_flip_direction_all(); + start_all_indicators_for_mode(INDICATOR_MODE_OK); + } else { + relay_chn_flip_direction(selected_channel); + led_indicator_start(indicators[selected_channel], INDICATOR_MODE_OK); + } +} + +static void on_click_tilt_up(void *arg, void *data) +{ + if (selected_channel == EXAMPLE_ALL_CHANNELS) { + relay_chn_tilt_forward_all(); + } else { + relay_chn_tilt_forward(selected_channel); + } +} + +static void on_click_tilt_down(void *arg, void *data) +{ + if (selected_channel == EXAMPLE_ALL_CHANNELS) { + relay_chn_tilt_reverse_all(); + } else { + relay_chn_tilt_reverse(selected_channel); + } +} + +static void on_release_tilt(void *arg, void *data) +{ + if (selected_channel == EXAMPLE_ALL_CHANNELS) { + relay_chn_tilt_stop_all(); + } else { + relay_chn_tilt_stop(selected_channel); + } +} + +static void on_click_select(void *arg, void *data) +{ + int64_t now_us = esp_timer_get_time(); + uint32_t delta_ms = (now_us - last_indc_update_us) / 1000; + if (delta_ms >= EXAMPLE_CHN_INDICATOR_ON_TIME_MS) { + // Channel indicator was off, turn it on first + if (selected_channel == EXAMPLE_ALL_CHANNELS) { + start_all_indicators_for_mode(INDICATOR_MODE_SELECT); + } else { + led_indicator_start(indicators[selected_channel], INDICATOR_MODE_SELECT); + } + last_indc_update_us = esp_timer_get_time(); // Save last selection time + return; + } + + if (selected_channel == EXAMPLE_ALL_CHANNELS) { + // All channels selected previously, restore the selected channel + selected_channel = selected_channel_backup; + ESP_LOGI(TAG, "Restored selected channel: %d", selected_channel); + stop_all_indicators_for_mode(INDICATOR_MODE_SELECT); + led_indicator_start(indicators[selected_channel], INDICATOR_MODE_SELECT); + return; + } + + // Channel indicator is turned on, select the next channel + selected_channel = ((selected_channel + 1) % CONFIG_RELAY_CHN_COUNT); + ESP_LOGI(TAG, "Selected channel: %d", selected_channel); + // Update the indicator + stop_all_indicators_for_mode(INDICATOR_MODE_SELECT); + led_indicator_start(indicators[selected_channel], INDICATOR_MODE_SELECT); + last_indc_update_us = esp_timer_get_time(); // Save last selection time +} + +static void on_click_select_all(void *arg, void *data) +{ + if (selected_channel == EXAMPLE_ALL_CHANNELS) { + ESP_LOGI(TAG, "All channels are selected already"); + int64_t now_us = esp_timer_get_time(); + uint32_t delta_ms = (now_us - last_indc_update_us) / 1000; + // If the indicators in sleep, wake up + if (delta_ms >= EXAMPLE_CHN_INDICATOR_ON_TIME_MS) { + start_all_indicators_for_mode(INDICATOR_MODE_SELECT); + } + return; + } + + selected_channel_backup = selected_channel; + selected_channel = EXAMPLE_ALL_CHANNELS; + ESP_LOGI(TAG, "Selected all channels"); + start_all_indicators_for_mode(INDICATOR_MODE_SELECT); + last_indc_update_us = esp_timer_get_time(); // Save last selection time +} + +static void start_all_indicators_for_mode(indicator_mode_t mode) +{ + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + led_indicator_start(indicators[i], mode); + } +} + +static void stop_all_indicators_for_mode(indicator_mode_t mode) +{ + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + led_indicator_stop(indicators[i], mode); + led_indicator_set_on_off(indicators[i], false); + } +} + +static void example_event_listener(uint8_t ch, relay_chn_state_t old_state, relay_chn_state_t new_state) +{ + ESP_LOGI(TAG, "example_event_listener: State change for #%d, from %s to %s", + ch, relay_chn_state_to_str(old_state), relay_chn_state_to_str(new_state)); + switch (new_state) { + case RELAY_CHN_STATE_FORWARD: + case RELAY_CHN_STATE_REVERSE: + led_indicator_start(indicators[ch], INDICATOR_MODE_RUNNING); + break; + + case RELAY_CHN_STATE_STOPPED: + case RELAY_CHN_STATE_IDLE: + if (old_state == RELAY_CHN_STATE_FORWARD || old_state == RELAY_CHN_STATE_REVERSE) { + led_indicator_stop(indicators[ch], INDICATOR_MODE_RUNNING); + // Make sure the indicator turned off + led_indicator_set_on_off(indicators[ch], false); + } + else if (old_state == RELAY_CHN_STATE_TILT_FORWARD || old_state == RELAY_CHN_STATE_TILT_REVERSE) { + led_indicator_stop(indicators[ch], INDICATOR_MODE_TILTING); + // Make sure the indicator turned off + led_indicator_set_on_off(indicators[ch], false); + } + break; + + case RELAY_CHN_STATE_TILT_FORWARD: + case RELAY_CHN_STATE_TILT_REVERSE: + led_indicator_start(indicators[ch], INDICATOR_MODE_TILTING); + break; + + default: // No-op + } +} + +static esp_err_t init_buttons() +{ + esp_err_t ret; + button_config_t btn_cfg = {0}; + + uint8_t active_level = CONFIG_EXAMPLE_RLCHN_BTN_ACTIVE_LEVEL_LOW ? 0 : 1; + + button_gpio_config_t btn_gpio_ccfg = { + .gpio_num = (gpio_num_t) CONFIG_EXAMPLE_RLCHN_BTN_UP_IO_NUM, + .active_level = active_level + }; + + ESP_LOGI(TAG, "Initializing buttons with active level: %u", active_level); + button_handle_t btn_up = NULL, btn_down = NULL, btn_stop = NULL, btn_select = NULL; + // --- Create buttons --- + ret = iot_button_new_gpio_device(&btn_cfg, &btn_gpio_ccfg, &btn_up); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create UP button"); + ESP_RETURN_ON_FALSE(btn_up != NULL, ret, TAG, "Failed to create UP button"); + + btn_gpio_ccfg.gpio_num = (gpio_num_t) CONFIG_EXAMPLE_RLCHN_BTN_DOWN_IO_NUM; + ret = iot_button_new_gpio_device(&btn_cfg, &btn_gpio_ccfg, &btn_down); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create DOWN button"); + ESP_RETURN_ON_FALSE(btn_down != NULL, ret, TAG, "Failed to create DOWN button"); + + btn_gpio_ccfg.gpio_num = (gpio_num_t) CONFIG_EXAMPLE_RLCHN_BTN_STOP_IO_NUM; + ret = iot_button_new_gpio_device(&btn_cfg, &btn_gpio_ccfg, &btn_stop); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create STOP button"); + ESP_RETURN_ON_FALSE(btn_stop != NULL, ret, TAG, "Failed to create STOP button"); + + btn_gpio_ccfg.gpio_num = (gpio_num_t) CONFIG_EXAMPLE_RLCHN_BTN_SELECT_IO_NUM; + ret = iot_button_new_gpio_device(&btn_cfg, &btn_gpio_ccfg, &btn_select); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create SELECT button"); + ESP_RETURN_ON_FALSE(btn_select != NULL, ret, TAG, "Failed to create SELECT button"); + // --- Create buttons --- + + // --- Register button callbacks --- + ESP_LOGI(TAG, "Setting up button callbacks. Configured long press time: %d ms", CONFIG_EXAMPLE_RLCHN_BTN_LONG_PRESS_TIME_MS); + button_event_args_t btn_event_args = { + .long_press.press_time = CONFIG_EXAMPLE_RLCHN_BTN_LONG_PRESS_TIME_MS + }; + // --- Register UP and TILT_UP operations on UP button --- + ret = iot_button_register_cb(btn_up, BUTTON_SINGLE_CLICK, NULL, on_click_up, NULL); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register UP button click callback"); + ret = iot_button_register_cb(btn_up, BUTTON_LONG_PRESS_START, &btn_event_args, on_click_tilt_up, NULL); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register TILT_UP button press callback"); + ret = iot_button_register_cb(btn_up, BUTTON_LONG_PRESS_UP, NULL, on_release_tilt, NULL); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register TILT_UP button release callback"); + + // --- Register DOWN and TILT_DOWN operations on DOWN button --- + ret = iot_button_register_cb(btn_down, BUTTON_SINGLE_CLICK, NULL, on_click_down, NULL); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register DOWN button click callback"); + ret = iot_button_register_cb(btn_down, BUTTON_LONG_PRESS_START, &btn_event_args, on_click_tilt_down, NULL); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register TILT_DOWN button press callback"); + ret = iot_button_register_cb(btn_down, BUTTON_LONG_PRESS_UP, NULL, on_release_tilt, NULL); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register TILT_DOWN button release callback"); + + // --- Register STOP and FLIP operations on STOP --- + ret = iot_button_register_cb(btn_stop, BUTTON_SINGLE_CLICK, NULL, on_click_stop, NULL); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register STOP button click callback"); + ret = iot_button_register_cb(btn_stop, BUTTON_LONG_PRESS_START, &btn_event_args, on_click_flip, NULL); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register FLIP button press callback"); + + // --- Register channel SELECT and SELECT_ALL --- + ret = iot_button_register_cb(btn_select, BUTTON_SINGLE_CLICK, NULL, on_click_select, NULL); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register SELECT button click callback"); + ret = iot_button_register_cb(btn_select, BUTTON_LONG_PRESS_START, &btn_event_args, on_click_select_all, NULL); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register SELECT_ALL press callback"); + + return ESP_OK; +} + +static esp_err_t init_led_indicators() +{ + const int indicator_io_nums[CONFIG_RELAY_CHN_COUNT] = { + CONFIG_EXAMPLE_RLCHN_LED_INDICATOR1_IO_NUM, + CONFIG_EXAMPLE_RLCHN_LED_INDICATOR2_IO_NUM, + CONFIG_EXAMPLE_RLCHN_LED_INDICATOR3_IO_NUM + }; + + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + gpio_num_t indicator_io_num = (gpio_num_t) indicator_io_nums[i]; + gpio_reset_pin(indicator_io_num); // Clear the output buffers + + led_indicator_gpio_config_t led_indicator_gpio_cfg = { + .gpio_num = indicator_io_num, + .is_active_level_high = true + }; + + led_indicator_config_t led_indicator_cfg = { + .mode = LED_GPIO_MODE, + .led_indicator_gpio_config = &led_indicator_gpio_cfg, + .blink_lists = led_indicator_modes, + .blink_list_num = INDICATOR_MODE_MAX + }; + + indicators[i] = led_indicator_create(&led_indicator_cfg); + if (!indicators[i]) { + ESP_LOGE(TAG, "Failed to create LED indicator"); + return ESP_FAIL; + } + } + + return ESP_OK; +} diff --git a/examples/relay_chn_multi/sdkconfig.defaults b/examples/relay_chn_multi/sdkconfig.defaults new file mode 100644 index 0000000..2eeaa43 --- /dev/null +++ b/examples/relay_chn_multi/sdkconfig.defaults @@ -0,0 +1,10 @@ +# Halt on panic +CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT=y + +# Relay Channel Configs +CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT=y +CONFIG_RELAY_CHN_COUNT=3 +# Keep this as short as possible for example purposes +CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC=5 +CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC=20 +CONFIG_RELAY_CHN_ENABLE_TILTING=y From 6caa4f1bd563aad823b575f4b028feaa97458c67 Mon Sep 17 00:00:00 2001 From: ismail Date: Thu, 11 Sep 2025 16:03:29 +0300 Subject: [PATCH 62/69] Ignore session specific settings files --- .gitignore | 1 + .vscode/settings.json | 3 --- examples/relay_chn_multi/.gitignore | 5 ++++- examples/relay_chn_multi/.vscode/settings.json | 6 ------ examples/relay_chn_single/.gitignore | 5 ++++- examples/relay_chn_single/.vscode/settings.json | 17 ----------------- 6 files changed, 9 insertions(+), 28 deletions(-) delete mode 100644 .vscode/settings.json delete mode 100644 examples/relay_chn_multi/.vscode/settings.json delete mode 100644 examples/relay_chn_single/.vscode/settings.json diff --git a/.gitignore b/.gitignore index 3b5303e..827f905 100644 --- a/.gitignore +++ b/.gitignore @@ -73,6 +73,7 @@ test_multi_heap_host # VS Code Settings # .vscode/ +settings.json # VIM files *.swp diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index b53318f..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "C_Cpp.intelliSenseEngine": "default" -} \ No newline at end of file diff --git a/examples/relay_chn_multi/.gitignore b/examples/relay_chn_multi/.gitignore index 51c9513..f91069f 100644 --- a/examples/relay_chn_multi/.gitignore +++ b/examples/relay_chn_multi/.gitignore @@ -1,3 +1,6 @@ build/ sdkconfig -sdkconfig.old \ No newline at end of file +sdkconfig.old + +# Exclude auto-populated settings file +settings.json \ No newline at end of file diff --git a/examples/relay_chn_multi/.vscode/settings.json b/examples/relay_chn_multi/.vscode/settings.json deleted file mode 100644 index 9fa2d52..0000000 --- a/examples/relay_chn_multi/.vscode/settings.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "C_Cpp.intelliSenseEngine": "default", - "files.associations": { - "led_indicator_blink_default.h": "c" - } -} diff --git a/examples/relay_chn_single/.gitignore b/examples/relay_chn_single/.gitignore index 51c9513..f91069f 100644 --- a/examples/relay_chn_single/.gitignore +++ b/examples/relay_chn_single/.gitignore @@ -1,3 +1,6 @@ build/ sdkconfig -sdkconfig.old \ No newline at end of file +sdkconfig.old + +# Exclude auto-populated settings file +settings.json \ No newline at end of file diff --git a/examples/relay_chn_single/.vscode/settings.json b/examples/relay_chn_single/.vscode/settings.json deleted file mode 100644 index 81b74eb..0000000 --- a/examples/relay_chn_single/.vscode/settings.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "C_Cpp.intelliSenseEngine": "default", - "idf.espIdfPath": "/disk/Depo/Developer/SDK/esp-idf/v5.4/esp-idf", - "idf.pythonInstallPath": "/usr/bin/python3", - "idf.openOcdConfigs": [ - "board/esp32c3-builtin.cfg" - ], - "idf.port": "/dev/ttyUSB0", - "idf.toolsPath": "/home/ismail/.espressif", - "idf.customExtraVars": { - "IDF_TARGET": "esp32c3" - }, - "idf.flashType": "UART", - "files.associations": { - "led_indicator_blink_default.h": "c" - } -} From a1ff54b6e99175459245f323be4de30d86fe7e7c Mon Sep 17 00:00:00 2001 From: ismail Date: Fri, 12 Sep 2025 09:39:16 +0300 Subject: [PATCH 63/69] Bump versions to 1.0.0 --- README.md | 2 +- idf_component.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3b40d17..66852ca 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ dependencies: # Add as a custom component from git repository relay_chn: git: https://git.kozmotronik.com.tr/KozmotronikTech/relay_chn.git - version: '>=0.5.0' + version: '>=1.0.0' ``` ## Usage diff --git a/idf_component.yml b/idf_component.yml index bad0713..414decb 100644 --- a/idf_component.yml +++ b/idf_component.yml @@ -1,5 +1,5 @@ name: relay_chn -version: "0.5.0" +version: "1.0.0" description: "Custom component for relay channel control" license: "MIT" url: "https://git.kozmotronik.com.tr/KozmotronikTech/relay_chn" From 9f45a2310d106c554187eec02ceb7d307a46fd41 Mon Sep 17 00:00:00 2001 From: ismail Date: Fri, 12 Sep 2025 10:27:46 +0300 Subject: [PATCH 64/69] Update and enrich the manifest file --- idf_component.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/idf_component.yml b/idf_component.yml index 414decb..7af8b04 100644 --- a/idf_component.yml +++ b/idf_component.yml @@ -1,6 +1,12 @@ -name: relay_chn version: "1.0.0" -description: "Custom component for relay channel control" +description: "Relay channel driver for bipolar motors." license: "MIT" url: "https://git.kozmotronik.com.tr/KozmotronikTech/relay_chn" -repository: "https://git.kozmotronik.com.tr/KozmotronikTech/relay_chn.git" \ No newline at end of file +repository: "https://git.kozmotronik.com.tr/KozmotronikTech/relay_chn.git" +dependencies: + idf: ">=5.0" +examples: + - path: examples/relay_chn_single + - path: examples/relay_chn_multi +files: + use_gitignore: true \ No newline at end of file From 8416187d86485388a4e7dfe567b9178a09bfe681 Mon Sep 17 00:00:00 2001 From: ismail Date: Fri, 12 Sep 2025 11:23:06 +0300 Subject: [PATCH 65/69] Fix test_apps directory issue Fixed test_apps directory locating issue by fixing the path to "project_root/test_apps" and removed find command since it searches recursively and founds the similar directories in managed_components directory. Also added current time to the output. --- scripts/run_tests.sh | 5 ++--- scripts/run_tests_all_profiles.sh | 9 +++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/scripts/run_tests.sh b/scripts/run_tests.sh index 459c81d..197333b 100755 --- a/scripts/run_tests.sh +++ b/scripts/run_tests.sh @@ -98,8 +98,7 @@ fi script_dir=$(dirname "$(readlink -f "$0")") project_root=$(dirname "$script_dir") -echo "🔍 Searching for 'test_apps' directory in '$project_root'..." -test_apps_dir=$(find "$project_root" -type d -name "test_apps" | head -n 1) +test_apps_dir="${project_root}/test_apps" if [[ -z "$test_apps_dir" || ! -d "$test_apps_dir" ]]; then echo "❌ 'test_apps' directory not found within the project root: '$project_root'" @@ -107,7 +106,7 @@ if [[ -z "$test_apps_dir" || ! -d "$test_apps_dir" ]]; then exit 1 fi -echo "✅ Found 'test_apps' at: $test_apps_dir" +echo "⏳ Current time is: $(date +"%Y-%m-%d %H:%M:%S")" echo "🧪 Test mode: $arg_tag | Profile: $arg_profile" echo "🧹 Clean: $arg_clean | 📄 Log: $arg_log" diff --git a/scripts/run_tests_all_profiles.sh b/scripts/run_tests_all_profiles.sh index 4a04ab4..ce1fc60 100755 --- a/scripts/run_tests_all_profiles.sh +++ b/scripts/run_tests_all_profiles.sh @@ -14,10 +14,15 @@ project_root=$(dirname "$script_dir") echo "Script dir: ${script_dir}" echo "Project root: ${project_root}" -echo "🔍 Searching for 'test_apps' directory in '$project_root'..." -test_apps_dir=$(find "$project_root" -type d -name "test_apps" | head -n 1) +test_apps_dir="${project_root}/test_apps" echo "test_apps dir: ${test_apps_dir}" +if [[ -z "$test_apps_dir" || ! -d "$test_apps_dir" ]]; then + echo "❌ 'test_apps' directory not found within the project root: '$project_root'" + echo " Please ensure the script is in a 'scripts' directory and 'test_apps' is a sibling." + exit 1 +fi + # Execute tests for all profiles mapfile -t profiles < <(find "${test_apps_dir}/profiles" -maxdepth 1 -type f) From 5440440c4d7133155394ff5d946b82511cbfb163 Mon Sep 17 00:00:00 2001 From: ismail Date: Sat, 13 Sep 2025 09:39:24 +0300 Subject: [PATCH 66/69] Untrack autogenerated sdkconfig.old file --- .gitignore | 3 +- test_apps/sdkconfig.old | 1380 --------------------------------------- 2 files changed, 2 insertions(+), 1381 deletions(-) delete mode 100644 test_apps/sdkconfig.old diff --git a/.gitignore b/.gitignore index 827f905..6176a4a 100644 --- a/.gitignore +++ b/.gitignore @@ -59,6 +59,7 @@ tools/test_apps/**/sdkconfig.old # autogenerated config files sdkconfig test_apps/sdkconfig +test_apps/sdkconfig.old TEST_LOGS/ build_summary_*.xml @@ -113,4 +114,4 @@ XUNIT_RESULT*.xml .clangd # Vale -.vale/styles/* \ No newline at end of file +.vale/styles/* diff --git a/test_apps/sdkconfig.old b/test_apps/sdkconfig.old deleted file mode 100644 index 74f5cce..0000000 --- a/test_apps/sdkconfig.old +++ /dev/null @@ -1,1380 +0,0 @@ -# -# Automatically generated file. DO NOT EDIT. -# Espressif IoT Development Framework (ESP-IDF) 5.4.0 Project Configuration -# -CONFIG_SOC_BROWNOUT_RESET_SUPPORTED="Not determined" -CONFIG_SOC_TWAI_BRP_DIV_SUPPORTED="Not determined" -CONFIG_SOC_DPORT_WORKAROUND="Not determined" -CONFIG_SOC_CAPS_ECO_VER_MAX=301 -CONFIG_SOC_ADC_SUPPORTED=y -CONFIG_SOC_DAC_SUPPORTED=y -CONFIG_SOC_UART_SUPPORTED=y -CONFIG_SOC_MCPWM_SUPPORTED=y -CONFIG_SOC_GPTIMER_SUPPORTED=y -CONFIG_SOC_SDMMC_HOST_SUPPORTED=y -CONFIG_SOC_BT_SUPPORTED=y -CONFIG_SOC_PCNT_SUPPORTED=y -CONFIG_SOC_PHY_SUPPORTED=y -CONFIG_SOC_WIFI_SUPPORTED=y -CONFIG_SOC_SDIO_SLAVE_SUPPORTED=y -CONFIG_SOC_TWAI_SUPPORTED=y -CONFIG_SOC_EFUSE_SUPPORTED=y -CONFIG_SOC_EMAC_SUPPORTED=y -CONFIG_SOC_ULP_SUPPORTED=y -CONFIG_SOC_CCOMP_TIMER_SUPPORTED=y -CONFIG_SOC_RTC_FAST_MEM_SUPPORTED=y -CONFIG_SOC_RTC_SLOW_MEM_SUPPORTED=y -CONFIG_SOC_RTC_MEM_SUPPORTED=y -CONFIG_SOC_I2S_SUPPORTED=y -CONFIG_SOC_RMT_SUPPORTED=y -CONFIG_SOC_SDM_SUPPORTED=y -CONFIG_SOC_GPSPI_SUPPORTED=y -CONFIG_SOC_LEDC_SUPPORTED=y -CONFIG_SOC_I2C_SUPPORTED=y -CONFIG_SOC_SUPPORT_COEXISTENCE=y -CONFIG_SOC_AES_SUPPORTED=y -CONFIG_SOC_MPI_SUPPORTED=y -CONFIG_SOC_SHA_SUPPORTED=y -CONFIG_SOC_FLASH_ENC_SUPPORTED=y -CONFIG_SOC_SECURE_BOOT_SUPPORTED=y -CONFIG_SOC_TOUCH_SENSOR_SUPPORTED=y -CONFIG_SOC_BOD_SUPPORTED=y -CONFIG_SOC_ULP_FSM_SUPPORTED=y -CONFIG_SOC_CLK_TREE_SUPPORTED=y -CONFIG_SOC_MPU_SUPPORTED=y -CONFIG_SOC_WDT_SUPPORTED=y -CONFIG_SOC_SPI_FLASH_SUPPORTED=y -CONFIG_SOC_RNG_SUPPORTED=y -CONFIG_SOC_LIGHT_SLEEP_SUPPORTED=y -CONFIG_SOC_DEEP_SLEEP_SUPPORTED=y -CONFIG_SOC_LP_PERIPH_SHARE_INTERRUPT=y -CONFIG_SOC_PM_SUPPORTED=y -CONFIG_SOC_DPORT_WORKAROUND_DIS_INTERRUPT_LVL=5 -CONFIG_SOC_XTAL_SUPPORT_26M=y -CONFIG_SOC_XTAL_SUPPORT_40M=y -CONFIG_SOC_XTAL_SUPPORT_AUTO_DETECT=y -CONFIG_SOC_ADC_RTC_CTRL_SUPPORTED=y -CONFIG_SOC_ADC_DIG_CTRL_SUPPORTED=y -CONFIG_SOC_ADC_DMA_SUPPORTED=y -CONFIG_SOC_ADC_PERIPH_NUM=2 -CONFIG_SOC_ADC_MAX_CHANNEL_NUM=10 -CONFIG_SOC_ADC_ATTEN_NUM=4 -CONFIG_SOC_ADC_DIGI_CONTROLLER_NUM=2 -CONFIG_SOC_ADC_PATT_LEN_MAX=16 -CONFIG_SOC_ADC_DIGI_MIN_BITWIDTH=9 -CONFIG_SOC_ADC_DIGI_MAX_BITWIDTH=12 -CONFIG_SOC_ADC_DIGI_RESULT_BYTES=2 -CONFIG_SOC_ADC_DIGI_DATA_BYTES_PER_CONV=4 -CONFIG_SOC_ADC_DIGI_MONITOR_NUM=0 -CONFIG_SOC_ADC_SAMPLE_FREQ_THRES_HIGH=2 -CONFIG_SOC_ADC_SAMPLE_FREQ_THRES_LOW=20 -CONFIG_SOC_ADC_RTC_MIN_BITWIDTH=9 -CONFIG_SOC_ADC_RTC_MAX_BITWIDTH=12 -CONFIG_SOC_ADC_SHARED_POWER=y -CONFIG_SOC_SHARED_IDCACHE_SUPPORTED=y -CONFIG_SOC_IDCACHE_PER_CORE=y -CONFIG_SOC_CPU_CORES_NUM=2 -CONFIG_SOC_CPU_INTR_NUM=32 -CONFIG_SOC_CPU_HAS_FPU=y -CONFIG_SOC_HP_CPU_HAS_MULTIPLE_CORES=y -CONFIG_SOC_CPU_BREAKPOINTS_NUM=2 -CONFIG_SOC_CPU_WATCHPOINTS_NUM=2 -CONFIG_SOC_CPU_WATCHPOINT_MAX_REGION_SIZE=64 -CONFIG_SOC_DAC_CHAN_NUM=2 -CONFIG_SOC_DAC_RESOLUTION=8 -CONFIG_SOC_DAC_DMA_16BIT_ALIGN=y -CONFIG_SOC_GPIO_PORT=1 -CONFIG_SOC_GPIO_PIN_COUNT=40 -CONFIG_SOC_GPIO_VALID_GPIO_MASK=0xFFFFFFFFFF -CONFIG_SOC_GPIO_IN_RANGE_MAX=39 -CONFIG_SOC_GPIO_OUT_RANGE_MAX=33 -CONFIG_SOC_GPIO_VALID_DIGITAL_IO_PAD_MASK=0xEF0FEA -CONFIG_SOC_GPIO_CLOCKOUT_BY_IO_MUX=y -CONFIG_SOC_GPIO_CLOCKOUT_CHANNEL_NUM=3 -CONFIG_SOC_GPIO_SUPPORT_HOLD_IO_IN_DSLP=y -CONFIG_SOC_I2C_NUM=2 -CONFIG_SOC_HP_I2C_NUM=2 -CONFIG_SOC_I2C_FIFO_LEN=32 -CONFIG_SOC_I2C_CMD_REG_NUM=16 -CONFIG_SOC_I2C_SUPPORT_SLAVE=y -CONFIG_SOC_I2C_SUPPORT_APB=y -CONFIG_SOC_I2C_STOP_INDEPENDENT=y -CONFIG_SOC_I2S_NUM=2 -CONFIG_SOC_I2S_HW_VERSION_1=y -CONFIG_SOC_I2S_SUPPORTS_APLL=y -CONFIG_SOC_I2S_SUPPORTS_PLL_F160M=y -CONFIG_SOC_I2S_SUPPORTS_PDM=y -CONFIG_SOC_I2S_SUPPORTS_PDM_TX=y -CONFIG_SOC_I2S_PDM_MAX_TX_LINES=1 -CONFIG_SOC_I2S_SUPPORTS_PDM_RX=y -CONFIG_SOC_I2S_PDM_MAX_RX_LINES=1 -CONFIG_SOC_I2S_SUPPORTS_ADC_DAC=y -CONFIG_SOC_I2S_SUPPORTS_ADC=y -CONFIG_SOC_I2S_SUPPORTS_DAC=y -CONFIG_SOC_I2S_SUPPORTS_LCD_CAMERA=y -CONFIG_SOC_I2S_MAX_DATA_WIDTH=24 -CONFIG_SOC_I2S_TRANS_SIZE_ALIGN_WORD=y -CONFIG_SOC_I2S_LCD_I80_VARIANT=y -CONFIG_SOC_LCD_I80_SUPPORTED=y -CONFIG_SOC_LCD_I80_BUSES=2 -CONFIG_SOC_LCD_I80_BUS_WIDTH=24 -CONFIG_SOC_LEDC_HAS_TIMER_SPECIFIC_MUX=y -CONFIG_SOC_LEDC_SUPPORT_APB_CLOCK=y -CONFIG_SOC_LEDC_SUPPORT_REF_TICK=y -CONFIG_SOC_LEDC_SUPPORT_HS_MODE=y -CONFIG_SOC_LEDC_TIMER_NUM=4 -CONFIG_SOC_LEDC_CHANNEL_NUM=8 -CONFIG_SOC_LEDC_TIMER_BIT_WIDTH=20 -CONFIG_SOC_MCPWM_GROUPS=2 -CONFIG_SOC_MCPWM_TIMERS_PER_GROUP=3 -CONFIG_SOC_MCPWM_OPERATORS_PER_GROUP=3 -CONFIG_SOC_MCPWM_COMPARATORS_PER_OPERATOR=2 -CONFIG_SOC_MCPWM_GENERATORS_PER_OPERATOR=2 -CONFIG_SOC_MCPWM_TRIGGERS_PER_OPERATOR=2 -CONFIG_SOC_MCPWM_GPIO_FAULTS_PER_GROUP=3 -CONFIG_SOC_MCPWM_CAPTURE_TIMERS_PER_GROUP=y -CONFIG_SOC_MCPWM_CAPTURE_CHANNELS_PER_TIMER=3 -CONFIG_SOC_MCPWM_GPIO_SYNCHROS_PER_GROUP=3 -CONFIG_SOC_MMU_PERIPH_NUM=2 -CONFIG_SOC_MMU_LINEAR_ADDRESS_REGION_NUM=3 -CONFIG_SOC_MPU_MIN_REGION_SIZE=0x20000000 -CONFIG_SOC_MPU_REGIONS_MAX_NUM=8 -CONFIG_SOC_PCNT_GROUPS=1 -CONFIG_SOC_PCNT_UNITS_PER_GROUP=8 -CONFIG_SOC_PCNT_CHANNELS_PER_UNIT=2 -CONFIG_SOC_PCNT_THRES_POINT_PER_UNIT=2 -CONFIG_SOC_RMT_GROUPS=1 -CONFIG_SOC_RMT_TX_CANDIDATES_PER_GROUP=8 -CONFIG_SOC_RMT_RX_CANDIDATES_PER_GROUP=8 -CONFIG_SOC_RMT_CHANNELS_PER_GROUP=8 -CONFIG_SOC_RMT_MEM_WORDS_PER_CHANNEL=64 -CONFIG_SOC_RMT_SUPPORT_REF_TICK=y -CONFIG_SOC_RMT_SUPPORT_APB=y -CONFIG_SOC_RMT_CHANNEL_CLK_INDEPENDENT=y -CONFIG_SOC_RTCIO_PIN_COUNT=18 -CONFIG_SOC_RTCIO_INPUT_OUTPUT_SUPPORTED=y -CONFIG_SOC_RTCIO_HOLD_SUPPORTED=y -CONFIG_SOC_RTCIO_WAKE_SUPPORTED=y -CONFIG_SOC_SDM_GROUPS=1 -CONFIG_SOC_SDM_CHANNELS_PER_GROUP=8 -CONFIG_SOC_SDM_CLK_SUPPORT_APB=y -CONFIG_SOC_SPI_HD_BOTH_INOUT_SUPPORTED=y -CONFIG_SOC_SPI_AS_CS_SUPPORTED=y -CONFIG_SOC_SPI_PERIPH_NUM=3 -CONFIG_SOC_SPI_DMA_CHAN_NUM=2 -CONFIG_SOC_SPI_MAX_CS_NUM=3 -CONFIG_SOC_SPI_SUPPORT_CLK_APB=y -CONFIG_SOC_SPI_MAXIMUM_BUFFER_SIZE=64 -CONFIG_SOC_SPI_MAX_PRE_DIVIDER=8192 -CONFIG_SOC_MEMSPI_SRC_FREQ_80M_SUPPORTED=y -CONFIG_SOC_MEMSPI_SRC_FREQ_40M_SUPPORTED=y -CONFIG_SOC_MEMSPI_SRC_FREQ_26M_SUPPORTED=y -CONFIG_SOC_MEMSPI_SRC_FREQ_20M_SUPPORTED=y -CONFIG_SOC_TIMER_GROUPS=2 -CONFIG_SOC_TIMER_GROUP_TIMERS_PER_GROUP=2 -CONFIG_SOC_TIMER_GROUP_COUNTER_BIT_WIDTH=64 -CONFIG_SOC_TIMER_GROUP_TOTAL_TIMERS=4 -CONFIG_SOC_TIMER_GROUP_SUPPORT_APB=y -CONFIG_SOC_TOUCH_SENSOR_VERSION=1 -CONFIG_SOC_TOUCH_SENSOR_NUM=10 -CONFIG_SOC_TOUCH_SAMPLE_CFG_NUM=1 -CONFIG_SOC_TWAI_CONTROLLER_NUM=1 -CONFIG_SOC_TWAI_BRP_MIN=2 -CONFIG_SOC_TWAI_CLK_SUPPORT_APB=y -CONFIG_SOC_TWAI_SUPPORT_MULTI_ADDRESS_LAYOUT=y -CONFIG_SOC_UART_NUM=3 -CONFIG_SOC_UART_HP_NUM=3 -CONFIG_SOC_UART_SUPPORT_APB_CLK=y -CONFIG_SOC_UART_SUPPORT_REF_TICK=y -CONFIG_SOC_UART_FIFO_LEN=128 -CONFIG_SOC_UART_BITRATE_MAX=5000000 -CONFIG_SOC_SPIRAM_SUPPORTED=y -CONFIG_SOC_SPI_MEM_SUPPORT_CONFIG_GPIO_BY_EFUSE=y -CONFIG_SOC_SHA_SUPPORT_PARALLEL_ENG=y -CONFIG_SOC_SHA_ENDIANNESS_BE=y -CONFIG_SOC_SHA_SUPPORT_SHA1=y -CONFIG_SOC_SHA_SUPPORT_SHA256=y -CONFIG_SOC_SHA_SUPPORT_SHA384=y -CONFIG_SOC_SHA_SUPPORT_SHA512=y -CONFIG_SOC_MPI_MEM_BLOCKS_NUM=4 -CONFIG_SOC_MPI_OPERATIONS_NUM=y -CONFIG_SOC_RSA_MAX_BIT_LEN=4096 -CONFIG_SOC_AES_SUPPORT_AES_128=y -CONFIG_SOC_AES_SUPPORT_AES_192=y -CONFIG_SOC_AES_SUPPORT_AES_256=y -CONFIG_SOC_SECURE_BOOT_V1=y -CONFIG_SOC_EFUSE_SECURE_BOOT_KEY_DIGESTS=y -CONFIG_SOC_FLASH_ENCRYPTED_XTS_AES_BLOCK_MAX=32 -CONFIG_SOC_PHY_DIG_REGS_MEM_SIZE=21 -CONFIG_SOC_PM_SUPPORT_EXT0_WAKEUP=y -CONFIG_SOC_PM_SUPPORT_EXT1_WAKEUP=y -CONFIG_SOC_PM_SUPPORT_EXT_WAKEUP=y -CONFIG_SOC_PM_SUPPORT_TOUCH_SENSOR_WAKEUP=y -CONFIG_SOC_PM_SUPPORT_RTC_PERIPH_PD=y -CONFIG_SOC_PM_SUPPORT_RTC_FAST_MEM_PD=y -CONFIG_SOC_PM_SUPPORT_RTC_SLOW_MEM_PD=y -CONFIG_SOC_PM_SUPPORT_RC_FAST_PD=y -CONFIG_SOC_PM_SUPPORT_VDDSDIO_PD=y -CONFIG_SOC_PM_SUPPORT_MODEM_PD=y -CONFIG_SOC_CONFIGURABLE_VDDSDIO_SUPPORTED=y -CONFIG_SOC_PM_MODEM_PD_BY_SW=y -CONFIG_SOC_CLK_APLL_SUPPORTED=y -CONFIG_SOC_CLK_RC_FAST_D256_SUPPORTED=y -CONFIG_SOC_RTC_SLOW_CLK_SUPPORT_RC_FAST_D256=y -CONFIG_SOC_CLK_RC_FAST_SUPPORT_CALIBRATION=y -CONFIG_SOC_CLK_XTAL32K_SUPPORTED=y -CONFIG_SOC_SDMMC_USE_IOMUX=y -CONFIG_SOC_SDMMC_NUM_SLOTS=2 -CONFIG_SOC_WIFI_WAPI_SUPPORT=y -CONFIG_SOC_WIFI_CSI_SUPPORT=y -CONFIG_SOC_WIFI_MESH_SUPPORT=y -CONFIG_SOC_WIFI_SUPPORT_VARIABLE_BEACON_WINDOW=y -CONFIG_SOC_WIFI_NAN_SUPPORT=y -CONFIG_SOC_BLE_SUPPORTED=y -CONFIG_SOC_BLE_MESH_SUPPORTED=y -CONFIG_SOC_BT_CLASSIC_SUPPORTED=y -CONFIG_SOC_BLUFI_SUPPORTED=y -CONFIG_SOC_BT_H2C_ENC_KEY_CTRL_ENH_VSC_SUPPORTED=y -CONFIG_SOC_ULP_HAS_ADC=y -CONFIG_SOC_PHY_COMBO_MODULE=y -CONFIG_SOC_EMAC_RMII_CLK_OUT_INTERNAL_LOOPBACK=y -CONFIG_IDF_CMAKE=y -CONFIG_IDF_TOOLCHAIN="gcc" -CONFIG_IDF_TOOLCHAIN_GCC=y -CONFIG_IDF_TARGET_ARCH_XTENSA=y -CONFIG_IDF_TARGET_ARCH="xtensa" -CONFIG_IDF_TARGET="esp32" -CONFIG_IDF_INIT_VERSION="$IDF_INIT_VERSION" -CONFIG_IDF_TARGET_ESP32=y -CONFIG_IDF_FIRMWARE_CHIP_ID=0x0000 - -# -# Build type -# -CONFIG_APP_BUILD_TYPE_APP_2NDBOOT=y -# CONFIG_APP_BUILD_TYPE_RAM is not set -CONFIG_APP_BUILD_GENERATE_BINARIES=y -CONFIG_APP_BUILD_BOOTLOADER=y -CONFIG_APP_BUILD_USE_FLASH_SECTIONS=y -# CONFIG_APP_REPRODUCIBLE_BUILD is not set -# CONFIG_APP_NO_BLOBS is not set -# CONFIG_APP_COMPATIBLE_PRE_V2_1_BOOTLOADERS is not set -# CONFIG_APP_COMPATIBLE_PRE_V3_1_BOOTLOADERS is not set -# end of Build type - -# -# Bootloader config -# - -# -# Bootloader manager -# -CONFIG_BOOTLOADER_COMPILE_TIME_DATE=y -CONFIG_BOOTLOADER_PROJECT_VER=1 -# end of Bootloader manager - -CONFIG_BOOTLOADER_OFFSET_IN_FLASH=0x1000 -CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y -# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_DEBUG is not set -# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_PERF is not set -# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_NONE is not set - -# -# Log -# -# CONFIG_BOOTLOADER_LOG_LEVEL_NONE is not set -# CONFIG_BOOTLOADER_LOG_LEVEL_ERROR is not set -# CONFIG_BOOTLOADER_LOG_LEVEL_WARN is not set -# CONFIG_BOOTLOADER_LOG_LEVEL_INFO is not set -CONFIG_BOOTLOADER_LOG_LEVEL_DEBUG=y -# CONFIG_BOOTLOADER_LOG_LEVEL_VERBOSE is not set -CONFIG_BOOTLOADER_LOG_LEVEL=4 - -# -# Format -# -# CONFIG_BOOTLOADER_LOG_COLORS is not set -CONFIG_BOOTLOADER_LOG_TIMESTAMP_SOURCE_CPU_TICKS=y -# end of Format -# end of Log - -# -# Serial Flash Configurations -# -# CONFIG_BOOTLOADER_FLASH_DC_AWARE is not set -CONFIG_BOOTLOADER_FLASH_XMC_SUPPORT=y -# end of Serial Flash Configurations - -# CONFIG_BOOTLOADER_VDDSDIO_BOOST_1_8V is not set -CONFIG_BOOTLOADER_VDDSDIO_BOOST_1_9V=y -# CONFIG_BOOTLOADER_FACTORY_RESET is not set -# CONFIG_BOOTLOADER_APP_TEST is not set -CONFIG_BOOTLOADER_REGION_PROTECTION_ENABLE=y -CONFIG_BOOTLOADER_WDT_ENABLE=y -# CONFIG_BOOTLOADER_WDT_DISABLE_IN_USER_CODE is not set -CONFIG_BOOTLOADER_WDT_TIME_MS=9000 -# CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE is not set -# CONFIG_BOOTLOADER_SKIP_VALIDATE_IN_DEEP_SLEEP is not set -# CONFIG_BOOTLOADER_SKIP_VALIDATE_ON_POWER_ON is not set -# CONFIG_BOOTLOADER_SKIP_VALIDATE_ALWAYS is not set -CONFIG_BOOTLOADER_RESERVE_RTC_SIZE=0 -# CONFIG_BOOTLOADER_CUSTOM_RESERVE_RTC is not set -# end of Bootloader config - -# -# Security features -# -CONFIG_SECURE_BOOT_V1_SUPPORTED=y -# CONFIG_SECURE_SIGNED_APPS_NO_SECURE_BOOT is not set -# CONFIG_SECURE_BOOT is not set -# CONFIG_SECURE_FLASH_ENC_ENABLED is not set -# end of Security features - -# -# Application manager -# -CONFIG_APP_COMPILE_TIME_DATE=y -# CONFIG_APP_EXCLUDE_PROJECT_VER_VAR is not set -# CONFIG_APP_EXCLUDE_PROJECT_NAME_VAR is not set -# CONFIG_APP_PROJECT_VER_FROM_CONFIG is not set -CONFIG_APP_RETRIEVE_LEN_ELF_SHA=9 -# end of Application manager - -CONFIG_ESP_ROM_HAS_CRC_LE=y -CONFIG_ESP_ROM_HAS_CRC_BE=y -CONFIG_ESP_ROM_HAS_MZ_CRC32=y -CONFIG_ESP_ROM_HAS_JPEG_DECODE=y -CONFIG_ESP_ROM_HAS_UART_BUF_SWITCH=y -CONFIG_ESP_ROM_NEEDS_SWSETUP_WORKAROUND=y -CONFIG_ESP_ROM_HAS_NEWLIB=y -CONFIG_ESP_ROM_HAS_NEWLIB_NANO_FORMAT=y -CONFIG_ESP_ROM_HAS_NEWLIB_32BIT_TIME=y -CONFIG_ESP_ROM_HAS_SW_FLOAT=y -CONFIG_ESP_ROM_USB_OTG_NUM=-1 -CONFIG_ESP_ROM_USB_SERIAL_DEVICE_NUM=-1 -CONFIG_ESP_ROM_SUPPORT_DEEP_SLEEP_WAKEUP_STUB=y -CONFIG_ESP_ROM_HAS_OUTPUT_PUTC_FUNC=y - -# -# Serial flasher config -# -# CONFIG_ESPTOOLPY_NO_STUB is not set -# CONFIG_ESPTOOLPY_FLASHMODE_QIO is not set -# CONFIG_ESPTOOLPY_FLASHMODE_QOUT is not set -CONFIG_ESPTOOLPY_FLASHMODE_DIO=y -# CONFIG_ESPTOOLPY_FLASHMODE_DOUT is not set -CONFIG_ESPTOOLPY_FLASH_SAMPLE_MODE_STR=y -CONFIG_ESPTOOLPY_FLASHMODE="dio" -# CONFIG_ESPTOOLPY_FLASHFREQ_80M is not set -CONFIG_ESPTOOLPY_FLASHFREQ_40M=y -# CONFIG_ESPTOOLPY_FLASHFREQ_26M is not set -# CONFIG_ESPTOOLPY_FLASHFREQ_20M is not set -CONFIG_ESPTOOLPY_FLASHFREQ="40m" -# CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set -CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y -# CONFIG_ESPTOOLPY_FLASHSIZE_4MB is not set -# CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set -# CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set -# CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set -# CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set -# CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set -CONFIG_ESPTOOLPY_FLASHSIZE="2MB" -# CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE is not set -CONFIG_ESPTOOLPY_BEFORE_RESET=y -# CONFIG_ESPTOOLPY_BEFORE_NORESET is not set -CONFIG_ESPTOOLPY_BEFORE="default_reset" -CONFIG_ESPTOOLPY_AFTER_RESET=y -# CONFIG_ESPTOOLPY_AFTER_NORESET is not set -CONFIG_ESPTOOLPY_AFTER="hard_reset" -CONFIG_ESPTOOLPY_MONITOR_BAUD=115200 -# end of Serial flasher config - -# -# Partition Table -# -CONFIG_PARTITION_TABLE_SINGLE_APP=y -# CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE is not set -# CONFIG_PARTITION_TABLE_TWO_OTA is not set -# CONFIG_PARTITION_TABLE_TWO_OTA_LARGE is not set -# CONFIG_PARTITION_TABLE_CUSTOM is not set -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" -CONFIG_PARTITION_TABLE_FILENAME="partitions_singleapp.csv" -CONFIG_PARTITION_TABLE_OFFSET=0x9000 -CONFIG_PARTITION_TABLE_MD5=y -# end of Partition Table - -# -# Compiler options -# -CONFIG_COMPILER_OPTIMIZATION_DEBUG=y -# CONFIG_COMPILER_OPTIMIZATION_SIZE is not set -# CONFIG_COMPILER_OPTIMIZATION_PERF is not set -# CONFIG_COMPILER_OPTIMIZATION_NONE is not set -CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y -# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT is not set -# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE is not set -CONFIG_COMPILER_ASSERT_NDEBUG_EVALUATE=y -CONFIG_COMPILER_FLOAT_LIB_FROM_GCCLIB=y -CONFIG_COMPILER_OPTIMIZATION_ASSERTION_LEVEL=2 -# CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT is not set -CONFIG_COMPILER_HIDE_PATHS_MACROS=y -# CONFIG_COMPILER_CXX_EXCEPTIONS is not set -# CONFIG_COMPILER_CXX_RTTI is not set -CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y -# CONFIG_COMPILER_STACK_CHECK_MODE_NORM is not set -# CONFIG_COMPILER_STACK_CHECK_MODE_STRONG is not set -# CONFIG_COMPILER_STACK_CHECK_MODE_ALL is not set -# CONFIG_COMPILER_NO_MERGE_CONSTANTS is not set -# CONFIG_COMPILER_WARN_WRITE_STRINGS is not set -CONFIG_COMPILER_DISABLE_DEFAULT_ERRORS=y -# CONFIG_COMPILER_DISABLE_GCC12_WARNINGS is not set -# CONFIG_COMPILER_DISABLE_GCC13_WARNINGS is not set -# CONFIG_COMPILER_DISABLE_GCC14_WARNINGS is not set -# CONFIG_COMPILER_DUMP_RTL_FILES is not set -CONFIG_COMPILER_RT_LIB_GCCLIB=y -CONFIG_COMPILER_RT_LIB_NAME="gcc" -CONFIG_COMPILER_ORPHAN_SECTIONS_WARNING=y -# CONFIG_COMPILER_ORPHAN_SECTIONS_PLACE is not set -# CONFIG_COMPILER_STATIC_ANALYZER is not set -# end of Compiler options - -# -# Component config -# - -# -# Driver Configurations -# - -# -# TWAI Configuration -# -# CONFIG_TWAI_ISR_IN_IRAM is not set -CONFIG_TWAI_ERRATA_FIX_BUS_OFF_REC=y -CONFIG_TWAI_ERRATA_FIX_TX_INTR_LOST=y -CONFIG_TWAI_ERRATA_FIX_RX_FRAME_INVALID=y -CONFIG_TWAI_ERRATA_FIX_RX_FIFO_CORRUPT=y -CONFIG_TWAI_ERRATA_FIX_LISTEN_ONLY_DOM=y -# end of TWAI Configuration - -# -# Legacy ADC Driver Configuration -# -CONFIG_ADC_DISABLE_DAC=y -# CONFIG_ADC_SUPPRESS_DEPRECATE_WARN is not set - -# -# Legacy ADC Calibration Configuration -# -CONFIG_ADC_CAL_EFUSE_TP_ENABLE=y -CONFIG_ADC_CAL_EFUSE_VREF_ENABLE=y -CONFIG_ADC_CAL_LUT_ENABLE=y -# CONFIG_ADC_CALI_SUPPRESS_DEPRECATE_WARN is not set -# end of Legacy ADC Calibration Configuration -# end of Legacy ADC Driver Configuration - -# -# Legacy DAC Driver Configurations -# -# CONFIG_DAC_SUPPRESS_DEPRECATE_WARN is not set -# end of Legacy DAC Driver Configurations - -# -# Legacy MCPWM Driver Configurations -# -# CONFIG_MCPWM_SUPPRESS_DEPRECATE_WARN is not set -# end of Legacy MCPWM Driver Configurations - -# -# Legacy Timer Group Driver Configurations -# -# CONFIG_GPTIMER_SUPPRESS_DEPRECATE_WARN is not set -# end of Legacy Timer Group Driver Configurations - -# -# Legacy RMT Driver Configurations -# -# CONFIG_RMT_SUPPRESS_DEPRECATE_WARN is not set -# end of Legacy RMT Driver Configurations - -# -# Legacy I2S Driver Configurations -# -# CONFIG_I2S_SUPPRESS_DEPRECATE_WARN is not set -# end of Legacy I2S Driver Configurations - -# -# Legacy PCNT Driver Configurations -# -# CONFIG_PCNT_SUPPRESS_DEPRECATE_WARN is not set -# end of Legacy PCNT Driver Configurations - -# -# Legacy SDM Driver Configurations -# -# CONFIG_SDM_SUPPRESS_DEPRECATE_WARN is not set -# end of Legacy SDM Driver Configurations -# end of Driver Configurations - -# -# eFuse Bit Manager -# -# CONFIG_EFUSE_CUSTOM_TABLE is not set -# CONFIG_EFUSE_VIRTUAL is not set -# CONFIG_EFUSE_CODE_SCHEME_COMPAT_NONE is not set -CONFIG_EFUSE_CODE_SCHEME_COMPAT_3_4=y -# CONFIG_EFUSE_CODE_SCHEME_COMPAT_REPEAT is not set -CONFIG_EFUSE_MAX_BLK_LEN=192 -# end of eFuse Bit Manager - -# -# Common ESP-related -# -CONFIG_ESP_ERR_TO_NAME_LOOKUP=y -# end of Common ESP-related - -# -# ESP-Driver:DAC Configurations -# -# CONFIG_DAC_CTRL_FUNC_IN_IRAM is not set -# CONFIG_DAC_ISR_IRAM_SAFE is not set -# CONFIG_DAC_ENABLE_DEBUG_LOG is not set -CONFIG_DAC_DMA_AUTO_16BIT_ALIGN=y -# end of ESP-Driver:DAC Configurations - -# -# ESP-Driver:GPIO Configurations -# -# CONFIG_GPIO_ESP32_SUPPORT_SWITCH_SLP_PULL is not set -# CONFIG_GPIO_CTRL_FUNC_IN_IRAM is not set -# end of ESP-Driver:GPIO Configurations - -# -# ESP-Driver:GPTimer Configurations -# -CONFIG_GPTIMER_ISR_HANDLER_IN_IRAM=y -# CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM is not set -# CONFIG_GPTIMER_ISR_IRAM_SAFE is not set -# CONFIG_GPTIMER_ENABLE_DEBUG_LOG is not set -# end of ESP-Driver:GPTimer Configurations - -# -# ESP-Driver:I2C Configurations -# -# CONFIG_I2C_ISR_IRAM_SAFE is not set -# CONFIG_I2C_ENABLE_DEBUG_LOG is not set -# CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2 is not set -# end of ESP-Driver:I2C Configurations - -# -# ESP-Driver:I2S Configurations -# -# CONFIG_I2S_ISR_IRAM_SAFE is not set -# CONFIG_I2S_ENABLE_DEBUG_LOG is not set -# end of ESP-Driver:I2S Configurations - -# -# ESP-Driver:LEDC Configurations -# -# CONFIG_LEDC_CTRL_FUNC_IN_IRAM is not set -# end of ESP-Driver:LEDC Configurations - -# -# ESP-Driver:MCPWM Configurations -# -# CONFIG_MCPWM_ISR_IRAM_SAFE is not set -# CONFIG_MCPWM_CTRL_FUNC_IN_IRAM is not set -# CONFIG_MCPWM_ENABLE_DEBUG_LOG is not set -# end of ESP-Driver:MCPWM Configurations - -# -# ESP-Driver:PCNT Configurations -# -# CONFIG_PCNT_CTRL_FUNC_IN_IRAM is not set -# CONFIG_PCNT_ISR_IRAM_SAFE is not set -# CONFIG_PCNT_ENABLE_DEBUG_LOG is not set -# end of ESP-Driver:PCNT Configurations - -# -# ESP-Driver:RMT Configurations -# -# CONFIG_RMT_ISR_IRAM_SAFE is not set -# CONFIG_RMT_RECV_FUNC_IN_IRAM is not set -# CONFIG_RMT_ENABLE_DEBUG_LOG is not set -# end of ESP-Driver:RMT Configurations - -# -# ESP-Driver:Sigma Delta Modulator Configurations -# -# CONFIG_SDM_CTRL_FUNC_IN_IRAM is not set -# CONFIG_SDM_ENABLE_DEBUG_LOG is not set -# end of ESP-Driver:Sigma Delta Modulator Configurations - -# -# ESP-Driver:SPI Configurations -# -# CONFIG_SPI_MASTER_IN_IRAM is not set -CONFIG_SPI_MASTER_ISR_IN_IRAM=y -# CONFIG_SPI_SLAVE_IN_IRAM is not set -CONFIG_SPI_SLAVE_ISR_IN_IRAM=y -# end of ESP-Driver:SPI Configurations - -# -# ESP-Driver:UART Configurations -# -# CONFIG_UART_ISR_IN_IRAM is not set -# end of ESP-Driver:UART Configurations - -# -# Event Loop Library -# -# CONFIG_ESP_EVENT_LOOP_PROFILING is not set -CONFIG_ESP_EVENT_POST_FROM_ISR=y -CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y -# end of Event Loop Library - -# -# Hardware Settings -# - -# -# Chip revision -# -CONFIG_ESP32_REV_MIN_0=y -# CONFIG_ESP32_REV_MIN_1 is not set -# CONFIG_ESP32_REV_MIN_1_1 is not set -# CONFIG_ESP32_REV_MIN_2 is not set -# CONFIG_ESP32_REV_MIN_3 is not set -# CONFIG_ESP32_REV_MIN_3_1 is not set -CONFIG_ESP32_REV_MIN=0 -CONFIG_ESP32_REV_MIN_FULL=0 -CONFIG_ESP_REV_MIN_FULL=0 - -# -# Maximum Supported ESP32 Revision (Rev v3.99) -# -CONFIG_ESP32_REV_MAX_FULL=399 -CONFIG_ESP_REV_MAX_FULL=399 -CONFIG_ESP_EFUSE_BLOCK_REV_MIN_FULL=0 -CONFIG_ESP_EFUSE_BLOCK_REV_MAX_FULL=99 - -# -# Maximum Supported ESP32 eFuse Block Revision (eFuse Block Rev v0.99) -# -# end of Chip revision - -# -# MAC Config -# -CONFIG_ESP_MAC_ADDR_UNIVERSE_WIFI_STA=y -CONFIG_ESP_MAC_ADDR_UNIVERSE_WIFI_AP=y -CONFIG_ESP_MAC_ADDR_UNIVERSE_BT=y -CONFIG_ESP_MAC_ADDR_UNIVERSE_ETH=y -CONFIG_ESP_MAC_UNIVERSAL_MAC_ADDRESSES_FOUR=y -CONFIG_ESP_MAC_UNIVERSAL_MAC_ADDRESSES=4 -# CONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES_TWO is not set -CONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES_FOUR=y -CONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES=4 -# CONFIG_ESP_MAC_IGNORE_MAC_CRC_ERROR is not set -# CONFIG_ESP_MAC_USE_CUSTOM_MAC_AS_BASE_MAC is not set -# end of MAC Config - -# -# Sleep Config -# -# CONFIG_ESP_SLEEP_POWER_DOWN_FLASH is not set -CONFIG_ESP_SLEEP_FLASH_LEAKAGE_WORKAROUND=y -# CONFIG_ESP_SLEEP_MSPI_NEED_ALL_IO_PU is not set -CONFIG_ESP_SLEEP_RTC_BUS_ISO_WORKAROUND=y -# CONFIG_ESP_SLEEP_GPIO_RESET_WORKAROUND is not set -CONFIG_ESP_SLEEP_WAIT_FLASH_READY_EXTRA_DELAY=2000 -# CONFIG_ESP_SLEEP_CACHE_SAFE_ASSERTION is not set -# CONFIG_ESP_SLEEP_DEBUG is not set -CONFIG_ESP_SLEEP_GPIO_ENABLE_INTERNAL_RESISTORS=y -# end of Sleep Config - -# -# RTC Clock Config -# -CONFIG_RTC_CLK_SRC_INT_RC=y -# CONFIG_RTC_CLK_SRC_EXT_CRYS is not set -# CONFIG_RTC_CLK_SRC_EXT_OSC is not set -# CONFIG_RTC_CLK_SRC_INT_8MD256 is not set -CONFIG_RTC_CLK_CAL_CYCLES=1024 -# end of RTC Clock Config - -# -# Peripheral Control -# -CONFIG_PERIPH_CTRL_FUNC_IN_IRAM=y -# end of Peripheral Control - -# -# Main XTAL Config -# -# CONFIG_XTAL_FREQ_26 is not set -# CONFIG_XTAL_FREQ_32 is not set -CONFIG_XTAL_FREQ_40=y -# CONFIG_XTAL_FREQ_AUTO is not set -CONFIG_XTAL_FREQ=40 -# end of Main XTAL Config - -CONFIG_ESP_SPI_BUS_LOCK_ISR_FUNCS_IN_IRAM=y -# end of Hardware Settings - -# -# ESP-MM: Memory Management Configurations -# -# end of ESP-MM: Memory Management Configurations - -# -# Partition API Configuration -# -# end of Partition API Configuration - -# -# Power Management -# -# CONFIG_PM_ENABLE is not set -# CONFIG_PM_SLP_IRAM_OPT is not set -# end of Power Management - -# -# ESP Ringbuf -# -# CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH is not set -# end of ESP Ringbuf - -# -# ESP Security Specific -# -# end of ESP Security Specific - -# -# ESP System Settings -# -# CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_80 is not set -CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_160=y -# CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240 is not set -CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=160 - -# -# Memory -# -# CONFIG_ESP32_USE_FIXED_STATIC_RAM_SIZE is not set - -# -# Non-backward compatible options -# -# CONFIG_ESP_SYSTEM_ESP32_SRAM1_REGION_AS_IRAM is not set -# end of Non-backward compatible options -# end of Memory - -# -# Trace memory -# -# CONFIG_ESP32_TRAX is not set -CONFIG_ESP32_TRACEMEM_RESERVE_DRAM=0x0 -# end of Trace memory - -# CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT is not set -CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT=y -# CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT is not set -CONFIG_ESP_SYSTEM_PANIC_REBOOT_DELAY_SECONDS=0 - -# -# Memory protection -# -# end of Memory protection - -CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=32 -CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=2304 -CONFIG_ESP_MAIN_TASK_STACK_SIZE=3584 -CONFIG_ESP_MAIN_TASK_AFFINITY_CPU0=y -# CONFIG_ESP_MAIN_TASK_AFFINITY_CPU1 is not set -# CONFIG_ESP_MAIN_TASK_AFFINITY_NO_AFFINITY is not set -CONFIG_ESP_MAIN_TASK_AFFINITY=0x0 -CONFIG_ESP_MINIMAL_SHARED_STACK_SIZE=2048 -CONFIG_ESP_CONSOLE_UART_DEFAULT=y -# CONFIG_ESP_CONSOLE_UART_CUSTOM is not set -# CONFIG_ESP_CONSOLE_NONE is not set -CONFIG_ESP_CONSOLE_UART=y -CONFIG_ESP_CONSOLE_UART_NUM=0 -CONFIG_ESP_CONSOLE_ROM_SERIAL_PORT_NUM=0 -CONFIG_ESP_CONSOLE_UART_BAUDRATE=115200 -CONFIG_ESP_INT_WDT=y -CONFIG_ESP_INT_WDT_TIMEOUT_MS=300 -CONFIG_ESP_INT_WDT_CHECK_CPU1=y -CONFIG_ESP_TASK_WDT_EN=y -# CONFIG_ESP_TASK_WDT_INIT is not set -# CONFIG_ESP_PANIC_HANDLER_IRAM is not set -# CONFIG_ESP_DEBUG_STUBS_ENABLE is not set -CONFIG_ESP_DEBUG_OCDAWARE=y -# CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5 is not set -CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_4=y - -# -# Brownout Detector -# -CONFIG_ESP_BROWNOUT_DET=y -CONFIG_ESP_BROWNOUT_DET_LVL_SEL_0=y -# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_1 is not set -# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_2 is not set -# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_3 is not set -# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_4 is not set -# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_5 is not set -# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_6 is not set -# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_7 is not set -CONFIG_ESP_BROWNOUT_DET_LVL=0 -# end of Brownout Detector - -# CONFIG_ESP32_DISABLE_BASIC_ROM_CONSOLE is not set -CONFIG_ESP_SYSTEM_BROWNOUT_INTR=y -# end of ESP System Settings - -# -# IPC (Inter-Processor Call) -# -CONFIG_ESP_IPC_TASK_STACK_SIZE=1024 -CONFIG_ESP_IPC_USES_CALLERS_PRIORITY=y -CONFIG_ESP_IPC_ISR_ENABLE=y -# end of IPC (Inter-Processor Call) - -# -# ESP Timer (High Resolution Timer) -# -# CONFIG_ESP_TIMER_PROFILING is not set -CONFIG_ESP_TIME_FUNCS_USE_RTC_TIMER=y -CONFIG_ESP_TIME_FUNCS_USE_ESP_TIMER=y -CONFIG_ESP_TIMER_TASK_STACK_SIZE=3584 -CONFIG_ESP_TIMER_INTERRUPT_LEVEL=1 -# CONFIG_ESP_TIMER_SHOW_EXPERIMENTAL is not set -CONFIG_ESP_TIMER_TASK_AFFINITY=0x0 -CONFIG_ESP_TIMER_TASK_AFFINITY_CPU0=y -CONFIG_ESP_TIMER_ISR_AFFINITY_CPU0=y -# CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD is not set -CONFIG_ESP_TIMER_IMPL_TG0_LAC=y -# end of ESP Timer (High Resolution Timer) - -# -# FreeRTOS -# - -# -# Kernel -# -# CONFIG_FREERTOS_SMP is not set -# CONFIG_FREERTOS_UNICORE is not set -CONFIG_FREERTOS_HZ=100 -# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_NONE is not set -# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_PTRVAL is not set -CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY=y -CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=1 -CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=1536 -# CONFIG_FREERTOS_USE_IDLE_HOOK is not set -# CONFIG_FREERTOS_USE_TICK_HOOK is not set -CONFIG_FREERTOS_MAX_TASK_NAME_LEN=16 -# CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY is not set -CONFIG_FREERTOS_USE_TIMERS=y -CONFIG_FREERTOS_TIMER_SERVICE_TASK_NAME="Tmr Svc" -# CONFIG_FREERTOS_TIMER_TASK_AFFINITY_CPU0 is not set -# CONFIG_FREERTOS_TIMER_TASK_AFFINITY_CPU1 is not set -CONFIG_FREERTOS_TIMER_TASK_NO_AFFINITY=y -CONFIG_FREERTOS_TIMER_SERVICE_TASK_CORE_AFFINITY=0x7FFFFFFF -CONFIG_FREERTOS_TIMER_TASK_PRIORITY=1 -CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=2048 -CONFIG_FREERTOS_TIMER_QUEUE_LENGTH=10 -CONFIG_FREERTOS_QUEUE_REGISTRY_SIZE=0 -CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=1 -# CONFIG_FREERTOS_USE_TRACE_FACILITY is not set -# CONFIG_FREERTOS_USE_LIST_DATA_INTEGRITY_CHECK_BYTES is not set -# CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS is not set -# CONFIG_FREERTOS_USE_APPLICATION_TASK_TAG is not set -# end of Kernel - -# -# Port -# -CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=y -# CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK is not set -CONFIG_FREERTOS_TLSP_DELETION_CALLBACKS=y -# CONFIG_FREERTOS_TASK_PRE_DELETION_HOOK is not set -# CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP is not set -CONFIG_FREERTOS_CHECK_MUTEX_GIVEN_BY_OWNER=y -CONFIG_FREERTOS_ISR_STACKSIZE=1536 -CONFIG_FREERTOS_INTERRUPT_BACKTRACE=y -# CONFIG_FREERTOS_FPU_IN_ISR is not set -CONFIG_FREERTOS_TICK_SUPPORT_CORETIMER=y -CONFIG_FREERTOS_CORETIMER_0=y -# CONFIG_FREERTOS_CORETIMER_1 is not set -CONFIG_FREERTOS_SYSTICK_USES_CCOUNT=y -# CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH is not set -# CONFIG_FREERTOS_CHECK_PORT_CRITICAL_COMPLIANCE is not set -# end of Port - -# -# Extra -# -# end of Extra - -CONFIG_FREERTOS_PORT=y -CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF -CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION=y -CONFIG_FREERTOS_DEBUG_OCDAWARE=y -CONFIG_FREERTOS_ENABLE_TASK_SNAPSHOT=y -CONFIG_FREERTOS_PLACE_SNAPSHOT_FUNS_INTO_FLASH=y -CONFIG_FREERTOS_NUMBER_OF_CORES=2 -# end of FreeRTOS - -# -# Hardware Abstraction Layer (HAL) and Low Level (LL) -# -CONFIG_HAL_ASSERTION_EQUALS_SYSTEM=y -# CONFIG_HAL_ASSERTION_DISABLE is not set -# CONFIG_HAL_ASSERTION_SILENT is not set -# CONFIG_HAL_ASSERTION_ENABLE is not set -CONFIG_HAL_DEFAULT_ASSERTION_LEVEL=2 -CONFIG_HAL_SPI_MASTER_FUNC_IN_IRAM=y -CONFIG_HAL_SPI_SLAVE_FUNC_IN_IRAM=y -# CONFIG_HAL_ECDSA_GEN_SIG_CM is not set -# end of Hardware Abstraction Layer (HAL) and Low Level (LL) - -# -# Heap memory debugging -# -CONFIG_HEAP_POISONING_DISABLED=y -# CONFIG_HEAP_POISONING_LIGHT is not set -# CONFIG_HEAP_POISONING_COMPREHENSIVE is not set -CONFIG_HEAP_TRACING_OFF=y -# CONFIG_HEAP_TRACING_STANDALONE is not set -# CONFIG_HEAP_TRACING_TOHOST is not set -# CONFIG_HEAP_USE_HOOKS is not set -# CONFIG_HEAP_TASK_TRACKING is not set -# CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS is not set -# CONFIG_HEAP_PLACE_FUNCTION_INTO_FLASH is not set -# end of Heap memory debugging - -# -# Log -# - -# -# Log Level -# -# CONFIG_LOG_DEFAULT_LEVEL_NONE is not set -# CONFIG_LOG_DEFAULT_LEVEL_ERROR is not set -# CONFIG_LOG_DEFAULT_LEVEL_WARN is not set -CONFIG_LOG_DEFAULT_LEVEL_INFO=y -# CONFIG_LOG_DEFAULT_LEVEL_DEBUG is not set -# CONFIG_LOG_DEFAULT_LEVEL_VERBOSE is not set -CONFIG_LOG_DEFAULT_LEVEL=3 -CONFIG_LOG_MAXIMUM_EQUALS_DEFAULT=y -# CONFIG_LOG_MAXIMUM_LEVEL_DEBUG is not set -# CONFIG_LOG_MAXIMUM_LEVEL_VERBOSE is not set -CONFIG_LOG_MAXIMUM_LEVEL=3 - -# -# Level Settings -# -# CONFIG_LOG_MASTER_LEVEL is not set -CONFIG_LOG_DYNAMIC_LEVEL_CONTROL=y -# CONFIG_LOG_TAG_LEVEL_IMPL_NONE is not set -# CONFIG_LOG_TAG_LEVEL_IMPL_LINKED_LIST is not set -CONFIG_LOG_TAG_LEVEL_IMPL_CACHE_AND_LINKED_LIST=y -# CONFIG_LOG_TAG_LEVEL_CACHE_ARRAY is not set -CONFIG_LOG_TAG_LEVEL_CACHE_BINARY_MIN_HEAP=y -CONFIG_LOG_TAG_LEVEL_IMPL_CACHE_SIZE=31 -# end of Level Settings -# end of Log Level - -# -# Format -# -# CONFIG_LOG_COLORS is not set -CONFIG_LOG_TIMESTAMP_SOURCE_RTOS=y -# CONFIG_LOG_TIMESTAMP_SOURCE_SYSTEM is not set -# end of Format -# end of Log - -# -# mbedTLS -# -CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y -# CONFIG_MBEDTLS_DEFAULT_MEM_ALLOC is not set -# CONFIG_MBEDTLS_CUSTOM_MEM_ALLOC is not set -CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN=y -CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=16384 -CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=4096 -# CONFIG_MBEDTLS_DYNAMIC_BUFFER is not set -# CONFIG_MBEDTLS_DEBUG is not set - -# -# mbedTLS v3.x related -# -# CONFIG_MBEDTLS_SSL_PROTO_TLS1_3 is not set -# CONFIG_MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH is not set -# CONFIG_MBEDTLS_X509_TRUSTED_CERT_CALLBACK is not set -# CONFIG_MBEDTLS_SSL_CONTEXT_SERIALIZATION is not set -CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE=y -CONFIG_MBEDTLS_PKCS7_C=y -# end of mbedTLS v3.x related - -# -# Certificate Bundle -# -CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y -CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=y -# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN is not set -# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_NONE is not set -# CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE is not set -# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEPRECATED_LIST is not set -CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_MAX_CERTS=200 -# end of Certificate Bundle - -# CONFIG_MBEDTLS_ECP_RESTARTABLE is not set -# CONFIG_MBEDTLS_CMAC_C is not set -CONFIG_MBEDTLS_HARDWARE_AES=y -CONFIG_MBEDTLS_GCM_SUPPORT_NON_AES_CIPHER=y -CONFIG_MBEDTLS_HARDWARE_MPI=y -# CONFIG_MBEDTLS_LARGE_KEY_SOFTWARE_MPI is not set -CONFIG_MBEDTLS_HARDWARE_SHA=y -CONFIG_MBEDTLS_ROM_MD5=y -# CONFIG_MBEDTLS_ATCA_HW_ECDSA_SIGN is not set -# CONFIG_MBEDTLS_ATCA_HW_ECDSA_VERIFY is not set -CONFIG_MBEDTLS_HAVE_TIME=y -# CONFIG_MBEDTLS_PLATFORM_TIME_ALT is not set -# CONFIG_MBEDTLS_HAVE_TIME_DATE is not set -CONFIG_MBEDTLS_ECDSA_DETERMINISTIC=y -CONFIG_MBEDTLS_SHA512_C=y -# CONFIG_MBEDTLS_SHA3_C is not set -CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT=y -# CONFIG_MBEDTLS_TLS_SERVER_ONLY is not set -# CONFIG_MBEDTLS_TLS_CLIENT_ONLY is not set -# CONFIG_MBEDTLS_TLS_DISABLED is not set -CONFIG_MBEDTLS_TLS_SERVER=y -CONFIG_MBEDTLS_TLS_CLIENT=y -CONFIG_MBEDTLS_TLS_ENABLED=y - -# -# TLS Key Exchange Methods -# -# CONFIG_MBEDTLS_PSK_MODES is not set -CONFIG_MBEDTLS_KEY_EXCHANGE_RSA=y -CONFIG_MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE=y -CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_RSA=y -CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA=y -CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA=y -CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_RSA=y -# end of TLS Key Exchange Methods - -CONFIG_MBEDTLS_SSL_RENEGOTIATION=y -CONFIG_MBEDTLS_SSL_PROTO_TLS1_2=y -# CONFIG_MBEDTLS_SSL_PROTO_GMTSSL1_1 is not set -# CONFIG_MBEDTLS_SSL_PROTO_DTLS is not set -CONFIG_MBEDTLS_SSL_ALPN=y -CONFIG_MBEDTLS_CLIENT_SSL_SESSION_TICKETS=y -CONFIG_MBEDTLS_SERVER_SSL_SESSION_TICKETS=y - -# -# Symmetric Ciphers -# -CONFIG_MBEDTLS_AES_C=y -# CONFIG_MBEDTLS_CAMELLIA_C is not set -# CONFIG_MBEDTLS_DES_C is not set -# CONFIG_MBEDTLS_BLOWFISH_C is not set -# CONFIG_MBEDTLS_XTEA_C is not set -CONFIG_MBEDTLS_CCM_C=y -CONFIG_MBEDTLS_GCM_C=y -# CONFIG_MBEDTLS_NIST_KW_C is not set -# end of Symmetric Ciphers - -# CONFIG_MBEDTLS_RIPEMD160_C is not set - -# -# Certificates -# -CONFIG_MBEDTLS_PEM_PARSE_C=y -CONFIG_MBEDTLS_PEM_WRITE_C=y -CONFIG_MBEDTLS_X509_CRL_PARSE_C=y -CONFIG_MBEDTLS_X509_CSR_PARSE_C=y -# end of Certificates - -CONFIG_MBEDTLS_ECP_C=y -CONFIG_MBEDTLS_PK_PARSE_EC_EXTENDED=y -CONFIG_MBEDTLS_PK_PARSE_EC_COMPRESSED=y -# CONFIG_MBEDTLS_DHM_C is not set -CONFIG_MBEDTLS_ECDH_C=y -CONFIG_MBEDTLS_ECDSA_C=y -# CONFIG_MBEDTLS_ECJPAKE_C is not set -CONFIG_MBEDTLS_ECP_DP_SECP192R1_ENABLED=y -CONFIG_MBEDTLS_ECP_DP_SECP224R1_ENABLED=y -CONFIG_MBEDTLS_ECP_DP_SECP256R1_ENABLED=y -CONFIG_MBEDTLS_ECP_DP_SECP384R1_ENABLED=y -CONFIG_MBEDTLS_ECP_DP_SECP521R1_ENABLED=y -CONFIG_MBEDTLS_ECP_DP_SECP192K1_ENABLED=y -CONFIG_MBEDTLS_ECP_DP_SECP224K1_ENABLED=y -CONFIG_MBEDTLS_ECP_DP_SECP256K1_ENABLED=y -CONFIG_MBEDTLS_ECP_DP_BP256R1_ENABLED=y -CONFIG_MBEDTLS_ECP_DP_BP384R1_ENABLED=y -CONFIG_MBEDTLS_ECP_DP_BP512R1_ENABLED=y -CONFIG_MBEDTLS_ECP_DP_CURVE25519_ENABLED=y -CONFIG_MBEDTLS_ECP_NIST_OPTIM=y -# CONFIG_MBEDTLS_ECP_FIXED_POINT_OPTIM is not set -# CONFIG_MBEDTLS_POLY1305_C is not set -# CONFIG_MBEDTLS_CHACHA20_C is not set -# CONFIG_MBEDTLS_HKDF_C is not set -# CONFIG_MBEDTLS_THREADING_C is not set -CONFIG_MBEDTLS_ERROR_STRINGS=y -# end of mbedTLS - -# -# Newlib -# -CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF=y -# CONFIG_NEWLIB_STDOUT_LINE_ENDING_LF is not set -# CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR is not set -# CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF is not set -# CONFIG_NEWLIB_STDIN_LINE_ENDING_LF is not set -CONFIG_NEWLIB_STDIN_LINE_ENDING_CR=y -# CONFIG_NEWLIB_NANO_FORMAT is not set -CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC_HRT=y -# CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC is not set -# CONFIG_NEWLIB_TIME_SYSCALL_USE_HRT is not set -# CONFIG_NEWLIB_TIME_SYSCALL_USE_NONE is not set -# end of Newlib - -# -# PThreads -# -CONFIG_PTHREAD_TASK_PRIO_DEFAULT=5 -CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072 -CONFIG_PTHREAD_STACK_MIN=768 -CONFIG_PTHREAD_DEFAULT_CORE_NO_AFFINITY=y -# CONFIG_PTHREAD_DEFAULT_CORE_0 is not set -# CONFIG_PTHREAD_DEFAULT_CORE_1 is not set -CONFIG_PTHREAD_TASK_CORE_DEFAULT=-1 -CONFIG_PTHREAD_TASK_NAME_DEFAULT="pthread" -# end of PThreads - -# -# MMU Config -# -CONFIG_MMU_PAGE_SIZE_64KB=y -CONFIG_MMU_PAGE_MODE="64KB" -CONFIG_MMU_PAGE_SIZE=0x10000 -# end of MMU Config - -# -# Main Flash configuration -# - -# -# SPI Flash behavior when brownout -# -CONFIG_SPI_FLASH_BROWNOUT_RESET_XMC=y -CONFIG_SPI_FLASH_BROWNOUT_RESET=y -# end of SPI Flash behavior when brownout - -# -# Optional and Experimental Features (READ DOCS FIRST) -# - -# -# Features here require specific hardware (READ DOCS FIRST!) -# -CONFIG_SPI_FLASH_SUSPEND_TSUS_VAL_US=50 -# CONFIG_SPI_FLASH_FORCE_ENABLE_XMC_C_SUSPEND is not set -# end of Optional and Experimental Features (READ DOCS FIRST) -# end of Main Flash configuration - -# -# SPI Flash driver -# -# CONFIG_SPI_FLASH_VERIFY_WRITE is not set -# CONFIG_SPI_FLASH_ENABLE_COUNTERS is not set -CONFIG_SPI_FLASH_ROM_DRIVER_PATCH=y -CONFIG_SPI_FLASH_DANGEROUS_WRITE_ABORTS=y -# CONFIG_SPI_FLASH_DANGEROUS_WRITE_FAILS is not set -# CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWED is not set -# CONFIG_SPI_FLASH_SHARE_SPI1_BUS is not set -# CONFIG_SPI_FLASH_BYPASS_BLOCK_ERASE is not set -CONFIG_SPI_FLASH_YIELD_DURING_ERASE=y -CONFIG_SPI_FLASH_ERASE_YIELD_DURATION_MS=20 -CONFIG_SPI_FLASH_ERASE_YIELD_TICKS=1 -CONFIG_SPI_FLASH_WRITE_CHUNK_SIZE=8192 -# CONFIG_SPI_FLASH_SIZE_OVERRIDE is not set -# CONFIG_SPI_FLASH_CHECK_ERASE_TIMEOUT_DISABLED is not set -# CONFIG_SPI_FLASH_OVERRIDE_CHIP_DRIVER_LIST is not set - -# -# Auto-detect flash chips -# -CONFIG_SPI_FLASH_VENDOR_XMC_SUPPORTED=y -CONFIG_SPI_FLASH_VENDOR_GD_SUPPORTED=y -CONFIG_SPI_FLASH_VENDOR_ISSI_SUPPORTED=y -CONFIG_SPI_FLASH_VENDOR_MXIC_SUPPORTED=y -CONFIG_SPI_FLASH_VENDOR_WINBOND_SUPPORTED=y -CONFIG_SPI_FLASH_SUPPORT_ISSI_CHIP=y -CONFIG_SPI_FLASH_SUPPORT_MXIC_CHIP=y -CONFIG_SPI_FLASH_SUPPORT_GD_CHIP=y -CONFIG_SPI_FLASH_SUPPORT_WINBOND_CHIP=y -# CONFIG_SPI_FLASH_SUPPORT_BOYA_CHIP is not set -# CONFIG_SPI_FLASH_SUPPORT_TH_CHIP is not set -# end of Auto-detect flash chips - -CONFIG_SPI_FLASH_ENABLE_ENCRYPTED_READ_WRITE=y -# end of SPI Flash driver - -# -# Unity unit testing library -# -CONFIG_UNITY_ENABLE_FLOAT=y -CONFIG_UNITY_ENABLE_DOUBLE=y -# CONFIG_UNITY_ENABLE_64BIT is not set -# CONFIG_UNITY_ENABLE_COLOR is not set -CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=y -# CONFIG_UNITY_ENABLE_FIXTURE is not set -# CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL is not set -# end of Unity unit testing library - -# -# Relay Channel Driver Configuration -# -CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 -CONFIG_RELAY_CHN_COUNT=2 -CONFIG_RELAY_CHN_ENABLE_TILTING=y -# end of Relay Channel Driver Configuration -# end of Component config - -# CONFIG_IDF_EXPERIMENTAL_FEATURES is not set - -# Deprecated options for backward compatibility -# CONFIG_APP_BUILD_TYPE_ELF_RAM is not set -# CONFIG_NO_BLOBS is not set -# CONFIG_ESP32_NO_BLOBS is not set -# CONFIG_ESP32_COMPATIBLE_PRE_V2_1_BOOTLOADERS is not set -# CONFIG_ESP32_COMPATIBLE_PRE_V3_1_BOOTLOADERS is not set -# CONFIG_LOG_BOOTLOADER_LEVEL_NONE is not set -# CONFIG_LOG_BOOTLOADER_LEVEL_ERROR is not set -# CONFIG_LOG_BOOTLOADER_LEVEL_WARN is not set -# CONFIG_LOG_BOOTLOADER_LEVEL_INFO is not set -CONFIG_LOG_BOOTLOADER_LEVEL_DEBUG=y -# CONFIG_LOG_BOOTLOADER_LEVEL_VERBOSE is not set -CONFIG_LOG_BOOTLOADER_LEVEL=4 -# CONFIG_APP_ROLLBACK_ENABLE is not set -# CONFIG_FLASH_ENCRYPTION_ENABLED is not set -# CONFIG_FLASHMODE_QIO is not set -# CONFIG_FLASHMODE_QOUT is not set -CONFIG_FLASHMODE_DIO=y -# CONFIG_FLASHMODE_DOUT is not set -CONFIG_MONITOR_BAUD=115200 -CONFIG_OPTIMIZATION_LEVEL_DEBUG=y -CONFIG_COMPILER_OPTIMIZATION_LEVEL_DEBUG=y -CONFIG_COMPILER_OPTIMIZATION_DEFAULT=y -# CONFIG_OPTIMIZATION_LEVEL_RELEASE is not set -# CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE is not set -CONFIG_OPTIMIZATION_ASSERTIONS_ENABLED=y -# CONFIG_OPTIMIZATION_ASSERTIONS_SILENT is not set -# CONFIG_OPTIMIZATION_ASSERTIONS_DISABLED is not set -CONFIG_OPTIMIZATION_ASSERTION_LEVEL=2 -# CONFIG_CXX_EXCEPTIONS is not set -CONFIG_STACK_CHECK_NONE=y -# CONFIG_STACK_CHECK_NORM is not set -# CONFIG_STACK_CHECK_STRONG is not set -# CONFIG_STACK_CHECK_ALL is not set -# CONFIG_WARN_WRITE_STRINGS is not set -CONFIG_ADC2_DISABLE_DAC=y -# CONFIG_MCPWM_ISR_IN_IRAM is not set -# CONFIG_EVENT_LOOP_PROFILING is not set -CONFIG_POST_EVENTS_FROM_ISR=y -CONFIG_POST_EVENTS_FROM_IRAM_ISR=y -# CONFIG_TWO_UNIVERSAL_MAC_ADDRESS is not set -CONFIG_FOUR_UNIVERSAL_MAC_ADDRESS=y -CONFIG_NUMBER_OF_UNIVERSAL_MAC_ADDRESS=4 -# CONFIG_ESP_SYSTEM_PD_FLASH is not set -CONFIG_ESP32_DEEP_SLEEP_WAKEUP_DELAY=2000 -CONFIG_ESP_SLEEP_DEEP_SLEEP_WAKEUP_DELAY=2000 -CONFIG_ESP32_RTC_CLK_SRC_INT_RC=y -CONFIG_ESP32_RTC_CLOCK_SOURCE_INTERNAL_RC=y -# CONFIG_ESP32_RTC_CLK_SRC_EXT_CRYS is not set -# CONFIG_ESP32_RTC_CLOCK_SOURCE_EXTERNAL_CRYSTAL is not set -# CONFIG_ESP32_RTC_CLK_SRC_EXT_OSC is not set -# CONFIG_ESP32_RTC_CLOCK_SOURCE_EXTERNAL_OSC is not set -# CONFIG_ESP32_RTC_CLK_SRC_INT_8MD256 is not set -# CONFIG_ESP32_RTC_CLOCK_SOURCE_INTERNAL_8MD256 is not set -CONFIG_ESP32_RTC_CLK_CAL_CYCLES=1024 -# CONFIG_ESP32_XTAL_FREQ_26 is not set -CONFIG_ESP32_XTAL_FREQ_40=y -# CONFIG_ESP32_XTAL_FREQ_AUTO is not set -CONFIG_ESP32_XTAL_FREQ=40 -# CONFIG_ESP32_DEFAULT_CPU_FREQ_80 is not set -CONFIG_ESP32_DEFAULT_CPU_FREQ_160=y -# CONFIG_ESP32_DEFAULT_CPU_FREQ_240 is not set -CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ=160 -CONFIG_TRACEMEM_RESERVE_DRAM=0x0 -# CONFIG_ESP32_PANIC_PRINT_HALT is not set -CONFIG_ESP32_PANIC_PRINT_REBOOT=y -# CONFIG_ESP32_PANIC_SILENT_REBOOT is not set -CONFIG_SYSTEM_EVENT_QUEUE_SIZE=32 -CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=2304 -CONFIG_MAIN_TASK_STACK_SIZE=3584 -CONFIG_CONSOLE_UART_DEFAULT=y -# CONFIG_CONSOLE_UART_CUSTOM is not set -# CONFIG_CONSOLE_UART_NONE is not set -# CONFIG_ESP_CONSOLE_UART_NONE is not set -CONFIG_CONSOLE_UART=y -CONFIG_CONSOLE_UART_NUM=0 -CONFIG_CONSOLE_UART_BAUDRATE=115200 -CONFIG_INT_WDT=y -CONFIG_INT_WDT_TIMEOUT_MS=300 -CONFIG_INT_WDT_CHECK_CPU1=y -# CONFIG_TASK_WDT is not set -# CONFIG_ESP_TASK_WDT is not set -# CONFIG_ESP32_DEBUG_STUBS_ENABLE is not set -CONFIG_ESP32_DEBUG_OCDAWARE=y -CONFIG_BROWNOUT_DET=y -CONFIG_ESP32_BROWNOUT_DET=y -CONFIG_BROWNOUT_DET_LVL_SEL_0=y -CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_0=y -# CONFIG_BROWNOUT_DET_LVL_SEL_1 is not set -# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_1 is not set -# CONFIG_BROWNOUT_DET_LVL_SEL_2 is not set -# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_2 is not set -# CONFIG_BROWNOUT_DET_LVL_SEL_3 is not set -# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_3 is not set -# CONFIG_BROWNOUT_DET_LVL_SEL_4 is not set -# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_4 is not set -# CONFIG_BROWNOUT_DET_LVL_SEL_5 is not set -# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_5 is not set -# CONFIG_BROWNOUT_DET_LVL_SEL_6 is not set -# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_6 is not set -# CONFIG_BROWNOUT_DET_LVL_SEL_7 is not set -# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_7 is not set -CONFIG_BROWNOUT_DET_LVL=0 -CONFIG_ESP32_BROWNOUT_DET_LVL=0 -# CONFIG_DISABLE_BASIC_ROM_CONSOLE is not set -CONFIG_IPC_TASK_STACK_SIZE=1024 -CONFIG_TIMER_TASK_STACK_SIZE=3584 -CONFIG_TIMER_TASK_PRIORITY=1 -CONFIG_TIMER_TASK_STACK_DEPTH=2048 -CONFIG_TIMER_QUEUE_LENGTH=10 -# CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK is not set -# CONFIG_HAL_ASSERTION_SILIENT is not set -CONFIG_ESP32_TIME_SYSCALL_USE_RTC_HRT=y -CONFIG_ESP32_TIME_SYSCALL_USE_RTC_FRC1=y -# CONFIG_ESP32_TIME_SYSCALL_USE_RTC is not set -# CONFIG_ESP32_TIME_SYSCALL_USE_HRT is not set -# CONFIG_ESP32_TIME_SYSCALL_USE_FRC1 is not set -# CONFIG_ESP32_TIME_SYSCALL_USE_NONE is not set -CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT=5 -CONFIG_ESP32_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072 -CONFIG_ESP32_PTHREAD_STACK_MIN=768 -CONFIG_ESP32_DEFAULT_PTHREAD_CORE_NO_AFFINITY=y -# CONFIG_ESP32_DEFAULT_PTHREAD_CORE_0 is not set -# CONFIG_ESP32_DEFAULT_PTHREAD_CORE_1 is not set -CONFIG_ESP32_PTHREAD_TASK_CORE_DEFAULT=-1 -CONFIG_ESP32_PTHREAD_TASK_NAME_DEFAULT="pthread" -CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ABORTS=y -# CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_FAILS is not set -# CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ALLOWED is not set -# End of deprecated options From 300f9a13172a68a90a89587c5074b27bce82caab Mon Sep 17 00:00:00 2001 From: ismail Date: Sat, 13 Sep 2025 09:45:13 +0300 Subject: [PATCH 67/69] Fix unused variable warning TAG constant is only used when run limit is enabled. As a result, the compiler generates an "unused variable" warning for other cases. Fixed this warning by adding the unused attribute. --- src/relay_chn_ctl_single.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/relay_chn_ctl_single.c b/src/relay_chn_ctl_single.c index dcb3ac7..753174d 100644 --- a/src/relay_chn_ctl_single.c +++ b/src/relay_chn_ctl_single.c @@ -15,7 +15,7 @@ #include "relay_chn_nvs.h" #endif -static const char *TAG = "RELAY_CHN_CTL"; +static const char *TAG __attribute__((unused)) = "RELAY_CHN_CTL"; static relay_chn_ctl_t s_chn_ctl; From 95ca976bc6e0155cddc1c8198b05f58352883434 Mon Sep 17 00:00:00 2001 From: ismail Date: Sat, 13 Sep 2025 09:46:10 +0300 Subject: [PATCH 68/69] Allow changing log level dynamically --- test_apps/sdkconfig.defaults | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test_apps/sdkconfig.defaults b/test_apps/sdkconfig.defaults index 00de340..bc53ceb 100644 --- a/test_apps/sdkconfig.defaults +++ b/test_apps/sdkconfig.defaults @@ -1,6 +1,9 @@ # Disable task WDT for tests CONFIG_ESP_TASK_WDT_INIT=n +CONFIG_LOG_MAXIMUM_LEVEL_DEBUG=y +CONFIG_LOG_MAXIMUM_LEVEL=4 + # Relay Channel Driver Default Configuration for Testing # Keep this as short as possible for tests CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 \ No newline at end of file From 497f45e336aadf6ff70c022af8efb5a71bfa4af2 Mon Sep 17 00:00:00 2001 From: ismail Date: Sat, 13 Sep 2025 09:52:09 +0300 Subject: [PATCH 69/69] Fix and optimize tilt test case issues Tilt tests started to fail after the latest changes. Had to make some fixes and optimizations so that the test code resets the tilt controls correctly and aligns with relay_chn's timing requirements. Fixes #1115 --- test_apps/main/test_relay_chn_tilt_multi.c | 124 ++++++++++++++------ test_apps/main/test_relay_chn_tilt_single.c | 68 +++++------ 2 files changed, 123 insertions(+), 69 deletions(-) diff --git a/test_apps/main/test_relay_chn_tilt_multi.c b/test_apps/main/test_relay_chn_tilt_multi.c index 0934444..4895f48 100644 --- a/test_apps/main/test_relay_chn_tilt_multi.c +++ b/test_apps/main/test_relay_chn_tilt_multi.c @@ -21,14 +21,13 @@ void check_all_channels_for_state(relay_chn_state_t state) { for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + // ESP_LOGI(TEST_TAG, "Checking channel %d for state %d", i, state); TEST_ASSERT_EQUAL(state, relay_chn_get_state(i)); } } // Helper function to prepare channel for tilt tests void prepare_channels_for_tilt_with_mixed_runs() { - // Ensure the channel reset tilt control - relay_chn_tilt_stop_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Ensure the channel has had a 'last_run_cmd' @@ -40,16 +39,19 @@ void prepare_channels_for_tilt_with_mixed_runs() { } } - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow command to process - relay_chn_stop_all(); // Stop it to set last_run_cmd but return to FREE for next test - vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); - check_all_channels_for_state(RELAY_CHN_STATE_IDLE); + for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { + relay_chn_state_t expect_state; + if (i % 2 == 0) { + expect_state = RELAY_CHN_STATE_FORWARD; + } else { // Assuming initial_cmd is RELAY_CHN_CMD_REVERSE + expect_state = RELAY_CHN_STATE_REVERSE; + } + TEST_ASSERT_EQUAL(expect_state, relay_chn_get_state(i)); + } } // Helper function to prepare channel for tilt tests void prepare_all_channels_for_tilt(int initial_cmd) { - // Ensure the channel reset tilt control - relay_chn_tilt_stop_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // If the channels are not IDLE yet, wait more @@ -64,6 +66,8 @@ void prepare_all_channels_for_tilt(int initial_cmd) { if (not_idle) { vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS)); } + // Ensure all channels are IDLE + check_all_channels_for_state(RELAY_CHN_STATE_IDLE); // Ensure the channel has had a 'last_run_cmd' if (initial_cmd == RELAY_CHN_CMD_FORWARD) { @@ -71,20 +75,17 @@ void prepare_all_channels_for_tilt(int initial_cmd) { } else { // Assuming initial_cmd is RELAY_CHN_CMD_REVERSE relay_chn_run_reverse_all(); } - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow command to process - relay_chn_stop_all(); // Stop all to set last_run_cmd but return to FREE for next test - vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); - check_all_channels_for_state(RELAY_CHN_STATE_IDLE); + relay_chn_state_t expect_state = initial_cmd == RELAY_CHN_CMD_FORWARD + ? RELAY_CHN_STATE_FORWARD : RELAY_CHN_STATE_REVERSE; + check_all_channels_for_state(expect_state); + ESP_LOGI(TEST_TAG, "All channels prepared for tilt test"); } // TEST_CASE: Test transition from running forward to tilt forward // Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_tilt_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_FORWARD TEST_CASE("Run Forward to Tilt Forward transition with inertia", "[relay_chn][tilt][inertia]") { - // Prepare channel by running forward first to set last_run_cmd - prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD); - // 1. Start in forward direction relay_chn_run_forward_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); @@ -99,15 +100,15 @@ TEST_CASE("Run Forward to Tilt Forward transition with inertia", "[relay_chn][ti // Wait for the inertia period (after which the tilt command will be dispatched) vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS)); check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD); + + // Ensure the channel reset tilt control + relay_chn_tilt_stop_all(); } // TEST_CASE: Test transition from running reverse to tilt reverse // Scenario: RELAY_CHN_STATE_REVERSE -> (relay_chn_tilt_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_REVERSE TEST_CASE("Run Reverse to Tilt Reverse transition with inertia", "[relay_chn][tilt][inertia]") { - // Prepare channel by running reverse first to set last_run_cmd - prepare_all_channels_for_tilt(RELAY_CHN_CMD_REVERSE); - // 1. Start in reverse direction relay_chn_run_reverse_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); @@ -120,6 +121,9 @@ TEST_CASE("Run Reverse to Tilt Reverse transition with inertia", "[relay_chn][ti vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); check_all_channels_for_state(RELAY_CHN_STATE_TILT_REVERSE); + + // Ensure the channel reset tilt control + relay_chn_tilt_stop_all(); } // TEST_CASE: Test transition from FREE state to tilt forward (now with preparation) @@ -128,12 +132,19 @@ TEST_CASE("FREE to Tilt Forward transition with inertia (prepared)", "[relay_chn { // Prepare channel by running forward first to set last_run_cmd prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD); + relay_chn_stop_all(); // Stop to trigger IDLE + // Wait for the channel to transition to IDLE + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); + check_all_channels_for_state(RELAY_CHN_STATE_IDLE); // Ensure we are back to IDLE // Issue tilt forward command relay_chn_tilt_forward_all(); // From FREE state, tilt command should still incur the inertia due to the internal timer logic vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD); + + // Ensure the channel reset tilt control + relay_chn_tilt_stop_all(); } // TEST_CASE: Test transition from FREE state to tilt reverse (now with preparation) @@ -142,11 +153,18 @@ TEST_CASE("FREE to Tilt Reverse transition with inertia (prepared)", "[relay_chn { // Prepare channel by running reverse first to set last_run_cmd prepare_all_channels_for_tilt(RELAY_CHN_CMD_REVERSE); + relay_chn_stop_all(); // Stop to trigger IDLE + // Wait for the channel to transition to IDLE + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); + check_all_channels_for_state(RELAY_CHN_STATE_IDLE); // Ensure we are back to IDLE // Issue tilt reverse command relay_chn_tilt_reverse_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); check_all_channels_for_state(RELAY_CHN_STATE_TILT_REVERSE); + + // Ensure the channel reset tilt control + relay_chn_tilt_stop_all(); } // TEST_CASE: Test transition from tilt forward to run forward (inertia expected for run) @@ -165,6 +183,9 @@ TEST_CASE("Tilt Forward to Run Forward transition with inertia", "[relay_chn][ti check_all_channels_for_state(RELAY_CHN_STATE_FORWARD_PENDING); vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); check_all_channels_for_state(RELAY_CHN_STATE_FORWARD); + + // Ensure the channel reset tilt control + relay_chn_tilt_stop_all(); } // TEST_CASE: Test transition from tilt reverse to run reverse (no inertia expected for run) @@ -182,6 +203,9 @@ TEST_CASE("Tilt Reverse to Run Reverse transition with inertia", "[relay_chn][ti check_all_channels_for_state(RELAY_CHN_STATE_REVERSE_PENDING); vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); check_all_channels_for_state(RELAY_CHN_STATE_REVERSE); + + // Ensure the channel reset tilt control + relay_chn_tilt_stop_all(); } // TEST_CASE: Test transition from tilt forward to run reverse (without inertia) @@ -198,6 +222,9 @@ TEST_CASE("Tilt Forward to Run Reverse transition without inertia", "[relay_chn] relay_chn_run_reverse_all(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); check_all_channels_for_state(RELAY_CHN_STATE_REVERSE); + + // Ensure the channel reset tilt control + relay_chn_tilt_stop_all(); } // TEST_CASE: Test stopping from a tilt state (no inertia for stop command itself) @@ -228,10 +255,14 @@ TEST_CASE("tilt_forward_all sets all channels to TILT_FORWARD", "[relay_chn][til // 2. Issue tilt forward to all channels relay_chn_tilt_forward_all(); - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Tilt from FREE doesn't have stop-inertia + // Should incur inertia timer + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); // 3. Verify all channels are tilting forward check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD); + + // Ensure the channel reset tilt control + relay_chn_tilt_stop_all(); } TEST_CASE("tilt_reverse_all sets all channels to TILT_REVERSE", "[relay_chn][tilt][batch]") @@ -241,10 +272,14 @@ TEST_CASE("tilt_reverse_all sets all channels to TILT_REVERSE", "[relay_chn][til // 2. Issue tilt reverse to all channels relay_chn_tilt_reverse_all(); - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + // Should incur inertia timer + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); // 3. Verify all channels are tilting reverse check_all_channels_for_state(RELAY_CHN_STATE_TILT_REVERSE); + + // Ensure the channel reset tilt control + relay_chn_tilt_stop_all(); } TEST_CASE("tilt_stop_all stops all tilting channels", "[relay_chn][tilt][batch]") @@ -253,7 +288,10 @@ TEST_CASE("tilt_stop_all stops all tilting channels", "[relay_chn][tilt][batch]" prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD); relay_chn_tilt_forward_all(); - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + // Should incur inertia timer + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); + // 3. Verify all channels are tilting forward + check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD); // 2. Stop tilting on all channels relay_chn_tilt_stop_all(); @@ -265,15 +303,13 @@ TEST_CASE("tilt_stop_all stops all tilting channels", "[relay_chn][tilt][batch]" TEST_CASE("tilt_auto_all tilts channels based on last run direction", "[relay_chn][tilt][batch]") { - // This test requires at least 2 channels to demonstrate different behaviors - TEST_ASSERT_GREATER_OR_EQUAL_MESSAGE(2, CONFIG_RELAY_CHN_COUNT, "Test requires at least 2 channels"); - // 1. Prepare channel 0 with last run FORWARD and channel 1 with last run REVERSE prepare_channels_for_tilt_with_mixed_runs(); // 2. Issue auto tilt command to all channels relay_chn_tilt_auto_all(); - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Tilt from FREE state is dispatched immediately + // Should incur inertia timer + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); // 3. Verify even channels tilt forward (last run was forward) and odd channels tilt reverse (last run was reverse) for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { @@ -282,6 +318,9 @@ TEST_CASE("tilt_auto_all tilts channels based on last run direction", "[relay_ch TEST_ASSERT_EQUAL(state, relay_chn_get_state(i)); } + + // Ensure the channel reset tilt control + relay_chn_tilt_stop_all(); } // Test relay_chn_tilt_auto() chooses correct tilt direction @@ -290,19 +329,24 @@ TEST_CASE("relay_chn_tilt_auto chooses correct direction", "[relay_chn][tilt][au // Prepare FORWARD prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD); relay_chn_tilt_auto_all(); - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + // Should incur inertia timer + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); // Verify all tilt forward check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD); + // Ensure the channel reset tilt control relay_chn_tilt_stop_all(); - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Prepare REVERSE prepare_all_channels_for_tilt(RELAY_CHN_CMD_REVERSE); relay_chn_tilt_auto_all(); - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + // Should incur inertia timer + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); // Verify all tilt reverse check_all_channels_for_state(RELAY_CHN_STATE_TILT_REVERSE); + + // Ensure the channel reset tilt control + relay_chn_tilt_stop_all(); } // Test sensitivity set/get @@ -375,7 +419,14 @@ TEST_CASE("tilt counter logic: forward and reverse consumption", "[relay_chn][ti #define TEST_TILT_EXECUTION_TIME_MS 100 prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD); + relay_chn_tilt_set_sensitivity_all_with(100); // Set sentivity to max for fastest execution + // Ensure sensitivities are set correctly + uint8_t sensitivities[CONFIG_RELAY_CHN_COUNT]; + uint8_t expect[CONFIG_RELAY_CHN_COUNT]; + memset(expect, 100, CONFIG_RELAY_CHN_COUNT); + relay_chn_tilt_get_sensitivity_all(sensitivities); + TEST_ASSERT_EQUAL_UINT8_ARRAY(expect, sensitivities, CONFIG_RELAY_CHN_COUNT); // Tilt forward 3 times relay_chn_tilt_forward_all(); @@ -393,12 +444,12 @@ TEST_CASE("tilt counter logic: forward and reverse consumption", "[relay_chn][ti // Now tilt reverse 3 times (should succeed) relay_chn_tilt_reverse_all(); - vTaskDelay(pdMS_TO_TICKS(TEST_TILT_EXECUTION_TIME_MS * 3 + TEST_DELAY_MARGIN_MS)); + vTaskDelay(pdMS_TO_TICKS(TEST_TILT_EXECUTION_TIME_MS)); check_all_channels_for_state(RELAY_CHN_STATE_TILT_REVERSE); - - // One more reverse tilt should fail (counter exhausted) + // Let it execute 2 at least, or more vTaskDelay(pdMS_TO_TICKS(TEST_TILT_EXECUTION_TIME_MS * 3)); - // Should not enter TILT_REVERSE, should remain IDLE + + // More reverse tilt should fail (counter exhausted) check_all_channels_for_state(RELAY_CHN_STATE_IDLE); } @@ -423,12 +474,12 @@ TEST_CASE("run_all command during active tilt cycle stops tilt", "[relay_chn][ti // Set a known sensitivity for predictable timing. // For sensitivity=50, move_time=30ms, pause_time=270ms. relay_chn_tilt_set_sensitivity_all_with(50); - const uint32_t move_time_ms = 30; // --- Test interrupting during MOVE step --- prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD); relay_chn_tilt_forward_all(); - vTaskDelay(pdMS_TO_TICKS(move_time_ms / 2)); // Wait for half of the move time + // Should incur inertia timer + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD); // Interrupt with run_reverse_all while in the MOVE part of the cycle @@ -438,9 +489,12 @@ TEST_CASE("run_all command during active tilt cycle stops tilt", "[relay_chn][ti check_all_channels_for_state(RELAY_CHN_STATE_REVERSE); // --- Test interrupting during PAUSE step --- + relay_chn_stop_all(); // Stop the reverse runs + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD); relay_chn_tilt_forward_all(); - vTaskDelay(pdMS_TO_TICKS(move_time_ms + TEST_DELAY_MARGIN_MS)); // Wait past MOVE, into PAUSE + // Should incur inertia timer + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); // Wait past MOVE, into PAUSE check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD); // Interrupt with run_forward_all while in the PAUSE part of the cycle diff --git a/test_apps/main/test_relay_chn_tilt_single.c b/test_apps/main/test_relay_chn_tilt_single.c index 481793b..a312c12 100644 --- a/test_apps/main/test_relay_chn_tilt_single.c +++ b/test_apps/main/test_relay_chn_tilt_single.c @@ -30,19 +30,15 @@ void prepare_channel_for_tilt(int initial_cmd) { } else { // Assuming initial_cmd is RELAY_CHN_CMD_REVERSE relay_chn_run_reverse(); } - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow command to process - relay_chn_stop(); // Stop it to set last_run_cmd but return to FREE for next test vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); + relay_chn_state_t expect_state = initial_cmd == RELAY_CHN_CMD_FORWARD ? RELAY_CHN_STATE_FORWARD : RELAY_CHN_STATE_REVERSE; + TEST_ASSERT_EQUAL(expect_state, relay_chn_get_state()); } // Test transition from running forward to tilt forward // Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_tilt_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_FORWARD TEST_CASE("Run Forward to Tilt Forward transition with inertia", "[relay_chn][tilt][inertia]") { - // Prepare channel by running forward first to set last_run_cmd - prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); - // 1. Start in forward direction relay_chn_run_forward(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); @@ -63,9 +59,6 @@ TEST_CASE("Run Forward to Tilt Forward transition with inertia", "[relay_chn][ti // Scenario: RELAY_CHN_STATE_REVERSE -> (relay_chn_tilt_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_REVERSE TEST_CASE("Run Reverse to Tilt Reverse transition with inertia", "[relay_chn][tilt][inertia]") { - // Prepare channel by running reverse first to set last_run_cmd - prepare_channel_for_tilt(RELAY_CHN_CMD_REVERSE); - // 1. Start in reverse direction relay_chn_run_reverse(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); @@ -86,6 +79,9 @@ TEST_CASE("FREE to Tilt Forward transition with inertia (prepared)", "[relay_chn { // Prepare channel by running forward first to set last_run_cmd prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); + relay_chn_stop(); // Stop to trigger IDLE + // Wait for the channel to transition to IDLE + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); // Ensure we are back to FREE // Issue tilt forward command @@ -101,6 +97,9 @@ TEST_CASE("FREE to Tilt Reverse transition with inertia (prepared)", "[relay_chn { // Prepare channel by running reverse first to set last_run_cmd prepare_channel_for_tilt(RELAY_CHN_CMD_REVERSE); + relay_chn_stop(); // Stop to trigger IDLE + // Wait for the channel to transition to IDLE + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); // Ensure we are back to FREE // Issue tilt reverse command @@ -183,7 +182,8 @@ TEST_CASE("relay_chn_tilt_auto chooses correct direction", "[relay_chn][tilt][au // Prepare FORWARD prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); relay_chn_tilt_auto(); - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + // Should incur inertia timer + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state()); relay_chn_tilt_stop(); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); @@ -191,7 +191,8 @@ TEST_CASE("relay_chn_tilt_auto chooses correct direction", "[relay_chn][tilt][au // Prepare REVERSE prepare_channel_for_tilt(RELAY_CHN_CMD_REVERSE); relay_chn_tilt_auto(); - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + // Should incur inertia timer + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state()); } @@ -237,31 +238,25 @@ TEST_CASE("relay_chn_tilt_set_sensitivity handles upper boundary", "[relay_chn][ // Test tilt counter logic: forward x3, reverse x3, extra reverse fails TEST_CASE("tilt counter logic: forward and reverse consumption", "[relay_chn][tilt][counter]") { +// Tilt execution time at 100% sensitivity in milliseconds (10 + 90) +#define TEST_TILT_EXECUTION_TIME_MS 100 + prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); // Tilt forward 3 times - for (int i = 0; i < 3; ++i) { - relay_chn_tilt_forward(); - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state()); - relay_chn_tilt_stop(); - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - } + relay_chn_tilt_forward(); + vTaskDelay(pdMS_TO_TICKS(TEST_TILT_EXECUTION_TIME_MS * 3 + TEST_DELAY_MARGIN_MS)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state()); + relay_chn_tilt_stop(); // Now tilt reverse 3 times (should succeed) - for (int i = 0; i < 3; ++i) { - relay_chn_tilt_reverse(); - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - if (i < 3) { - TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state()); - relay_chn_tilt_stop(); - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); - } - } - - // Extra reverse tilt should fail (counter exhausted) relay_chn_tilt_reverse(); - vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); + // Let it execute one time + vTaskDelay(pdMS_TO_TICKS(TEST_TILT_EXECUTION_TIME_MS)); + TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state()); + // Let it execute 2 at least, or more + vTaskDelay(pdMS_TO_TICKS(TEST_TILT_EXECUTION_TIME_MS * 3)); + // Should not enter TILT_REVERSE, should remain FREE or STOPPED relay_chn_state_t state = relay_chn_get_state(); TEST_ASSERT(state != RELAY_CHN_STATE_TILT_REVERSE); @@ -289,12 +284,12 @@ TEST_CASE("run command during active tilt cycle stops tilt", "[relay_chn][tilt][ // Set a known sensitivity for predictable timing. // For sensitivity=50, move_time=30ms, pause_time=270ms. relay_chn_tilt_set_sensitivity(50); - const uint32_t move_time_ms = 30; // --- Test interrupting during MOVE step --- prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); relay_chn_tilt_forward(); - vTaskDelay(pdMS_TO_TICKS(move_time_ms / 2)); // Wait for half of the move time + // Should incur inertia timer + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state()); // Interrupt with run_reverse while in the MOVE part of the cycle @@ -304,9 +299,14 @@ TEST_CASE("run command during active tilt cycle stops tilt", "[relay_chn][tilt][ TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state()); // --- Test interrupting during PAUSE step --- - prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); + relay_chn_stop(); // Stop the reverse run + // Wait the channel to be IDLE + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); + + prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); // Prepare channel again relay_chn_tilt_forward(); - vTaskDelay(pdMS_TO_TICKS(move_time_ms + TEST_DELAY_MARGIN_MS)); // Wait past MOVE, into PAUSE + // Should incur inertia timer + vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state()); // Interrupt with run_forward while in the PAUSE part of the cycle