14 Commits

Author SHA1 Message Date
Kozmotronik
8d96914a38 Merge pull request 'fix-update-documentation' (#14) from fix-update-documentation into dev
Reviewed-on: https://kozmotronik.nohost.me/gitea/KozmotronikTech/relay_chn/pulls/14
2025-02-22 11:13:25 +03:00
b7a23f3633 Add missing docs for the tilting interface. 2025-02-22 10:59:02 +03:00
df935a593b Update Readme and add some tilting feature details. 2025-02-22 10:24:39 +03:00
Kozmotronik
f72fe6b1f0 Merge pull request 'Fix movement transition issue.' (#12) from movement-transition-patch into dev
Reviewed-on: https://kozmotronik.nohost.me/gitea/KozmotronikTech/relay_chn/pulls/12
2025-02-22 09:01:06 +03:00
Kozmotronik
e54e28020c Fix movement transition issue.
When transitioning the movements directly the channel should be stopped first.
2025-02-22 08:48:22 +03:00
Kozmotronik
7d3f08b56b Merge pull request 'Add tilt feature, fix bugs, improve code.' (#6) from feature-tilt into dev
Reviewed-on: https://kozmotronik.nohost.me/gitea/KozmotronikTech/relay_chn/pulls/6
2025-02-21 12:46:10 +03:00
a694938224 Add tilt feature, fix bugs, improve code.
* Add tilt feature.

* Fix the following bugs:
  * warning: comparison is always true due to limited range of data type.
  * Remove unnecessary esp_timer checks.
  * The scheduled FREE command disrupts the current command.
  * Fatal pin mapping issue.

* Make code optimizations and improvements:
  * Optimize event loop queue size depending on channel count.
  * Change the channels' starting state to FREE.
  * Remove the unnecessary relay_chn_invalidate_inertia_timer function.
  * Change the relay_chn_start_inertia_timer function as relay_chn_start_esp_timer_once and modify the function so that it be a generic esp timer start function.
  * Optimize the if statement that checks the last run cmd in the relay_chn_execute_stop.
2025-02-21 12:43:00 +03:00
dcb5453522 Resolve unseen conflicts. 2025-02-14 17:01:22 +03:00
feb1f4ac81 Update docs for the state_listener_manager API. 2025-02-14 15:12:37 +03:00
069363205a Rename inertia timer to distinguish timers.
Rename timer member and relevant functions for the purpose they used,
in order to distinguish between timers.
2025-02-14 15:00:02 +03:00
d4fdff949a Rename inertia timer to distinguish timers. 2025-02-14 14:50:35 +03:00
dc2dcfec7d Add support for addressing all relay channels. 2025-02-12 16:50:00 +03:00
8517993358 Update file name according to the extension guide. 2025-02-12 08:56:51 +03:00
e21bfb5b26 Update the root gitignore to ignore the build and unity-app. 2025-02-12 08:44:38 +03:00
7 changed files with 741 additions and 52 deletions

5
.gitignore vendored
View File

@@ -100,3 +100,8 @@ CTestTestfile.cmake
_deps _deps
CMakeUserPresets.json CMakeUserPresets.json
# Build directory
build
# unity-app directory
unity-app

5
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,5 @@
{
"files.associations": {
"relay_chn.h": "c"
}
}

View File

@@ -17,4 +17,13 @@ menu "Relay Channel Driver Configuration"
help help
Number of relay channels between 1 and 8. Number of relay channels between 1 and 8.
config RELAY_CHN_ENABLE_TILTING
bool "Enable tilting on relay channels"
default n
help
This option controls enabling tilting on channels. Tilting makes
a channel move with a specific pattern moving with small steps
at a time. Tilting is specifically designed for controlling some
types of curtains that need to be adjusted to let enter specific
amount of day light.
endmenu endmenu

View File

@@ -11,17 +11,23 @@ An ESP-IDF component for controlling relay channels, specifically designed for d
- Forward/Reverse direction control - Forward/Reverse direction control
- Direction flipping capability - Direction flipping capability
- State monitoring and reporting - State monitoring and reporting
- Optional sensitivty adjustable tilting feature
## Description ## Description
Each relay channel consists of 2 output relays controlled by 2 GPIO pins. The component provides APIs to control these relay pairs while ensuring safe operation, particularly for driving bipolar motors. It prevents short-circuits by automatically managing direction changes with configurable inertia timing. Each relay channel consists of 2 output relays controlled by 2 GPIO pins. The component provides APIs to control these relay pairs while ensuring safe operation, particularly for driving bipolar motors. It prevents short-circuits by automatically managing direction changes with configurable inertia timing.
It also provides an optional tilting interface per channel base. Tilting makes a channel move with a specific pattern moving with small steps at a time. Tilting is specifically designed for controlling some types of curtains that need to be adjusted to let enter specific amount of day light.
Since it operates on relays, the switching frequency is limited to 10Hz which complies with the most of the general purpose relays' requirements. The minimum frequency is 2Hz and the duty cycle is about 10% in all ranges.
The module also handles all the required timing between the movement transitions automatically to ensure reliable operation.
## Configuration ## Configuration
Configure the component through menuconfig under "Relay Channel Driver Configuration": Configure the component through menuconfig under "Relay Channel Driver Configuration":
- `CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS`: Time to wait before changing direction (200-1500ms, default: 800ms) - `CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS`: Time to wait before changing direction (200-1500ms, default: 800ms)
- `CONFIG_RELAY_CHN_COUNT`: Number of relay channels (1-8, default: 1) - `CONFIG_RELAY_CHN_COUNT`: Number of relay channels (1-8, default: 1)
- `CONFIG_RELAY_CHN_ENABLE_TILTING`: Enable tilting interface on all channels. (default: n)
## Installation ## Installation
@@ -77,6 +83,30 @@ char *state_str = relay_chn_get_state_str(0);
relay_chn_direction_t direction = relay_chn_get_direction(0); relay_chn_direction_t direction = relay_chn_get_direction(0);
``` ```
### 4. Tilting Interface (if enabled)
```c
// Assuming CONFIG_RELAY_CHN_ENABLE_TILTING is enabled
// Start tilting automatically (channel 0)
relay_chn_tilt_auto(0);
// Tilt forward (channel 0)
relay_chn_tilt_forward(0);
// Tilt reverse (channel 0)
relay_chn_tilt_reverse(0);
// Stop tilting (channel 0)
relay_chn_tilt_stop(0);
// Set tilting sensitivity (channel 0, sensitivity as percentage)
relay_chn_tilt_sensitivity_set(0, 90);
// Get tilting sensitivity (channel 0, sensitivty as percentage)
uint8_t sensitivity = relay_chn_tilt_sensitivity_get(0);
## License ## License
[MIT License](LICENSE) - Copyright (c) 2025 kozmotronik. [MIT License](LICENSE) - Copyright (c) 2025 kozmotronik.

View File

@@ -23,11 +23,14 @@
#include "esp_err.h" #include "esp_err.h"
#include "driver/gpio.h" #include "driver/gpio.h"
#include <stdint.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
#define RELAY_CHN_ID_ALL CONFIG_RELAY_CHN_COUNT ///< Special ID to address all channels
/** /**
* @brief Enumeration for relay channel direction. * @brief Enumeration for relay channel direction.
*/ */
@@ -51,6 +54,10 @@ enum relay_chn_state_enum {
RELAY_CHN_STATE_REVERSE, ///< The relay channel is running in the reverse 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_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. 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
}; };
/** /**
@@ -58,6 +65,20 @@ enum relay_chn_state_enum {
*/ */
typedef enum relay_chn_state_enum relay_chn_state_t; 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. * @brief Create and initialize relay channels.
@@ -74,6 +95,26 @@ typedef enum relay_chn_state_enum relay_chn_state_t;
*/ */
esp_err_t relay_chn_create(const gpio_num_t* gpio_map, uint8_t gpio_count); esp_err_t relay_chn_create(const gpio_num_t* gpio_map, uint8_t gpio_count);
/**
* @brief Register a channel state change listener.
*
* @param listener A function that implements relay_chn_state_listener_t interface.
*
* @return
* - ESP_OK: Success
* - ESP_ERR_INVALID_ARG: Invalid argument
* - ESP_ERR_NO_MEM: No enough memory
* - ESP_FAIL: General failure
*/
esp_err_t relay_chn_register_listener(relay_chn_state_listener_t listener);
/**
* @brief Unregister a channel state change listener.
*
* @param listener A function that implements relay_chn_state_listener_t interface.
*/
void relay_chn_unregister_listener(relay_chn_state_listener_t listener);
/** /**
* @brief Get the state of the specified relay channel. * @brief Get the state of the specified relay channel.
* *
@@ -100,6 +141,14 @@ relay_chn_state_t relay_chn_get_state(uint8_t chn_id);
*/ */
char *relay_chn_get_state_str(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. * @brief Runs the relay channel in the forward direction.
* *
@@ -153,6 +202,73 @@ void relay_chn_flip_direction(uint8_t chn_id);
*/ */
relay_chn_direction_t relay_chn_get_direction(uint8_t chn_id); relay_chn_direction_t relay_chn_get_direction(uint8_t chn_id);
#if CONFIG_RELAY_CHN_ENABLE_TILTING == 1
/**
* @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.
*
* @param chn_id The ID of the relay channel to enable automatic tilting.
*/
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()`.
*
* @param chn_id The ID of the relay channel to tilt forward.
*/
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()`.
*
* @param chn_id The ID of the relay channel to tilt reverse.
*/
void relay_chn_tilt_reverse(uint8_t chn_id);
/**
* @brief Stops the tilting action on the specified relay channel.
*
* This function stops any ongoing tilting action (automatic or manual) on the specified relay channel.
*
* @param chn_id The ID of the relay channel to stop tilting.
*/
void relay_chn_tilt_stop(uint8_t chn_id);
/**
* @brief Sets the tilting sensitivity for the specified 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 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);
/**
* @brief Gets the tilting sensitivity for the specified relay channel.
*
* This function retrieves the currently set sensitivity for the specified relay channel's automatic
* tilting mechanism.
*
* @param chn_id The ID of the relay channel to get the sensitivity for.
* @return The current sensitivity in percentage: 0 - 100%.
*/
uint8_t relay_chn_tilt_sensitivity_get(uint8_t chn_id);
#endif // CONFIG_RELAY_CHN_ENABLE_TILTING
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@@ -13,6 +13,7 @@
*/ */
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include "esp_err.h" #include "esp_err.h"
#include "esp_log.h" #include "esp_log.h"
#include "esp_task.h" #include "esp_task.h"
@@ -23,12 +24,12 @@
#include "relay_chn.h" #include "relay_chn.h"
#include "sdkconfig.h" #include "sdkconfig.h"
// TODO: on_state change API si ekle
#define RELAY_CHN_OPPOSITE_INERTIA_MS CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS #define RELAY_CHN_OPPOSITE_INERTIA_MS CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS
#define RELAY_CHN_COUNT CONFIG_RELAY_CHN_COUNT #define RELAY_CHN_COUNT CONFIG_RELAY_CHN_COUNT
#define RELAY_CHN_ENABLE_TILTING CONFIG_RELAY_CHN_ENABLE_TILTING
const char* TAG = "relay_chn"; static const char *TAG = "relay_chn";
ESP_EVENT_DEFINE_BASE(RELAY_CHN_CMD_EVENT); ESP_EVENT_DEFINE_BASE(RELAY_CHN_CMD_EVENT);
@@ -73,6 +74,8 @@ typedef struct relay_chn_type relay_chn_t; // Forward declaration
*/ */
typedef void(*relay_chn_cmd_fn_t)(relay_chn_t*); 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. * @brief Structure to hold the state and configuration of a relay channel.
*/ */
@@ -82,9 +85,95 @@ typedef struct relay_chn_type {
relay_chn_run_info_t run_info; ///< Runtime information 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_output_t output; ///< Output configuration of the relay channel.
relay_chn_cmd_t pending_cmd; ///< The command that is pending to be issued relay_chn_cmd_t pending_cmd; ///< The command that is pending to be issued
esp_timer_handle_t timer; ///< Timer to handle the opposite direction inertia time. esp_timer_handle_t inertia_timer; ///< Timer to handle the opposite direction inertia time.
} relay_chn_t; } 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) )
/// @}
/// @brief Tilt commands.
enum relay_chn_tilt_cmd_enum {
RELAY_CHN_TILT_CMD_NONE, ///< No command.
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_RUN, ///< Run step. Tilt is either driving 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 percent value (%).
uint32_t run_time_ms; ///< Run time in milliseconds.
uint32_t pause_time_ms; ///< Pause time in milliseconds.
} relay_chn_tilt_timing_t;
/// @brief Tilt control structure to manage tilt operations.
typedef struct relay_chn_tilt_control_struct {
relay_chn_tilt_cmd_t cmd; ///< Current tilt command.
relay_chn_tilt_step_t step; ///< Current tilt step.
relay_chn_tilt_timing_t tilt_timing; ///< Tilt timing 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;
} relay_chn_t;
static esp_err_t relay_chn_init_tilt_control(relay_chn_t *relay_chn);
static void relay_chn_tilt_state_handler(uint8_t chn_id, relay_chn_state_t old_state, relay_chn_state_t new_state);
static uint32_t relay_chn_tilting_channels;
#endif // RELAY_CHN_ENABLE_TILTING
/**
* @brief Structure to manage the state change listeners.
*/
struct relay_chn_state_listener_manager_type {
uint8_t listener_count; ///< The number of registered listeners.
relay_chn_state_listener_t *listeners; ///< The list that holds references to the registered listeners.
} relay_chn_state_listener_manager;
static relay_chn_t relay_channels[RELAY_CHN_COUNT]; static relay_chn_t relay_channels[RELAY_CHN_COUNT];
static esp_event_loop_handle_t relay_chn_event_loop; static esp_event_loop_handle_t relay_chn_event_loop;
@@ -154,7 +243,7 @@ static esp_err_t relay_chn_init_timer(relay_chn_t *relay_chn)
.arg = &relay_chn->id, .arg = &relay_chn->id,
.name = timer_name .name = timer_name
}; };
return esp_timer_create(&timer_args, &relay_chn->timer); return esp_timer_create(&timer_args, &relay_chn->inertia_timer);
} }
/** /**
@@ -172,7 +261,7 @@ static bool relay_chn_is_gpio_valid(gpio_num_t gpio)
static esp_err_t relay_chn_create_event_loop() static esp_err_t relay_chn_create_event_loop()
{ {
esp_event_loop_args_t loop_args = { esp_event_loop_args_t loop_args = {
.queue_size = 10, .queue_size = RELAY_CHN_COUNT * 8,
.task_name = "relay_chn_event_loop", .task_name = "relay_chn_event_loop",
.task_priority = ESP_TASKD_EVENT_PRIO - 1, .task_priority = ESP_TASKD_EVENT_PRIO - 1,
.task_stack_size = 2048, .task_stack_size = 2048,
@@ -204,8 +293,9 @@ esp_err_t relay_chn_create(const gpio_num_t* gpio_map, uint8_t gpio_count)
esp_err_t ret; esp_err_t ret;
for (int i = 0; i < RELAY_CHN_COUNT; i++) { for (int i = 0; i < RELAY_CHN_COUNT; i++) {
gpio_num_t forward_pin = gpio_map[i]; int gpio_index = i << 1; // gpio_index = i * 2
gpio_num_t reverse_pin = gpio_map[i+1]; 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 // Check if the GPIOs are valid
if (!relay_chn_is_gpio_valid(forward_pin)) { if (!relay_chn_is_gpio_valid(forward_pin)) {
ESP_LOGE(TAG, "Invalid GPIO pin number: %d", forward_pin); ESP_LOGE(TAG, "Invalid GPIO pin number: %d", forward_pin);
@@ -235,22 +325,106 @@ esp_err_t relay_chn_create(const gpio_num_t* gpio_map, uint8_t gpio_count)
relay_chn->output.forward_pin = forward_pin; relay_chn->output.forward_pin = forward_pin;
relay_chn->output.reverse_pin = reverse_pin; relay_chn->output.reverse_pin = reverse_pin;
relay_chn->output.direction = RELAY_CHN_DIRECTION_DEFAULT; relay_chn->output.direction = RELAY_CHN_DIRECTION_DEFAULT;
relay_chn->state = RELAY_CHN_STATE_STOPPED; relay_chn->state = RELAY_CHN_STATE_FREE;
relay_chn->pending_cmd = RELAY_CHN_CMD_NONE; relay_chn->pending_cmd = RELAY_CHN_CMD_NONE;
relay_chn->run_info.last_run_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 ret |= relay_chn_init_timer(relay_chn); // Create direction change inertia timer
#if RELAY_CHN_ENABLE_TILTING == 1
ret |= relay_chn_init_tilt_control(relay_chn);
#endif
if (ret != ESP_OK) { if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize relay channel %d!", i); ESP_LOGE(TAG, "Failed to initialize relay channel %d!", i);
return ret; return ret;
} }
} }
#if RELAY_CHN_ENABLE_TILTING == 1
relay_chn_tilting_channels = 0;
#endif
// Create relay channel command event loop // Create relay channel command event loop
ret |= relay_chn_create_event_loop(); ret |= relay_chn_create_event_loop();
// Init the state listener manager
relay_chn_state_listener_manager.listeners = malloc(sizeof(relay_chn_state_listener_t*));
if (relay_chn_state_listener_manager.listeners == NULL) {
ESP_LOGE(TAG, "Failed to initialize memory for the listeners!");
ret = ESP_ERR_NO_MEM;
}
return ret; return ret;
} }
static int relay_chn_listener_index(relay_chn_state_listener_t listener)
{
for (int i = 0; i < relay_chn_state_listener_manager.listener_count; i++) {
if (relay_chn_state_listener_manager.listeners[i] == listener) {
// This is the listener to unregister. Check if it is in the middle
ESP_LOGD(TAG, "relay_chn_listener_index: Listener %p; found at index %d.", listener, i);
return i;
}
}
return -1;
}
esp_err_t relay_chn_register_listener(relay_chn_state_listener_t listener)
{
if (listener == NULL) {
ESP_LOGE(TAG, "relay_chn_register_listener: A NULL listener given.");
return ESP_ERR_INVALID_ARG;
}
if (relay_chn_listener_index(listener) > -1) {
ESP_LOGD(TAG, "relay_chn_register_listener: The listener %p is already registered.", listener);
return ESP_OK;
}
ESP_LOGD(TAG, "relay_chn_register_listener: Register listener: %p", listener);
relay_chn_state_listener_manager.listeners[relay_chn_state_listener_manager.listener_count] = listener;
// Update listener count
relay_chn_state_listener_manager.listener_count++;
return ESP_OK;
}
void relay_chn_unregister_listener(relay_chn_state_listener_t listener)
{
if (listener == NULL) {
ESP_LOGD(TAG, "relay_chn_unregister_listener: A NULL listener given, nothing to do.");
return;
}
// Search the listener in the listeners list and get its index if exists
int i = relay_chn_listener_index(listener);
if (i == -1) {
ESP_LOGD(TAG, "relay_chn_unregister_listener: %p is not registered already.", listener);
return;
}
uint8_t max_index = relay_chn_state_listener_manager.listener_count - 1;
// Check whether the listener's index is in the middle
if (i == max_index) {
// free(&relay_chn_state_listener_manager.listeners[i]);
relay_chn_state_listener_manager.listeners[i] = NULL;
}
else {
// It is in the middle, so align the next elements in the list and then free the last empty pointer
// Align the next elements
uint8_t num_of_elements = max_index - i;
relay_chn_state_listener_t *pnext = NULL;
// (i + j): current index; (i + j + 1): next index
for (uint8_t j = 0; j < num_of_elements; j++) {
uint8_t current_index = i + j;
uint8_t next_index = current_index + 1;
pnext = &relay_chn_state_listener_manager.listeners[next_index];
relay_chn_state_listener_manager.listeners[current_index] = *pnext;
}
// free(&relay_chn_state_listener_manager.listeners[max_index]); // Free the last element
relay_chn_state_listener_manager.listeners[max_index] = NULL; // Free the last element
}
// Decrease listener count
relay_chn_state_listener_manager.listener_count--;
}
/** /**
* @brief Check channel ID validity * @brief Check channel ID validity
* *
@@ -260,7 +434,7 @@ esp_err_t relay_chn_create(const gpio_num_t* gpio_map, uint8_t gpio_count)
*/ */
static bool relay_chn_is_channel_id_valid(uint8_t chn_id) static bool relay_chn_is_channel_id_valid(uint8_t chn_id)
{ {
bool valid = chn_id >= 0 && chn_id < RELAY_CHN_COUNT; bool valid = (chn_id < RELAY_CHN_COUNT) || chn_id == RELAY_CHN_ID_ALL;
if (!valid) { if (!valid) {
ESP_LOGE(TAG, "Invalid channel ID: %d", chn_id); ESP_LOGE(TAG, "Invalid channel ID: %d", chn_id);
} }
@@ -280,19 +454,45 @@ static void relay_chn_dispatch_cmd(relay_chn_t *relay_chn, relay_chn_cmd_t cmd)
sizeof(relay_chn->id), portMAX_DELAY); sizeof(relay_chn->id), portMAX_DELAY);
} }
static esp_err_t relay_chn_invalidate_timer(relay_chn_t *relay_chn) static esp_err_t relay_chn_start_esp_timer_once(esp_timer_handle_t esp_timer, uint32_t time_ms)
{ {
if (esp_timer_is_active(relay_chn->timer)) { esp_err_t ret = esp_timer_start_once(esp_timer, time_ms * 1000);
return esp_timer_stop(relay_chn->timer); 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 ESP_OK; return ret;
} }
static esp_err_t relay_chn_start_timer(relay_chn_t *relay_chn, uint32_t time_ms) static void relay_chn_update_state(relay_chn_t *relay_chn, relay_chn_state_t new_state)
{ {
// Invalidate the channel's timer if it is active relay_chn_state_t old = relay_chn->state;
relay_chn_invalidate_timer(relay_chn); relay_chn->state = new_state;
return esp_timer_start_once(relay_chn->timer, time_ms * 1000);
#if RELAY_CHN_ENABLE_TILTING == 1
if (relay_chn->tilt_control.cmd != RELAY_CHN_TILT_CMD_NONE) {
// The channel is tilting, pipe the internal state to the tilt state handler
// unless the state sent from the tilt module
if (relay_chn->state != RELAY_CHN_STATE_TILT_FORWARD && relay_chn->state != RELAY_CHN_STATE_TILT_REVERSE) {
relay_chn_tilt_state_handler(relay_chn->id, old, new_state);
return;
}
}
#endif
for (uint8_t i = 0; i < relay_chn_state_listener_manager.listener_count; i++) {
relay_chn_state_listener_t listener = relay_chn_state_listener_manager.listeners[i];
if (listener == NULL) {
relay_chn_state_listener_manager.listener_count -= 1;
ESP_LOGD(TAG, "relay_chn_update_state: A listener is NULL at index: %u", i);
}
// Emit the state change to the listeners
listener(relay_chn->id, old, new_state);
}
} }
/** /**
@@ -344,8 +544,14 @@ static void relay_chn_issue_cmd(relay_chn_t* relay_chn, relay_chn_cmd_t cmd)
break; break;
case RELAY_CHN_STATE_STOPPED: case RELAY_CHN_STATE_STOPPED:
if (relay_chn->run_info.last_run_cmd == cmd) { if (relay_chn->run_info.last_run_cmd == cmd || relay_chn->run_info.last_run_cmd == RELAY_CHN_CMD_NONE) {
// If the last run command is the same as the current command, run the command immediately // 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); relay_chn_dispatch_cmd(relay_chn, cmd);
} }
else { else {
@@ -355,11 +561,11 @@ static void relay_chn_issue_cmd(relay_chn_t* relay_chn, relay_chn_cmd_t cmd)
uint32_t inertia_time_ms = RELAY_CHN_OPPOSITE_INERTIA_MS - inertia_time_passed_ms; uint32_t inertia_time_ms = RELAY_CHN_OPPOSITE_INERTIA_MS - inertia_time_passed_ms;
if (inertia_time_ms > 0) { if (inertia_time_ms > 0) {
relay_chn->pending_cmd = cmd; relay_chn->pending_cmd = cmd;
relay_chn->state = cmd == RELAY_CHN_CMD_FORWARD relay_chn_state_t new_state = cmd == RELAY_CHN_CMD_FORWARD
? RELAY_CHN_STATE_FORWARD_PENDING ? RELAY_CHN_STATE_FORWARD_PENDING : RELAY_CHN_STATE_REVERSE_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 // If the time passed is less than the opposite inertia time, wait for the remaining time
relay_chn_start_timer(relay_chn, inertia_time_ms); relay_chn_start_esp_timer_once(relay_chn->inertia_timer, inertia_time_ms);
} }
else { else {
// If the time passed is more than the opposite inertia time, run the command immediately // If the time passed is more than the opposite inertia time, run the command immediately
@@ -382,10 +588,15 @@ static void relay_chn_issue_cmd(relay_chn_t* relay_chn, relay_chn_cmd_t cmd)
return; 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 // If the last run command is different from the current command, wait for the opposite inertia time
relay_chn->pending_cmd = cmd; relay_chn->pending_cmd = cmd;
relay_chn->state = cmd == RELAY_CHN_CMD_FORWARD ? RELAY_CHN_STATE_FORWARD_PENDING : RELAY_CHN_STATE_REVERSE_PENDING; relay_chn_state_t new_state = cmd == RELAY_CHN_CMD_FORWARD
relay_chn_start_timer(relay_chn, RELAY_CHN_OPPOSITE_INERTIA_MS); ? 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; break;
default: ESP_LOGD(TAG, "relay_chn_evaluate: Unknown relay channel state!"); default: ESP_LOGD(TAG, "relay_chn_evaluate: Unknown relay channel state!");
@@ -406,21 +617,13 @@ char *relay_chn_get_state_str(uint8_t chn_id)
if (!relay_chn_is_channel_id_valid(chn_id)) { if (!relay_chn_is_channel_id_valid(chn_id)) {
return "INVALID"; return "INVALID";
} }
switch (relay_channels[chn_id].state) { return relay_chn_state_str(relay_channels[chn_id].state);
case RELAY_CHN_STATE_FREE: }
return "FREE";
case RELAY_CHN_STATE_STOPPED: static void relay_chn_issue_cmd_on_all_channels(relay_chn_cmd_t cmd)
return "STOPPED"; {
case RELAY_CHN_STATE_FORWARD: for (int i = 0; i < RELAY_CHN_COUNT; i++) {
return "FORWARD"; relay_chn_issue_cmd(&relay_channels[i], cmd);
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";
default:
return "UNKNOWN";
} }
} }
@@ -428,6 +631,11 @@ void relay_chn_run_forward(uint8_t chn_id)
{ {
if (!relay_chn_is_channel_id_valid(chn_id)) return; if (!relay_chn_is_channel_id_valid(chn_id)) 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_t* relay_chn = &relay_channels[chn_id];
relay_chn_issue_cmd(relay_chn, RELAY_CHN_CMD_FORWARD); relay_chn_issue_cmd(relay_chn, RELAY_CHN_CMD_FORWARD);
} }
@@ -436,6 +644,11 @@ void relay_chn_run_reverse(uint8_t chn_id)
{ {
if (!relay_chn_is_channel_id_valid(chn_id)) return; if (!relay_chn_is_channel_id_valid(chn_id)) 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_t* relay_chn = &relay_channels[chn_id];
relay_chn_issue_cmd(relay_chn, RELAY_CHN_CMD_REVERSE); relay_chn_issue_cmd(relay_chn, RELAY_CHN_CMD_REVERSE);
} }
@@ -444,6 +657,11 @@ void relay_chn_stop(uint8_t chn_id)
{ {
if (!relay_chn_is_channel_id_valid(chn_id)) return; 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_t* relay_chn = &relay_channels[chn_id];
relay_chn_issue_cmd(relay_chn, RELAY_CHN_CMD_STOP); relay_chn_issue_cmd(relay_chn, RELAY_CHN_CMD_STOP);
} }
@@ -452,6 +670,11 @@ void relay_chn_flip_direction(uint8_t chn_id)
{ {
if (!relay_chn_is_channel_id_valid(chn_id)) return; if (!relay_chn_is_channel_id_valid(chn_id)) 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_t* relay_chn = &relay_channels[chn_id];
relay_chn_issue_cmd(relay_chn, RELAY_CHN_CMD_FLIP); relay_chn_issue_cmd(relay_chn, RELAY_CHN_CMD_FLIP);
} }
@@ -470,21 +693,24 @@ static void relay_chn_execute_stop(relay_chn_t *relay_chn)
{ {
gpio_set_level(relay_chn->output.forward_pin, 0); gpio_set_level(relay_chn->output.forward_pin, 0);
gpio_set_level(relay_chn->output.reverse_pin, 0); gpio_set_level(relay_chn->output.reverse_pin, 0);
relay_chn->state = RELAY_CHN_STATE_STOPPED; relay_chn_update_state(relay_chn, RELAY_CHN_STATE_STOPPED);
#if RELAY_CHN_ENABLE_TILTING == 1
// Just stop and update state if tilting is active
if (relay_chn->tilt_control.cmd != RELAY_CHN_TILT_CMD_NONE) return;
#endif
// If there is any pending command, cancel it since the STOP command is issued right after it // 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; relay_chn->pending_cmd = RELAY_CHN_CMD_NONE;
// Invalidate the channel's timer if it is active // Invalidate the channel's timer if it is active
relay_chn_invalidate_timer(relay_chn); esp_timer_stop(relay_chn->inertia_timer);
// If the channel was running, schedule a free command for the channel // If the channel was running, schedule a free command for the channel
relay_chn_cmd_t last_run_cmd = relay_chn->run_info.last_run_cmd; if (relay_chn->run_info.last_run_cmd != RELAY_CHN_CMD_NONE) {
if (last_run_cmd == RELAY_CHN_CMD_FORWARD || last_run_cmd == RELAY_CHN_CMD_REVERSE) {
// Record the command's last run time // Record the command's last run time
relay_chn->run_info.last_run_cmd_time_ms = esp_timer_get_time() / 1000; relay_chn->run_info.last_run_cmd_time_ms = esp_timer_get_time() / 1000;
// Schedule a free command for the channel // Schedule a free command for the channel
relay_chn->pending_cmd = RELAY_CHN_CMD_FREE; relay_chn->pending_cmd = RELAY_CHN_CMD_FREE;
relay_chn_start_timer(relay_chn, RELAY_CHN_OPPOSITE_INERTIA_MS); relay_chn_start_esp_timer_once(relay_chn->inertia_timer, RELAY_CHN_OPPOSITE_INERTIA_MS);
} else { } else {
// If the channel was not running, issue a free command immediately // If the channel was not running, issue a free command immediately
relay_chn_dispatch_cmd(relay_chn, RELAY_CHN_CMD_FREE); relay_chn_dispatch_cmd(relay_chn, RELAY_CHN_CMD_FREE);
@@ -495,16 +721,16 @@ static void relay_chn_execute_forward(relay_chn_t *relay_chn)
{ {
gpio_set_level(relay_chn->output.reverse_pin, 0); gpio_set_level(relay_chn->output.reverse_pin, 0);
gpio_set_level(relay_chn->output.forward_pin, 1); gpio_set_level(relay_chn->output.forward_pin, 1);
relay_chn->state = RELAY_CHN_STATE_FORWARD;
relay_chn->run_info.last_run_cmd = RELAY_CHN_CMD_FORWARD; 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) static void relay_chn_execute_reverse(relay_chn_t *relay_chn)
{ {
gpio_set_level(relay_chn->output.forward_pin, 0); gpio_set_level(relay_chn->output.forward_pin, 0);
gpio_set_level(relay_chn->output.reverse_pin, 1); gpio_set_level(relay_chn->output.reverse_pin, 1);
relay_chn->state = RELAY_CHN_STATE_REVERSE;
relay_chn->run_info.last_run_cmd = RELAY_CHN_CMD_REVERSE; 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) static void relay_chn_execute_flip(relay_chn_t *relay_chn)
@@ -519,15 +745,15 @@ static void relay_chn_execute_flip(relay_chn_t *relay_chn)
: RELAY_CHN_DIRECTION_DEFAULT; : RELAY_CHN_DIRECTION_DEFAULT;
// Set an inertia on the channel to prevent any immediate movement // Set an inertia on the channel to prevent any immediate movement
relay_chn->pending_cmd = RELAY_CHN_CMD_FREE; relay_chn->pending_cmd = RELAY_CHN_CMD_FREE;
relay_chn_start_timer(relay_chn, RELAY_CHN_OPPOSITE_INERTIA_MS); 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) void relay_chn_execute_free(relay_chn_t *relay_chn)
{ {
relay_chn->state = RELAY_CHN_STATE_FREE;
relay_chn->pending_cmd = RELAY_CHN_CMD_NONE; relay_chn->pending_cmd = RELAY_CHN_CMD_NONE;
// Invalidate the channel's timer if it is active // Invalidate the channel's timer if it is active
relay_chn_invalidate_timer(relay_chn); 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) static void relay_chn_event_handler(void* handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
@@ -577,4 +803,302 @@ static char *relay_chn_cmd_str(relay_chn_cmd_t cmd)
} }
} }
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
// 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_RUN:
relay_chn_issue_cmd(relay_chn, RELAY_CHN_CMD_STOP);
break;
case RELAY_CHN_TILT_STEP_PAUSE:
if (relay_chn->tilt_control.cmd == RELAY_CHN_TILT_CMD_FORWARD) {
relay_chn_issue_cmd(relay_chn, RELAY_CHN_CMD_REVERSE);
}
else if (relay_chn->tilt_control.cmd == RELAY_CHN_TILT_CMD_REVERSE) {
relay_chn_issue_cmd(relay_chn, RELAY_CHN_CMD_FORWARD);
}
break;
default:
break;
}
}
// This listener is active until the relay_chn_tilt_stop() is called.
static void relay_chn_tilt_state_handler(uint8_t chn_id, relay_chn_state_t old_state, relay_chn_state_t new_state)
{
ESP_LOGD(TAG, "relay_chn_tilt_state_listener: #%u, old_state: %s, new_state: %s",
chn_id, relay_chn_state_str(old_state), relay_chn_state_str(new_state));
relay_chn_t* relay_chn = &relay_channels[chn_id];
// Check whether this channel is the one that's been tilting
if (relay_chn->tilt_control.cmd == RELAY_CHN_TILT_CMD_NONE) {
return;
}
switch (new_state)
{
case RELAY_CHN_STATE_FORWARD:
case RELAY_CHN_STATE_REVERSE:
relay_chn->tilt_control.step = RELAY_CHN_TILT_STEP_RUN;
// Start the tilt run timer
esp_timer_start_once(relay_chn->tilt_control.tilt_timer,
relay_chn->tilt_control.tilt_timing.run_time_ms * 1000);
break;
case RELAY_CHN_STATE_STOPPED:
relay_chn->tilt_control.step = RELAY_CHN_TILT_STEP_PAUSE;
esp_timer_start_once(relay_chn->tilt_control.tilt_timer,
relay_chn->tilt_control.tilt_timing.pause_time_ms * 1000);
break;
default:
break;
}
}
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;
}
else if (relay_chn->run_info.last_run_cmd == RELAY_CHN_CMD_REVERSE && cmd == RELAY_CHN_TILT_CMD_FORWARD) {
ESP_LOGD(TAG, "relay_chn_issue_tilt_cmd: Invalid tilt command: TILT_FORWARD after the REVERSE command issued");
return;
}
else if (relay_chn->run_info.last_run_cmd == RELAY_CHN_CMD_FORWARD && cmd == RELAY_CHN_TILT_CMD_REVERSE) {
ESP_LOGD(TAG, "relay_chn_issue_tilt_cmd: Invalid tilt command: TILT_REVERSE after the FORWARD command issued");
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 tilt control parameters
relay_chn->tilt_control.cmd = cmd;
relay_chn->tilt_control.step = RELAY_CHN_TILT_STEP_NONE;
// Set channel tilting active flag
relay_chn_tilting_channels |= (1 << chn_id);
if (cmd == RELAY_CHN_TILT_CMD_FORWARD) {
relay_chn_issue_cmd(relay_chn, RELAY_CHN_CMD_REVERSE);
// Emit the tilt state change for the channel
relay_chn_update_state(relay_chn, RELAY_CHN_STATE_TILT_FORWARD);
}
else if (cmd == RELAY_CHN_TILT_CMD_REVERSE) {
relay_chn_issue_cmd(relay_chn, RELAY_CHN_CMD_FORWARD);
// Emit the tilt state change for the channel
relay_chn_update_state(relay_chn, RELAY_CHN_STATE_TILT_REVERSE);
}
}
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_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_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_issue_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) {
// 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;
// Unset channel tilting active flag
relay_chn_tilting_channels &= ~(1 << chn_id);
// Stop the channel
relay_chn_issue_cmd(relay_chn, RELAY_CHN_CMD_STOP);
}
}
void relay_chn_tilt_stop(uint8_t chn_id)
{
if (!relay_chn_is_channel_id_valid(chn_id)) {
return;
}
// Check whether there is an active tilting channel
if (!relay_chn_tilting_channels) {
// No active tilting channels, so nothing to do
return;
}
if (chn_id == RELAY_CHN_ID_ALL) {
// Any channel executing tilt?
for (int i = 0; i < RELAY_CHN_COUNT; i++) {
relay_chn_issue_tilt_stop(i);
}
}
else {
relay_chn_issue_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->run_time_ms = run_time_ms;
tilt_timing->pause_time_ms = 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;
}
relay_chn_t* relay_chn = &relay_channels[chn_id];
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);
return;
}
else if (sensitivity == 0) {
relay_chn_set_tilt_timing_values(&relay_chn->tilt_control.tilt_timing,
0,
RELAY_CHN_TILT_RUN_MAX_MS,
RELAY_CHN_TILT_PAUSE_MAX_MS);
return;
}
// 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);
}
uint8_t relay_chn_tilt_sensitivity_get(uint8_t chn_id)
{
if (!relay_chn_is_channel_id_valid(chn_id)) {
return 0;
}
relay_chn_t* relay_chn = &relay_channels[chn_id];
return relay_chn->tilt_control.tilt_timing.sensitivity;
}
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.run_time_ms = RELAY_CHN_TILT_DEFAULT_RUN_MS;
tilt_control->tilt_timing.pause_time_ms = RELAY_CHN_TILT_DEFAULT_PAUSE_MS;
// 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);
}
#endif // RELAY_CHN_ENABLE_TILTING
/// @} /// @}