Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fb425edc4b | |||
| 805df016fe | |||
| f230477cad | |||
| e19bd09389 | |||
| 496755ed56 | |||
| 5fe76bb738 | |||
| 46dd0db939 | |||
| 46f7c28829 | |||
| 0e68c1f627 | |||
|
|
9ff243c673 | ||
| 330c996b7b | |||
|
|
4b8b6fd636 | ||
|
|
8d96914a38 | ||
| b7a23f3633 | |||
| df935a593b | |||
|
|
f72fe6b1f0 | ||
|
|
e54e28020c | ||
|
|
f5481f79e7 | ||
|
|
7d3f08b56b | ||
| a694938224 |
180
.gitignore
vendored
180
.gitignore
vendored
@@ -1,107 +1,111 @@
|
||||
# ---> C
|
||||
# Prerequisites
|
||||
*.d
|
||||
|
||||
# Object files
|
||||
.config
|
||||
*.o
|
||||
*.ko
|
||||
*.obj
|
||||
*.elf
|
||||
*.pyc
|
||||
|
||||
# Linker output
|
||||
*.ilk
|
||||
*.map
|
||||
*.exp
|
||||
# gtags
|
||||
GTAGS
|
||||
GRTAGS
|
||||
GPATH
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
# emacs
|
||||
.dir-locals.el
|
||||
|
||||
# Libraries
|
||||
*.lib
|
||||
*.a
|
||||
*.la
|
||||
*.lo
|
||||
# emacs temp file suffixes
|
||||
*~
|
||||
.#*
|
||||
\#*#
|
||||
|
||||
# Shared objects (inc. Windows DLLs)
|
||||
*.dll
|
||||
*.so
|
||||
*.so.*
|
||||
*.dylib
|
||||
# eclipse setting
|
||||
.settings
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
*.i*86
|
||||
*.x86_64
|
||||
*.hex
|
||||
# MacOS directory files
|
||||
.DS_Store
|
||||
|
||||
# Debug files
|
||||
*.dSYM/
|
||||
*.su
|
||||
*.idb
|
||||
*.pdb
|
||||
# cache dir
|
||||
.cache/
|
||||
|
||||
# Kernel Module Compile Results
|
||||
*.mod*
|
||||
*.cmd
|
||||
.tmp_versions/
|
||||
modules.order
|
||||
Module.symvers
|
||||
Mkfile.old
|
||||
dkms.conf
|
||||
# Doc build artifacts
|
||||
docs/_build/
|
||||
docs/doxygen_sqlite3.db
|
||||
|
||||
# ---> C++
|
||||
# Prerequisites
|
||||
*.d
|
||||
# Downloaded font files
|
||||
docs/_static/DejaVuSans.ttf
|
||||
docs/_static/NotoSansSC-Regular.otf
|
||||
|
||||
# Compiled Object files
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
*.obj
|
||||
# Components Unit Test Apps files
|
||||
components/**/build/
|
||||
components/**/build_*_*/
|
||||
components/**/sdkconfig
|
||||
components/**/sdkconfig.old
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
# Example project files
|
||||
examples/**/build/
|
||||
examples/**/build_*_*/
|
||||
examples/**/sdkconfig
|
||||
examples/**/sdkconfig.old
|
||||
|
||||
# Compiled Dynamic libraries
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
# Unit test app files
|
||||
tools/unit-test-app/build
|
||||
tools/unit-test-app/build_*_*/
|
||||
tools/unit-test-app/sdkconfig
|
||||
tools/unit-test-app/sdkconfig.old
|
||||
|
||||
# Fortran module files
|
||||
*.mod
|
||||
*.smod
|
||||
# test application build files
|
||||
tools/test_apps/**/build/
|
||||
tools/test_apps/**/build_*_*/
|
||||
tools/test_apps/**/sdkconfig
|
||||
tools/test_apps/**/sdkconfig.old
|
||||
|
||||
# Compiled Static libraries
|
||||
*.lai
|
||||
*.la
|
||||
*.a
|
||||
*.lib
|
||||
TEST_LOGS/
|
||||
build_summary_*.xml
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
# gcov coverage reports
|
||||
*.gcda
|
||||
*.gcno
|
||||
coverage.info
|
||||
coverage_report/
|
||||
|
||||
# ---> CMake
|
||||
CMakeLists.txt.user
|
||||
CMakeCache.txt
|
||||
CMakeFiles
|
||||
CMakeScripts
|
||||
Testing
|
||||
Makefile
|
||||
cmake_install.cmake
|
||||
install_manifest.txt
|
||||
compile_commands.json
|
||||
CTestTestfile.cmake
|
||||
_deps
|
||||
CMakeUserPresets.json
|
||||
test_multi_heap_host
|
||||
|
||||
# Build directory
|
||||
# VS Code Settings
|
||||
.vscode/
|
||||
|
||||
# VIM files
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Sublime Text files
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
|
||||
# Clion IDE CMake build & config
|
||||
.idea/
|
||||
cmake-build-*/
|
||||
|
||||
# Results for the checking of the Python coding style and static analysis
|
||||
.mypy_cache
|
||||
flake8_output.txt
|
||||
|
||||
# ESP-IDF default build directory name
|
||||
build
|
||||
|
||||
# unity-app directory
|
||||
unity-app
|
||||
# lock files for examples and components
|
||||
dependencies.lock
|
||||
|
||||
# managed_components for examples
|
||||
managed_components
|
||||
|
||||
# pytest log
|
||||
pytest-embedded/
|
||||
# legacy one
|
||||
pytest_embedded_log/
|
||||
list_job*.txt
|
||||
size_info*.txt
|
||||
XUNIT_RESULT*.xml
|
||||
.manifest_sha
|
||||
|
||||
# clang config (for LSP)
|
||||
.clangd
|
||||
|
||||
# Vale
|
||||
.vale/styles/*
|
||||
9
Kconfig
9
Kconfig
@@ -17,4 +17,13 @@ menu "Relay Channel Driver Configuration"
|
||||
help
|
||||
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
|
||||
30
README.md
30
README.md
@@ -11,17 +11,23 @@ An ESP-IDF component for controlling relay channels, specifically designed for d
|
||||
- Forward/Reverse direction control
|
||||
- Direction flipping capability
|
||||
- State monitoring and reporting
|
||||
- Optional sensitivty adjustable tilting feature
|
||||
|
||||
## 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.
|
||||
|
||||
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
|
||||
|
||||
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_COUNT`: Number of relay channels (1-8, default: 1)
|
||||
- `CONFIG_RELAY_CHN_ENABLE_TILTING`: Enable tilting interface on all channels. (default: n)
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -77,6 +83,30 @@ char *state_str = relay_chn_get_state_str(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
|
||||
|
||||
[MIT License](LICENSE) - Copyright (c) 2025 kozmotronik.
|
||||
|
||||
@@ -54,6 +54,10 @@ enum relay_chn_state_enum {
|
||||
RELAY_CHN_STATE_REVERSE, ///< The relay channel is running in the reverse direction.
|
||||
RELAY_CHN_STATE_FORWARD_PENDING, ///< The relay channel is pending to run in the forward direction.
|
||||
RELAY_CHN_STATE_REVERSE_PENDING, ///< The relay channel is pending to run in the reverse direction.
|
||||
#if CONFIG_RELAY_CHN_ENABLE_TILTING == 1
|
||||
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
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -198,6 +202,77 @@ void relay_chn_flip_direction(uint8_t chn_id);
|
||||
*/
|
||||
relay_chn_direction_t relay_chn_get_direction(uint8_t chn_id);
|
||||
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_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.
|
||||
* @param sensitivity The pointer to the memory in to which the sensitivity values will be copied.
|
||||
* @param length The length of the sensitvity memory.
|
||||
* @return
|
||||
* - ESP_OK: Success
|
||||
* - ESP_ERR_INVALID_ARG: Invalid argument
|
||||
*/
|
||||
esp_err_t relay_chn_tilt_sensitivity_get(uint8_t chn_id, uint8_t *sensitivity, size_t length);
|
||||
|
||||
#endif // CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
759
src/relay_chn.c
759
src/relay_chn.c
@@ -15,6 +15,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "esp_err.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_task.h"
|
||||
#include "driver/gpio.h"
|
||||
@@ -27,6 +28,7 @@
|
||||
|
||||
#define RELAY_CHN_OPPOSITE_INERTIA_MS CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS
|
||||
#define RELAY_CHN_COUNT CONFIG_RELAY_CHN_COUNT
|
||||
#define RELAY_CHN_ENABLE_TILTING CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
|
||||
static const char *TAG = "relay_chn";
|
||||
|
||||
@@ -73,6 +75,8 @@ typedef struct relay_chn_type relay_chn_t; // Forward declaration
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@@ -85,6 +89,95 @@ typedef struct relay_chn_type {
|
||||
esp_timer_handle_t inertia_timer; ///< Timer to handle the opposite direction inertia time.
|
||||
} relay_chn_t;
|
||||
|
||||
#else
|
||||
|
||||
/**
|
||||
* @name Tilt Pattern Timing Definitions
|
||||
* @{
|
||||
* The min and max timing definitions as well as the default timing definitions.
|
||||
* These definitions are used to define and adjust the tilt sensitivity.
|
||||
*/
|
||||
|
||||
#define RELAY_CHN_TILT_RUN_MIN_MS 50
|
||||
#define RELAY_CHN_TILT_RUN_MAX_MS 10
|
||||
#define RELAY_CHN_TILT_PAUSE_MIN_MS 450
|
||||
#define RELAY_CHN_TILT_PAUSE_MAX_MS 90
|
||||
|
||||
#define RELAY_CHN_TILT_DEFAULT_RUN_MS 15
|
||||
#define RELAY_CHN_TILT_DEFAULT_PAUSE_MS 150
|
||||
|
||||
#define RELAY_CHN_TILT_DEFAULT_SENSITIVITY \
|
||||
( (RELAY_CHN_TILT_DEFAULT_RUN_MS - RELAY_CHN_TILT_RUN_MIN_MS) \
|
||||
* 100 / (RELAY_CHN_TILT_RUN_MAX_MS - RELAY_CHN_TILT_RUN_MIN_MS) )
|
||||
/// @}
|
||||
|
||||
|
||||
ESP_EVENT_DEFINE_BASE(RELAY_CHN_TILT_CMD_EVENT_BASE);
|
||||
|
||||
/// @brief Tilt commands.
|
||||
enum relay_chn_tilt_cmd_enum {
|
||||
RELAY_CHN_TILT_CMD_NONE, ///< No command.
|
||||
RELAY_CHN_TILT_CMD_STOP, ///< Tilt command stop.
|
||||
RELAY_CHN_TILT_CMD_FORWARD, ///< Tilt command for forward.
|
||||
RELAY_CHN_TILT_CMD_REVERSE ///< Tilt command for reverse.
|
||||
};
|
||||
|
||||
/// @brief Alias for the enum type relay_chn_tilt_cmd_enum.
|
||||
typedef enum relay_chn_tilt_cmd_enum relay_chn_tilt_cmd_t;
|
||||
|
||||
/// @brief Tilt steps.
|
||||
enum relay_chn_tilt_step_enum {
|
||||
RELAY_CHN_TILT_STEP_NONE, ///< No step.
|
||||
RELAY_CHN_TILT_STEP_PENDING, ///< Pending step.
|
||||
RELAY_CHN_TILT_STEP_MOVE, ///< Move step. Tilt is driving either for forward or reverse.
|
||||
RELAY_CHN_TILT_STEP_PAUSE ///< Pause step. Tilt is paused.
|
||||
};
|
||||
|
||||
/// @brief Alias for the enum relay_chn_tilt_step_enum.
|
||||
typedef enum relay_chn_tilt_step_enum relay_chn_tilt_step_t;
|
||||
|
||||
/// @brief Tilt timing structure to manage tilt pattern timing.
|
||||
typedef struct relay_chn_tilt_timing_struct {
|
||||
uint8_t sensitivity; ///< Tilt sensitivity in percentage (%).
|
||||
uint32_t move_time_ms; ///< Move time in milliseconds.
|
||||
uint32_t pause_time_ms; ///< Pause time in milliseconds.
|
||||
} relay_chn_tilt_timing_t;
|
||||
|
||||
/// @brief Tilt counter structure to manage tilt count.
|
||||
typedef struct relay_chn_tilt_counter_struct {
|
||||
uint32_t tilt_forward_count; ///< Tilt forward count.
|
||||
uint32_t tilt_reverse_count; ///< Tilt reverse count.
|
||||
} relay_chn_tilt_counter_t;
|
||||
|
||||
/// @brief Tilt control structure to manage tilt operations.
|
||||
typedef struct relay_chn_tilt_control_struct {
|
||||
relay_chn_tilt_cmd_t cmd; ///< The tilt command in process.
|
||||
relay_chn_tilt_step_t step; ///< Current tilt step.
|
||||
relay_chn_tilt_timing_t tilt_timing; ///< Tilt timing structure.
|
||||
relay_chn_tilt_counter_t tilt_counter; ///< Tilt counter structure.
|
||||
esp_timer_handle_t tilt_timer; ///< Tilt timer handle.
|
||||
} relay_chn_tilt_control_t;
|
||||
|
||||
/**
|
||||
* @brief Structure to hold the state and configuration of a relay channel.
|
||||
*/
|
||||
typedef struct relay_chn_type {
|
||||
uint8_t id; ///< The ID of the relay channel.
|
||||
relay_chn_state_t state; ///< The current state of the relay channel.
|
||||
relay_chn_run_info_t run_info; ///< Runtime information of the relay channel.
|
||||
relay_chn_output_t output; ///< Output configuration of the relay channel.
|
||||
relay_chn_cmd_t pending_cmd; ///< The command that is pending to be issued
|
||||
esp_timer_handle_t inertia_timer; ///< Timer to handle the opposite direction inertia time.
|
||||
relay_chn_tilt_control_t tilt_control; ///< Tilt control block.
|
||||
} relay_chn_t;
|
||||
|
||||
static esp_err_t relay_chn_init_tilt_control(relay_chn_t *relay_chn);
|
||||
static esp_err_t relay_chn_tilt_init(void);
|
||||
static void relay_chn_tilt_count_reset(relay_chn_t *relay_chn);
|
||||
|
||||
#endif // RELAY_CHN_ENABLE_TILTING
|
||||
|
||||
|
||||
/**
|
||||
* @brief Structure to manage the state change listeners.
|
||||
*/
|
||||
@@ -180,7 +273,7 @@ static bool relay_chn_is_gpio_valid(gpio_num_t gpio)
|
||||
static esp_err_t relay_chn_create_event_loop()
|
||||
{
|
||||
esp_event_loop_args_t loop_args = {
|
||||
.queue_size = 10,
|
||||
.queue_size = RELAY_CHN_COUNT * 8,
|
||||
.task_name = "relay_chn_event_loop",
|
||||
.task_priority = ESP_TASKD_EVENT_PRIO - 1,
|
||||
.task_stack_size = 2048,
|
||||
@@ -212,8 +305,9 @@ esp_err_t relay_chn_create(const gpio_num_t* gpio_map, uint8_t gpio_count)
|
||||
|
||||
esp_err_t ret;
|
||||
for (int i = 0; i < RELAY_CHN_COUNT; i++) {
|
||||
gpio_num_t forward_pin = gpio_map[i];
|
||||
gpio_num_t reverse_pin = gpio_map[i+1];
|
||||
int gpio_index = i << 1; // gpio_index = i * 2
|
||||
gpio_num_t forward_pin = gpio_map[gpio_index];
|
||||
gpio_num_t reverse_pin = gpio_map[gpio_index + 1];
|
||||
// Check if the GPIOs are valid
|
||||
if (!relay_chn_is_gpio_valid(forward_pin)) {
|
||||
ESP_LOGE(TAG, "Invalid GPIO pin number: %d", forward_pin);
|
||||
@@ -227,14 +321,14 @@ esp_err_t relay_chn_create(const gpio_num_t* gpio_map, uint8_t gpio_count)
|
||||
|
||||
// Initialize the GPIOs
|
||||
ret = gpio_reset_pin(forward_pin);
|
||||
ret |= gpio_set_direction(forward_pin, GPIO_MODE_OUTPUT);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to reset GPIO forward pin for channel %d", i);
|
||||
ret = gpio_set_direction(forward_pin, GPIO_MODE_OUTPUT);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set GPIO direction for forward pin for channel %d", i);
|
||||
|
||||
ret |= gpio_reset_pin(reverse_pin);
|
||||
ret |= gpio_set_direction(reverse_pin, GPIO_MODE_OUTPUT);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to initialize GPIOs relay channel %d!", i);
|
||||
return ret;
|
||||
}
|
||||
ret = gpio_reset_pin(reverse_pin);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to reset GPIO reverse pin for channel %d", i);
|
||||
ret = gpio_set_direction(reverse_pin, GPIO_MODE_OUTPUT);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set GPIO direction for reverse pin for channel %d", i);
|
||||
// Initialize the GPIOs
|
||||
|
||||
// Initialize the relay channel
|
||||
@@ -243,18 +337,26 @@ 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.reverse_pin = reverse_pin;
|
||||
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->run_info.last_run_cmd = RELAY_CHN_CMD_NONE;
|
||||
ret |= relay_chn_init_timer(relay_chn);// Create direction change inertia timer
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to initialize relay channel %d!", i);
|
||||
return ret;
|
||||
}
|
||||
ret = relay_chn_init_timer(relay_chn); // Create direction change inertia timer
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create relay channel timer for channel %d", i);
|
||||
#if RELAY_CHN_ENABLE_TILTING == 1
|
||||
ret = relay_chn_init_tilt_control(relay_chn);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize tilt control for channel %d", i);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Create relay channel command event loop
|
||||
ret |= relay_chn_create_event_loop();
|
||||
ret = relay_chn_create_event_loop();
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create relay channel event loop");
|
||||
|
||||
#if RELAY_CHN_ENABLE_TILTING == 1
|
||||
// Must call after the event loop is initialized
|
||||
ret = relay_chn_tilt_init(); // Initialize tilt feature
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize tilt feature");
|
||||
#endif
|
||||
|
||||
// Init the state listener manager
|
||||
relay_chn_state_listener_manager.listeners = malloc(sizeof(relay_chn_state_listener_t*));
|
||||
@@ -345,7 +447,7 @@ void relay_chn_unregister_listener(relay_chn_state_listener_t listener)
|
||||
*/
|
||||
static bool relay_chn_is_channel_id_valid(uint8_t chn_id)
|
||||
{
|
||||
bool valid = (chn_id >= 0 && chn_id < RELAY_CHN_COUNT) || chn_id == RELAY_CHN_ID_ALL;
|
||||
bool valid = (chn_id < RELAY_CHN_COUNT) || chn_id == RELAY_CHN_ID_ALL;
|
||||
if (!valid) {
|
||||
ESP_LOGE(TAG, "Invalid channel ID: %d", chn_id);
|
||||
}
|
||||
@@ -363,27 +465,34 @@ static void relay_chn_dispatch_cmd(relay_chn_t *relay_chn, relay_chn_cmd_t cmd)
|
||||
cmd,
|
||||
&relay_chn->id,
|
||||
sizeof(relay_chn->id), portMAX_DELAY);
|
||||
}
|
||||
|
||||
static esp_err_t relay_chn_invalidate_inertia_timer(relay_chn_t *relay_chn)
|
||||
{
|
||||
if (esp_timer_is_active(relay_chn->inertia_timer)) {
|
||||
return esp_timer_stop(relay_chn->inertia_timer);
|
||||
#if RELAY_CHN_ENABLE_TILTING == 1
|
||||
// Reset the tilt counter when the command is either FORWARD or REVERSE
|
||||
if (cmd == RELAY_CHN_CMD_FORWARD || cmd == RELAY_CHN_CMD_REVERSE) {
|
||||
relay_chn_tilt_count_reset(relay_chn);
|
||||
}
|
||||
return ESP_OK;
|
||||
#endif
|
||||
}
|
||||
|
||||
static esp_err_t relay_chn_start_inertia_timer(relay_chn_t *relay_chn, uint32_t time_ms)
|
||||
static esp_err_t relay_chn_start_esp_timer_once(esp_timer_handle_t esp_timer, uint32_t time_ms)
|
||||
{
|
||||
// Invalidate the channel's timer if it is active
|
||||
relay_chn_invalidate_inertia_timer(relay_chn);
|
||||
return esp_timer_start_once(relay_chn->inertia_timer, time_ms * 1000);
|
||||
esp_err_t ret = esp_timer_start_once(esp_timer, time_ms * 1000);
|
||||
if (ret == ESP_ERR_INVALID_STATE) {
|
||||
// This timer is already running, stop the timer first
|
||||
ret = esp_timer_stop(esp_timer);
|
||||
if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) {
|
||||
return ret;
|
||||
}
|
||||
ret = esp_timer_start_once(esp_timer, time_ms * 1000);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void relay_chn_update_state(relay_chn_t *relay_chn, relay_chn_state_t new_state)
|
||||
{
|
||||
relay_chn_state_t old = relay_chn->state;
|
||||
relay_chn->state = new_state;
|
||||
|
||||
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) {
|
||||
@@ -445,7 +554,13 @@ static void relay_chn_issue_cmd(relay_chn_t* relay_chn, relay_chn_cmd_t cmd)
|
||||
|
||||
case RELAY_CHN_STATE_STOPPED:
|
||||
if (relay_chn->run_info.last_run_cmd == cmd || relay_chn->run_info.last_run_cmd == RELAY_CHN_CMD_NONE) {
|
||||
// If this is the first run or 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);
|
||||
}
|
||||
else {
|
||||
@@ -459,7 +574,7 @@ static void relay_chn_issue_cmd(relay_chn_t* relay_chn, relay_chn_cmd_t cmd)
|
||||
? RELAY_CHN_STATE_FORWARD_PENDING : RELAY_CHN_STATE_REVERSE_PENDING;
|
||||
relay_chn_update_state(relay_chn, new_state);
|
||||
// If the time passed is less than the opposite inertia time, wait for the remaining time
|
||||
relay_chn_start_inertia_timer(relay_chn, inertia_time_ms);
|
||||
relay_chn_start_esp_timer_once(relay_chn->inertia_timer, inertia_time_ms);
|
||||
}
|
||||
else {
|
||||
// If the time passed is more than the opposite inertia time, run the command immediately
|
||||
@@ -476,18 +591,21 @@ static void relay_chn_issue_cmd(relay_chn_t* relay_chn, relay_chn_cmd_t cmd)
|
||||
relay_chn_dispatch_cmd(relay_chn, cmd);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (relay_chn->run_info.last_run_cmd == cmd) {
|
||||
// If the last run command is the same as the current command, do nothing
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop the channel first before the schedule
|
||||
relay_chn_dispatch_cmd(relay_chn, RELAY_CHN_CMD_STOP);
|
||||
|
||||
// If the last run command is different from the current command, wait for the opposite inertia time
|
||||
relay_chn->pending_cmd = cmd;
|
||||
relay_chn_state_t new_state = cmd == RELAY_CHN_CMD_FORWARD
|
||||
? RELAY_CHN_STATE_FORWARD_PENDING : RELAY_CHN_STATE_REVERSE_PENDING;
|
||||
relay_chn_update_state(relay_chn, new_state);
|
||||
relay_chn_start_inertia_timer(relay_chn, RELAY_CHN_OPPOSITE_INERTIA_MS);
|
||||
relay_chn_start_esp_timer_once(relay_chn->inertia_timer, RELAY_CHN_OPPOSITE_INERTIA_MS);
|
||||
break;
|
||||
|
||||
default: ESP_LOGD(TAG, "relay_chn_evaluate: Unknown relay channel state!");
|
||||
@@ -579,44 +697,73 @@ relay_chn_direction_t relay_chn_get_direction(uint8_t chn_id)
|
||||
}
|
||||
/* relay_chn APIs */
|
||||
|
||||
static esp_err_t relay_chn_output_stop(relay_chn_t *relay_chn)
|
||||
{
|
||||
esp_err_t ret;
|
||||
ret = gpio_set_level(relay_chn->output.forward_pin, 0);
|
||||
ret |= gpio_set_level(relay_chn->output.reverse_pin, 0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t relay_chn_output_forward(relay_chn_t *relay_chn)
|
||||
{
|
||||
esp_err_t ret;
|
||||
ret = gpio_set_level(relay_chn->output.forward_pin, 1);
|
||||
ret |= gpio_set_level(relay_chn->output.reverse_pin, 0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t relay_chn_output_reverse(relay_chn_t *relay_chn)
|
||||
{
|
||||
esp_err_t ret;
|
||||
ret = gpio_set_level(relay_chn->output.forward_pin, 0);
|
||||
ret |= gpio_set_level(relay_chn->output.reverse_pin, 1);
|
||||
return ret;
|
||||
}
|
||||
|
||||
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.reverse_pin, 0);
|
||||
if (relay_chn_output_stop(relay_chn) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "relay_chn_execute_stop: Failed to output stop for relay channel #%d!", relay_chn->id);
|
||||
}
|
||||
relay_chn_state_t previous_state = relay_chn->state;
|
||||
relay_chn_update_state(relay_chn, RELAY_CHN_STATE_STOPPED);
|
||||
|
||||
|
||||
// If there is any pending command, cancel it since the STOP command is issued right after it
|
||||
relay_chn->pending_cmd = RELAY_CHN_CMD_NONE;
|
||||
// Invalidate the channel's timer if it is active
|
||||
relay_chn_invalidate_inertia_timer(relay_chn);
|
||||
esp_timer_stop(relay_chn->inertia_timer);
|
||||
|
||||
// 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 (last_run_cmd == RELAY_CHN_CMD_FORWARD || last_run_cmd == RELAY_CHN_CMD_REVERSE) {
|
||||
// Save the last run time only if the previous state was either STATE FORWARD
|
||||
// or STATE_REVERSE. Then schedule a free command.
|
||||
if (previous_state == RELAY_CHN_STATE_FORWARD || previous_state == RELAY_CHN_STATE_REVERSE) {
|
||||
// Record the command's last run time
|
||||
relay_chn->run_info.last_run_cmd_time_ms = esp_timer_get_time() / 1000;
|
||||
// Schedule a free command for the channel
|
||||
relay_chn->pending_cmd = RELAY_CHN_CMD_FREE;
|
||||
relay_chn_start_inertia_timer(relay_chn, RELAY_CHN_OPPOSITE_INERTIA_MS);
|
||||
relay_chn_start_esp_timer_once(relay_chn->inertia_timer, RELAY_CHN_OPPOSITE_INERTIA_MS);
|
||||
} else {
|
||||
// If the channel was not running, issue a free command immediately
|
||||
// If the channel was not running one of the run or fwd, issue a free command immediately
|
||||
relay_chn_dispatch_cmd(relay_chn, RELAY_CHN_CMD_FREE);
|
||||
}
|
||||
}
|
||||
|
||||
static void relay_chn_execute_forward(relay_chn_t *relay_chn)
|
||||
{
|
||||
gpio_set_level(relay_chn->output.reverse_pin, 0);
|
||||
gpio_set_level(relay_chn->output.forward_pin, 1);
|
||||
if (relay_chn_output_forward(relay_chn) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "relay_chn_execute_forward: Failed to output forward for relay channel #%d!", relay_chn->id);
|
||||
return;
|
||||
}
|
||||
relay_chn->run_info.last_run_cmd = RELAY_CHN_CMD_FORWARD;
|
||||
relay_chn_update_state(relay_chn, RELAY_CHN_STATE_FORWARD);
|
||||
}
|
||||
|
||||
static void relay_chn_execute_reverse(relay_chn_t *relay_chn)
|
||||
{
|
||||
gpio_set_level(relay_chn->output.forward_pin, 0);
|
||||
gpio_set_level(relay_chn->output.reverse_pin, 1);
|
||||
if (relay_chn_output_reverse(relay_chn) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "relay_chn_execute_reverse: Failed to output reverse for relay channel #%d!", relay_chn->id);
|
||||
return;
|
||||
}
|
||||
relay_chn->run_info.last_run_cmd = RELAY_CHN_CMD_REVERSE;
|
||||
relay_chn_update_state(relay_chn, RELAY_CHN_STATE_REVERSE);
|
||||
}
|
||||
@@ -633,14 +780,14 @@ static void relay_chn_execute_flip(relay_chn_t *relay_chn)
|
||||
: RELAY_CHN_DIRECTION_DEFAULT;
|
||||
// Set an inertia on the channel to prevent any immediate movement
|
||||
relay_chn->pending_cmd = RELAY_CHN_CMD_FREE;
|
||||
relay_chn_start_inertia_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)
|
||||
{
|
||||
relay_chn->pending_cmd = RELAY_CHN_CMD_NONE;
|
||||
// Invalidate the channel's timer if it is active
|
||||
relay_chn_invalidate_inertia_timer(relay_chn);
|
||||
esp_timer_stop(relay_chn->inertia_timer);
|
||||
relay_chn_update_state(relay_chn, RELAY_CHN_STATE_FREE);
|
||||
}
|
||||
|
||||
@@ -706,9 +853,527 @@ char *relay_chn_state_str(relay_chn_state_t state)
|
||||
return "FORWARD_PENDING";
|
||||
case RELAY_CHN_STATE_REVERSE_PENDING:
|
||||
return "REVERSE_PENDING";
|
||||
#if RELAY_CHN_ENABLE_TILTING == 1
|
||||
case RELAY_CHN_STATE_TILT_FORWARD:
|
||||
return "TILT_FORWARD";
|
||||
case RELAY_CHN_STATE_TILT_REVERSE:
|
||||
return "TILT_REVERSE";
|
||||
#endif
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
#if RELAY_CHN_ENABLE_TILTING == 1
|
||||
|
||||
/**
|
||||
* @brief Dispatch a tilt command to the relay channel event loop.
|
||||
*
|
||||
* @param relay_chn The relay channel to send the command to.
|
||||
* @param cmd The tilt command.
|
||||
* @return
|
||||
* - ESP_OK on success.
|
||||
* - ESP_ERR_INVALID_ARG if the command is none.
|
||||
* - Other error codes on failure.
|
||||
*/
|
||||
static esp_err_t relay_chn_dispatch_tilt_cmd(relay_chn_t *relay_chn, relay_chn_tilt_cmd_t cmd)
|
||||
{
|
||||
if (cmd == RELAY_CHN_TILT_CMD_NONE) return ESP_ERR_INVALID_ARG;
|
||||
return esp_event_post_to(relay_chn_event_loop,
|
||||
RELAY_CHN_TILT_CMD_EVENT_BASE,
|
||||
cmd,
|
||||
&relay_chn->id,
|
||||
sizeof(relay_chn->id), portMAX_DELAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the required timing before tilting depending on the last run.
|
||||
*
|
||||
* @param relay_chn the relay channel.
|
||||
* @param cmd The tilt command.
|
||||
* @return The time that is required in ms.
|
||||
*/
|
||||
static uint32_t relay_chn_get_required_timing_before_tilting(relay_chn_t *relay_chn, relay_chn_tilt_cmd_t cmd)
|
||||
{
|
||||
if (cmd == RELAY_CHN_TILT_CMD_FORWARD && relay_chn->run_info.last_run_cmd == RELAY_CHN_CMD_REVERSE)
|
||||
return 0;
|
||||
else if (cmd == RELAY_CHN_TILT_CMD_REVERSE && relay_chn->run_info.last_run_cmd == RELAY_CHN_CMD_FORWARD)
|
||||
return 0;
|
||||
|
||||
uint32_t inertia_time_passed_ms = (uint32_t) (esp_timer_get_time() / 1000) - relay_chn->run_info.last_run_cmd_time_ms;
|
||||
return RELAY_CHN_OPPOSITE_INERTIA_MS - inertia_time_passed_ms;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Issue a tilt command to a specific relay channel.
|
||||
*
|
||||
* @param chn_id The channel ID.
|
||||
* @param cmd The tilt command.
|
||||
*/
|
||||
static void relay_chn_issue_tilt_cmd(uint8_t chn_id, relay_chn_tilt_cmd_t cmd)
|
||||
{
|
||||
relay_chn_t* relay_chn = &relay_channels[chn_id];
|
||||
|
||||
if (relay_chn->run_info.last_run_cmd == RELAY_CHN_CMD_NONE) {
|
||||
// Do not tilt if the channel hasn't been run before
|
||||
ESP_LOGD(TAG, "relay_chn_issue_tilt_cmd: Tilt will not be executed since the channel hasn't been run yet");
|
||||
return;
|
||||
}
|
||||
|
||||
if (relay_chn->tilt_control.cmd == cmd) {
|
||||
ESP_LOGD(TAG, "relay_chn_issue_tilt_cmd: There is already a tilt command in progress!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the command that will be processed
|
||||
relay_chn->tilt_control.cmd = cmd;
|
||||
switch (relay_chn->state) {
|
||||
case RELAY_CHN_STATE_FREE:
|
||||
// Relay channel is free, tilt can be issued immediately
|
||||
relay_chn_dispatch_tilt_cmd(relay_chn, cmd);
|
||||
break;
|
||||
|
||||
case RELAY_CHN_STATE_FORWARD_PENDING:
|
||||
case RELAY_CHN_STATE_REVERSE_PENDING:
|
||||
// Issue a stop command first so that the timer and pending cmd get cleared
|
||||
relay_chn_dispatch_cmd(relay_chn, RELAY_CHN_CMD_STOP);
|
||||
// break not put intentionally
|
||||
case RELAY_CHN_STATE_STOPPED: {
|
||||
// Check if channel needs timing before tilting
|
||||
uint32_t req_timing_ms = relay_chn_get_required_timing_before_tilting(relay_chn, cmd);
|
||||
if (req_timing_ms == 0) {
|
||||
relay_chn_dispatch_tilt_cmd(relay_chn, cmd);
|
||||
} else {
|
||||
// Channel needs timing before running tilting action, schedule it
|
||||
relay_chn->tilt_control.step = RELAY_CHN_TILT_STEP_PENDING;
|
||||
relay_chn_start_esp_timer_once(relay_chn->tilt_control.tilt_timer, req_timing_ms);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case RELAY_CHN_STATE_FORWARD:
|
||||
if (cmd == RELAY_CHN_TILT_CMD_FORWARD) {
|
||||
// Stop the running channel first
|
||||
relay_chn_dispatch_cmd(relay_chn, RELAY_CHN_CMD_STOP);
|
||||
// Schedule for tilting
|
||||
relay_chn->tilt_control.step = RELAY_CHN_TILT_STEP_PENDING;
|
||||
relay_chn_start_esp_timer_once(relay_chn->tilt_control.tilt_timer, RELAY_CHN_OPPOSITE_INERTIA_MS);
|
||||
} else if (cmd == RELAY_CHN_TILT_CMD_REVERSE) {
|
||||
// Stop the running channel first
|
||||
relay_chn_dispatch_cmd(relay_chn, RELAY_CHN_CMD_STOP);
|
||||
// If the tilt cmd is TILT_REVERSE then dispatch it immediately
|
||||
relay_chn_dispatch_tilt_cmd(relay_chn, cmd);
|
||||
}
|
||||
break;
|
||||
|
||||
case RELAY_CHN_STATE_REVERSE:
|
||||
if (cmd == RELAY_CHN_TILT_CMD_REVERSE) {
|
||||
// Stop the running channel first
|
||||
relay_chn_dispatch_cmd(relay_chn, RELAY_CHN_CMD_STOP);
|
||||
// Schedule for tilting
|
||||
relay_chn->tilt_control.step = RELAY_CHN_TILT_STEP_PENDING;
|
||||
relay_chn_start_esp_timer_once(relay_chn->tilt_control.tilt_timer, RELAY_CHN_OPPOSITE_INERTIA_MS);
|
||||
} else if (cmd == RELAY_CHN_TILT_CMD_FORWARD) {
|
||||
// Stop the running channel first
|
||||
relay_chn_dispatch_cmd(relay_chn, RELAY_CHN_CMD_STOP);
|
||||
// If the tilt cmd is TILT_FORWARD then dispatch it immediately
|
||||
relay_chn_dispatch_tilt_cmd(relay_chn, cmd);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
ESP_LOGD(TAG, "relay_chn_issue_tilt_cmd: Unexpected relay channel state: %s!", relay_chn_state_str(relay_chn->state));
|
||||
}
|
||||
}
|
||||
|
||||
static void relay_chn_issue_tilt_cmd_on_all_channels(relay_chn_tilt_cmd_t cmd)
|
||||
{
|
||||
for (int i = 0; i < RELAY_CHN_COUNT; i++) {
|
||||
relay_chn_issue_tilt_cmd(i, cmd);
|
||||
}
|
||||
}
|
||||
|
||||
static void relay_chn_issue_tilt_auto(uint8_t chn_id)
|
||||
{
|
||||
relay_chn_t* relay_chn = &relay_channels[chn_id];
|
||||
if (relay_chn->run_info.last_run_cmd == RELAY_CHN_CMD_FORWARD || relay_chn->state == RELAY_CHN_STATE_FORWARD) {
|
||||
relay_chn_issue_tilt_cmd(chn_id, RELAY_CHN_TILT_CMD_FORWARD);
|
||||
}
|
||||
else if (relay_chn->run_info.last_run_cmd == RELAY_CHN_CMD_REVERSE || relay_chn->state == RELAY_CHN_STATE_REVERSE) {
|
||||
relay_chn_issue_tilt_cmd(chn_id, RELAY_CHN_TILT_CMD_REVERSE);
|
||||
}
|
||||
}
|
||||
|
||||
void relay_chn_tilt_auto(uint8_t chn_id)
|
||||
{
|
||||
if (!relay_chn_is_channel_id_valid(chn_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Execute for all channels
|
||||
if (chn_id == RELAY_CHN_ID_ALL) {
|
||||
for (int i = 0; i < RELAY_CHN_COUNT; i++) {
|
||||
relay_chn_issue_tilt_auto(i);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Execute for a single channel
|
||||
else relay_chn_issue_tilt_auto(chn_id);
|
||||
}
|
||||
|
||||
void relay_chn_tilt_forward(uint8_t chn_id)
|
||||
{
|
||||
if (!relay_chn_is_channel_id_valid(chn_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (chn_id == RELAY_CHN_ID_ALL) relay_chn_issue_tilt_cmd_on_all_channels(RELAY_CHN_TILT_CMD_FORWARD);
|
||||
else relay_chn_issue_tilt_cmd(chn_id, RELAY_CHN_TILT_CMD_FORWARD);
|
||||
}
|
||||
|
||||
void relay_chn_tilt_reverse(uint8_t chn_id)
|
||||
{
|
||||
if (!relay_chn_is_channel_id_valid(chn_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (chn_id == RELAY_CHN_ID_ALL) relay_chn_issue_tilt_cmd_on_all_channels(RELAY_CHN_TILT_CMD_REVERSE);
|
||||
else relay_chn_issue_tilt_cmd(chn_id, RELAY_CHN_TILT_CMD_REVERSE);
|
||||
}
|
||||
|
||||
static void _relay_chn_tilt_stop(uint8_t chn_id)
|
||||
{
|
||||
relay_chn_t* relay_chn = &relay_channels[chn_id];
|
||||
if (relay_chn->tilt_control.cmd != RELAY_CHN_TILT_CMD_NONE) {
|
||||
esp_event_post_to(relay_chn_event_loop,
|
||||
RELAY_CHN_TILT_CMD_EVENT_BASE,
|
||||
RELAY_CHN_TILT_CMD_STOP,
|
||||
&relay_chn->id,
|
||||
sizeof(relay_chn->id), portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
void relay_chn_tilt_stop(uint8_t chn_id)
|
||||
{
|
||||
if (!relay_chn_is_channel_id_valid(chn_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (chn_id == RELAY_CHN_ID_ALL) {
|
||||
for (int i = 0; i < RELAY_CHN_COUNT; i++) {
|
||||
_relay_chn_tilt_stop(i);
|
||||
}
|
||||
}
|
||||
else {
|
||||
_relay_chn_tilt_stop(chn_id);
|
||||
}
|
||||
}
|
||||
|
||||
static void relay_chn_set_tilt_timing_values(relay_chn_tilt_timing_t *tilt_timing,
|
||||
uint8_t sensitivity,
|
||||
uint32_t run_time_ms,
|
||||
uint32_t pause_time_ms)
|
||||
{
|
||||
tilt_timing->sensitivity = sensitivity;
|
||||
tilt_timing->move_time_ms = run_time_ms;
|
||||
tilt_timing->pause_time_ms = pause_time_ms;
|
||||
}
|
||||
|
||||
static void _relay_chn_tilt_sensitivity_set(relay_chn_t *relay_chn, uint8_t sensitivity)
|
||||
{
|
||||
if (sensitivity >= 100) {
|
||||
relay_chn_set_tilt_timing_values(&relay_chn->tilt_control.tilt_timing,
|
||||
100,
|
||||
RELAY_CHN_TILT_RUN_MAX_MS,
|
||||
RELAY_CHN_TILT_PAUSE_MAX_MS);
|
||||
}
|
||||
else if (sensitivity == 0) {
|
||||
relay_chn_set_tilt_timing_values(&relay_chn->tilt_control.tilt_timing,
|
||||
0,
|
||||
RELAY_CHN_TILT_RUN_MIN_MS,
|
||||
RELAY_CHN_TILT_PAUSE_MIN_MS);
|
||||
}
|
||||
else {
|
||||
// Compute the new timing values from the sensitivity percent value by using linear interpolation
|
||||
uint32_t tilt_run_time_ms = 0, tilt_pause_time_ms = 0;
|
||||
tilt_run_time_ms = RELAY_CHN_TILT_RUN_MIN_MS + (sensitivity * (RELAY_CHN_TILT_RUN_MAX_MS - RELAY_CHN_TILT_RUN_MIN_MS) / 100);
|
||||
tilt_pause_time_ms = RELAY_CHN_TILT_PAUSE_MIN_MS + (sensitivity * (RELAY_CHN_TILT_PAUSE_MAX_MS - RELAY_CHN_TILT_PAUSE_MIN_MS) / 100);
|
||||
relay_chn_set_tilt_timing_values(&relay_chn->tilt_control.tilt_timing,
|
||||
sensitivity,
|
||||
tilt_run_time_ms,
|
||||
tilt_pause_time_ms);
|
||||
}
|
||||
}
|
||||
|
||||
void relay_chn_tilt_sensitivity_set(uint8_t chn_id, uint8_t sensitivity)
|
||||
{
|
||||
if (!relay_chn_is_channel_id_valid(chn_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (chn_id == RELAY_CHN_ID_ALL) {
|
||||
for (int i = 0; i < RELAY_CHN_COUNT; i++) {
|
||||
_relay_chn_tilt_sensitivity_set(&relay_channels[i], sensitivity);
|
||||
}
|
||||
}
|
||||
else {
|
||||
_relay_chn_tilt_sensitivity_set(&relay_channels[chn_id], sensitivity);
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_tilt_sensitivity_get(uint8_t chn_id, uint8_t *sensitivity, size_t length)
|
||||
{
|
||||
if (!relay_chn_is_channel_id_valid(chn_id)) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (sensitivity == NULL) {
|
||||
ESP_LOGD(TAG, "relay_chn_tilt_sensitivity_get: sensitivity is NULL");
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (chn_id == RELAY_CHN_ID_ALL) {
|
||||
if (length < RELAY_CHN_COUNT) {
|
||||
ESP_LOGD(TAG, "relay_chn_tilt_sensitivity_get: length is too short to store all sensitivity values");
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
for (int i = 0; i < RELAY_CHN_COUNT; i++) {
|
||||
sensitivity[i] = relay_channels[i].tilt_control.tilt_timing.sensitivity;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
*sensitivity = relay_channels[chn_id].tilt_control.tilt_timing.sensitivity;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void relay_chn_tilt_count_reset(relay_chn_t *relay_chn)
|
||||
{
|
||||
relay_chn->tilt_control.tilt_counter.tilt_forward_count = 0;
|
||||
relay_chn->tilt_control.tilt_counter.tilt_reverse_count = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update tilt count automatically and return the current value.
|
||||
*
|
||||
* This helper function updates the relevant tilt count depending on the
|
||||
* last run info and helps the tilt module in deciding whether the requested
|
||||
* tilt should execute or not.
|
||||
* This is useful to control reverse tilting particularly. For example:
|
||||
* - If the channel's last run was FORWARD and a TILT_FORWARD is requested,
|
||||
* then the tilt counter will count up on the
|
||||
* relay_chn_tilt_counter_struct::tilt_forward_count and the function will
|
||||
* return the actual count.
|
||||
* - If the channel's last run was FORWARD and a TILT_REVERSE is requested,
|
||||
* then the relay_chn_tilt_counter_struct::tilt_forward_count will be checked
|
||||
* against zero first, and then it will count down and return the actual count
|
||||
* if it is greater than 0, else the function will return 0.
|
||||
* - If the tilt command is irrelevant then the function will return 0.
|
||||
* - If the last run is irrelevant then the function will return 0.
|
||||
*
|
||||
* @param relay_chn The relay channel handle.
|
||||
* @return uint32_t The actual value of the relevant counter.
|
||||
* @return 0 if:
|
||||
* - related counter is already 0.
|
||||
* - tilt command is irrelevant.
|
||||
* - last run info is irrelevant.
|
||||
*/
|
||||
static uint32_t relay_chn_tilt_count_update(relay_chn_t *relay_chn)
|
||||
{
|
||||
if (relay_chn->run_info.last_run_cmd == RELAY_CHN_CMD_FORWARD) {
|
||||
if (relay_chn->tilt_control.cmd == RELAY_CHN_TILT_CMD_FORWARD) {
|
||||
return ++relay_chn->tilt_control.tilt_counter.tilt_forward_count;
|
||||
}
|
||||
else if (relay_chn->tilt_control.cmd == RELAY_CHN_TILT_CMD_REVERSE) {
|
||||
if (relay_chn->tilt_control.tilt_counter.tilt_forward_count > 0)
|
||||
return --relay_chn->tilt_control.tilt_counter.tilt_forward_count;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
relay_chn_tilt_count_reset(relay_chn);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else if (relay_chn->run_info.last_run_cmd == RELAY_CHN_CMD_REVERSE) {
|
||||
if (relay_chn->tilt_control.cmd == RELAY_CHN_TILT_CMD_REVERSE) {
|
||||
return ++relay_chn->tilt_control.tilt_counter.tilt_reverse_count;
|
||||
}
|
||||
else if (relay_chn->tilt_control.cmd == RELAY_CHN_TILT_CMD_FORWARD) {
|
||||
if (relay_chn->tilt_control.tilt_counter.tilt_reverse_count > 0)
|
||||
return --relay_chn->tilt_control.tilt_counter.tilt_reverse_count;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
relay_chn_tilt_count_reset(relay_chn);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void relay_chn_tilt_execute_tilt_stop(relay_chn_t *relay_chn)
|
||||
{
|
||||
// Stop the channel's timer if active
|
||||
esp_timer_stop(relay_chn->tilt_control.tilt_timer);
|
||||
// Invalidate tilt cmd and step
|
||||
relay_chn->tilt_control.cmd = RELAY_CHN_TILT_CMD_NONE;
|
||||
relay_chn->tilt_control.step = RELAY_CHN_TILT_STEP_NONE;
|
||||
// Stop the channel
|
||||
if (relay_chn_output_stop(relay_chn) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "relay_chn_tilt_execute_tilt_stop: Failed to output stop for relay channel #%d!", relay_chn->id);
|
||||
}
|
||||
relay_chn_dispatch_cmd(relay_chn, RELAY_CHN_CMD_STOP);
|
||||
}
|
||||
|
||||
static void relay_chn_tilt_execute_tilt_forward(relay_chn_t *relay_chn)
|
||||
{
|
||||
if (relay_chn_output_reverse(relay_chn) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "relay_chn_tilt_execute_tilt_forward: Failed to output reverse for relay channel #%d!", relay_chn->id);
|
||||
// Stop tilting because of the error
|
||||
relay_chn_dispatch_tilt_cmd(relay_chn, RELAY_CHN_TILT_CMD_STOP);
|
||||
return;
|
||||
}
|
||||
// Set the move time timer
|
||||
relay_chn_start_esp_timer_once(relay_chn->tilt_control.tilt_timer,
|
||||
relay_chn->tilt_control.tilt_timing.move_time_ms);
|
||||
// Set to pause step
|
||||
relay_chn->tilt_control.step = RELAY_CHN_TILT_STEP_PAUSE;
|
||||
}
|
||||
|
||||
static void relay_chn_tilt_execute_tilt_reverse(relay_chn_t *relay_chn)
|
||||
{
|
||||
if (relay_chn_output_forward(relay_chn) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "relay_chn_tilt_execute_tilt_reverse: Failed to output forward for relay channel #%d!", relay_chn->id);
|
||||
// Stop tilting because of the error
|
||||
relay_chn_dispatch_tilt_cmd(relay_chn, RELAY_CHN_TILT_CMD_STOP);
|
||||
return;
|
||||
}
|
||||
// Set the move time timer
|
||||
relay_chn_start_esp_timer_once(relay_chn->tilt_control.tilt_timer,
|
||||
relay_chn->tilt_control.tilt_timing.move_time_ms);
|
||||
// Set to pause step
|
||||
relay_chn->tilt_control.step = RELAY_CHN_TILT_STEP_PAUSE;
|
||||
}
|
||||
|
||||
static void relay_chn_tilt_execute_tilt_pause(relay_chn_t *relay_chn)
|
||||
{
|
||||
// Pause the channel
|
||||
if (relay_chn_output_stop(relay_chn) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "relay_chn_tilt_execute_tilt_stop: Failed to output stop for relay channel #%d!", relay_chn->id);
|
||||
// Stop tilting because of the error
|
||||
relay_chn_dispatch_tilt_cmd(relay_chn, RELAY_CHN_TILT_CMD_STOP);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the tilt counter before the next move and expect the return value to be greater than 0
|
||||
if (relay_chn_tilt_count_update(relay_chn) == 0) {
|
||||
ESP_LOGD(TAG, "relay_chn_tilt_execute_tilt_stop: Relay channel cannot tilt anymore");
|
||||
// Stop tilting since the tilting limit has been reached
|
||||
relay_chn_dispatch_tilt_cmd(relay_chn, RELAY_CHN_TILT_CMD_STOP);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the pause time timer
|
||||
relay_chn_start_esp_timer_once(relay_chn->tilt_control.tilt_timer,
|
||||
relay_chn->tilt_control.tilt_timing.pause_time_ms);
|
||||
// Set to move step
|
||||
relay_chn->tilt_control.step = RELAY_CHN_TILT_STEP_MOVE;
|
||||
}
|
||||
|
||||
static void relay_chn_tilt_event_handler(void *handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
|
||||
{
|
||||
uint8_t chn_id = *(uint8_t*) event_data;
|
||||
if (!relay_chn_is_channel_id_valid(chn_id)) {
|
||||
return;
|
||||
}
|
||||
relay_chn_t* relay_chn = &relay_channels[chn_id];
|
||||
ESP_LOGD(TAG, "relay_chn_event_handler: Channel %d, Command: %s", relay_chn->id, relay_chn_cmd_str(event_id));
|
||||
switch(event_id) {
|
||||
case RELAY_CHN_TILT_CMD_STOP:
|
||||
relay_chn_tilt_execute_tilt_stop(relay_chn);
|
||||
break;
|
||||
case RELAY_CHN_TILT_CMD_FORWARD:
|
||||
relay_chn_tilt_execute_tilt_forward(relay_chn);
|
||||
// Update channel state
|
||||
relay_chn_update_state(relay_chn, RELAY_CHN_STATE_TILT_FORWARD);
|
||||
break;
|
||||
case RELAY_CHN_TILT_CMD_REVERSE:
|
||||
relay_chn_tilt_execute_tilt_reverse(relay_chn);
|
||||
// Update channel state
|
||||
relay_chn_update_state(relay_chn, RELAY_CHN_STATE_TILT_REVERSE);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unexpected relay channel tilt command: %ld!", event_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Timer callback for the relay_chn_tilt_control_t::tilt_timer
|
||||
static void relay_chn_tilt_timer_cb(void *arg)
|
||||
{
|
||||
uint8_t chn_id = *(uint8_t*) arg;
|
||||
if (!relay_chn_is_channel_id_valid(chn_id)) {
|
||||
ESP_LOGE(TAG, "relay_chn_tilt_timer_cb: Invalid relay channel ID!");
|
||||
return;
|
||||
}
|
||||
relay_chn_t* relay_chn = &relay_channels[chn_id];
|
||||
|
||||
switch (relay_chn->tilt_control.step)
|
||||
{
|
||||
case RELAY_CHN_TILT_STEP_MOVE:
|
||||
if (relay_chn->tilt_control.cmd == RELAY_CHN_TILT_CMD_FORWARD) {
|
||||
relay_chn_tilt_execute_tilt_forward(relay_chn);
|
||||
}
|
||||
else if (relay_chn->tilt_control.cmd == RELAY_CHN_TILT_CMD_REVERSE) {
|
||||
relay_chn_tilt_execute_tilt_reverse(relay_chn);
|
||||
}
|
||||
break;
|
||||
|
||||
case RELAY_CHN_TILT_STEP_PAUSE:
|
||||
relay_chn_tilt_execute_tilt_pause(relay_chn);
|
||||
break;
|
||||
|
||||
case RELAY_CHN_TILT_STEP_PENDING:
|
||||
// Just dispatch the pending tilt command
|
||||
relay_chn_dispatch_tilt_cmd(relay_chn, relay_chn->tilt_control.cmd);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t relay_chn_init_tilt_control(relay_chn_t *relay_chn)
|
||||
{
|
||||
relay_chn_tilt_control_t *tilt_control = &relay_chn->tilt_control;
|
||||
tilt_control->cmd = RELAY_CHN_TILT_CMD_NONE;
|
||||
tilt_control->step = RELAY_CHN_TILT_STEP_NONE;
|
||||
tilt_control->tilt_timing.sensitivity = RELAY_CHN_TILT_DEFAULT_SENSITIVITY;
|
||||
tilt_control->tilt_timing.move_time_ms = RELAY_CHN_TILT_DEFAULT_RUN_MS;
|
||||
tilt_control->tilt_timing.pause_time_ms = RELAY_CHN_TILT_DEFAULT_PAUSE_MS;
|
||||
relay_chn_tilt_count_reset(relay_chn);
|
||||
|
||||
// Create tilt timer for the channel
|
||||
char timer_name[32];
|
||||
snprintf(timer_name, sizeof(timer_name), "relay_chn_%2d_tilt_timer", relay_chn->id);
|
||||
esp_timer_create_args_t timer_args = {
|
||||
.callback = relay_chn_tilt_timer_cb,
|
||||
.arg = &relay_chn->id,
|
||||
.name = timer_name
|
||||
};
|
||||
return esp_timer_create(&timer_args, &relay_chn->tilt_control.tilt_timer);
|
||||
}
|
||||
|
||||
// Should call once from relay_chn_init
|
||||
static esp_err_t relay_chn_tilt_init(void)
|
||||
{
|
||||
esp_err_t ret;
|
||||
ret = esp_event_handler_register_with(relay_chn_event_loop,
|
||||
RELAY_CHN_TILT_CMD_EVENT_BASE,
|
||||
ESP_EVENT_ANY_ID,
|
||||
relay_chn_tilt_event_handler, NULL);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif // RELAY_CHN_ENABLE_TILTING
|
||||
|
||||
/// @}
|
||||
Reference in New Issue
Block a user