feat/957-single-channel-mode #32

Merged
ismail merged 7 commits from feat/957-single-channel-mode into dev 2025-08-14 08:50:36 +02:00
17 changed files with 2453 additions and 1472 deletions
Showing only changes of commit 61f8ed440e - Show all commits

21
.vscode/settings.json vendored
View File

@@ -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"
}

186
include/relay_chn_adapter.h Normal file
View File

@@ -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

24
include/relay_chn_defs.h Normal file
View File

@@ -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

58
include/relay_chn_types.h Normal file
View File

@@ -0,0 +1,58 @@
/*
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <stdint.h>
#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

View File

@@ -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

View File

@@ -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

View File

@@ -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 <stdint.h>
#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

View File

@@ -0,0 +1,78 @@
/*
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <stdint.h>
#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

View File

@@ -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

View File

@@ -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

File diff suppressed because it is too large Load Diff

574
src/relay_chn_core.c Normal file
View File

@@ -0,0 +1,574 @@
/*
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <stdlib.h>
#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";
}
}

137
src/relay_chn_ctl_multi.c Normal file
View File

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

View File

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

168
src/relay_chn_output.c Normal file
View File

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

74
src/relay_chn_run_info.c Normal file
View File

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

634
src/relay_chn_tilt.c Normal file
View File

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