From 61f8ed440e9de79758cd9f58357faa9c19d924d1 Mon Sep 17 00:00:00 2001 From: ismail Date: Wed, 13 Aug 2025 17:53:27 +0300 Subject: [PATCH 1/3] 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 2/3] 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 3/3] 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