Compare commits
80 Commits
feat/1029-
...
497f45e336
| Author | SHA1 | Date | |
|---|---|---|---|
|
497f45e336
|
|||
|
95ca976bc6
|
|||
|
300f9a1317
|
|||
|
5440440c4d
|
|||
|
8416187d86
|
|||
|
9f45a2310d
|
|||
|
a1ff54b6e9
|
|||
| c718a1380f | |||
|
6caa4f1bd5
|
|||
|
4edebf206e
|
|||
|
e30b445b91
|
|||
|
9ee974e677
|
|||
|
31e351a129
|
|||
|
3ce079c2e8
|
|||
|
a5b320c152
|
|||
|
087deb338e
|
|||
|
fbf8b5dfc8
|
|||
| a3f83eaaee | |||
|
61ca2197e1
|
|||
|
86cc29a33b
|
|||
|
bf5e3a4426
|
|||
|
7244b57061
|
|||
|
7bafc4845f
|
|||
|
ad377ebfc8
|
|||
|
a1a54e2ca0
|
|||
|
639533cbb6
|
|||
|
2c9ee40ff4
|
|||
|
0122ef0803
|
|||
|
5e8e5a4cab
|
|||
|
d2b38a5b4e
|
|||
|
db55c0b7e4
|
|||
|
dea9f1e986
|
|||
|
a2e8e3c120
|
|||
|
a6d38327b7
|
|||
|
0cd6b4e263
|
|||
|
ec1b25d489
|
|||
|
4eb1bb03a0
|
|||
|
7d597f3725
|
|||
|
71b632737e
|
|||
|
374647732c
|
|||
|
ae33204a87
|
|||
|
79a66c19d7
|
|||
|
9a6b8c9f80
|
|||
|
0b75df35d1
|
|||
|
da953846c9
|
|||
|
15d1673e77
|
|||
|
329812aecc
|
|||
|
54c8dc26fc
|
|||
|
396a02b5ae
|
|||
|
3831384169
|
|||
|
be4a2ebef6
|
|||
|
6ff16b5797
|
|||
|
5afefc4dc0
|
|||
|
9d3f8ddbff
|
|||
| 6a4872f194 | |||
|
cb38f71d8e
|
|||
|
7a0f9b1420
|
|||
|
e73c205e3d
|
|||
|
fb4f34e895
|
|||
|
29803c063e
|
|||
|
40633e03d8
|
|||
| 19d02836dd | |||
|
a3762cff5f
|
|||
| fe383d7003 | |||
|
b99622bd23
|
|||
|
c5fa8a63ae
|
|||
|
aeeda44a2c
|
|||
| dc2aa93d2d | |||
|
96bb139751
|
|||
|
61edf11b75
|
|||
|
b19f0c553b
|
|||
|
f04632dc77
|
|||
| b29768edad | |||
|
f1cda4531d
|
|||
|
f8d6e74f23
|
|||
|
9f1134763e
|
|||
|
61f8ed440e
|
|||
| 1776c81c8d | |||
| 2165e9d571 | |||
| c0c7fbf3df |
10
.gitignore
vendored
10
.gitignore
vendored
@@ -56,6 +56,11 @@ tools/test_apps/**/build_*_*/
|
||||
tools/test_apps/**/sdkconfig
|
||||
tools/test_apps/**/sdkconfig.old
|
||||
|
||||
# autogenerated config files
|
||||
sdkconfig
|
||||
test_apps/sdkconfig
|
||||
test_apps/sdkconfig.old
|
||||
|
||||
TEST_LOGS/
|
||||
build_summary_*.xml
|
||||
|
||||
@@ -68,7 +73,8 @@ coverage_report/
|
||||
test_multi_heap_host
|
||||
|
||||
# VS Code Settings
|
||||
.vscode/
|
||||
# .vscode/
|
||||
settings.json
|
||||
|
||||
# VIM files
|
||||
*.swp
|
||||
@@ -108,4 +114,4 @@ XUNIT_RESULT*.xml
|
||||
.clangd
|
||||
|
||||
# Vale
|
||||
.vale/styles/*
|
||||
.vale/styles/*
|
||||
|
||||
23
.vscode/c_cpp_properties.json
vendored
Normal file
23
.vscode/c_cpp_properties.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "ESP-IDF",
|
||||
"compilerPath": "${config:idf.toolsPath}/tools/riscv32-esp-elf/esp-14.2.0_20241119/riscv32-esp-elf/bin/riscv32-esp-elf-gcc",
|
||||
"compileCommands": "${workspaceFolder}/test_apps/build/compile_commands.json",
|
||||
"includePath": [
|
||||
"${config:idf.espIdfPath}/components/**",
|
||||
"${config:idf.espIdfPathWin}/components/**",
|
||||
"${workspaceFolder}/**"
|
||||
],
|
||||
"browse": {
|
||||
"path": [
|
||||
"${config:idf.espIdfPath}/components",
|
||||
"${config:idf.espIdfPathWin}/components",
|
||||
"${workspaceFolder}"
|
||||
],
|
||||
"limitSymbolsToIncludedHeaders": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
}
|
||||
15
.vscode/launch.json
vendored
Normal file
15
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "gdbtarget",
|
||||
"request": "attach",
|
||||
"name": "Eclipse CDT GDB Adapter"
|
||||
},
|
||||
{
|
||||
"type": "espidf",
|
||||
"name": "Launch",
|
||||
"request": "launch"
|
||||
}
|
||||
]
|
||||
}
|
||||
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"relay_chn.h": "c"
|
||||
},
|
||||
"idf.port": "/dev/ttyUSB0"
|
||||
}
|
||||
@@ -1,4 +1,27 @@
|
||||
idf_component_register(SRCS "src/relay_chn.c"
|
||||
INCLUDE_DIRS include
|
||||
REQUIRES driver
|
||||
PRIV_REQUIRES esp_timer esp_event)
|
||||
set(include_dirs "include")
|
||||
set(priv_include_dirs "private_include")
|
||||
|
||||
set(srcs "src/relay_chn_core.c"
|
||||
"src/relay_chn_output.c"
|
||||
"src/relay_chn_run_info.c"
|
||||
"src/relay_chn_notify.c")
|
||||
|
||||
if(CONFIG_RELAY_CHN_ENABLE_TILTING)
|
||||
list(APPEND srcs "src/relay_chn_tilt.c")
|
||||
endif()
|
||||
|
||||
if(CONFIG_RELAY_CHN_COUNT GREATER 1)
|
||||
list(APPEND srcs "src/relay_chn_ctl_multi.c")
|
||||
else()
|
||||
list(APPEND srcs "src/relay_chn_ctl_single.c")
|
||||
endif()
|
||||
|
||||
if(CONFIG_RELAY_CHN_ENABLE_NVS)
|
||||
list(APPEND srcs "src/relay_chn_nvs.c")
|
||||
endif()
|
||||
|
||||
|
||||
idf_component_register(SRCS ${srcs}
|
||||
INCLUDE_DIRS ${include_dirs}
|
||||
PRIV_INCLUDE_DIRS ${priv_include_dirs}
|
||||
REQUIRES driver esp_timer nvs_flash)
|
||||
|
||||
71
Kconfig
71
Kconfig
@@ -9,14 +9,20 @@ menu "Relay Channel Driver Configuration"
|
||||
starting the output. This is useful for the motors or some other
|
||||
mechanical actuators to allow them to stop and settle before
|
||||
changing the direction.
|
||||
|
||||
|
||||
config RELAY_CHN_COUNT
|
||||
int "Number of relay channels"
|
||||
range 1 8
|
||||
default 1
|
||||
help
|
||||
Number of relay channels between 1 and 8.
|
||||
|
||||
|
||||
config RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
bool "Enable run limit for channels"
|
||||
default n
|
||||
help
|
||||
Enable run limit for channels as an extra layer of output protection.
|
||||
|
||||
config RELAY_CHN_ENABLE_TILTING
|
||||
bool "Enable tilting on relay channels"
|
||||
default n
|
||||
@@ -26,4 +32,65 @@ menu "Relay Channel Driver Configuration"
|
||||
at a time. Tilting is specifically designed for controlling some
|
||||
types of curtains that need to be adjusted to let enter specific
|
||||
amount of day light.
|
||||
|
||||
config RELAY_CHN_ENABLE_NVS
|
||||
bool "Enable persistent NVS storage for relay channel"
|
||||
default n
|
||||
help
|
||||
If enabled, relay channel configuration will be stored in NVS.
|
||||
|
||||
endmenu
|
||||
|
||||
menu "Relay Channel NVS Storage Configuration"
|
||||
depends on RELAY_CHN_ENABLE_NVS
|
||||
|
||||
config RELAY_CHN_NVS_NAMESPACE
|
||||
string "NVS namespace for relay channel storage"
|
||||
default "relay_chn"
|
||||
help
|
||||
The NVS namespace used for storing relay channel configuration.
|
||||
This should be unique to avoid conflicts with other components.
|
||||
|
||||
config RELAY_CHN_NVS_CUSTOM_PARTITION
|
||||
bool "Use custom NVS partition for relay channel storage"
|
||||
default n
|
||||
help
|
||||
If enabled, a custom NVS partition will be used for storing
|
||||
relay channel configuration. If disabled, the default NVS
|
||||
partition will be used.
|
||||
|
||||
config RELAY_CHN_NVS_CUSTOM_PARTITION_NAME
|
||||
string "Custom NVS partition name"
|
||||
depends on RELAY_CHN_NVS_CUSTOM_PARTITION
|
||||
default "app_data"
|
||||
help
|
||||
The name of the custom NVS partition used for storing relay channel
|
||||
configuration. Make sure the name is exactly the same as label defined
|
||||
in the relevant partition table.
|
||||
endmenu
|
||||
|
||||
menu "Relay Channel Run Limit Configuration"
|
||||
depends on RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
|
||||
config RELAY_CHN_RUN_LIMIT_MIN_SEC
|
||||
int "Minimum run limit in seconds"
|
||||
range 1 60
|
||||
default 10
|
||||
help
|
||||
Minimum run limit in seconds for channels.
|
||||
|
||||
config RELAY_CHN_RUN_LIMIT_MAX_SEC
|
||||
int "Maximum run limit in seconds"
|
||||
range 60 3600
|
||||
default 600
|
||||
help
|
||||
Maximum run limit in seconds for channels.
|
||||
|
||||
config RELAY_CHN_RUN_LIMIT_DEFAULT_SEC
|
||||
int "Default run limit in seconds"
|
||||
range 10 3600
|
||||
default 60
|
||||
help
|
||||
Default run limit in seconds for channels.
|
||||
|
||||
endmenu
|
||||
299
README.md
299
README.md
@@ -12,14 +12,34 @@ An ESP-IDF component for controlling relay channels, specifically designed for d
|
||||
- Direction flipping capability
|
||||
- State monitoring and reporting
|
||||
- Optional sensitivty adjustable tilting feature
|
||||
- Optional NVS storage for persistent configuration
|
||||
- Optional configurable run limit protection
|
||||
|
||||
## Description
|
||||
|
||||
Each relay channel consists of 2 output relays controlled by 2 GPIO pins. The component provides APIs to control these relay pairs while ensuring safe operation, particularly for driving bipolar motors. To prevent mechanical strain on the motor, the component automatically manages direction changes with a configurable inertia delay, protecting it from abrupt reversals.
|
||||
Each relay channel consists of 2 output relays controlled by 2 GPIO pins. The component provides APIs to control these relay pairs while ensuring safe operation, particularly for driving bipolar motors. To prevent mechanical strain on the motor, the component automatically manages direction changes with a configurable inertia delay, protecting it from abrupt reversals. Hence, the component handles all the required timing between the movement transitions automatically to ensure reliable operation.
|
||||
|
||||
The run limit feature provides an additional layer of protection by automatically stopping channels after a configurable time period. This is particularly useful for motor-driven applications where continuous operation beyond a certain duration could cause damage or safety issues. Each channel can have its own run limit setting, and when enabled, the component will automatically stop the channel once it has been running for the specified duration.
|
||||
|
||||
It also provides an optional tilting interface per channel base. Tilting makes a channel move with a specific pattern moving with small steps at a time. Tilting is specifically designed for controlling some types of curtains that need to be adjusted to let enter specific amount of day light.
|
||||
Since it operates on relays, the switching frequency is limited to 10Hz which complies with the most of the general purpose relays' requirements. The minimum frequency is 2Hz and the duty cycle is about 10% in all ranges.
|
||||
|
||||
Another optional feature is NVS storage, which saves the configuration permanently across reboots of the device. These configurations are:
|
||||
|
||||
- Direction
|
||||
- Run limit duration
|
||||
- Tilt sensitivity
|
||||
- Last tilt position
|
||||
|
||||
### NVS Operation Details
|
||||
When NVS storage is enabled (CONFIG_RELAY_CHN_ENABLE_NVS=y), the component creates a dedicated background task to manage all NVS write operations. This design has important implications for how you use the NVS-related functions:
|
||||
- **Asynchronous Writes:** All `set` operations (e.g., `relay_chn_flip_direction()`, `relay_chn_set_run_limit()`) are asynchronous. They add the write request to a queue and return immediately, preventing the calling task from being blocked.
|
||||
- **Synchronous Reads:** All get operations (e.g., `relay_chn_get_direction()`) are synchronous. They read the value directly from the NVS storage and will block the calling task until the read is complete.
|
||||
- **Batched Commits:** To optimize performance and minimize flash wear, the NVS task uses a batching mechanism for writes. It collects multiple write requests and commits them to the NVS flash in a single operation after a short period of inactivity (typically 200ms).
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Due to the asynchronous and batched nature of write operations, a call to a get function may not immediately reflect a value that was just written by a set function. Your application should account for this small delay.
|
||||
|
||||
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
|
||||
|
||||
@@ -27,7 +47,79 @@ Configure the component through menuconfig under "Relay Channel Driver Configura
|
||||
|
||||
- `CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS`: Time to wait before changing direction (200-1500ms, default: 800ms)
|
||||
- `CONFIG_RELAY_CHN_COUNT`: Number of relay channels (1-8, default: 1)
|
||||
- `CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT`: Enable run limit protection (default: n)
|
||||
- `CONFIG_RELAY_CHN_ENABLE_TILTING`: Enable tilting interface on all channels. (default: n)
|
||||
- `CONFIG_RELAY_CHN_ENABLE_NVS`: Enable persistent storage in NVS (default: n)
|
||||
|
||||
When run limit is enabled (`CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT`), the following configuration options become available:
|
||||
|
||||
- `CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC`: Minimum allowed run limit duration (1-60s, default: 10s)
|
||||
- `CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC`: Maximum allowed run limit duration (60-3600s, default: 600s)
|
||||
- `CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC`: Default run limit duration for channels (10-3600s, default: 60s)
|
||||
|
||||
When NVS storage is enabled (`CONFIG_RELAY_CHN_ENABLE_NVS`), additional configuration options become available:
|
||||
|
||||
- `CONFIG_RELAY_CHN_NVS_NAMESPACE`: NVS namespace for storing relay channel data (default: "relay_chn")
|
||||
- `CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION`: Use custom NVS partition instead of default (default: n)
|
||||
- `CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME`: Name of the custom partition if enabled (default "app_data")
|
||||
|
||||
### NVS Storage Prerequisites
|
||||
|
||||
> [!WARNING]
|
||||
> `relay_chn` component does not initialize the NVS flash.
|
||||
|
||||
If NVS storage is enabled, you must initialize NVS flash before calling `relay_chn_create()` in your application code. The `relay_chn` component can use either the default or a custom NVS partition from your application, depending on the configuration settings.
|
||||
|
||||
#### Initialize for Default Partition
|
||||
|
||||
1. Enable NVS, but keep the custom partition option disabled in `menuconfig`:
|
||||
|
||||
```ini
|
||||
CONFIG_RELAY_CHN_ENABLE_NVS=y
|
||||
CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION=n
|
||||
```
|
||||
|
||||
2. Initialize the default NVS flash:
|
||||
|
||||
```c
|
||||
// Initialize default NVS partition
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(ret);
|
||||
|
||||
// Now you can create relay channels
|
||||
ret = relay_chn_create(gpio_map, gpio_count);
|
||||
```
|
||||
|
||||
#### Initialize for Custom Partition
|
||||
|
||||
1. Enable both NVS and custom partition, also set the custom partition name in `menuconfig`.
|
||||
|
||||
```ini
|
||||
CONFIG_RELAY_CHN_ENABLE_NVS=y
|
||||
CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION=n
|
||||
CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME=my_custom_partition
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> The `CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME` **must match exactly the label** defined for the custom NVS partition in the partition table. Otherwise the component initialisation will fail due to the `ESP_ERR_NVS_PART_NOT_FOUND` error.
|
||||
|
||||
2. Initialize the custom NVS partition:
|
||||
|
||||
```c
|
||||
esp_err_t ret = nvs_flash_init_partition(YOUR_CUSTOM_PARTITION_NAME);
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase_partition(YOUR_CUSTOM_PARTITION_NAME));
|
||||
ret = nvs_flash_init_partition(YOUR_CUSTOM_PARTITION_NAME);
|
||||
}
|
||||
ESP_ERROR_CHECK(ret);
|
||||
|
||||
// Now you can create relay channels
|
||||
ret = relay_chn_create(gpio_map, gpio_count);
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -38,16 +130,33 @@ dependencies:
|
||||
# Add as a custom component from git repository
|
||||
relay_chn:
|
||||
git: https://git.kozmotronik.com.tr/KozmotronikTech/relay_chn.git
|
||||
version: '>=0.5.0'
|
||||
version: '>=1.0.0'
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
The `relay_chn` component can be used in two different modes, which are determined by the 'CONFIG_RELAY_CHN_COUNT' configuration:
|
||||
|
||||
- Single channel mode (`CONFIG_RELAY_CHN_COUNT == 1`)
|
||||
- Multi channel mode (`CONFIG_RELAY_CHN_COUNT > 1`)
|
||||
|
||||
Depending on the mode, the component will be built selectively, so the signatures of some available API functions may vary, either including or excluding a channel ID parameter:
|
||||
|
||||
```c
|
||||
relay_chn_run_forward(); // No channel ID parameter for single channel mode
|
||||
// or
|
||||
relay_chn_run_forward(2); // Channel ID parameters will be needed in multi channel mode
|
||||
```
|
||||
|
||||
See the examples for further reference
|
||||
|
||||
### 1. Initialize relay channels
|
||||
|
||||
```c
|
||||
// Define GPIO pins for relay channels
|
||||
const gpio_num_t gpio_map[] = {GPIO_NUM_4, GPIO_NUM_5}; // One channel example
|
||||
const uint8_t gpio_map[] = {4, 5}; // One channel example
|
||||
/*------------------------------------------------------------------------*/
|
||||
const uint8_t gpio_map[] = {4, 5, 9, 10, 18, 19}; // Or a 3 channel example
|
||||
const uint8_t gpio_count = sizeof(gpio_map) / sizeof(gpio_map[0]);
|
||||
|
||||
// Create and initialize relay channels
|
||||
@@ -59,53 +168,191 @@ if (ret != ESP_OK) {
|
||||
|
||||
### 2. Control relay channels
|
||||
|
||||
For single mode:
|
||||
|
||||
```c
|
||||
// Run channel 0 forward
|
||||
// Run the channel forward
|
||||
relay_chn_run_forward();
|
||||
|
||||
// Run the channel reverse
|
||||
relay_chn_run_reverse();
|
||||
|
||||
// Stop the channel
|
||||
relay_chn_stop();
|
||||
|
||||
// Flip the direction of the channel
|
||||
relay_chn_flip_direction();
|
||||
```
|
||||
|
||||
For multi mode
|
||||
|
||||
```c
|
||||
// Run channel #0 forward
|
||||
relay_chn_run_forward(0);
|
||||
// Run all channels forward
|
||||
relay_chn_run_forward_all();
|
||||
|
||||
// Run channel 0 reverse
|
||||
relay_chn_run_reverse(0);
|
||||
// Run channel #1 reverse
|
||||
relay_chn_run_reverse(1);
|
||||
// Run all channels reverse
|
||||
relay_chn_run_reverse_all();
|
||||
|
||||
// Stop channel 0
|
||||
relay_chn_stop(0);
|
||||
// Stop channel #1
|
||||
relay_chn_stop(1);
|
||||
// Stop all channels
|
||||
relay_chn_stop_all();
|
||||
|
||||
// Flip direction of channel 0
|
||||
// Flip direction of channel #0
|
||||
relay_chn_flip_direction(0);
|
||||
// Flip direction of all channels
|
||||
relay_chn_flip_direction_all();
|
||||
```
|
||||
|
||||
### 3. Monitor channel state
|
||||
|
||||
> [!WARNING]
|
||||
> Listener callbacks are executed from the context of the notification dispatcher task. To ensure system responsiveness and prevent event loss, callbacks must be lightweight and non-blocking. Avoid any long-running operations or functions that may block, such as `vTaskDelay()` or semaphore takes with long timeouts, inside the callback.
|
||||
|
||||
For single mode:
|
||||
|
||||
```c
|
||||
// Get channel state
|
||||
relay_chn_state_t state = relay_chn_get_state(0);
|
||||
char *state_str = relay_chn_get_state_str(0);
|
||||
relay_chn_state_t state = relay_chn_get_state();
|
||||
// Get the string representation of the state of the channel
|
||||
char *state_str = relay_chn_get_state_str();
|
||||
|
||||
// Get channel direction
|
||||
relay_chn_direction_t direction = relay_chn_get_direction(0);
|
||||
relay_chn_direction_t direction = relay_chn_get_direction();
|
||||
|
||||
// Listen to relay channel state changes
|
||||
static void relay_chn_listener(uint8_t chn_id, relay_chn_state_t old_state, relay_chn_state_t new_state) {
|
||||
/* The channel id can be ignored in single mode */
|
||||
/* Handle state changes */
|
||||
}
|
||||
// Register the listener callback
|
||||
relay_chn_register_listener(relay_chn_listener);
|
||||
// Unregister the listener when it is not needed anymore
|
||||
relay_chn_unregister_listener(relay_chn_listener);
|
||||
```
|
||||
|
||||
### 4. Tilting Interface (if enabled)
|
||||
For multi mode:
|
||||
|
||||
```c
|
||||
// Get channel #0 state
|
||||
relay_chn_state_t state = relay_chn_get_state(0);
|
||||
|
||||
// Get states for all channels
|
||||
relay_chn_state_t states[CONFIG_RELAY_CHN_COUNT];
|
||||
relay_chn_get_states(states);
|
||||
|
||||
// Get the string representation of the state of the channel #0
|
||||
char *state_str = relay_chn_get_state_str(0);
|
||||
|
||||
// Get channel #0 direction
|
||||
relay_chn_direction_t direction = relay_chn_get_direction(0);
|
||||
|
||||
// Get directions for all channels
|
||||
relay_chn_direction_t directions[CONFIG_RELAY_CHN_COUNT];
|
||||
relay_chn_get_directions(directions);
|
||||
|
||||
/* The listener is same for multi mode */
|
||||
```
|
||||
|
||||
### 4. Run Limit Control (if enabled)
|
||||
|
||||
For single mode:
|
||||
|
||||
```c
|
||||
// Assuming CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT is enabled
|
||||
|
||||
// Get current run limit (in seconds)
|
||||
uint16_t limit = relay_chn_get_run_limit();
|
||||
|
||||
// Set new run limit (in seconds)
|
||||
relay_chn_set_run_limit(120); // Set to 120 seconds
|
||||
```
|
||||
|
||||
For multi mode:
|
||||
|
||||
```c
|
||||
// Assuming CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT is enabled
|
||||
|
||||
// Get run limit for channel #0 (in seconds)
|
||||
uint16_t limit = relay_chn_get_run_limit(0);
|
||||
|
||||
// Set new run limit for specific channels (in seconds)
|
||||
relay_chn_set_run_limit(0, 120); // Set channel #0 to 120 seconds
|
||||
relay_chn_set_run_limit(1, 180); // Set channel #1 to 180 seconds
|
||||
relay_chn_set_run_limit_all_with(90); // Set all channels to 90 seconds
|
||||
|
||||
// Assuming the CONFIG_RELAY_CHN_COUNT is 4
|
||||
uint16_t limits_sec[CONFIG_RELAY_CHN_COUNT] = { 30, 35, 40, 45 };
|
||||
relay_chn_set_run_limit_all(limits_sec); // Set all channels according to the array
|
||||
```
|
||||
> [!NOTE]
|
||||
> When a channel reaches its run limit, it will automatically stop. The run limit timer is reset whenever the channel starts running in either direction.
|
||||
|
||||
### 5. Tilting Interface (if enabled)
|
||||
|
||||
For single mode:
|
||||
|
||||
```c
|
||||
// Assuming CONFIG_RELAY_CHN_ENABLE_TILTING is enabled
|
||||
|
||||
// Start tilting automatically (channel 0)
|
||||
// Start tilting automatically
|
||||
relay_chn_tilt_auto();
|
||||
|
||||
// Tilt forward
|
||||
relay_chn_tilt_forward();
|
||||
|
||||
// Tilt reverse
|
||||
relay_chn_tilt_reverse();
|
||||
|
||||
// Stop tilting
|
||||
relay_chn_tilt_stop();
|
||||
|
||||
// Set tilting sensitivity (sensitivity as percentage)
|
||||
relay_chn_tilt_set_sensitivity(90);
|
||||
|
||||
// Get tilting sensitivity (sensitivty as percentage)
|
||||
uint8_t sensitivity = relay_chn_tilt_get_sensitivity();
|
||||
```
|
||||
|
||||
For multi mode:
|
||||
|
||||
```c
|
||||
// Assuming CONFIG_RELAY_CHN_ENABLE_TILTING is enabled
|
||||
|
||||
// Start tilting automatically on channel #0
|
||||
relay_chn_tilt_auto(0);
|
||||
relay_chn_tilt_auto_all(); // on all channels
|
||||
|
||||
// Tilt forward (channel 0)
|
||||
relay_chn_tilt_forward(0);
|
||||
// Tilt forward on channel #1
|
||||
relay_chn_tilt_forward(1);
|
||||
relay_chn_tilt_forward_all();
|
||||
|
||||
// Tilt reverse (channel 0)
|
||||
relay_chn_tilt_reverse(0);
|
||||
// Tilt reverse on channel #2
|
||||
relay_chn_tilt_reverse(2);
|
||||
relay_chn_tilt_reverse_all();
|
||||
|
||||
// Stop tilting (channel 0)
|
||||
// Stop tilting on channel #0
|
||||
relay_chn_tilt_stop(0);
|
||||
relay_chn_tilt_stop_all();
|
||||
|
||||
// Set tilting sensitivity (channel 0, sensitivity as percentage)
|
||||
relay_chn_tilt_sensitivity_set(0, 90);
|
||||
// Set tilting sensitivity (sensitivity as percentage) for channel #0
|
||||
relay_chn_tilt_set_sensitivity(0, 90);
|
||||
relay_chn_tilt_set_sensitivity_all_with(90);
|
||||
|
||||
// Get tilting sensitivity (channel 0, sensitivty as percentage)
|
||||
uint8_t sensitivity = relay_chn_tilt_sensitivity_get(0);
|
||||
// Assuming the CONFIG_RELAY_CHN_COUNT is 4
|
||||
uint8_t sensitivities[CONFIG_RELAY_CHN_COUNT] = { 90, 85, 80, 75 };
|
||||
relay_chn_tilt_set_sensitivity_all(sensitivity); // Set all channels according to the array
|
||||
|
||||
// Get tilt sensitivity for channel #0
|
||||
uint8_t sensitivity = relay_chn_tilt_get_sensitivity(0);
|
||||
|
||||
// Get tilting sensitivity (sensitivty as percentage)
|
||||
uint8_t sensitivities[CONFIG_RELAY_CHN_COUNT];
|
||||
relay_chn_tilt_get_sensitivity_all(sensitivities);
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
6
examples/relay_chn_multi/.gitignore
vendored
Normal file
6
examples/relay_chn_multi/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
build/
|
||||
sdkconfig
|
||||
sdkconfig.old
|
||||
|
||||
# Exclude auto-populated settings file
|
||||
settings.json
|
||||
23
examples/relay_chn_multi/.vscode/c_cpp_properties.json
vendored
Normal file
23
examples/relay_chn_multi/.vscode/c_cpp_properties.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "ESP-IDF",
|
||||
"compilerPath": "${config:idf.toolsPath}/tools/riscv32-esp-elf/esp-14.2.0_20241119/riscv32-esp-elf/bin/riscv32-esp-elf-gcc",
|
||||
"compileCommands": "/disk/Projeler/ESP-Components/relay_chn/examples/relay_chn_single/build/compile_commands.json",
|
||||
"includePath": [
|
||||
"${config:idf.espIdfPath}/components/**",
|
||||
"${config:idf.espIdfPathWin}/components/**",
|
||||
"${workspaceFolder}/**"
|
||||
],
|
||||
"browse": {
|
||||
"path": [
|
||||
"${config:idf.espIdfPath}/components",
|
||||
"${config:idf.espIdfPathWin}/components",
|
||||
"${workspaceFolder}"
|
||||
],
|
||||
"limitSymbolsToIncludedHeaders": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
}
|
||||
15
examples/relay_chn_multi/.vscode/launch.json
vendored
Normal file
15
examples/relay_chn_multi/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "gdbtarget",
|
||||
"request": "attach",
|
||||
"name": "Eclipse CDT GDB Adapter"
|
||||
},
|
||||
{
|
||||
"type": "espidf",
|
||||
"name": "Launch",
|
||||
"request": "launch"
|
||||
}
|
||||
]
|
||||
}
|
||||
10
examples/relay_chn_multi/CMakeLists.txt
Normal file
10
examples/relay_chn_multi/CMakeLists.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
# For more information about build system see
|
||||
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
|
||||
# The following five lines of boilerplate have to be in your project's
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
set(COMPONENTS main)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(relay_chn_single)
|
||||
176
examples/relay_chn_multi/README.md
Normal file
176
examples/relay_chn_multi/README.md
Normal file
@@ -0,0 +1,176 @@
|
||||
# Relay Channel Multi Example
|
||||
|
||||
## Introduction
|
||||
|
||||
This example demonstrates how to use the relay channel component to control a 3-channel setup with button inputs and LED status indication. It showcases:
|
||||
|
||||
- Basic relay channel operations (forward/reverse running, stopping)
|
||||
- Secondary operations (tilting, direction flipping)
|
||||
- State change event handling with a listener
|
||||
- Relay channel run limit
|
||||
- Button event handling using esp-iot-solution's button component
|
||||
- Visual feedback using esp-iot-solution's LED indicator component
|
||||
|
||||
## How to Use Example
|
||||
|
||||
This example has been tested on an `ESP32-C3-DevKitM-1U` board. However, it can be adapted to any ESP32-based board with at least six available GPIO pins by adjusting the configuration options.
|
||||
|
||||
### Hardware Required
|
||||
|
||||
* An ESP32-based development board
|
||||
* 2 relays connected to GPIO pins (default: GPIO4, GPIO5)
|
||||
* 3 buttons connected to GPIO pins:
|
||||
- UP button (default: GPIO0)
|
||||
- DOWN button (default: GPIO1)
|
||||
- STOP button (default: GPIO2)
|
||||
- SELECT button (default: GPIO3)
|
||||
* LED indicators for status indication of each channel (default: GPIO4, GPIO10 and GPIO9 respectively)
|
||||
|
||||
#### Hardware Schematic
|
||||
|
||||
Relay blocks are ommitted for simplicity. You can refer to schematic of the [Single-channel example](/examples/relay_chn_single/README.md) for a fully implemented relay block.
|
||||
|
||||
> [!NOTE]
|
||||
> A single relay channel consists of two relay block and two GPIO pins.
|
||||
|
||||

|
||||
|
||||
### Configuration
|
||||
|
||||
The example can be configured through `menuconfig` under "Relay Channel Multi Example Configuration":
|
||||
|
||||
1. Button active level (`EXAMPLE_RLCHN_BTN_ACTIVE_LEVEL`)
|
||||
- Select between active LOW or HIGH logic level for buttons
|
||||
|
||||
2. GPIO assignments:
|
||||
- UP button pin (`EXAMPLE_RLCHN_BTN_UP_IO_NUM`, default: 0)
|
||||
- DOWN button pin (`EXAMPLE_RLCHN_BTN_DOWN_IO_NUM`, default: 1)
|
||||
- STOP button pin (`EXAMPLE_RLCHN_BTN_STOP_IO_NUM`, default: 2)
|
||||
- SELECT button pin (`EXAMPLE_RLCHN_BTN_SELECT_IO_NUM`, default: 3)
|
||||
- LED indicator pin (`EXAMPLE_RLCHN_LED_INDICATOR1_IO_NUM`, default: 4)
|
||||
- LED indicator pin (`EXAMPLE_RLCHN_LED_INDICATOR2_IO_NUM`, default: 10)
|
||||
- LED indicator pin (`EXAMPLE_RLCHN_LED_INDICATOR3_IO_NUM`, default: 9)
|
||||
|
||||
3. Long press timing:
|
||||
- `EXAMPLE_RLCHN_BTN_LONG_PRESS_TIME_MS`: Duration for long press actions (1500-3000ms, default: 2000ms)
|
||||
|
||||
### Button Operations
|
||||
|
||||
The example uses esp-iot-solution's `button` component to handle the following operations:
|
||||
|
||||
- **UP button**:
|
||||
* Short press: Start forward movement
|
||||
* Long press: Start forward tilt (stops on release)
|
||||
|
||||
- **DOWN button**:
|
||||
* Short press: Start reverse movement
|
||||
* Long press: Start reverse tilt (stops on release)
|
||||
|
||||
- **STOP button**:
|
||||
* Short press: Stop movement
|
||||
* Long press: Flip movement direction
|
||||
|
||||
- **SELECT button**:
|
||||
* Short press: Selects a channel to operate
|
||||
* Long press: Selects all channels to operate in batch
|
||||
|
||||
### LED Indicator States
|
||||
|
||||
The example uses esp-iot-solution's `led_indicator` component to show different states:
|
||||
|
||||
- **Running**: LED blinks at 300ms on, 100ms off
|
||||
- **Tilting**: Fast blink at 100ms on, 50ms off
|
||||
- **Operation Success**: Two quick blinks
|
||||
- **Operation Fail**: One long blink
|
||||
|
||||
### Dependencies
|
||||
|
||||
This example requires:
|
||||
- ESP-IDF v4.1 or later
|
||||
- esp-iot-solution components:
|
||||
* button v4.1.1 or later
|
||||
* led_indicator v1.1.1 or later
|
||||
- relay_chn component
|
||||
|
||||
## Example Output
|
||||
|
||||
When the application boots, it will wait for a button event. Then ta state listener will print state changes.
|
||||
|
||||
```log
|
||||
I (269) main_task: Calling app_main()
|
||||
I (269) RELAY_CHN_MULTI_EXAMPLE: Initializing relay channel
|
||||
I (279) gpio: GPIO[18]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (279) gpio: GPIO[19]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (289) gpio: GPIO[5]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (299) gpio: GPIO[6]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (309) gpio: GPIO[7]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (319) gpio: GPIO[8]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (329) RELAY_CHN_MULTI_EXAMPLE: Initializing buttons
|
||||
I (329) RELAY_CHN_MULTI_EXAMPLE: Initializing buttons with active level: 0
|
||||
I (339) gpio: GPIO[0]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (339) button: IoT Button Version: 4.1.3
|
||||
I (349) gpio: GPIO[1]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (359) gpio: GPIO[2]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (359) gpio: GPIO[3]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (369) RELAY_CHN_MULTI_EXAMPLE: Setting up button callbacks. Configured long press time: 2000 ms
|
||||
I (379) RELAY_CHN_MULTI_EXAMPLE: Initializing LED indicator
|
||||
I (389) gpio: GPIO[4]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (389) led_indicator: LED Indicator Version: 1.1.1
|
||||
I (399) gpio: GPIO[4]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
|
||||
I (409) led_indicator: Indicator create successfully. type:GPIO mode, hardware_data:0x3fc96498, blink_lists:custom
|
||||
I (419) gpio: GPIO[10]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (429) led_indicator: LED Indicator Version: 1.1.1
|
||||
I (429) gpio: GPIO[10]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
|
||||
I (439) led_indicator: Indicator create successfully. type:GPIO mode, hardware_data:0x3fc965a4, blink_lists:custom
|
||||
I (449) gpio: GPIO[9]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (459) led_indicator: LED Indicator Version: 1.1.1
|
||||
I (459) gpio: GPIO[9]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
|
||||
I (469) led_indicator: Indicator create successfully. type:GPIO mode, hardware_data:0x3fc966b0, blink_lists:custom
|
||||
I (479) RELAY_CHN_MULTI_EXAMPLE: Relay Channel Multi Example is ready to operate
|
||||
I (489) main_task: Returned from app_main()
|
||||
I (3759) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from IDLE to FORWARD
|
||||
I (6639) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from FORWARD to STOPPED
|
||||
I (7439) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from STOPPED to IDLE
|
||||
I (7439) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from IDLE to TILT_FORWARD
|
||||
I (8119) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from TILT_FORWARD to IDLE
|
||||
I (13579) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from IDLE to TILT_REVERSE
|
||||
I (14419) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from TILT_REVERSE to IDLE
|
||||
I (20319) RELAY_CHN_MULTI_EXAMPLE: Selected channel: 1
|
||||
I (21479) RELAY_CHN_MULTI_EXAMPLE: Selected channel: 2
|
||||
I (22229) RELAY_CHN_MULTI_EXAMPLE: Selected channel: 0
|
||||
I (23169) RELAY_CHN_MULTI_EXAMPLE: Selected channel: 1
|
||||
I (29039) RELAY_CHN_MULTI_EXAMPLE: Selected all channels
|
||||
I (35419) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from IDLE to FORWARD
|
||||
I (35419) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #1, from IDLE to FORWARD
|
||||
I (35419) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #2, from IDLE to FORWARD
|
||||
I (39349) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from FORWARD to STOPPED
|
||||
I (39349) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #1, from FORWARD to STOPPED
|
||||
I (39359) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #2, from FORWARD to STOPPED
|
||||
I (40149) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from STOPPED to IDLE
|
||||
I (40149) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from IDLE to TILT_FORWARD
|
||||
I (40159) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #1, from STOPPED to IDLE
|
||||
I (40169) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #1, from IDLE to TILT_FORWARD
|
||||
I (40179) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #2, from STOPPED to IDLE
|
||||
I (40189) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #2, from IDLE to TILT_FORWARD
|
||||
I (41699) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from TILT_FORWARD to IDLE
|
||||
I (41699) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #1, from TILT_FORWARD to IDLE
|
||||
I (41709) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #2, from TILT_FORWARD to IDLE
|
||||
I (64129) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from IDLE to TILT_REVERSE
|
||||
I (64129) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #1, from IDLE to TILT_REVERSE
|
||||
I (64139) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #2, from IDLE to TILT_REVERSE
|
||||
I (68929) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from TILT_REVERSE to IDLE
|
||||
I (68929) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #1, from TILT_REVERSE to IDLE
|
||||
I (68939) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #2, from TILT_REVERSE to IDLE
|
||||
I (280109) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from IDLE to FORWARD
|
||||
I (280109) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #1, from IDLE to FORWARD
|
||||
I (280109) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #2, from IDLE to FORWARD
|
||||
I (283219) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from FORWARD to STOPPED
|
||||
I (283219) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from STOPPED to REVERSE_PENDING
|
||||
I (283229) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #1, from FORWARD to STOPPED
|
||||
I (283239) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #1, from STOPPED to REVERSE_PENDING
|
||||
I (283249) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #2, from FORWARD to STOPPED
|
||||
I (283259) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #2, from STOPPED to REVERSE_PENDING
|
||||
I (284019) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from REVERSE_PENDING to REVERSE
|
||||
I (284019) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #1, from REVERSE_PENDING to REVERSE
|
||||
I (284029) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #2, from REVERSE_PENDING to REVERSE
|
||||
```
|
||||
BIN
examples/relay_chn_multi/example_schematic.png
Normal file
BIN
examples/relay_chn_multi/example_schematic.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 132 KiB |
2
examples/relay_chn_multi/main/CMakeLists.txt
Normal file
2
examples/relay_chn_multi/main/CMakeLists.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "relay_chn_multi_main.c"
|
||||
PRIV_REQUIRES button led_indicator relay_chn)
|
||||
52
examples/relay_chn_multi/main/Kconfig.projbuild
Normal file
52
examples/relay_chn_multi/main/Kconfig.projbuild
Normal file
@@ -0,0 +1,52 @@
|
||||
menu "Relay Channel Multi Example Configuration"
|
||||
|
||||
choice EXAMPLE_RLCHN_BTN_ACTIVE_LEVEL
|
||||
prompt "Choose an active level for buttons"
|
||||
default EXAMPLE_RLCHN_BTN_ACTIVE_LEVEL_LOW
|
||||
help
|
||||
Specify the active level for buttons.
|
||||
|
||||
config EXAMPLE_RLCHN_BTN_ACTIVE_LEVEL_LOW
|
||||
bool "Active level LOW"
|
||||
|
||||
config EXAMPLE_RLCHN_BTN_ACTIVE_LEVEL_HIGH
|
||||
bool "Active level HIGH"
|
||||
endchoice
|
||||
|
||||
config EXAMPLE_RLCHN_BTN_UP_IO_NUM
|
||||
int "GPIO number for UP button"
|
||||
default 0
|
||||
|
||||
config EXAMPLE_RLCHN_BTN_DOWN_IO_NUM
|
||||
int "GPIO number for DOWN button"
|
||||
default 1
|
||||
|
||||
config EXAMPLE_RLCHN_BTN_STOP_IO_NUM
|
||||
int "GPIO number for STOP button"
|
||||
default 2
|
||||
|
||||
config EXAMPLE_RLCHN_BTN_SELECT_IO_NUM
|
||||
int "GPIO number for STOP button"
|
||||
default 3
|
||||
|
||||
config EXAMPLE_RLCHN_LED_INDICATOR1_IO_NUM
|
||||
int "GPIO number for LED indicator output for channel 1"
|
||||
default 4
|
||||
|
||||
config EXAMPLE_RLCHN_LED_INDICATOR2_IO_NUM
|
||||
int "GPIO number for LED indicator output for channel 2"
|
||||
default 10
|
||||
|
||||
config EXAMPLE_RLCHN_LED_INDICATOR3_IO_NUM
|
||||
int "GPIO number for LED indicator output for channel 3"
|
||||
default 9
|
||||
|
||||
config EXAMPLE_RLCHN_BTN_LONG_PRESS_TIME_MS
|
||||
int "Long press time in ms to start secondary actions"
|
||||
range 1500 3000
|
||||
default 2000
|
||||
help
|
||||
Long press time in milliseconds is required to start secondary actions
|
||||
like tilting and flipping.
|
||||
|
||||
endmenu
|
||||
8
examples/relay_chn_multi/main/idf_component.yml
Normal file
8
examples/relay_chn_multi/main/idf_component.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
dependencies:
|
||||
idf:
|
||||
version: '>=4.1.0'
|
||||
espressif/button: ^4.1.1
|
||||
espressif/led_indicator: ^1.1.1
|
||||
relay_chn:
|
||||
version: '*'
|
||||
override_path: ../../../
|
||||
427
examples/relay_chn_multi/main/relay_chn_multi_main.c
Normal file
427
examples/relay_chn_multi/main/relay_chn_multi_main.c
Normal file
@@ -0,0 +1,427 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_check.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_timer.h"
|
||||
#include "button_gpio.h"
|
||||
#include "iot_button.h"
|
||||
#include "led_indicator.h"
|
||||
#include "relay_chn.h"
|
||||
|
||||
static const char *TAG = "RELAY_CHN_MULTI_EXAMPLE";
|
||||
|
||||
#define EXAMPLE_CHN_INDICATOR_ON_TIME_MS 3000
|
||||
#define EXAMPLE_ALL_CHANNELS CONFIG_RELAY_CHN_COUNT
|
||||
|
||||
|
||||
/**
|
||||
* @brief LED indicator modes for different states.
|
||||
*/
|
||||
typedef enum {
|
||||
INDICATOR_MODE_SELECT, /*!< Channel select indication */
|
||||
INDICATOR_MODE_OK, /*!< OK/Success indication */
|
||||
INDICATOR_MODE_FAIL, /*!< Fail/Error indication */
|
||||
INDICATOR_MODE_TILTING, /*!< Tilting operation in progress */
|
||||
INDICATOR_MODE_RUNNING, /*!< Full run operation in progress */
|
||||
INDICATOR_MODE_MAX /*!< Maximum number of indicator modes */
|
||||
} indicator_mode_t;
|
||||
|
||||
/** @brief Blink pattern for channel select indication. */
|
||||
static const blink_step_t indc_mode_select[] = {
|
||||
{LED_BLINK_HOLD, LED_STATE_ON, EXAMPLE_CHN_INDICATOR_ON_TIME_MS}, // step1: turn on LED 3000 ms
|
||||
{LED_BLINK_HOLD, LED_STATE_OFF, 10}, // step2: turn off LED 500 ms
|
||||
{LED_BLINK_STOP, 0, 0}, // step4: stop blink (off)
|
||||
};
|
||||
|
||||
/** @brief Blink pattern for OK/Success indication. */
|
||||
static const blink_step_t indc_mode_ok[] = {
|
||||
{LED_BLINK_HOLD, LED_STATE_ON, 100}, // step1: turn on LED 100 ms
|
||||
{LED_BLINK_HOLD, LED_STATE_OFF, 50}, // step2: turn off LED 50 ms
|
||||
{LED_BLINK_HOLD, LED_STATE_ON, 100}, // step3: turn on LED 100 ms
|
||||
{LED_BLINK_HOLD, LED_STATE_OFF, 50}, // step4: turn off LED 50 ms
|
||||
{LED_BLINK_STOP, 0, 0}, // step5: stop blink (off)
|
||||
};
|
||||
|
||||
/** @brief Blink pattern for Fail/Error indication. */
|
||||
static const blink_step_t indc_mode_fail[] = {
|
||||
{LED_BLINK_HOLD, LED_STATE_ON, 1000}, // step1: turn on LED 1000 ms
|
||||
{LED_BLINK_HOLD, LED_STATE_OFF, 500}, // step2: turn off LED 500 ms
|
||||
{LED_BLINK_STOP, 0, 0}, // step4: stop blink (off)
|
||||
};
|
||||
|
||||
/** @brief Blink pattern for full run operation. */
|
||||
static const blink_step_t indc_mode_running[] = {
|
||||
{LED_BLINK_HOLD, LED_STATE_ON, 300}, // step1: turn on LED 300 ms
|
||||
{LED_BLINK_HOLD, LED_STATE_OFF, 100}, // step2: turn off LED 100 ms
|
||||
{LED_BLINK_LOOP, 0, 0}, // step3: loop from step1
|
||||
};
|
||||
|
||||
/** @brief Blink pattern for tilting operation. */
|
||||
static const blink_step_t indc_mode_tilting[] = {
|
||||
{LED_BLINK_HOLD, LED_STATE_ON, 100}, // step1: turn on LED 100 ms
|
||||
{LED_BLINK_HOLD, LED_STATE_OFF, 50}, // step2: turn off LED 50 ms
|
||||
{LED_BLINK_LOOP, 0, 0}, // step3: loop from step1
|
||||
};
|
||||
|
||||
/** @brief Array of LED indicator blink patterns. */
|
||||
blink_step_t const *led_indicator_modes[] = {
|
||||
[INDICATOR_MODE_SELECT] = indc_mode_select,
|
||||
[INDICATOR_MODE_OK] = indc_mode_ok,
|
||||
[INDICATOR_MODE_FAIL] = indc_mode_fail,
|
||||
[INDICATOR_MODE_RUNNING] = indc_mode_running,
|
||||
[INDICATOR_MODE_TILTING] = indc_mode_tilting,
|
||||
[INDICATOR_MODE_MAX] = NULL,
|
||||
};
|
||||
|
||||
/** @brief Handle for the LED indicator. */
|
||||
static led_indicator_handle_t indicators[CONFIG_RELAY_CHN_COUNT];
|
||||
static uint8_t selected_channel = 0; /*!< Currently selected channel */
|
||||
static uint8_t selected_channel_backup = 0; /*!< Backup for last selected channel */
|
||||
static int64_t last_indc_update_us = 0; /*!< Timestamp of the last channel selection */
|
||||
|
||||
|
||||
/**
|
||||
* @brief Initializes the buttons for user interaction.
|
||||
*
|
||||
* This function configures and creates GPIO buttons for UP, DOWN, and STOP
|
||||
* operations. It also registers callbacks for single-click and long-press
|
||||
* events to control the relay channel.
|
||||
*
|
||||
* @return esp_err_t
|
||||
* - ESP_OK: Success
|
||||
* - Others: Fail
|
||||
*/
|
||||
static esp_err_t init_buttons(void);
|
||||
|
||||
/**
|
||||
* @brief Initializes the LED indicator.
|
||||
*
|
||||
* This function configures and creates the LED indicator used to provide
|
||||
* visual feedback on the relay channel's status.
|
||||
*
|
||||
* @return esp_err_t
|
||||
* - ESP_OK: Success
|
||||
* - ESP_FAIL: Fail
|
||||
*/
|
||||
static esp_err_t init_led_indicators(void);
|
||||
|
||||
/**
|
||||
* @brief Starts all indicators with the specified mode.
|
||||
*
|
||||
* @param mode One of the indicator modes defined with indicator_mode_t.
|
||||
*/
|
||||
static void start_all_indicators_for_mode(indicator_mode_t mode);
|
||||
|
||||
/**
|
||||
* @brief Stops all indicators with the specified mode.
|
||||
*
|
||||
* @param mode One of the indicator modes defined with indicator_mode_t.
|
||||
*/
|
||||
static void stop_all_indicators_for_mode(indicator_mode_t mode);
|
||||
|
||||
/**
|
||||
* @brief Event listener for relay channel state changes to log the state transition.
|
||||
*/
|
||||
static void example_event_listener(uint8_t ch, relay_chn_state_t old_state, relay_chn_state_t new_state);
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
const uint8_t gpio_map[] = { 18, 19, 5, 6, 7, 8 };
|
||||
const uint8_t gpio_count = sizeof(gpio_map) / sizeof(gpio_map[0]);
|
||||
|
||||
ESP_LOGI(TAG, "Initializing relay channel");
|
||||
ESP_ERROR_CHECK(relay_chn_create(gpio_map, gpio_count));
|
||||
ESP_ERROR_CHECK(relay_chn_register_listener(example_event_listener));
|
||||
|
||||
ESP_LOGI(TAG, "Initializing buttons");
|
||||
ESP_ERROR_CHECK(init_buttons());
|
||||
ESP_LOGI(TAG, "Initializing LED indicator");
|
||||
ESP_ERROR_CHECK(init_led_indicators());
|
||||
|
||||
ESP_LOGI(TAG, "Relay Channel Multi Example is ready to operate");
|
||||
|
||||
// Indicate init was successful
|
||||
start_all_indicators_for_mode(INDICATOR_MODE_OK);
|
||||
}
|
||||
|
||||
static void on_click_up(void *arg, void *data)
|
||||
{
|
||||
if (selected_channel == EXAMPLE_ALL_CHANNELS) {
|
||||
relay_chn_run_forward_all();
|
||||
} else {
|
||||
relay_chn_run_forward(selected_channel);
|
||||
}
|
||||
}
|
||||
|
||||
static void on_click_down(void *arg, void *data)
|
||||
{
|
||||
if (selected_channel == EXAMPLE_ALL_CHANNELS) {
|
||||
relay_chn_run_reverse_all();
|
||||
} else {
|
||||
relay_chn_run_reverse(selected_channel);
|
||||
}
|
||||
}
|
||||
|
||||
static void on_click_stop(void *arg, void *data)
|
||||
{
|
||||
if (selected_channel == EXAMPLE_ALL_CHANNELS) {
|
||||
relay_chn_stop_all();
|
||||
} else {
|
||||
relay_chn_stop(selected_channel);
|
||||
}
|
||||
}
|
||||
|
||||
static void on_click_flip(void *arg, void *data)
|
||||
{
|
||||
if (selected_channel == EXAMPLE_ALL_CHANNELS) {
|
||||
relay_chn_flip_direction_all();
|
||||
start_all_indicators_for_mode(INDICATOR_MODE_OK);
|
||||
} else {
|
||||
relay_chn_flip_direction(selected_channel);
|
||||
led_indicator_start(indicators[selected_channel], INDICATOR_MODE_OK);
|
||||
}
|
||||
}
|
||||
|
||||
static void on_click_tilt_up(void *arg, void *data)
|
||||
{
|
||||
if (selected_channel == EXAMPLE_ALL_CHANNELS) {
|
||||
relay_chn_tilt_forward_all();
|
||||
} else {
|
||||
relay_chn_tilt_forward(selected_channel);
|
||||
}
|
||||
}
|
||||
|
||||
static void on_click_tilt_down(void *arg, void *data)
|
||||
{
|
||||
if (selected_channel == EXAMPLE_ALL_CHANNELS) {
|
||||
relay_chn_tilt_reverse_all();
|
||||
} else {
|
||||
relay_chn_tilt_reverse(selected_channel);
|
||||
}
|
||||
}
|
||||
|
||||
static void on_release_tilt(void *arg, void *data)
|
||||
{
|
||||
if (selected_channel == EXAMPLE_ALL_CHANNELS) {
|
||||
relay_chn_tilt_stop_all();
|
||||
} else {
|
||||
relay_chn_tilt_stop(selected_channel);
|
||||
}
|
||||
}
|
||||
|
||||
static void on_click_select(void *arg, void *data)
|
||||
{
|
||||
int64_t now_us = esp_timer_get_time();
|
||||
uint32_t delta_ms = (now_us - last_indc_update_us) / 1000;
|
||||
if (delta_ms >= EXAMPLE_CHN_INDICATOR_ON_TIME_MS) {
|
||||
// Channel indicator was off, turn it on first
|
||||
if (selected_channel == EXAMPLE_ALL_CHANNELS) {
|
||||
start_all_indicators_for_mode(INDICATOR_MODE_SELECT);
|
||||
} else {
|
||||
led_indicator_start(indicators[selected_channel], INDICATOR_MODE_SELECT);
|
||||
}
|
||||
last_indc_update_us = esp_timer_get_time(); // Save last selection time
|
||||
return;
|
||||
}
|
||||
|
||||
if (selected_channel == EXAMPLE_ALL_CHANNELS) {
|
||||
// All channels selected previously, restore the selected channel
|
||||
selected_channel = selected_channel_backup;
|
||||
ESP_LOGI(TAG, "Restored selected channel: %d", selected_channel);
|
||||
stop_all_indicators_for_mode(INDICATOR_MODE_SELECT);
|
||||
led_indicator_start(indicators[selected_channel], INDICATOR_MODE_SELECT);
|
||||
return;
|
||||
}
|
||||
|
||||
// Channel indicator is turned on, select the next channel
|
||||
selected_channel = ((selected_channel + 1) % CONFIG_RELAY_CHN_COUNT);
|
||||
ESP_LOGI(TAG, "Selected channel: %d", selected_channel);
|
||||
// Update the indicator
|
||||
stop_all_indicators_for_mode(INDICATOR_MODE_SELECT);
|
||||
led_indicator_start(indicators[selected_channel], INDICATOR_MODE_SELECT);
|
||||
last_indc_update_us = esp_timer_get_time(); // Save last selection time
|
||||
}
|
||||
|
||||
static void on_click_select_all(void *arg, void *data)
|
||||
{
|
||||
if (selected_channel == EXAMPLE_ALL_CHANNELS) {
|
||||
ESP_LOGI(TAG, "All channels are selected already");
|
||||
int64_t now_us = esp_timer_get_time();
|
||||
uint32_t delta_ms = (now_us - last_indc_update_us) / 1000;
|
||||
// If the indicators in sleep, wake up
|
||||
if (delta_ms >= EXAMPLE_CHN_INDICATOR_ON_TIME_MS) {
|
||||
start_all_indicators_for_mode(INDICATOR_MODE_SELECT);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
selected_channel_backup = selected_channel;
|
||||
selected_channel = EXAMPLE_ALL_CHANNELS;
|
||||
ESP_LOGI(TAG, "Selected all channels");
|
||||
start_all_indicators_for_mode(INDICATOR_MODE_SELECT);
|
||||
last_indc_update_us = esp_timer_get_time(); // Save last selection time
|
||||
}
|
||||
|
||||
static void start_all_indicators_for_mode(indicator_mode_t mode)
|
||||
{
|
||||
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
led_indicator_start(indicators[i], mode);
|
||||
}
|
||||
}
|
||||
|
||||
static void stop_all_indicators_for_mode(indicator_mode_t mode)
|
||||
{
|
||||
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
led_indicator_stop(indicators[i], mode);
|
||||
led_indicator_set_on_off(indicators[i], false);
|
||||
}
|
||||
}
|
||||
|
||||
static void example_event_listener(uint8_t ch, relay_chn_state_t old_state, relay_chn_state_t new_state)
|
||||
{
|
||||
ESP_LOGI(TAG, "example_event_listener: State change for #%d, from %s to %s",
|
||||
ch, relay_chn_state_to_str(old_state), relay_chn_state_to_str(new_state));
|
||||
switch (new_state) {
|
||||
case RELAY_CHN_STATE_FORWARD:
|
||||
case RELAY_CHN_STATE_REVERSE:
|
||||
led_indicator_start(indicators[ch], INDICATOR_MODE_RUNNING);
|
||||
break;
|
||||
|
||||
case RELAY_CHN_STATE_STOPPED:
|
||||
case RELAY_CHN_STATE_IDLE:
|
||||
if (old_state == RELAY_CHN_STATE_FORWARD || old_state == RELAY_CHN_STATE_REVERSE) {
|
||||
led_indicator_stop(indicators[ch], INDICATOR_MODE_RUNNING);
|
||||
// Make sure the indicator turned off
|
||||
led_indicator_set_on_off(indicators[ch], false);
|
||||
}
|
||||
else if (old_state == RELAY_CHN_STATE_TILT_FORWARD || old_state == RELAY_CHN_STATE_TILT_REVERSE) {
|
||||
led_indicator_stop(indicators[ch], INDICATOR_MODE_TILTING);
|
||||
// Make sure the indicator turned off
|
||||
led_indicator_set_on_off(indicators[ch], false);
|
||||
}
|
||||
break;
|
||||
|
||||
case RELAY_CHN_STATE_TILT_FORWARD:
|
||||
case RELAY_CHN_STATE_TILT_REVERSE:
|
||||
led_indicator_start(indicators[ch], INDICATOR_MODE_TILTING);
|
||||
break;
|
||||
|
||||
default: // No-op
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t init_buttons()
|
||||
{
|
||||
esp_err_t ret;
|
||||
button_config_t btn_cfg = {0};
|
||||
|
||||
uint8_t active_level = CONFIG_EXAMPLE_RLCHN_BTN_ACTIVE_LEVEL_LOW ? 0 : 1;
|
||||
|
||||
button_gpio_config_t btn_gpio_ccfg = {
|
||||
.gpio_num = (gpio_num_t) CONFIG_EXAMPLE_RLCHN_BTN_UP_IO_NUM,
|
||||
.active_level = active_level
|
||||
};
|
||||
|
||||
ESP_LOGI(TAG, "Initializing buttons with active level: %u", active_level);
|
||||
button_handle_t btn_up = NULL, btn_down = NULL, btn_stop = NULL, btn_select = NULL;
|
||||
// --- Create buttons ---
|
||||
ret = iot_button_new_gpio_device(&btn_cfg, &btn_gpio_ccfg, &btn_up);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create UP button");
|
||||
ESP_RETURN_ON_FALSE(btn_up != NULL, ret, TAG, "Failed to create UP button");
|
||||
|
||||
btn_gpio_ccfg.gpio_num = (gpio_num_t) CONFIG_EXAMPLE_RLCHN_BTN_DOWN_IO_NUM;
|
||||
ret = iot_button_new_gpio_device(&btn_cfg, &btn_gpio_ccfg, &btn_down);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create DOWN button");
|
||||
ESP_RETURN_ON_FALSE(btn_down != NULL, ret, TAG, "Failed to create DOWN button");
|
||||
|
||||
btn_gpio_ccfg.gpio_num = (gpio_num_t) CONFIG_EXAMPLE_RLCHN_BTN_STOP_IO_NUM;
|
||||
ret = iot_button_new_gpio_device(&btn_cfg, &btn_gpio_ccfg, &btn_stop);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create STOP button");
|
||||
ESP_RETURN_ON_FALSE(btn_stop != NULL, ret, TAG, "Failed to create STOP button");
|
||||
|
||||
btn_gpio_ccfg.gpio_num = (gpio_num_t) CONFIG_EXAMPLE_RLCHN_BTN_SELECT_IO_NUM;
|
||||
ret = iot_button_new_gpio_device(&btn_cfg, &btn_gpio_ccfg, &btn_select);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create SELECT button");
|
||||
ESP_RETURN_ON_FALSE(btn_select != NULL, ret, TAG, "Failed to create SELECT button");
|
||||
// --- Create buttons ---
|
||||
|
||||
// --- Register button callbacks ---
|
||||
ESP_LOGI(TAG, "Setting up button callbacks. Configured long press time: %d ms", CONFIG_EXAMPLE_RLCHN_BTN_LONG_PRESS_TIME_MS);
|
||||
button_event_args_t btn_event_args = {
|
||||
.long_press.press_time = CONFIG_EXAMPLE_RLCHN_BTN_LONG_PRESS_TIME_MS
|
||||
};
|
||||
// --- Register UP and TILT_UP operations on UP button ---
|
||||
ret = iot_button_register_cb(btn_up, BUTTON_SINGLE_CLICK, NULL, on_click_up, NULL);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register UP button click callback");
|
||||
ret = iot_button_register_cb(btn_up, BUTTON_LONG_PRESS_START, &btn_event_args, on_click_tilt_up, NULL);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register TILT_UP button press callback");
|
||||
ret = iot_button_register_cb(btn_up, BUTTON_LONG_PRESS_UP, NULL, on_release_tilt, NULL);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register TILT_UP button release callback");
|
||||
|
||||
// --- Register DOWN and TILT_DOWN operations on DOWN button ---
|
||||
ret = iot_button_register_cb(btn_down, BUTTON_SINGLE_CLICK, NULL, on_click_down, NULL);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register DOWN button click callback");
|
||||
ret = iot_button_register_cb(btn_down, BUTTON_LONG_PRESS_START, &btn_event_args, on_click_tilt_down, NULL);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register TILT_DOWN button press callback");
|
||||
ret = iot_button_register_cb(btn_down, BUTTON_LONG_PRESS_UP, NULL, on_release_tilt, NULL);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register TILT_DOWN button release callback");
|
||||
|
||||
// --- Register STOP and FLIP operations on STOP ---
|
||||
ret = iot_button_register_cb(btn_stop, BUTTON_SINGLE_CLICK, NULL, on_click_stop, NULL);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register STOP button click callback");
|
||||
ret = iot_button_register_cb(btn_stop, BUTTON_LONG_PRESS_START, &btn_event_args, on_click_flip, NULL);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register FLIP button press callback");
|
||||
|
||||
// --- Register channel SELECT and SELECT_ALL ---
|
||||
ret = iot_button_register_cb(btn_select, BUTTON_SINGLE_CLICK, NULL, on_click_select, NULL);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register SELECT button click callback");
|
||||
ret = iot_button_register_cb(btn_select, BUTTON_LONG_PRESS_START, &btn_event_args, on_click_select_all, NULL);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register SELECT_ALL press callback");
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t init_led_indicators()
|
||||
{
|
||||
const int indicator_io_nums[CONFIG_RELAY_CHN_COUNT] = {
|
||||
CONFIG_EXAMPLE_RLCHN_LED_INDICATOR1_IO_NUM,
|
||||
CONFIG_EXAMPLE_RLCHN_LED_INDICATOR2_IO_NUM,
|
||||
CONFIG_EXAMPLE_RLCHN_LED_INDICATOR3_IO_NUM
|
||||
};
|
||||
|
||||
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
gpio_num_t indicator_io_num = (gpio_num_t) indicator_io_nums[i];
|
||||
gpio_reset_pin(indicator_io_num); // Clear the output buffers
|
||||
|
||||
led_indicator_gpio_config_t led_indicator_gpio_cfg = {
|
||||
.gpio_num = indicator_io_num,
|
||||
.is_active_level_high = true
|
||||
};
|
||||
|
||||
led_indicator_config_t led_indicator_cfg = {
|
||||
.mode = LED_GPIO_MODE,
|
||||
.led_indicator_gpio_config = &led_indicator_gpio_cfg,
|
||||
.blink_lists = led_indicator_modes,
|
||||
.blink_list_num = INDICATOR_MODE_MAX
|
||||
};
|
||||
|
||||
indicators[i] = led_indicator_create(&led_indicator_cfg);
|
||||
if (!indicators[i]) {
|
||||
ESP_LOGE(TAG, "Failed to create LED indicator");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
10
examples/relay_chn_multi/sdkconfig.defaults
Normal file
10
examples/relay_chn_multi/sdkconfig.defaults
Normal file
@@ -0,0 +1,10 @@
|
||||
# Halt on panic
|
||||
CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT=y
|
||||
|
||||
# Relay Channel Configs
|
||||
CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT=y
|
||||
CONFIG_RELAY_CHN_COUNT=3
|
||||
# Keep this as short as possible for example purposes
|
||||
CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC=5
|
||||
CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC=20
|
||||
CONFIG_RELAY_CHN_ENABLE_TILTING=y
|
||||
6
examples/relay_chn_single/.gitignore
vendored
Normal file
6
examples/relay_chn_single/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
build/
|
||||
sdkconfig
|
||||
sdkconfig.old
|
||||
|
||||
# Exclude auto-populated settings file
|
||||
settings.json
|
||||
23
examples/relay_chn_single/.vscode/c_cpp_properties.json
vendored
Normal file
23
examples/relay_chn_single/.vscode/c_cpp_properties.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "ESP-IDF",
|
||||
"compilerPath": "${config:idf.toolsPath}/tools/riscv32-esp-elf/esp-14.2.0_20241119/riscv32-esp-elf/bin/riscv32-esp-elf-gcc",
|
||||
"compileCommands": "/disk/Projeler/ESP-Components/relay_chn/examples/relay_chn_single/build/compile_commands.json",
|
||||
"includePath": [
|
||||
"${config:idf.espIdfPath}/components/**",
|
||||
"${config:idf.espIdfPathWin}/components/**",
|
||||
"${workspaceFolder}/**"
|
||||
],
|
||||
"browse": {
|
||||
"path": [
|
||||
"${config:idf.espIdfPath}/components",
|
||||
"${config:idf.espIdfPathWin}/components",
|
||||
"${workspaceFolder}"
|
||||
],
|
||||
"limitSymbolsToIncludedHeaders": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
}
|
||||
15
examples/relay_chn_single/.vscode/launch.json
vendored
Normal file
15
examples/relay_chn_single/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "gdbtarget",
|
||||
"request": "attach",
|
||||
"name": "Eclipse CDT GDB Adapter"
|
||||
},
|
||||
{
|
||||
"type": "espidf",
|
||||
"name": "Launch",
|
||||
"request": "launch"
|
||||
}
|
||||
]
|
||||
}
|
||||
10
examples/relay_chn_single/CMakeLists.txt
Normal file
10
examples/relay_chn_single/CMakeLists.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
# For more information about build system see
|
||||
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
|
||||
# The following five lines of boilerplate have to be in your project's
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
set(COMPONENTS main)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(relay_chn_single)
|
||||
126
examples/relay_chn_single/README.md
Normal file
126
examples/relay_chn_single/README.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# Relay Channel Single Example
|
||||
|
||||
## Introduction
|
||||
|
||||
This example demonstrates how to use the relay channel component to control a 2-relay setup with button inputs and LED status indication. It showcases:
|
||||
|
||||
- Basic relay channel operations (forward/reverse running, stopping)
|
||||
- Secondary operations (tilting, direction flipping)
|
||||
- State change event handling with multiple listeners
|
||||
- Relay channel run limit
|
||||
- Button event handling using esp-iot-solution's button component
|
||||
- Visual feedback using esp-iot-solution's LED indicator component
|
||||
|
||||
## How to Use Example
|
||||
|
||||
This example has been tested on an `ESP32-C3-DevKitM-1U` board. However, it can be adapted to any ESP32-based board with at least six available GPIO pins by adjusting the configuration options.
|
||||
|
||||
### Hardware Required
|
||||
|
||||
* An ESP32-based development board
|
||||
* 2 relays connected to GPIO pins (default: GPIO4, GPIO5)
|
||||
* 3 buttons connected to GPIO pins:
|
||||
- UP button (default: GPIO0)
|
||||
- DOWN button (default: GPIO1)
|
||||
- STOP button (default: GPIO2)
|
||||
* 1 LED for status indication (default: GPIO3)
|
||||
|
||||
#### Hardware Schematic
|
||||
|
||||

|
||||
|
||||
### Configuration
|
||||
|
||||
The example can be configured through `menuconfig` under "Relay Channel Single Example Configuration":
|
||||
|
||||
1. Button active level (`EXAMPLE_RLCHN_BTN_ACTIVE_LEVEL`)
|
||||
- Select between active LOW or HIGH logic level for buttons
|
||||
|
||||
2. GPIO assignments:
|
||||
- UP button pin (`EXAMPLE_RLCHN_BTN_UP_IO_NUM`, default: 0)
|
||||
- DOWN button pin (`EXAMPLE_RLCHN_BTN_DOWN_IO_NUM`, default: 1)
|
||||
- STOP button pin (`EXAMPLE_RLCHN_BTN_STOP_IO_NUM`, default: 2)
|
||||
- LED indicator pin (`EXAMPLE_RLCHN_LED_INDICATOR_IO_NUM`, default: 3)
|
||||
|
||||
3. Long press timing:
|
||||
- `EXAMPLE_RLCHN_BTN_LONG_PRESS_TIME_MS`: Duration for long press actions (1500-3000ms, default: 2000ms)
|
||||
|
||||
### Button Operations
|
||||
|
||||
The example uses esp-iot-solution's `button` component to handle the following operations:
|
||||
|
||||
- **UP button**:
|
||||
* Short press: Start forward movement
|
||||
* Long press: Start forward tilt (stops on release)
|
||||
|
||||
- **DOWN button**:
|
||||
* Short press: Start reverse movement
|
||||
* Long press: Start reverse tilt (stops on release)
|
||||
|
||||
- **STOP button**:
|
||||
* Short press: Stop movement
|
||||
* Long press: Flip movement direction
|
||||
|
||||
### LED Indicator States
|
||||
|
||||
The example uses esp-iot-solution's `led_indicator` component to show different states:
|
||||
|
||||
- **Running**: LED blinks at 300ms on, 100ms off
|
||||
- **Tilting**: Fast blink at 100ms on, 50ms off
|
||||
- **Operation Success**: Two quick blinks
|
||||
- **Operation Fail**: One long blink
|
||||
|
||||
### Dependencies
|
||||
|
||||
This example requires:
|
||||
- ESP-IDF v4.1 or later
|
||||
- esp-iot-solution components:
|
||||
* button v4.1.1 or later
|
||||
* led_indicator v1.1.1 or later
|
||||
- relay_chn component
|
||||
|
||||
## Example Output
|
||||
When the application boots, it will wait for a button event. Then the two state listeners will print state changes.
|
||||
|
||||
```log
|
||||
I (273) main_task: Calling app_main()
|
||||
I (273) RELAY_CHN_SINGLE_EXAMPLE: Initializing default NVS storage
|
||||
I (283) RELAY_CHN_SINGLE_EXAMPLE: nvs_flash_init: NVS flash init return: ESP_OK
|
||||
I (283) RELAY_CHN_SINGLE_EXAMPLE: Initializing relay channel
|
||||
I (293) gpio: GPIO[4]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (303) gpio: GPIO[5]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (313) RELAY_CHN_SINGLE_EXAMPLE: Initializing buttons
|
||||
I (313) RELAY_CHN_SINGLE_EXAMPLE: Initializing buttons with active level: 0
|
||||
I (323) gpio: GPIO[0]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (323) button: IoT Button Version: 4.1.3
|
||||
I (333) gpio: GPIO[1]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (343) gpio: GPIO[2]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (343) RELAY_CHN_SINGLE_EXAMPLE: Setting up button callbacks. Configured long press time: 2000 ms
|
||||
I (353) RELAY_CHN_SINGLE_EXAMPLE: Initializing LED indicator
|
||||
I (363) gpio: GPIO[3]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (373) led_indicator: LED Indicator Version: 1.1.1
|
||||
I (373) gpio: GPIO[3]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
|
||||
I (383) led_indicator: Indicator create successfully. type:GPIO mode, hardware_data:0x3fc97be4, blink_lists:custom
|
||||
I (393) RELAY_CHN_SINGLE_EXAMPLE: Relay Channel Single Example is ready to operate
|
||||
I (403) main_task: Returned from app_main()
|
||||
I (3683) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_1: Defining new indicator mode for #0 and state change from 1 to 3
|
||||
I (3683) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_2: State change for #0, from IDLE to FORWARD
|
||||
I (9513) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_1: Defining new indicator mode for #0 and state change from 3 to 2
|
||||
I (9513) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_2: State change for #0, from FORWARD to STOPPED
|
||||
I (9523) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_1: Defining new indicator mode for #0 and state change from 2 to 6
|
||||
I (9533) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_2: State change for #0, from STOPPED to REVERSE_PENDING
|
||||
I (10313) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_1: Defining new indicator mode for #0 and state change from 6 to 4
|
||||
I (10313) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_2: State change for #0, from REVERSE_PENDING to REVERSE
|
||||
I (32173) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_1: Defining new indicator mode for #0 and state change from 4 to 2
|
||||
I (32173) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_2: State change for #0, from REVERSE to STOPPED
|
||||
I (32973) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_1: Defining new indicator mode for #0 and state change from 2 to 1
|
||||
I (32973) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_2: State change for #0, from STOPPED to IDLE
|
||||
I (36423) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_1: Defining new indicator mode for #0 and state change from 1 to 8
|
||||
I (36423) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_2: State change for #0, from IDLE to TILT_REVERSE
|
||||
I (41153) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_1: Defining new indicator mode for #0 and state change from 8 to 1
|
||||
I (41153) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_2: State change for #0, from TILT_REVERSE to IDLE
|
||||
I (47113) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_1: Defining new indicator mode for #0 and state change from 1 to 7
|
||||
I (47113) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_2: State change for #0, from IDLE to TILT_FORWARD
|
||||
I (51913) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_1: Defining new indicator mode for #0 and state change from 7 to 1
|
||||
I (51913) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_2: State change for #0, from TILT_FORWARD to IDLE
|
||||
```
|
||||
BIN
examples/relay_chn_single/example_schematic.png
Normal file
BIN
examples/relay_chn_single/example_schematic.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 132 KiB |
2
examples/relay_chn_single/main/CMakeLists.txt
Normal file
2
examples/relay_chn_single/main/CMakeLists.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "relay_chn_single_main.c"
|
||||
PRIV_REQUIRES button led_indicator relay_chn)
|
||||
40
examples/relay_chn_single/main/Kconfig.projbuild
Normal file
40
examples/relay_chn_single/main/Kconfig.projbuild
Normal file
@@ -0,0 +1,40 @@
|
||||
menu "Relay Channel Single Example Configuration"
|
||||
|
||||
choice EXAMPLE_RLCHN_BTN_ACTIVE_LEVEL
|
||||
prompt "Choose an active level for buttons"
|
||||
default EXAMPLE_RLCHN_BTN_ACTIVE_LEVEL_LOW
|
||||
help
|
||||
Specify the active level for buttons.
|
||||
|
||||
config EXAMPLE_RLCHN_BTN_ACTIVE_LEVEL_LOW
|
||||
bool "Active level LOW"
|
||||
|
||||
config EXAMPLE_RLCHN_BTN_ACTIVE_LEVEL_HIGH
|
||||
bool "Active level HIGH"
|
||||
endchoice
|
||||
|
||||
config EXAMPLE_RLCHN_BTN_UP_IO_NUM
|
||||
int "GPIO number for UP button"
|
||||
default 0
|
||||
|
||||
config EXAMPLE_RLCHN_BTN_DOWN_IO_NUM
|
||||
int "GPIO number for DOWN button"
|
||||
default 1
|
||||
|
||||
config EXAMPLE_RLCHN_BTN_STOP_IO_NUM
|
||||
int "GPIO number for STOP button"
|
||||
default 2
|
||||
|
||||
config EXAMPLE_RLCHN_LED_INDICATOR_IO_NUM
|
||||
int "GPIO number for LED indicator output"
|
||||
default 3
|
||||
|
||||
config EXAMPLE_RLCHN_BTN_LONG_PRESS_TIME_MS
|
||||
int "Long press time in ms to start secondary actions"
|
||||
range 1500 3000
|
||||
default 2000
|
||||
help
|
||||
Long press time in milliseconds is required to start secondary actions
|
||||
like tilting and flipping.
|
||||
|
||||
endmenu
|
||||
8
examples/relay_chn_single/main/idf_component.yml
Normal file
8
examples/relay_chn_single/main/idf_component.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
dependencies:
|
||||
idf:
|
||||
version: '>=4.1.0'
|
||||
espressif/button: ^4.1.1
|
||||
espressif/led_indicator: ^1.1.1
|
||||
relay_chn:
|
||||
version: '*'
|
||||
override_path: ../../../
|
||||
290
examples/relay_chn_single/main/relay_chn_single_main.c
Normal file
290
examples/relay_chn_single/main/relay_chn_single_main.c
Normal file
@@ -0,0 +1,290 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_check.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "button_gpio.h"
|
||||
#include "iot_button.h"
|
||||
#include "led_indicator.h"
|
||||
#include "relay_chn.h"
|
||||
|
||||
static const char *TAG = "RELAY_CHN_SINGLE_EXAMPLE";
|
||||
|
||||
/**
|
||||
* @brief LED indicator modes for different states.
|
||||
*/
|
||||
typedef enum {
|
||||
INDICATOR_MODE_OK, /*!< OK/Success indication */
|
||||
INDICATOR_MODE_FAIL, /*!< Fail/Error indication */
|
||||
INDICATOR_MODE_TILTING, /*!< Tilting operation in progress */
|
||||
INDICATOR_MODE_RUNNING, /*!< Full run operation in progress */
|
||||
INDICATOR_MODE_MAX /*!< Maximum number of indicator modes */
|
||||
} indicator_mode_t;
|
||||
|
||||
/** @brief Blink pattern for OK/Success indication. */
|
||||
static const blink_step_t indc_mode_ok[] = {
|
||||
{LED_BLINK_HOLD, LED_STATE_ON, 100}, // step1: turn on LED 100 ms
|
||||
{LED_BLINK_HOLD, LED_STATE_OFF, 50}, // step2: turn off LED 50 ms
|
||||
{LED_BLINK_HOLD, LED_STATE_ON, 100}, // step3: turn on LED 100 ms
|
||||
{LED_BLINK_HOLD, LED_STATE_OFF, 50}, // step4: turn off LED 50 ms
|
||||
{LED_BLINK_STOP, 0, 0}, // step5: stop blink (off)
|
||||
};
|
||||
|
||||
/** @brief Blink pattern for Fail/Error indication. */
|
||||
static const blink_step_t indc_mode_fail[] = {
|
||||
{LED_BLINK_HOLD, LED_STATE_ON, 1000}, // step1: turn on LED 1000 ms
|
||||
{LED_BLINK_HOLD, LED_STATE_OFF, 500}, // step2: turn off LED 500 ms
|
||||
{LED_BLINK_STOP, 0, 0}, // step4: stop blink (off)
|
||||
};
|
||||
|
||||
/** @brief Blink pattern for full run operation. */
|
||||
static const blink_step_t indc_mode_running[] = {
|
||||
{LED_BLINK_HOLD, LED_STATE_ON, 300}, // step1: turn on LED 300 ms
|
||||
{LED_BLINK_HOLD, LED_STATE_OFF, 100}, // step2: turn off LED 100 ms
|
||||
{LED_BLINK_LOOP, 0, 0}, // step3: loop from step1
|
||||
};
|
||||
|
||||
/** @brief Blink pattern for tilting operation. */
|
||||
static const blink_step_t indc_mode_tilting[] = {
|
||||
{LED_BLINK_HOLD, LED_STATE_ON, 100}, // step1: turn on LED 100 ms
|
||||
{LED_BLINK_HOLD, LED_STATE_OFF, 50}, // step2: turn off LED 50 ms
|
||||
{LED_BLINK_LOOP, 0, 0}, // step3: loop from step1
|
||||
};
|
||||
|
||||
/** @brief Array of LED indicator blink patterns. */
|
||||
blink_step_t const *led_indicator_modes[] = {
|
||||
[INDICATOR_MODE_OK] = indc_mode_ok,
|
||||
[INDICATOR_MODE_FAIL] = indc_mode_fail,
|
||||
[INDICATOR_MODE_RUNNING] = indc_mode_running,
|
||||
[INDICATOR_MODE_TILTING] = indc_mode_tilting,
|
||||
[INDICATOR_MODE_MAX] = NULL,
|
||||
};
|
||||
|
||||
/** @brief Handle for the LED indicator. */
|
||||
static led_indicator_handle_t indicator = NULL;
|
||||
|
||||
/**
|
||||
* @brief Initializes the buttons for user interaction.
|
||||
*
|
||||
* This function configures and creates GPIO buttons for UP, DOWN, and STOP
|
||||
* operations. It also registers callbacks for single-click and long-press
|
||||
* events to control the relay channel.
|
||||
*
|
||||
* @return esp_err_t
|
||||
* - ESP_OK: Success
|
||||
* - Others: Fail
|
||||
*/
|
||||
static esp_err_t init_buttons(void);
|
||||
|
||||
/**
|
||||
* @brief Initializes the LED indicator.
|
||||
*
|
||||
* This function configures and creates the LED indicator used to provide
|
||||
* visual feedback on the relay channel's status.
|
||||
*
|
||||
* @return esp_err_t
|
||||
* - ESP_OK: Success
|
||||
* - ESP_FAIL: Fail
|
||||
*/
|
||||
static esp_err_t init_led_indicator(void);
|
||||
|
||||
/**
|
||||
* @brief Event listener for relay channel state changes to control the LED indicator.
|
||||
*/
|
||||
static void example_event_listener_1(uint8_t ch, relay_chn_state_t old_state, relay_chn_state_t new_state);
|
||||
|
||||
/**
|
||||
* @brief Event listener for relay channel state changes to log the state transition.
|
||||
*/
|
||||
static void example_event_listener_2(uint8_t ch, relay_chn_state_t old_state, relay_chn_state_t new_state);
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
const uint8_t gpio_map[] = { 4, 5 };
|
||||
const uint8_t gpio_count = sizeof(gpio_map) / sizeof(gpio_map[0]);
|
||||
|
||||
ESP_LOGI(TAG, "Initializing relay channel");
|
||||
ESP_ERROR_CHECK(relay_chn_create(gpio_map, gpio_count));
|
||||
ESP_ERROR_CHECK(relay_chn_register_listener(example_event_listener_1));
|
||||
ESP_ERROR_CHECK(relay_chn_register_listener(example_event_listener_2));
|
||||
|
||||
ESP_LOGI(TAG, "Initializing buttons");
|
||||
ESP_ERROR_CHECK(init_buttons());
|
||||
ESP_LOGI(TAG, "Initializing LED indicator");
|
||||
ESP_ERROR_CHECK(init_led_indicator());
|
||||
|
||||
ESP_LOGI(TAG, "Relay Channel Single Example is ready to operate");
|
||||
|
||||
// Indicate init was successful
|
||||
led_indicator_start(indicator, INDICATOR_MODE_OK);
|
||||
}
|
||||
|
||||
static void on_click_up(void *arg, void *data)
|
||||
{
|
||||
relay_chn_run_forward();
|
||||
}
|
||||
|
||||
static void on_click_down(void *arg, void *data)
|
||||
{
|
||||
relay_chn_run_reverse();
|
||||
}
|
||||
|
||||
static void on_click_stop(void *arg, void *data)
|
||||
{
|
||||
relay_chn_stop();
|
||||
}
|
||||
|
||||
static void on_click_flip(void *arg, void *data)
|
||||
{
|
||||
relay_chn_flip_direction();
|
||||
led_indicator_start(indicator, INDICATOR_MODE_OK);
|
||||
}
|
||||
|
||||
static void on_click_tilt_up(void *arg, void *data)
|
||||
{
|
||||
relay_chn_tilt_forward();
|
||||
}
|
||||
|
||||
static void on_click_tilt_down(void *arg, void *data)
|
||||
{
|
||||
relay_chn_tilt_reverse();
|
||||
}
|
||||
|
||||
static void on_release_tilt(void *arg, void *data)
|
||||
{
|
||||
relay_chn_tilt_stop();
|
||||
}
|
||||
|
||||
static void example_event_listener_1(uint8_t ch, relay_chn_state_t old_state, relay_chn_state_t new_state)
|
||||
{
|
||||
ESP_LOGI(TAG, "example_event_listener_1: Defining new indicator mode for #%d and state change from %d to %d", ch, old_state, new_state);
|
||||
switch (new_state) {
|
||||
case RELAY_CHN_STATE_FORWARD:
|
||||
case RELAY_CHN_STATE_REVERSE:
|
||||
led_indicator_start(indicator, INDICATOR_MODE_RUNNING);
|
||||
break;
|
||||
|
||||
case RELAY_CHN_STATE_STOPPED:
|
||||
case RELAY_CHN_STATE_IDLE:
|
||||
if (old_state == RELAY_CHN_STATE_FORWARD || old_state == RELAY_CHN_STATE_REVERSE) {
|
||||
led_indicator_stop(indicator, INDICATOR_MODE_RUNNING);
|
||||
// Make sure the indicator turned off
|
||||
led_indicator_set_on_off(indicator, false);
|
||||
}
|
||||
else if (old_state == RELAY_CHN_STATE_TILT_FORWARD || old_state == RELAY_CHN_STATE_TILT_REVERSE) {
|
||||
led_indicator_stop(indicator, INDICATOR_MODE_TILTING);
|
||||
// Make sure the indicator turned off
|
||||
led_indicator_set_on_off(indicator, false);
|
||||
}
|
||||
break;
|
||||
|
||||
case RELAY_CHN_STATE_TILT_FORWARD:
|
||||
case RELAY_CHN_STATE_TILT_REVERSE:
|
||||
led_indicator_start(indicator, INDICATOR_MODE_TILTING);
|
||||
break;
|
||||
|
||||
default: // No-op
|
||||
}
|
||||
}
|
||||
|
||||
static void example_event_listener_2(uint8_t ch, relay_chn_state_t old_state, relay_chn_state_t new_state)
|
||||
{
|
||||
ESP_LOGI(TAG, "example_event_listener_2: State change for #%d, from %s to %s",
|
||||
ch, relay_chn_state_to_str(old_state), relay_chn_state_to_str(new_state));
|
||||
}
|
||||
|
||||
static esp_err_t init_buttons()
|
||||
{
|
||||
esp_err_t ret;
|
||||
button_config_t btn_cfg = {0};
|
||||
|
||||
uint8_t active_level = CONFIG_EXAMPLE_RLCHN_BTN_ACTIVE_LEVEL_LOW ? 0 : 1;
|
||||
|
||||
button_gpio_config_t btn_gpio_ccfg = {
|
||||
.gpio_num = (gpio_num_t) CONFIG_EXAMPLE_RLCHN_BTN_UP_IO_NUM,
|
||||
.active_level = active_level
|
||||
};
|
||||
|
||||
ESP_LOGI(TAG, "Initializing buttons with active level: %u", active_level);
|
||||
button_handle_t btn_up = NULL, btn_down = NULL, btn_stop = NULL;
|
||||
// --- Create buttons ---
|
||||
ret = iot_button_new_gpio_device(&btn_cfg, &btn_gpio_ccfg, &btn_up);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create UP button");
|
||||
ESP_RETURN_ON_FALSE(btn_up != NULL, ret, TAG, "Failed to create UP button");
|
||||
|
||||
btn_gpio_ccfg.gpio_num = (gpio_num_t) CONFIG_EXAMPLE_RLCHN_BTN_DOWN_IO_NUM;
|
||||
ret = iot_button_new_gpio_device(&btn_cfg, &btn_gpio_ccfg, &btn_down);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create DOWN button");
|
||||
ESP_RETURN_ON_FALSE(btn_down != NULL, ret, TAG, "Failed to create DOWN button");
|
||||
|
||||
btn_gpio_ccfg.gpio_num = (gpio_num_t) CONFIG_EXAMPLE_RLCHN_BTN_STOP_IO_NUM;
|
||||
ret = iot_button_new_gpio_device(&btn_cfg, &btn_gpio_ccfg, &btn_stop);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create STOP button");
|
||||
ESP_RETURN_ON_FALSE(btn_stop != NULL, ret, TAG, "Failed to create STOP button");
|
||||
// --- Create buttons ---
|
||||
|
||||
// --- Register button callbacks ---
|
||||
ESP_LOGI(TAG, "Setting up button callbacks. Configured long press time: %d ms", CONFIG_EXAMPLE_RLCHN_BTN_LONG_PRESS_TIME_MS);
|
||||
button_event_args_t btn_event_args = {
|
||||
.long_press.press_time = CONFIG_EXAMPLE_RLCHN_BTN_LONG_PRESS_TIME_MS
|
||||
};
|
||||
// --- Register UP and TILT_UP operations on UP button ---
|
||||
ret = iot_button_register_cb(btn_up, BUTTON_SINGLE_CLICK, NULL, on_click_up, NULL);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register UP button click callback");
|
||||
ret = iot_button_register_cb(btn_up, BUTTON_LONG_PRESS_START, &btn_event_args, on_click_tilt_up, NULL);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register TILT_UP button press callback");
|
||||
ret = iot_button_register_cb(btn_up, BUTTON_LONG_PRESS_UP, NULL, on_release_tilt, NULL);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register TILT_UP button release callback");
|
||||
|
||||
// --- Register DOWN and TILT_DOWN operations on DOWN button ---
|
||||
ret = iot_button_register_cb(btn_down, BUTTON_SINGLE_CLICK, NULL, on_click_down, NULL);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register DOWN button click callback");
|
||||
ret = iot_button_register_cb(btn_down, BUTTON_LONG_PRESS_START, &btn_event_args, on_click_tilt_down, NULL);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register TILT_DOWN button press callback");
|
||||
ret = iot_button_register_cb(btn_down, BUTTON_LONG_PRESS_UP, NULL, on_release_tilt, NULL);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register TILT_DOWN button release callback");
|
||||
|
||||
// --- Register STOP and FLIP operations on STOP ---
|
||||
ret = iot_button_register_cb(btn_stop, BUTTON_SINGLE_CLICK, NULL, on_click_stop, NULL);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register STOP button click callback");
|
||||
ret = iot_button_register_cb(btn_stop, BUTTON_LONG_PRESS_START, &btn_event_args, on_click_flip, NULL);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register FLIP button press callback");
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t init_led_indicator()
|
||||
{
|
||||
const gpio_num_t indicator_io_num = (gpio_num_t) CONFIG_EXAMPLE_RLCHN_LED_INDICATOR_IO_NUM;
|
||||
gpio_reset_pin(indicator_io_num); // Clear the output buffers
|
||||
|
||||
led_indicator_gpio_config_t led_indicator_gpio_cfg = {
|
||||
.gpio_num = indicator_io_num,
|
||||
.is_active_level_high = true
|
||||
};
|
||||
|
||||
led_indicator_config_t led_indicator_cfg = {
|
||||
.mode = LED_GPIO_MODE,
|
||||
.led_indicator_gpio_config = &led_indicator_gpio_cfg,
|
||||
.blink_lists = led_indicator_modes,
|
||||
.blink_list_num = INDICATOR_MODE_MAX
|
||||
};
|
||||
|
||||
indicator = led_indicator_create(&led_indicator_cfg);
|
||||
if (!indicator) {
|
||||
ESP_LOGE(TAG, "Failed to create LED indicator");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
9
examples/relay_chn_single/sdkconfig.defaults
Normal file
9
examples/relay_chn_single/sdkconfig.defaults
Normal file
@@ -0,0 +1,9 @@
|
||||
# Halt on panic
|
||||
CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT=y
|
||||
|
||||
# Relay Channel Configs
|
||||
CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT=y
|
||||
# Keep this as short as possible for example purposes
|
||||
CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC=5
|
||||
CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC=20
|
||||
CONFIG_RELAY_CHN_ENABLE_TILTING=y
|
||||
@@ -1,6 +1,12 @@
|
||||
name: relay_chn
|
||||
version: "0.5.0"
|
||||
description: "Custom component for relay channel control"
|
||||
version: "1.0.0"
|
||||
description: "Relay channel driver for bipolar motors."
|
||||
license: "MIT"
|
||||
url: "https://git.kozmotronik.com.tr/KozmotronikTech/relay_chn"
|
||||
repository: "https://git.kozmotronik.com.tr/KozmotronikTech/relay_chn.git"
|
||||
repository: "https://git.kozmotronik.com.tr/KozmotronikTech/relay_chn.git"
|
||||
dependencies:
|
||||
idf: ">=5.0"
|
||||
examples:
|
||||
- path: examples/relay_chn_single
|
||||
- path: examples/relay_chn_multi
|
||||
files:
|
||||
use_gitignore: true
|
||||
@@ -1,87 +1,26 @@
|
||||
#ifndef RELAY_CHN_H
|
||||
#define RELAY_CHN_H
|
||||
/**
|
||||
* @file relay_chn.h
|
||||
*
|
||||
* @author
|
||||
* Ismail Sahillioglu <ismailsahillioglu@gmail.com>
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* @date 2025.02.08
|
||||
*
|
||||
* @defgroup relay_chn Relay Channel Controller
|
||||
* @ingroup components
|
||||
* @{
|
||||
* One relay channel consists of 2 output relays, hence 2 GPIO pins are required for each relay channel.
|
||||
* This module provides an API to control the relay channels, specifically to drive bipolar motors.
|
||||
* It also provides APIs to control the direction of the relay channel, bipolar motors in mind.
|
||||
* To prevent mechanical strain on the motor, the component automatically manages direction changes
|
||||
* To prevent mechanical strain on the motor, the component automatically manages direction changes
|
||||
* with a configurable inertia delay, protecting it from abrupt reversals.
|
||||
* The STOP command overrides any other command and clears the pending command if any.
|
||||
*
|
||||
* The module internally uses a custom esp event loop to handle relay commands serially to ensure
|
||||
* reliability and prevent conflict operations. Also, the esp timer is used to manage the direction change inertia.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "driver/gpio.h"
|
||||
#include <stdint.h>
|
||||
#include "relay_chn_types.h"
|
||||
#include "relay_chn_adapter.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define RELAY_CHN_ID_ALL CONFIG_RELAY_CHN_COUNT ///< Special ID to address all channels
|
||||
|
||||
/**
|
||||
* @brief Enumeration for relay channel direction.
|
||||
*/
|
||||
enum relay_chn_direction_enum {
|
||||
RELAY_CHN_DIRECTION_DEFAULT, ///< Default direction of the relay channel.
|
||||
RELAY_CHN_DIRECTION_FLIPPED ///< Flipped direction of the relay channel.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Alias for the enum type relay_chn_direction_enum.
|
||||
*/
|
||||
typedef enum relay_chn_direction_enum relay_chn_direction_t;
|
||||
|
||||
/**
|
||||
* @brief Enums that represent the state of a relay channel.
|
||||
*/
|
||||
enum relay_chn_state_enum {
|
||||
RELAY_CHN_STATE_UNDEFINED, ///< The relay channel state is undefined.
|
||||
RELAY_CHN_STATE_FREE, ///< The relay channel is free to run or execute commands.
|
||||
RELAY_CHN_STATE_STOPPED, ///< The relay channel is stopped and not running.
|
||||
RELAY_CHN_STATE_FORWARD, ///< The relay channel is running in the forward direction.
|
||||
RELAY_CHN_STATE_REVERSE, ///< The relay channel is running in the reverse direction.
|
||||
RELAY_CHN_STATE_FORWARD_PENDING, ///< The relay channel is pending to run in the forward direction.
|
||||
RELAY_CHN_STATE_REVERSE_PENDING, ///< The relay channel is pending to run in the reverse direction.
|
||||
#if CONFIG_RELAY_CHN_ENABLE_TILTING == 1
|
||||
RELAY_CHN_STATE_TILT_FORWARD, ///< The relay channel is tilting for forward.
|
||||
RELAY_CHN_STATE_TILT_REVERSE, ///< The relay channel is tilting for reverse.
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Alias for the enum type relay_chn_state_enum.
|
||||
*/
|
||||
typedef enum relay_chn_state_enum relay_chn_state_t;
|
||||
|
||||
/**
|
||||
* @brief Relay channel state change listener.
|
||||
*
|
||||
* An optional interface to listen to the channel state change events.
|
||||
* The listeners SHOULD be implemented as light functions and SHOULD NOT contain
|
||||
* any blocking calls. Otherwise the relay_chn module would not function properly
|
||||
* since it is designed as event driven.
|
||||
*
|
||||
* @param chn_id The ID of the channel whose state has changed.
|
||||
* @param old_state The old state of the channel.
|
||||
* @param new_state The new state of the channel.
|
||||
*/
|
||||
typedef void (*relay_chn_state_listener_t)(uint8_t chn_id, relay_chn_state_t old_state, relay_chn_state_t new_state);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Create and initialize relay channels.
|
||||
*
|
||||
@@ -95,7 +34,7 @@ typedef void (*relay_chn_state_listener_t)(uint8_t chn_id, relay_chn_state_t old
|
||||
* - ESP_ERR_INVALID_ARG: Invalid argument
|
||||
* - ESP_FAIL: General failure
|
||||
*/
|
||||
esp_err_t relay_chn_create(const gpio_num_t* gpio_map, uint8_t gpio_count);
|
||||
esp_err_t relay_chn_create(const uint8_t* gpio_map, uint8_t gpio_count);
|
||||
|
||||
/**
|
||||
* @brief Destroy the relay channels and free resources.
|
||||
@@ -106,7 +45,7 @@ void relay_chn_destroy(void);
|
||||
|
||||
/**
|
||||
* @brief Register a channel state change listener.
|
||||
*
|
||||
*
|
||||
* @param listener A function that implements relay_chn_state_listener_t interface.
|
||||
*
|
||||
* @return
|
||||
@@ -119,11 +58,32 @@ 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 Return the text presentation of an state.
|
||||
*
|
||||
* @param state A state with type of relay_chn_state_t.
|
||||
* @return char* The text presentation of the state. "UNKNOWN" if the state is not known.
|
||||
*/
|
||||
char *relay_chn_state_to_str(relay_chn_state_t state);
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
/**
|
||||
* @brief Get the default tilting sensitivity for the relay channel.
|
||||
*
|
||||
* This function retrieves the default sensitivity for the relay channel's automatic
|
||||
* tilting mechanism.
|
||||
*
|
||||
* @return Sensitivity value in percentage: 0 - 100%.
|
||||
*/
|
||||
uint8_t relay_chn_tilt_get_default_sensitivity(void);
|
||||
#endif
|
||||
|
||||
#if CONFIG_RELAY_CHN_COUNT > 1
|
||||
/**
|
||||
* @brief Get the state of the specified relay channel.
|
||||
*
|
||||
@@ -134,6 +94,18 @@ void relay_chn_unregister_listener(relay_chn_state_listener_t listener);
|
||||
*/
|
||||
relay_chn_state_t relay_chn_get_state(uint8_t chn_id);
|
||||
|
||||
/**
|
||||
* @brief Gets the current state of all relay channels.
|
||||
*
|
||||
* This function populates an array with the current states of all configured
|
||||
* relay channels. The caller must ensure the `states` array is large enough
|
||||
* to hold `CONFIG_RELAY_CHN_COUNT` elements.
|
||||
*
|
||||
* @param states Pointer to an array where the states will be stored.
|
||||
* @return ESP_OK on success, ESP_ERR_INVALID_ARG if `states` is NULL.
|
||||
*/
|
||||
esp_err_t relay_chn_ctl_get_state_all(relay_chn_state_t *states);
|
||||
|
||||
/**
|
||||
* @brief Get the state string of the specified relay channel.
|
||||
*
|
||||
@@ -150,14 +122,6 @@ relay_chn_state_t relay_chn_get_state(uint8_t chn_id);
|
||||
*/
|
||||
char *relay_chn_get_state_str(uint8_t chn_id);
|
||||
|
||||
/**
|
||||
* @brief Return the text presentation of an state.
|
||||
*
|
||||
* @param state A state with type of relay_chn_state_t.
|
||||
* @return char* The text presentation of the state. "UNKNOWN" if the state is not known.
|
||||
*/
|
||||
char *relay_chn_state_str(relay_chn_state_t state);
|
||||
|
||||
/**
|
||||
* @brief Runs the relay channel in the forward direction.
|
||||
*
|
||||
@@ -167,6 +131,14 @@ char *relay_chn_state_str(relay_chn_state_t state);
|
||||
*/
|
||||
void relay_chn_run_forward(uint8_t chn_id);
|
||||
|
||||
/**
|
||||
* @brief Commands all configured relay channels to run in the forward direction.
|
||||
*
|
||||
* This function iterates through all configured relay channels and issues a command
|
||||
* to each to move in the forward direction.
|
||||
*/
|
||||
void relay_chn_ctl_run_forward_all(void);
|
||||
|
||||
/**
|
||||
* @brief Runs the relay channel in reverse.
|
||||
*
|
||||
@@ -176,29 +148,52 @@ void relay_chn_run_forward(uint8_t chn_id);
|
||||
*/
|
||||
void relay_chn_run_reverse(uint8_t chn_id);
|
||||
|
||||
/**
|
||||
* @brief Commands all configured relay channels to run in the reverse direction.
|
||||
*
|
||||
* This function iterates through all configured relay channels and issues a command
|
||||
* to each to move in the reverse direction.
|
||||
*/
|
||||
void relay_chn_ctl_run_reverse_all(void);
|
||||
|
||||
/**
|
||||
* @brief Stops the relay channel specified by the channel ID.
|
||||
*
|
||||
* This function stops the operation of the relay channel identified by the
|
||||
* provided channel ID. It is typically used to turn off or disable the relay
|
||||
* This function stops the operation of the relay channel identified by the
|
||||
* provided channel ID. It is typically used to turn off or disable the relay
|
||||
* channel.
|
||||
*
|
||||
* @param chn_id The ID of the relay channel to stop.
|
||||
*/
|
||||
void relay_chn_stop(uint8_t chn_id);
|
||||
|
||||
/**
|
||||
* @brief Commands all configured relay channels to stop.
|
||||
*
|
||||
* This function iterates through all configured relay channels and issues a command
|
||||
* to each to stop any ongoing movement.
|
||||
*/
|
||||
void relay_chn_ctl_stop_all(void);
|
||||
|
||||
/**
|
||||
* @brief Flips the direction of the specified relay channel.
|
||||
*
|
||||
* This function toggles the direction of the relay channel identified by the
|
||||
* given channel ID. It is typically used to change the state of the relay
|
||||
* from its current direction to the opposite direction.
|
||||
* given channel ID.
|
||||
*
|
||||
* @param chn_id The ID of the relay channel to flip. This should be a valid
|
||||
* channel ID within the range of available relay channels.
|
||||
*/
|
||||
void relay_chn_flip_direction(uint8_t chn_id);
|
||||
|
||||
/**
|
||||
* @brief Flips the logical direction of all configured relay channels.
|
||||
*
|
||||
* This function iterates through all configured relay channels and swaps the
|
||||
* physical GPIO pins assigned to the forward and reverse directions for each.
|
||||
*/
|
||||
void relay_chn_ctl_flip_direction_all(void);
|
||||
|
||||
/**
|
||||
* @brief Get the direction of the specified relay channel.
|
||||
*
|
||||
@@ -211,40 +206,132 @@ void relay_chn_flip_direction(uint8_t chn_id);
|
||||
*/
|
||||
relay_chn_direction_t relay_chn_get_direction(uint8_t chn_id);
|
||||
|
||||
/**
|
||||
* @brief Gets the current logical direction of all configured relay channels.
|
||||
*
|
||||
* This function populates an array with the current logical directions of all
|
||||
* configured relay channels. The caller must ensure the `directions` array is
|
||||
* large enough to hold `CONFIG_RELAY_CHN_COUNT` elements.
|
||||
*
|
||||
* @param directions Pointer to an array where the directions will be stored.
|
||||
* @return ESP_OK on success, ESP_ERR_INVALID_ARG if `directions` is NULL.
|
||||
*/
|
||||
esp_err_t relay_chn_ctl_get_direction_all(relay_chn_direction_t *directions);
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_TILTING == 1
|
||||
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
/**
|
||||
* @brief Get the run limit for the specified channel
|
||||
*
|
||||
* @param chn_id The ID of the relay channel to query.
|
||||
*
|
||||
* @return The run limit value for the relevant channel if the channel ID is valid.
|
||||
* 0 if the channel ID is invalid.
|
||||
*/
|
||||
uint16_t relay_chn_get_run_limit(uint8_t chn_id);
|
||||
|
||||
/**
|
||||
* @brief Gets the configured run limits for all configured relay channels.
|
||||
*
|
||||
* This function populates an array with the run limits (in seconds) of all
|
||||
* configured relay channels. The caller must ensure the `limits_sec` array is
|
||||
* large enough to hold `CONFIG_RELAY_CHN_COUNT` elements.
|
||||
*
|
||||
* @param limits_sec Pointer to an array where the run limits will be stored.
|
||||
* @return ESP_OK on success, ESP_ERR_INVALID_ARG if `limits_sec` is NULL.
|
||||
*/
|
||||
esp_err_t relay_chn_ctl_get_run_limit_all(uint16_t *limits_sec);
|
||||
|
||||
/**
|
||||
* @brief Set the run limit for the specified channel
|
||||
*
|
||||
* Sets the time limit in seconds for the specified channel. It will not proceed
|
||||
* if the channel ID is invalid.
|
||||
* If the limit_sec value is lesser than the CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC,
|
||||
* the value will be set to CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC.
|
||||
* If the limit_sec value is greater than the CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC,
|
||||
* the value will be set to CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC.
|
||||
*
|
||||
* @param chn_id The ID of the relay channel to query.
|
||||
* @param limit_sec The run limit time in seconds.
|
||||
*/
|
||||
void relay_chn_set_run_limit(uint8_t chn_id, uint16_t limit_sec);
|
||||
|
||||
/**
|
||||
* @brief Sets the run limits for all configured relay channels.
|
||||
*
|
||||
* This function iterates through all configured relay channels and sets their
|
||||
* run limits based on the values provided in the `limits_sec` array. Each value
|
||||
* will be clamped within the configured `RELAY_CHN_RUN_LIMIT_MIN_SEC` and
|
||||
* `RELAY_CHN_RUN_LIMIT_MAX_SEC` boundaries. The new run limits are persisted
|
||||
* in NVS if enabled.
|
||||
*
|
||||
* @param limits_sec Pointer to an array containing the desired run limits in seconds.
|
||||
* @return ESP_OK on success, ESP_ERR_INVALID_ARG if `limits_sec` is NULL.
|
||||
*/
|
||||
esp_err_t relay_chn_ctl_set_run_limit_all(uint16_t *limits_sec);
|
||||
|
||||
/**
|
||||
* @brief Sets a single run limit value for all configured relay channels.
|
||||
*
|
||||
* This function sets the same `limit_sec` value for all configured relay channels.
|
||||
* The value will be clamped within the configured `RELAY_CHN_RUN_LIMIT_MIN_SEC`
|
||||
* and `RELAY_CHN_RUN_LIMIT_MAX_SEC` boundaries.
|
||||
* @param limit_sec The desired run limit in seconds to apply to all channels.
|
||||
* @return ESP_OK on success.
|
||||
*/
|
||||
esp_err_t relay_chn_ctl_set_run_limit_all_with(uint16_t limit_sec);
|
||||
#endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1
|
||||
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
|
||||
/**
|
||||
* @brief Enables automatic tilting for the specified relay channel.
|
||||
*
|
||||
* This function enables automatic tilting mode for the given relay channel. The channel will automatically
|
||||
* switch between forward and reverse tilting based on some internal sensing mechanism (not detailed here).
|
||||
* Requires appropriate hardware support and configuration.
|
||||
* This function enables automatic tilting mode for the given relay channel.
|
||||
* The channel will automatically switch between forward and reverse tilting
|
||||
* based on the last movement of the channel
|
||||
*
|
||||
* @param chn_id The ID of the relay channel to enable automatic tilting.
|
||||
*/
|
||||
void relay_chn_tilt_auto(uint8_t chn_id);
|
||||
|
||||
/**
|
||||
* @brief Initiates an automatic tilt operation for all configured relay channels.
|
||||
*
|
||||
* This function iterates through all configured relay channels and initiates an
|
||||
* automatic tilt operation for each, based on their individual last run commands.
|
||||
*/
|
||||
void relay_chn_tilt_auto_all(void);
|
||||
|
||||
/**
|
||||
* @brief Tilts the specified relay channel forward.
|
||||
*
|
||||
* This function initiates a forward tilting action for the specified relay channel. This is a manual tilting
|
||||
* operation, unlike `relay_chn_tilt_auto()`.
|
||||
* This function initiates a forward tilting action for the specified relay channel.
|
||||
*
|
||||
* @param chn_id The ID of the relay channel to tilt forward.
|
||||
*/
|
||||
void relay_chn_tilt_forward(uint8_t chn_id);
|
||||
|
||||
/**
|
||||
* @brief Initiates a forward tilt operation for all configured relay channels.
|
||||
*/
|
||||
void relay_chn_tilt_forward_all(void);
|
||||
|
||||
/**
|
||||
* @brief Tilts the specified relay channel reverse.
|
||||
*
|
||||
* This function initiates a reverse tilting action for the specified relay channel. This is a manual tilting
|
||||
* operation, unlike `relay_chn_tilt_auto()`.
|
||||
* This function initiates a reverse tilting action for the specified relay channel.
|
||||
*
|
||||
* @param chn_id The ID of the relay channel to tilt reverse.
|
||||
*/
|
||||
void relay_chn_tilt_reverse(uint8_t chn_id);
|
||||
|
||||
/**
|
||||
* @brief Initiates a reverse tilt operation for all configured relay channels.
|
||||
*/
|
||||
void relay_chn_tilt_reverse_all(void);
|
||||
|
||||
/**
|
||||
* @brief Stops the tilting action on the specified relay channel.
|
||||
*
|
||||
@@ -254,6 +341,11 @@ void relay_chn_tilt_reverse(uint8_t chn_id);
|
||||
*/
|
||||
void relay_chn_tilt_stop(uint8_t chn_id);
|
||||
|
||||
/**
|
||||
* @brief Stops any ongoing tilt operation for all configured relay channels.
|
||||
*/
|
||||
void relay_chn_tilt_stop_all(void);
|
||||
|
||||
/**
|
||||
* @brief Sets the tilting sensitivity for the specified relay channel.
|
||||
*
|
||||
@@ -263,7 +355,34 @@ void relay_chn_tilt_stop(uint8_t chn_id);
|
||||
* @param chn_id The ID of the relay channel to set the sensitivity for.
|
||||
* @param sensitivity The sensitivity in percentage: 0 - 100%.
|
||||
*/
|
||||
void relay_chn_tilt_sensitivity_set(uint8_t chn_id, uint8_t sensitivity);
|
||||
void relay_chn_tilt_set_sensitivity(uint8_t chn_id, uint8_t sensitivity);
|
||||
|
||||
/**
|
||||
* @brief Sets the tilt sensitivity for all configured relay channels.
|
||||
*
|
||||
* This function sets the tilt sensitivity for each channel based on the values
|
||||
* provided in the `sensitivities` array. Each sensitivity value (0-100%)
|
||||
* determines the `move_time_ms` and `pause_time_ms` for tilt operations.
|
||||
* The new sensitivities are persisted in NVS if enabled.
|
||||
*
|
||||
* @param sensitivities Pointer to an array containing the desired tilt sensitivities.
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK: Success
|
||||
* - ESP_ERR_INVALID_ARG: When sensitivities parameter is NULL
|
||||
*/
|
||||
esp_err_t relay_chn_tilt_set_sensitivity_all(uint8_t *sensitivities);
|
||||
|
||||
/**
|
||||
* @brief Sets a single tilt sensitivity value for all configured relay channels.
|
||||
*
|
||||
* This function sets the same `sensitivity` value for all configured relay channels.
|
||||
* The sensitivity value (0-100%) determines the `move_time_ms` and `pause_time_ms`
|
||||
* for tilt operations. The new sensitivities are persisted in NVS if enabled.
|
||||
*
|
||||
* @param sensitivity The desired tilt sensitivity in percentage (0-100) to apply to all channels.
|
||||
*/
|
||||
void relay_chn_tilt_set_sensitivity_all_with(uint8_t sensitivity);
|
||||
|
||||
/**
|
||||
* @brief Gets the tilting sensitivity for the specified relay channel.
|
||||
@@ -278,14 +397,162 @@ void relay_chn_tilt_sensitivity_set(uint8_t chn_id, uint8_t sensitivity);
|
||||
* - ESP_OK: Success
|
||||
* - ESP_ERR_INVALID_ARG: Invalid argument
|
||||
*/
|
||||
esp_err_t relay_chn_tilt_sensitivity_get(uint8_t chn_id, uint8_t *sensitivity, size_t length);
|
||||
uint8_t relay_chn_tilt_get_sensitivity(uint8_t chn_id);
|
||||
|
||||
/**
|
||||
* @brief Gets the current tilt sensitivities for all configured relay channels.
|
||||
*
|
||||
* This function populates an array with the current tilt sensitivities (0-100%)
|
||||
* of all configured relay channels. The caller must ensure the `sensitivity` array
|
||||
* is large enough to hold `CONFIG_RELAY_CHN_COUNT` elements.
|
||||
*
|
||||
* @param sensitivity Pointer to an array where the sensitivities will be stored.
|
||||
* @return ESP_OK on success, ESP_ERR_INVALID_ARG if `sensitivity` is NULL.
|
||||
*/
|
||||
esp_err_t relay_chn_tilt_get_sensitivity_all(uint8_t *sensitivities);
|
||||
|
||||
#endif // CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
|
||||
#else // CONFIG_RELAY_CHN_COUNT > 1
|
||||
|
||||
/**
|
||||
* @brief Get the state of the relay channel.
|
||||
*
|
||||
* This function retrieves the current state of the relay channel.
|
||||
*
|
||||
* @return The current state of the relay channel.
|
||||
*/
|
||||
relay_chn_state_t relay_chn_get_state(void);
|
||||
|
||||
/**
|
||||
* @brief Get the state string of the relay channel.
|
||||
*
|
||||
* This function returns a string representation of the state of the relay channel.
|
||||
*
|
||||
* @return A pointer to a string representing the state of the relay
|
||||
* channel. The returned string is managed internally and should not be
|
||||
* modified or freed by the caller.
|
||||
*/
|
||||
char *relay_chn_get_state_str(void);
|
||||
|
||||
/**
|
||||
* @brief Runs the relay channel in the forward direction.
|
||||
*
|
||||
* This function activates the relay channel to run in the forward direction.
|
||||
*/
|
||||
void relay_chn_run_forward(void);
|
||||
|
||||
/**
|
||||
* @brief Runs the relay channel in reverse.
|
||||
*
|
||||
* This function activates the relay channel to run in reverse.
|
||||
*/
|
||||
void relay_chn_run_reverse(void);
|
||||
|
||||
/**
|
||||
* @brief Stops the relay channel.
|
||||
*
|
||||
* This function stops the operation of the relay channel.
|
||||
*/
|
||||
void relay_chn_stop(void);
|
||||
|
||||
/**
|
||||
* @brief Flips the direction of the relay channel.
|
||||
*
|
||||
* This function toggles the direction of the relay channel.
|
||||
*/
|
||||
void relay_chn_flip_direction(void);
|
||||
|
||||
/**
|
||||
* @brief Get the direction of the relay channel.
|
||||
*
|
||||
* This function retrieves the direction configuration of a relay channel.
|
||||
*
|
||||
* @return The direction of the relay channel as a value of type
|
||||
* relay_chn_direction_t.
|
||||
*/
|
||||
relay_chn_direction_t relay_chn_get_direction(void);
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
/**
|
||||
* @brief Get the run limit for the channel
|
||||
*
|
||||
* @return The run limit value for the channel.
|
||||
*/
|
||||
uint16_t relay_chn_get_run_limit(void);
|
||||
|
||||
/**
|
||||
* @brief Set the run limit for the channel
|
||||
*
|
||||
* Sets the time limit in seconds for the channel. It will not proceed
|
||||
* if the channel ID is invalid.
|
||||
* If the limit_sec value is lesser than the CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC,
|
||||
* the value will be set to CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC.
|
||||
* If the limit_sec value is greater than the CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC,
|
||||
* the value will be set to CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC.
|
||||
*
|
||||
* @param limit_sec The run limit time in seconds.
|
||||
*/
|
||||
void relay_chn_set_run_limit(uint16_t limit_sec);
|
||||
#endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1
|
||||
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
|
||||
/**
|
||||
* @brief Enables automatic tilting for the relay channel.
|
||||
*
|
||||
* This function enables automatic tilting mode for the given relay channel.
|
||||
* The channel will automatically switch between forward and reverse tilting
|
||||
* based on the last movement of the channel
|
||||
*/
|
||||
void relay_chn_tilt_auto(void);
|
||||
|
||||
/**
|
||||
* @brief Tilts the relay channel forward.
|
||||
*
|
||||
* This function initiates a forward tilting action for the relay channel.
|
||||
*/
|
||||
void relay_chn_tilt_forward(void);
|
||||
|
||||
/**
|
||||
* @brief Tilts the relay channel reverse.
|
||||
*
|
||||
* This function initiates a reverse tilting action for the relay channel.
|
||||
*/
|
||||
void relay_chn_tilt_reverse(void);
|
||||
|
||||
/**
|
||||
* @brief Stops the tilting action on the relay channel.
|
||||
*
|
||||
* This function stops any ongoing tilting action (automatic or manual) on the relay channel.
|
||||
*/
|
||||
void relay_chn_tilt_stop(void);
|
||||
|
||||
/**
|
||||
* @brief Sets the tilting sensitivity for the relay channel.
|
||||
*
|
||||
* This function sets the sensitivity for the automatic tilting mechanism. A higher sensitivity value
|
||||
* typically means the channel will react more readily to tilting events.
|
||||
*
|
||||
* @param sensitivity The sensitivity in percentage: 0 - 100%.
|
||||
*/
|
||||
void relay_chn_tilt_set_sensitivity(uint8_t sensitivity);
|
||||
|
||||
/**
|
||||
* @brief Gets the tilting sensitivity for the relay channel.
|
||||
*
|
||||
* This function retrieves the currently set sensitivity for the relay channel's automatic
|
||||
* tilting mechanism.
|
||||
*
|
||||
* @return Sensitivity value in percentage: 0 - 100%.
|
||||
*/
|
||||
uint8_t relay_chn_tilt_get_sensitivity(void);
|
||||
|
||||
#endif // CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
|
||||
#endif // CONFIG_RELAY_CHN_COUNT > 1
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/// @}
|
||||
|
||||
#endif // RELAY_CHN_H
|
||||
#endif
|
||||
410
include/relay_chn_adapter.h
Normal file
410
include/relay_chn_adapter.h
Normal file
@@ -0,0 +1,410 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* An adapter header to expose the appropriate API functions to the public API
|
||||
* depending on the CONFIG_RELAY_CHN_COUNT value which determines single or multi mode.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Register a channel state change listener.
|
||||
*
|
||||
* @param listener A function that implements relay_chn_state_listener_t interface.
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK: Success
|
||||
* - ESP_ERR_INVALID_ARG: Invalid argument
|
||||
* - ESP_ERR_NO_MEM: No enough memory
|
||||
* - ESP_FAIL: General failure
|
||||
*/
|
||||
extern esp_err_t relay_chn_notify_add_listener(relay_chn_state_listener_t listener);
|
||||
|
||||
/**
|
||||
* @brief Unregister a channel state change listener.
|
||||
*
|
||||
* @param listener A function that implements relay_chn_state_listener_t interface.
|
||||
*/
|
||||
extern void relay_chn_notify_remove_listener(relay_chn_state_listener_t listener);
|
||||
|
||||
static inline esp_err_t relay_chn_register_listener(relay_chn_state_listener_t listener)
|
||||
{
|
||||
return relay_chn_notify_add_listener(listener);
|
||||
}
|
||||
|
||||
static inline void relay_chn_unregister_listener(relay_chn_state_listener_t listener)
|
||||
{
|
||||
relay_chn_notify_remove_listener(listener);
|
||||
}
|
||||
|
||||
#if CONFIG_RELAY_CHN_COUNT > 1
|
||||
/**
|
||||
* @brief Get the current state of a relay channel.
|
||||
*
|
||||
* @param[in] chn_id Channel ID to get state for.
|
||||
* @return Current state of the specified channel, or RELAY_CHN_STATE_UNDEFINED if invalid.
|
||||
*/
|
||||
extern relay_chn_state_t relay_chn_ctl_get_state(uint8_t chn_id);
|
||||
|
||||
/**
|
||||
* @brief Gets the current state of all relay channels.
|
||||
*
|
||||
* This function populates an array with the current states of all configured
|
||||
* relay channels. The caller must ensure the `states` array is large enough
|
||||
* to hold `CONFIG_RELAY_CHN_COUNT` elements.
|
||||
*
|
||||
* @param states Pointer to an array where the states will be stored.
|
||||
*
|
||||
* @return ESP_OK on success, ESP_ERR_INVALID_ARG if `states` is NULL.
|
||||
*/
|
||||
extern esp_err_t relay_chn_ctl_get_state_all(relay_chn_state_t *states);
|
||||
|
||||
/**
|
||||
* @brief Get string representation of a relay channel's state.
|
||||
*
|
||||
* @param[in] chn_id Channel ID to get state string for.
|
||||
* @return String representation of channel state, or "UNDEFINED" if invalid.
|
||||
*/
|
||||
extern char *relay_chn_ctl_get_state_str(uint8_t chn_id);
|
||||
|
||||
/**
|
||||
* @brief Run a relay channel in forward direction.
|
||||
*
|
||||
* @param[in] chn_id Channel ID to run forward.
|
||||
*/
|
||||
extern void relay_chn_ctl_run_forward(uint8_t chn_id);
|
||||
|
||||
/**
|
||||
* @brief Commands all configured relay channels to run in the forward direction.
|
||||
*
|
||||
* This function iterates through all configured relay channels and issues a command
|
||||
* to each to move in the forward direction.
|
||||
*/
|
||||
extern void relay_chn_ctl_run_forward_all(void);
|
||||
|
||||
/**
|
||||
* @brief Run a relay channel in reverse direction.
|
||||
*
|
||||
* @param[in] chn_id Channel ID to run reverse.
|
||||
*/
|
||||
extern void relay_chn_ctl_run_reverse(uint8_t chn_id);
|
||||
|
||||
/**
|
||||
* @brief Commands all configured relay channels to run in the reverse direction.
|
||||
*
|
||||
* This function iterates through all configured relay channels and issues a command
|
||||
* to each to move in the reverse direction.
|
||||
*/
|
||||
extern void relay_chn_ctl_run_reverse_all(void);
|
||||
|
||||
/**
|
||||
* @brief Stop a relay channel.
|
||||
*
|
||||
* @param[in] chn_id Channel ID to stop.
|
||||
*/
|
||||
extern void relay_chn_ctl_stop(uint8_t chn_id);
|
||||
|
||||
/**
|
||||
* @brief Commands all configured relay channels to stop.
|
||||
*
|
||||
* This function iterates through all configured relay channels and issues a command to each to stop any ongoing movement.
|
||||
*/
|
||||
extern void relay_chn_ctl_stop_all(void);
|
||||
|
||||
/**
|
||||
* @brief Flip the running direction of a relay channel.
|
||||
*
|
||||
* @param[in] chn_id Channel ID to flip direction for.
|
||||
*/
|
||||
extern void relay_chn_ctl_flip_direction(uint8_t chn_id);
|
||||
|
||||
/**
|
||||
* @brief Flips the logical direction of all configured relay channels.
|
||||
*
|
||||
* This function iterates through all configured relay channels and swaps the
|
||||
* physical GPIO pins assigned to the forward and reverse directions for each.
|
||||
*/
|
||||
extern void relay_chn_ctl_flip_direction_all(void);
|
||||
|
||||
/**
|
||||
* @brief Get the current direction of a relay channel.
|
||||
*
|
||||
* @param[in] chn_id Channel ID to get direction for.
|
||||
* @return Current direction of the specified channel, or RELAY_CHN_DIRECTION_DEFAULT if invalid.
|
||||
*/
|
||||
extern relay_chn_direction_t relay_chn_ctl_get_direction(uint8_t chn_id);
|
||||
|
||||
/**
|
||||
* @brief Gets the current logical direction of all configured relay channels.
|
||||
*
|
||||
* This function populates an array with the current logical directions of all
|
||||
* configured relay channels. The caller must ensure the `directions` array is
|
||||
* large enough to hold `CONFIG_RELAY_CHN_COUNT` elements.
|
||||
*
|
||||
* @param directions Pointer to an array where the directions will be stored.
|
||||
* @return ESP_OK on success, ESP_ERR_INVALID_ARG if `directions` is NULL.
|
||||
*/
|
||||
esp_err_t relay_chn_ctl_get_direction_all(relay_chn_direction_t *directions);
|
||||
|
||||
static inline relay_chn_state_t relay_chn_get_state(uint8_t chn_id)
|
||||
{
|
||||
return relay_chn_ctl_get_state(chn_id);
|
||||
}
|
||||
|
||||
static inline esp_err_t relay_chn_get_state_all(relay_chn_state_t *states)
|
||||
{
|
||||
return relay_chn_ctl_get_state_all(states);
|
||||
}
|
||||
|
||||
static inline char *relay_chn_get_state_str(uint8_t chn_id)
|
||||
{
|
||||
return relay_chn_ctl_get_state_str(chn_id);
|
||||
}
|
||||
|
||||
static inline void relay_chn_run_forward(uint8_t chn_id)
|
||||
{
|
||||
relay_chn_ctl_run_forward(chn_id);
|
||||
}
|
||||
|
||||
static inline void relay_chn_run_forward_all(void)
|
||||
{
|
||||
relay_chn_ctl_run_forward_all();
|
||||
}
|
||||
|
||||
static inline void relay_chn_run_reverse(uint8_t chn_id)
|
||||
{
|
||||
relay_chn_ctl_run_reverse(chn_id);
|
||||
}
|
||||
|
||||
static inline void relay_chn_run_reverse_all(void)
|
||||
{
|
||||
relay_chn_ctl_run_reverse_all();
|
||||
}
|
||||
|
||||
static inline void relay_chn_stop(uint8_t chn_id)
|
||||
{
|
||||
relay_chn_ctl_stop(chn_id);
|
||||
}
|
||||
|
||||
static inline void relay_chn_stop_all(void)
|
||||
{
|
||||
relay_chn_ctl_stop_all();
|
||||
}
|
||||
|
||||
static inline void relay_chn_flip_direction(uint8_t chn_id)
|
||||
{
|
||||
relay_chn_ctl_flip_direction(chn_id);
|
||||
}
|
||||
|
||||
static inline void relay_chn_flip_direction_all(void)
|
||||
{
|
||||
relay_chn_ctl_flip_direction_all();
|
||||
}
|
||||
|
||||
static inline relay_chn_direction_t relay_chn_get_direction(uint8_t chn_id)
|
||||
{
|
||||
return relay_chn_ctl_get_direction(chn_id);
|
||||
}
|
||||
|
||||
static inline esp_err_t relay_chn_get_direction_all(relay_chn_direction_t *directions)
|
||||
{
|
||||
return relay_chn_ctl_get_direction_all(directions);
|
||||
}
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
/**
|
||||
* @brief Get the run limit for the specified channel
|
||||
*
|
||||
* @param chn_id The ID of the relay channel to query.
|
||||
*
|
||||
* @return The run limit value for the relevant channel if the channel ID is valid.
|
||||
* 0 if the channel ID is invalid.
|
||||
*/
|
||||
extern uint16_t relay_chn_ctl_get_run_limit(uint8_t chn_id);
|
||||
|
||||
/**
|
||||
* @brief Gets the configured run limits for all configured relay channels.
|
||||
*
|
||||
* This function populates an array with the run limits (in seconds) of all
|
||||
* configured relay channels. The caller must ensure the `limits_sec` array is
|
||||
* large enough to hold `CONFIG_RELAY_CHN_COUNT` elements.
|
||||
*
|
||||
* @param limits_sec Pointer to an array where the run limits will be stored.
|
||||
* @return ESP_OK on success, ESP_ERR_INVALID_ARG if `limits_sec` is NULL.
|
||||
*/
|
||||
esp_err_t relay_chn_ctl_get_run_limit_all(uint16_t *limits_sec);
|
||||
|
||||
/**
|
||||
* @brief Set the run limit for the specified channel
|
||||
*
|
||||
* @param chn_id The ID of the relay channel to query.
|
||||
* @param limit_sec The run limit time in seconds.
|
||||
*/
|
||||
extern void relay_chn_ctl_set_run_limit(uint8_t chn_id, uint16_t limit_sec);
|
||||
|
||||
/**
|
||||
* @brief Sets the run limits for all configured relay channels.
|
||||
*
|
||||
* This function iterates through all configured relay channels and sets their
|
||||
* run limits based on the values provided in the `limits_sec` array. Each value
|
||||
* will be clamped within the configured `RELAY_CHN_RUN_LIMIT_MIN_SEC` and
|
||||
* `RELAY_CHN_RUN_LIMIT_MAX_SEC` boundaries. The new run limits are persisted
|
||||
* in NVS if enabled.
|
||||
*
|
||||
* @param limits_sec Pointer to an array containing the desired run limits in seconds.
|
||||
* @return ESP_OK on success, ESP_ERR_INVALID_ARG if `limits_sec` is NULL.
|
||||
*/
|
||||
esp_err_t relay_chn_ctl_set_run_limit_all(uint16_t *limits_sec);
|
||||
|
||||
/**
|
||||
* @brief Sets a single run limit value for all configured relay channels.
|
||||
*
|
||||
* This function sets the same `limit_sec` value for all configured relay channels.
|
||||
* The value will be clamped within the configured `RELAY_CHN_RUN_LIMIT_MIN_SEC`
|
||||
* and `RELAY_CHN_RUN_LIMIT_MAX_SEC` boundaries.
|
||||
* @param limit_sec The desired run limit in seconds to apply to all channels.
|
||||
* @return ESP_OK on success.
|
||||
*/
|
||||
esp_err_t relay_chn_ctl_set_run_limit_all_with(uint16_t limit_sec);
|
||||
|
||||
static inline uint16_t relay_chn_get_run_limit(uint8_t chn_id)
|
||||
{
|
||||
return relay_chn_ctl_get_run_limit(chn_id);
|
||||
}
|
||||
|
||||
static inline esp_err_t relay_chn_get_run_limit_all(uint16_t *limits_sec)
|
||||
{
|
||||
return relay_chn_ctl_get_run_limit_all(limits_sec);
|
||||
}
|
||||
|
||||
static inline void relay_chn_set_run_limit(uint8_t chn_id, uint16_t limit_sec)
|
||||
{
|
||||
relay_chn_ctl_set_run_limit(chn_id, limit_sec);
|
||||
}
|
||||
|
||||
static inline esp_err_t relay_chn_set_run_limit_all(uint16_t *limits_sec)
|
||||
{
|
||||
return relay_chn_ctl_set_run_limit_all(limits_sec);
|
||||
}
|
||||
|
||||
static inline esp_err_t relay_chn_set_run_limit_all_with(uint16_t limit_sec)
|
||||
{
|
||||
return relay_chn_ctl_set_run_limit_all_with(limit_sec);
|
||||
}
|
||||
#endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1
|
||||
|
||||
#else
|
||||
|
||||
/**
|
||||
* @brief Get the current state of the relay channel.
|
||||
*
|
||||
* @return Current state of the channel.
|
||||
*/
|
||||
extern relay_chn_state_t relay_chn_ctl_get_state(void);
|
||||
|
||||
/**
|
||||
* @brief Get string representation of the relay channel's state.
|
||||
*
|
||||
* @return String representation of channel state.
|
||||
*/
|
||||
extern char *relay_chn_ctl_get_state_str(void);
|
||||
|
||||
/**
|
||||
* @brief Run the relay channel in forward direction.
|
||||
*/
|
||||
extern void relay_chn_ctl_run_forward(void);
|
||||
|
||||
/**
|
||||
* @brief Run the relay channel in reverse direction.
|
||||
*/
|
||||
extern void relay_chn_ctl_run_reverse(void);
|
||||
|
||||
/**
|
||||
* @brief Stop the relay channel.
|
||||
*/
|
||||
extern void relay_chn_ctl_stop(void);
|
||||
|
||||
/**
|
||||
* @brief Flip the running direction of the relay channel.
|
||||
*/
|
||||
extern void relay_chn_ctl_flip_direction(void);
|
||||
|
||||
/**
|
||||
* @brief Get the current direction of the relay channel.
|
||||
*
|
||||
* @return Current direction of the channel.
|
||||
*/
|
||||
extern relay_chn_direction_t relay_chn_ctl_get_direction(void);
|
||||
|
||||
static inline relay_chn_state_t relay_chn_get_state(void)
|
||||
{
|
||||
return relay_chn_ctl_get_state();
|
||||
}
|
||||
|
||||
static inline char *relay_chn_get_state_str(void)
|
||||
{
|
||||
return relay_chn_ctl_get_state_str();
|
||||
}
|
||||
|
||||
static inline void relay_chn_run_forward(void)
|
||||
{
|
||||
relay_chn_ctl_run_forward();
|
||||
}
|
||||
|
||||
static inline void relay_chn_run_reverse(void)
|
||||
{
|
||||
relay_chn_ctl_run_reverse();
|
||||
}
|
||||
|
||||
static inline void relay_chn_stop(void)
|
||||
{
|
||||
relay_chn_ctl_stop();
|
||||
}
|
||||
|
||||
static inline void relay_chn_flip_direction(void)
|
||||
{
|
||||
relay_chn_ctl_flip_direction();
|
||||
}
|
||||
|
||||
static inline relay_chn_direction_t relay_chn_get_direction(void)
|
||||
{
|
||||
return relay_chn_ctl_get_direction();
|
||||
}
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
/**
|
||||
* @brief Get the run limit for the channel
|
||||
*
|
||||
* @return The run limit value for the channel.
|
||||
*/
|
||||
extern uint16_t relay_chn_ctl_get_run_limit(void);
|
||||
|
||||
/**
|
||||
* @brief Set the run limit for the channel
|
||||
*
|
||||
* @param limit_sec The run limit time in seconds.
|
||||
*/
|
||||
extern void relay_chn_ctl_set_run_limit(uint16_t limit_sec);
|
||||
|
||||
static inline uint16_t relay_chn_get_run_limit(void)
|
||||
{
|
||||
return relay_chn_ctl_get_run_limit();
|
||||
}
|
||||
|
||||
static inline void relay_chn_set_run_limit(uint16_t limit_sec)
|
||||
{
|
||||
relay_chn_ctl_set_run_limit(limit_sec);
|
||||
}
|
||||
#endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1
|
||||
|
||||
#endif // CONFIG_RELAY_CHN_COUNT > 1
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
57
include/relay_chn_types.h
Normal file
57
include/relay_chn_types.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Enumeration for relay channel direction.
|
||||
*/
|
||||
typedef enum relay_chn_direction_enum {
|
||||
RELAY_CHN_DIRECTION_DEFAULT, /*!< Default direction of the relay channel */
|
||||
RELAY_CHN_DIRECTION_FLIPPED /*!< Flipped direction of the relay channel */
|
||||
} relay_chn_direction_t;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Enums that represent the state of a relay channel.
|
||||
*/
|
||||
typedef enum relay_chn_state_enum {
|
||||
RELAY_CHN_STATE_UNDEFINED, /*!< The relay channel state is undefined */
|
||||
RELAY_CHN_STATE_IDLE, /*!< The relay channel is free to run or execute commands */
|
||||
RELAY_CHN_STATE_STOPPED, /*!< The relay channel is stopped and not running */
|
||||
RELAY_CHN_STATE_FORWARD, /*!< The relay channel is running in the forward direction */
|
||||
RELAY_CHN_STATE_REVERSE, /*!< The relay channel is running in the reverse direction */
|
||||
RELAY_CHN_STATE_FORWARD_PENDING, /*!< The relay channel is pending to run in the forward direction */
|
||||
RELAY_CHN_STATE_REVERSE_PENDING, /*!< The relay channel is pending to run in the reverse direction */
|
||||
#if CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
RELAY_CHN_STATE_TILT_FORWARD, /*!< The relay channel is tilting for forward */
|
||||
RELAY_CHN_STATE_TILT_REVERSE, /*!< The relay channel is tilting for reverse */
|
||||
#endif
|
||||
} relay_chn_state_t;
|
||||
|
||||
/**
|
||||
* @brief Relay channel state change listener.
|
||||
*
|
||||
* An optional interface to listen to the channel state change events.
|
||||
* The listeners SHOULD be implemented as light functions and SHOULD NOT contain
|
||||
* any blocking calls. Otherwise the relay_chn module would not function properly
|
||||
* since it is designed as event driven.
|
||||
*
|
||||
* @param chn_id The ID of the channel whose state has changed.
|
||||
* @param old_state The old state of the channel.
|
||||
* @param new_state The new state of the channel.
|
||||
*/
|
||||
typedef void (*relay_chn_state_listener_t)(uint8_t chn_id, relay_chn_state_t old_state, relay_chn_state_t new_state);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
109
private_include/relay_chn_core.h
Normal file
109
private_include/relay_chn_core.h
Normal file
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
#include "relay_chn_types.h"
|
||||
#include "relay_chn_priv_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Initializes the relay channel timer.
|
||||
*
|
||||
* This function creates a timer for the relay channel to handle direction change inertia.
|
||||
* Required by *_ctl_* module.
|
||||
*
|
||||
* @param chn_ctl Pointer to the relay channel control structure.
|
||||
* @return esp_err_t ESP_OK on success, or an error code on failure.
|
||||
*/
|
||||
esp_err_t relay_chn_init_timer(relay_chn_ctl_t *chn_ctl);
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
/**
|
||||
* @brief Initializes the relay channel run limit timer.
|
||||
*
|
||||
* This function creates a timer for the relay channel to handle run time limit.
|
||||
* Required by *_ctl_* module.
|
||||
*
|
||||
* @param chn_ctl Pointer to the relay channel control structure.
|
||||
*
|
||||
* @return esp_err_t ESP_OK on success, or an error code on failure.
|
||||
*/
|
||||
esp_err_t relay_chn_init_run_limit_timer(relay_chn_ctl_t *chn_ctl);
|
||||
#endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
|
||||
/**
|
||||
* @brief Issues a command to the relay channel.
|
||||
*
|
||||
* Evaluates the current state of the relay channel and issues the command accordingly.
|
||||
* Required by *_core, *_ctl_* and *_tilt modules.
|
||||
*
|
||||
* @param chn_ctl Pointer to the relay channel control structure.
|
||||
* @param cmd The command to issue.
|
||||
*/
|
||||
void relay_chn_issue_cmd(relay_chn_ctl_t* chn_ctl, relay_chn_cmd_t cmd);
|
||||
|
||||
/**
|
||||
* @brief Dispatches a relay channel command.
|
||||
*
|
||||
* @param chn_ctl Pointer to the relay channel control structure.
|
||||
* @param cmd The command to dispatch.
|
||||
*/
|
||||
void relay_chn_dispatch_cmd(relay_chn_ctl_t *chn_ctl, relay_chn_cmd_t cmd);
|
||||
|
||||
/**
|
||||
* @brief Returns the string representation of a relay channel command.
|
||||
*
|
||||
* @param cmd The relay channel command.
|
||||
* @return char* The string representation of the command.
|
||||
*/
|
||||
char *relay_chn_cmd_str(relay_chn_cmd_t cmd);
|
||||
|
||||
/**
|
||||
* @brief Starts the ESP timer once with the specified time in milliseconds.
|
||||
*
|
||||
* Starts the ESP timer to run once after the specified time.
|
||||
* If the timer is already running, it stops it first and then starts it again.
|
||||
* Required by *_ctl_* and *_tilt modules.
|
||||
*
|
||||
* @param esp_timer The ESP timer handle.
|
||||
* @param time_ms The time in milliseconds to wait before the timer expires.
|
||||
* @return esp_err_t ESP_OK on success, or an error code on failure.
|
||||
*/
|
||||
esp_err_t relay_chn_start_esp_timer_once(esp_timer_handle_t esp_timer, uint32_t time_ms);
|
||||
|
||||
/**
|
||||
* @brief Updates the state of the relay channel and notifies listeners.
|
||||
*
|
||||
* This function updates the state of the relay channel and notifies all registered listeners
|
||||
* about the state change.
|
||||
* Required by *_ctl_* and *_tilt modules.
|
||||
*
|
||||
* @param chn_ctl Pointer to the relay channel control structure.
|
||||
* @param new_state The new state to set for the relay channel.
|
||||
*/
|
||||
void relay_chn_update_state(relay_chn_ctl_t *chn_ctl, relay_chn_state_t new_state);
|
||||
|
||||
#if CONFIG_RELAY_CHN_COUNT > 1
|
||||
/**
|
||||
* @brief Check if the provided channel ID is valid.
|
||||
*
|
||||
* @param chn_id Channel ID to check.
|
||||
* @return true Channel ID is valid.
|
||||
* @return false Channel ID is invalid.
|
||||
*/
|
||||
bool relay_chn_is_channel_id_valid(uint8_t chn_id);
|
||||
#endif // CONFIG_RELAY_CHN_COUNT > 1
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
56
private_include/relay_chn_ctl.h
Normal file
56
private_include/relay_chn_ctl.h
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech developer@kozmotronik.com.tr
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Expose the *_ctl functions required by *_core.c file.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "relay_chn_priv_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Initialize the relay channel control.
|
||||
*
|
||||
* @param output Pointer to the output object(s).
|
||||
* @param run_info Pointer to the runtime information object(s).
|
||||
*
|
||||
* @return esp_err_t Returns ESP_OK on success, or an error code on failure.
|
||||
*/
|
||||
esp_err_t relay_chn_ctl_init(relay_chn_output_t *output, relay_chn_run_info_t *run_info);
|
||||
|
||||
/**
|
||||
* @brief Deinitialize the relay channel control.
|
||||
*
|
||||
* This function cleans up resources used by the relay channel control.
|
||||
*/
|
||||
void relay_chn_ctl_deinit(void);
|
||||
|
||||
#if CONFIG_RELAY_CHN_COUNT > 1
|
||||
/**
|
||||
* @brief Get the control structure for a specific relay channel.
|
||||
*
|
||||
* @param chn_id The ID of the relay channel to retrieve.
|
||||
*
|
||||
* @return relay_chn_ctl_t* Pointer to the control structure for the specified channel, or NULL if not found.
|
||||
*/
|
||||
relay_chn_ctl_t *relay_chn_ctl_get(uint8_t chn_id);
|
||||
|
||||
/**
|
||||
* @brief Get the control structures for all relay channels.
|
||||
*
|
||||
* @return relay_chn_ctl_t* Pointer to the array of control structures for all channels.
|
||||
*/
|
||||
relay_chn_ctl_t *relay_chn_ctl_get_all(void);
|
||||
#else
|
||||
relay_chn_ctl_t *relay_chn_ctl_get(void);
|
||||
#endif // CONFIG_RELAY_CHN_COUNT > 1
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
51
private_include/relay_chn_notify.h
Normal file
51
private_include/relay_chn_notify.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
#include <stdint.h>
|
||||
#include "relay_chn_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Init the notify module.
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK: Success
|
||||
* - ESP_ERR_NO_MEM: Not enough memory to create notify queue
|
||||
*/
|
||||
esp_err_t relay_chn_notify_init(void);
|
||||
|
||||
/**
|
||||
* @brief Deinit the notify module.
|
||||
*
|
||||
* This function cleans up resources used by the notify module.
|
||||
*/
|
||||
void relay_chn_notify_deinit(void);
|
||||
|
||||
/**
|
||||
* @brief Notify all registered listeners about a state change.
|
||||
*
|
||||
* This function sends a state change event to an internal queue, which will then
|
||||
* be processed by a dedicated task to notify all registered listeners. This
|
||||
* function is typically called internally by the relay channel core logic.
|
||||
*
|
||||
* @param chn_id The ID of the relay channel whose state has changed.
|
||||
* @param old_state The previous state of the relay channel.
|
||||
* @param new_state The new state of the relay channel.
|
||||
*/
|
||||
esp_err_t relay_chn_notify_state_change(uint8_t chn_id,
|
||||
relay_chn_state_t old_state,
|
||||
relay_chn_state_t new_state);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
143
private_include/relay_chn_nvs.h
Normal file
143
private_include/relay_chn_nvs.h
Normal file
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "nvs.h"
|
||||
#include "relay_chn_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Initialize NVS storage for relay channels.
|
||||
*
|
||||
* @attention Before calling this function, make sure the NVS flash is initialised
|
||||
* using either the nvs_flash_init() function for the default NVS partition or the
|
||||
* nvs_flash_init_partition() function for a custom partition.
|
||||
*
|
||||
* @return ESP_OK on success, error code otherwise.
|
||||
*/
|
||||
esp_err_t relay_chn_nvs_init(void);
|
||||
|
||||
/**
|
||||
* @brief Store relay channel direction in NVS.
|
||||
*
|
||||
* @param[in] ch Channel number.
|
||||
* @param[in] direction Direction to store.
|
||||
*
|
||||
* @note This operation is asynchronous. The value is queued to be written
|
||||
* by a background task. A subsequent `get` call may not immediately
|
||||
* reflect the new value.
|
||||
* @return ESP_OK on success, error code otherwise.
|
||||
*/
|
||||
esp_err_t relay_chn_nvs_set_direction(uint8_t ch, relay_chn_direction_t direction);
|
||||
|
||||
/**
|
||||
* @brief Retrieve relay channel direction from NVS.
|
||||
*
|
||||
* @param[in] ch Channel number.
|
||||
* @param[out] direction Pointer to store retrieved direction.
|
||||
* @param[in] default_val Default value to use if not found in NVS.
|
||||
* @return ESP_OK on success, error code otherwise.
|
||||
*/
|
||||
esp_err_t relay_chn_nvs_get_direction(uint8_t ch, relay_chn_direction_t *direction, relay_chn_direction_t default_val);
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
/**
|
||||
* @brief Store relay channel run limit in NVS.
|
||||
*
|
||||
* @param[in] ch Channel number.
|
||||
* @param[in] limit_sec Run limit value to store.
|
||||
*
|
||||
* @note This operation is asynchronous. The value is queued to be written
|
||||
* by a background task. A subsequent `get` call may not immediately
|
||||
* reflect the new value.
|
||||
* @return ESP_OK on success, error code otherwise.
|
||||
*/
|
||||
esp_err_t relay_chn_nvs_set_run_limit(uint8_t ch, uint16_t limit_sec);
|
||||
|
||||
/**
|
||||
* @brief Retrieve relay channel run limit from NVS.
|
||||
*
|
||||
* @param[in] ch Channel number.
|
||||
* @param[out] limit_sec Pointer to store retrieved run limit value.
|
||||
* @param[in] default_val Default value to use if not found in NVS.
|
||||
* @return ESP_OK on success, error code otherwise.
|
||||
*/
|
||||
esp_err_t relay_chn_nvs_get_run_limit(uint8_t ch, uint16_t *limit_sec, uint16_t default_val);
|
||||
#endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
/**
|
||||
* @brief Store tilt sensitivity in NVS.
|
||||
*
|
||||
* @param[in] ch Channel number.
|
||||
* @param[in] sensitivity Sensitivity value to store.
|
||||
*
|
||||
* @note This operation is asynchronous. The value is queued to be written
|
||||
* by a background task. A subsequent `get` call may not immediately
|
||||
* reflect the new value.
|
||||
* @return ESP_OK on success, error code otherwise.
|
||||
*/
|
||||
esp_err_t relay_chn_nvs_set_tilt_sensitivity(uint8_t ch, uint8_t sensitivity);
|
||||
|
||||
/**
|
||||
* @brief Retrieve tilt sensitivity from NVS.
|
||||
*
|
||||
* @param[in] ch Channel number.
|
||||
* @param[out] sensitivity Pointer to store retrieved sensitivity.
|
||||
* @param[in] default_val Default value to use if not found in NVS.
|
||||
* @return ESP_OK on success, error code otherwise.
|
||||
*/
|
||||
esp_err_t relay_chn_nvs_get_tilt_sensitivity(uint8_t ch, uint8_t *sensitivity, uint8_t default_val);
|
||||
|
||||
/**
|
||||
* @brief Store tilt counters in NVS.
|
||||
*
|
||||
* @param[in] ch Channel number.
|
||||
* @param[in] tilt_count Tilt count value.
|
||||
*
|
||||
* @note This operation is asynchronous. The value is queued to be written
|
||||
* by a background task. A subsequent `get` call may not immediately
|
||||
* reflect the new value.
|
||||
* @return ESP_OK on success, error code otherwise.
|
||||
*/
|
||||
esp_err_t relay_chn_nvs_set_tilt_count(uint8_t ch, uint16_t tilt_count);
|
||||
|
||||
/**
|
||||
* @brief Retrieve tilt counters from NVS.
|
||||
*
|
||||
* @param[in] ch Channel number.
|
||||
* @param[out] tilt_count Pointer to store tilt count.
|
||||
* @param[in] default_val Default value to use if not found in NVS.
|
||||
* @return ESP_OK on success, error code otherwise.
|
||||
*/
|
||||
esp_err_t relay_chn_nvs_get_tilt_count(uint8_t ch, uint16_t *tilt_count, uint16_t default_val);
|
||||
#endif // CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
|
||||
/**
|
||||
* @brief Erase all keys in the NVS namespace.
|
||||
*
|
||||
* This function will erase all key-value pairs in the NVS namespace used by relay channels.
|
||||
* It will also flush all pending operations in the queue.
|
||||
*
|
||||
* @return ESP_OK on success, error code otherwise.
|
||||
*/
|
||||
esp_err_t relay_chn_nvs_erase_all(void);
|
||||
|
||||
/**
|
||||
* @brief Deinitialize NVS storage for relay channels.
|
||||
*/
|
||||
void relay_chn_nvs_deinit(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
113
private_include/relay_chn_output.h
Normal file
113
private_include/relay_chn_output.h
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Abstraction layer for controlling relay channel outputs. This is the layer
|
||||
* that interacts with the GPIO pins to control the relay channels.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
#include "relay_chn_priv_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Initialize relay channel outputs.
|
||||
*
|
||||
* Maps relay channels to GPIO pins and prepares them for operation.
|
||||
*
|
||||
* @param[in] gpio_map Array of GPIO pin numbers for each relay channel.
|
||||
* @param[in] gpio_count Number of GPIO pins (relay channels).
|
||||
*
|
||||
* @return ESP_OK on success, error code otherwise.
|
||||
*/
|
||||
esp_err_t relay_chn_output_init(const uint8_t* gpio_map, uint8_t gpio_count);
|
||||
|
||||
/**
|
||||
* @brief Deinitialize relay channel outputs.
|
||||
*
|
||||
* Releases resources and resets GPIO pins used for relay channels.
|
||||
*/
|
||||
void relay_chn_output_deinit(void);
|
||||
|
||||
#if CONFIG_RELAY_CHN_COUNT > 1
|
||||
/**
|
||||
* @brief Get the relay channel output object for a specific channel.
|
||||
*
|
||||
* @param[in] chn_id Channel ID.
|
||||
*
|
||||
* @return Pointer to relay channel output object, or NULL if invalid.
|
||||
*/
|
||||
relay_chn_output_t *relay_chn_output_get(uint8_t chn_id);
|
||||
|
||||
/**
|
||||
* @brief Get all relay channel output objects.
|
||||
*
|
||||
* @return Pointer to array of relay channel output objects.
|
||||
*/
|
||||
relay_chn_output_t *relay_chn_output_get_all(void);
|
||||
#else
|
||||
/**
|
||||
* @brief Get the relay channel output object.
|
||||
*
|
||||
* @return Pointer to relay channel output object.
|
||||
*/
|
||||
relay_chn_output_t *relay_chn_output_get(void);
|
||||
#endif // CONFIG_RELAY_CHN_COUNT > 1
|
||||
|
||||
/**
|
||||
* @brief Stop the relay channel output.
|
||||
*
|
||||
* Sets the relay channel to the stop state.
|
||||
*
|
||||
* @param[in] output Pointer to relay channel output object.
|
||||
*
|
||||
* @return ESP_OK on success, error code otherwise.
|
||||
*/
|
||||
esp_err_t relay_chn_output_stop(relay_chn_output_t *output);
|
||||
|
||||
/**
|
||||
* @brief Set relay channel output to forward direction.
|
||||
*
|
||||
* @param[in] output Pointer to relay channel output object.
|
||||
*
|
||||
* @return ESP_OK on success, error code otherwise.
|
||||
*/
|
||||
esp_err_t relay_chn_output_forward(relay_chn_output_t *output);
|
||||
|
||||
/**
|
||||
* @brief Set relay channel output to reverse direction.
|
||||
*
|
||||
* @param[in] output Pointer to relay channel output object.
|
||||
*
|
||||
* @return ESP_OK on success, error code otherwise.
|
||||
*/
|
||||
esp_err_t relay_chn_output_reverse(relay_chn_output_t *output);
|
||||
|
||||
/**
|
||||
* @brief Flip the direction of the relay channel output.
|
||||
*
|
||||
* Changes the direction from forward to reverse or vice versa.
|
||||
*
|
||||
* @param[in] output Pointer to relay channel output object.
|
||||
*/
|
||||
void relay_chn_output_flip(relay_chn_output_t *output);
|
||||
|
||||
/**
|
||||
* @brief Get the current direction of the relay channel output.
|
||||
*
|
||||
* @param[in] output Pointer to relay channel output object.
|
||||
*
|
||||
* @return Current direction of the relay channel.
|
||||
*/
|
||||
relay_chn_direction_t relay_chn_output_get_direction(relay_chn_output_t *output);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
82
private_include/relay_chn_priv_types.h
Normal file
82
private_include/relay_chn_priv_types.h
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_timer.h"
|
||||
#include "relay_chn_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Enumeration for relay channel commands.
|
||||
*/
|
||||
typedef enum {
|
||||
RELAY_CHN_CMD_NONE, /*!< No command */
|
||||
RELAY_CHN_CMD_STOP, /*!< Stop the relay channel */
|
||||
RELAY_CHN_CMD_FORWARD, /*!< Run the relay channel in the forward direction */
|
||||
RELAY_CHN_CMD_REVERSE, /*!< Run the relay channel in the reverse direction */
|
||||
RELAY_CHN_CMD_FLIP, /*!< Flip the direction of the relay channel */
|
||||
RELAY_CHN_CMD_IDLE /*!< Free the relay channel */
|
||||
} relay_chn_cmd_t;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Structure to hold the output configuration of a relay channel.
|
||||
*/
|
||||
typedef struct {
|
||||
gpio_num_t forward_pin; /*!< GPIO pin number for the forward direction */
|
||||
gpio_num_t reverse_pin; /*!< GPIO pin number for the reverse direction */
|
||||
relay_chn_direction_t direction; /*!< The current direction of the relay channel */
|
||||
} relay_chn_output_t;
|
||||
|
||||
/**
|
||||
* @brief Structure to hold runtime information for a relay channel.
|
||||
*/
|
||||
typedef struct {
|
||||
relay_chn_cmd_t last_run_cmd; /*!< The last run command issued on the relay channel; forward or reverse */
|
||||
uint32_t last_run_cmd_time_ms; /*!< The time in milliseconds when the last run command was issued */
|
||||
} relay_chn_run_info_t;
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
/// @brief Tilt commands.
|
||||
typedef enum {
|
||||
RELAY_CHN_TILT_CMD_NONE, /*!< No command */
|
||||
RELAY_CHN_TILT_CMD_STOP, /*!< Tilt command stop */
|
||||
RELAY_CHN_TILT_CMD_FORWARD, /*!< Tilt command for forward */
|
||||
RELAY_CHN_TILT_CMD_REVERSE /*!< Tilt command for reverse */
|
||||
} relay_chn_tilt_cmd_t;
|
||||
|
||||
/// Forward declaration for relay_chn_tilt_ctl
|
||||
typedef struct relay_chn_tilt_ctl relay_chn_tilt_ctl_t;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Structure to hold the state and configuration of a relay channel.
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t id; /*!< The ID of the relay channel */
|
||||
relay_chn_state_t state; /*!< The current state of the relay channel */
|
||||
relay_chn_run_info_t *run_info; /*!< Runtime information of the relay channel */
|
||||
relay_chn_output_t *output; /*!< Output configuration of the relay channel */
|
||||
relay_chn_cmd_t pending_cmd; /*!< The command that is pending to be issued */
|
||||
esp_timer_handle_t inertia_timer; /*!< Timer to handle the opposite direction inertia time */
|
||||
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
esp_timer_handle_t run_limit_timer; /*!< Timer to handle the run limit */
|
||||
uint16_t run_limit_sec; /*!< Run limit in seconds */
|
||||
#endif
|
||||
#if CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
relay_chn_tilt_ctl_t *tilt_ctl; /*!< Pointer to the tilt control structure if tilting is enabled */
|
||||
#endif
|
||||
} relay_chn_ctl_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
84
private_include/relay_chn_run_info.h
Normal file
84
private_include/relay_chn_run_info.h
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* This is for managing the run information of relay channels.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "relay_chn_priv_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Initialize relay channel run information.
|
||||
*
|
||||
* Initializes the run information for all relay channels with default values.
|
||||
*/
|
||||
void relay_chn_run_info_init(void);
|
||||
|
||||
#if CONFIG_RELAY_CHN_COUNT > 1
|
||||
/**
|
||||
* @brief Get run information object for a specific relay channel.
|
||||
*
|
||||
* @param[in] chn_id Channel ID to get run information for.
|
||||
* @return Pointer to run information structure, or NULL if channel ID is invalid.
|
||||
*/
|
||||
relay_chn_run_info_t *relay_chn_run_info_get(uint8_t chn_id);
|
||||
|
||||
/**
|
||||
* @brief Get run information objects for all relay channels.
|
||||
*
|
||||
* @return Pointer to array of run information structures.
|
||||
*/
|
||||
relay_chn_run_info_t *relay_chn_run_info_get_all(void);
|
||||
#else
|
||||
/**
|
||||
* @brief Get run information object for the single relay channel.
|
||||
*
|
||||
* @return Pointer to run information structure.
|
||||
*/
|
||||
relay_chn_run_info_t *relay_chn_run_info_get(void);
|
||||
#endif // CONFIG_RELAY_CHN_COUNT > 1
|
||||
|
||||
/**
|
||||
* @brief Get the last run command for a relay channel.
|
||||
*
|
||||
* @param[in] run_info Pointer to run information structure.
|
||||
*
|
||||
* @return Last command that was executed, or RELAY_CHN_CMD_NONE if invalid.
|
||||
*/
|
||||
relay_chn_cmd_t relay_chn_run_info_get_last_run_cmd(relay_chn_run_info_t *run_info);
|
||||
|
||||
/**
|
||||
* @brief Set the last run command for a relay channel.
|
||||
*
|
||||
* @param[in] run_info Pointer to run information structure.
|
||||
* @param[in] cmd Command to set as last run command.
|
||||
*/
|
||||
void relay_chn_run_info_set_last_run_cmd(relay_chn_run_info_t *run_info, relay_chn_cmd_t cmd);
|
||||
|
||||
/**
|
||||
* @brief Get the timestamp of the last run command.
|
||||
*
|
||||
* @param[in] run_info Pointer to run information structure.
|
||||
*
|
||||
* @return Timestamp in milliseconds of last command, or 0 if invalid.
|
||||
*/
|
||||
uint32_t relay_chn_run_info_get_last_run_cmd_time_ms(relay_chn_run_info_t *run_info);
|
||||
|
||||
/**
|
||||
* @brief Set the timestamp for the last run command.
|
||||
*
|
||||
* @param[in] run_info Pointer to run information structure.
|
||||
* @param[in] time_ms Timestamp in milliseconds to set.
|
||||
*/
|
||||
void relay_chn_run_info_set_last_run_cmd_time_ms(relay_chn_run_info_t *run_info, uint32_t time_ms);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
58
private_include/relay_chn_tilt.h
Normal file
58
private_include/relay_chn_tilt.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "relay_chn_priv_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Initialize relay channel tilt controls.
|
||||
*
|
||||
* Sets up tilt functionality for relay channels including timers.
|
||||
* Must be called before using any other tilt functions.
|
||||
*
|
||||
* @param[in] chn_ctls Array of relay channel control structures.
|
||||
*
|
||||
* @return ESP_OK on success, error code otherwise.
|
||||
*/
|
||||
esp_err_t relay_chn_tilt_init(relay_chn_ctl_t *chn_ctls);
|
||||
|
||||
/**
|
||||
* @brief Deinitialize relay channel tilt controls.
|
||||
*
|
||||
* Cleans up tilt resources including timers.
|
||||
* Should be called when tilt functionality is no longer needed.
|
||||
*/
|
||||
void relay_chn_tilt_deinit(void);
|
||||
|
||||
/**
|
||||
* @brief Dispatch a tilt command to a relay channel.
|
||||
*
|
||||
* Queues a tilt command for execution on the specified channel.
|
||||
*
|
||||
* @param[in] tilt_ctl Pointer to tilt control structure.
|
||||
* @param[in] cmd Tilt command to execute.
|
||||
*
|
||||
* @return ESP_OK on success, error code otherwise.
|
||||
*/
|
||||
esp_err_t relay_chn_tilt_dispatch_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_tilt_cmd_t cmd);
|
||||
|
||||
/**
|
||||
* @brief Reset tilt counters for a relay channel.
|
||||
*
|
||||
* Resets both forward and reverse tilt counters to zero.
|
||||
*
|
||||
* @param[in] tilt_ctl Pointer to tilt control structure.
|
||||
*/
|
||||
void relay_chn_tilt_reset_count(relay_chn_tilt_ctl_t *tilt_ctl);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -10,13 +10,13 @@ if [[ -z "$IDF_PATH" ]]; then
|
||||
fi
|
||||
|
||||
# ==== 2. Valid Modes and Defaults ====
|
||||
valid_test_tags=("core" "tilt" "listener" "all" "relay_chn")
|
||||
valid_test_tags=("core" "tilt" "notify" "all" "relay_chn" "nvs" "run_limit" "batch" "inertia" "direction" "auto" "sensitivity" "counter" "interrupt")
|
||||
valid_test_profiles=("run_limit" "tilt" "nvs" "nvs_custom" "multi" "full_single" "full_multi")
|
||||
arg_tag="all" # Default to 'all' if no tag specified
|
||||
arg_profile="full_multi" # Default to 'full_multi' if no profile specified
|
||||
arg_clean=false
|
||||
arg_log=false
|
||||
arg_dry_run=false
|
||||
arg_sdkconfig_file=""
|
||||
flag_file=false
|
||||
|
||||
print_help() {
|
||||
echo "Usage: $0 -t <tags> [OPTIONS]"
|
||||
@@ -24,10 +24,14 @@ print_help() {
|
||||
echo "This script builds and runs tests for the relay_chn component using QEMU."
|
||||
echo ""
|
||||
echo "Arguments:"
|
||||
echo " -t, --tag [relay_chn|core|tilt|listener|all] Specify which test tag to run."
|
||||
echo " -t, --tag [relay_chn|core|tilt|notify|nvs|run_limit|batch|inertia|direction|auto|sensitivity|counter|interrupt|all] Specify which test tag to run."
|
||||
echo ""
|
||||
echo " If no tag is specified, it defaults to 'all'."
|
||||
echo ""
|
||||
echo " -p, --profile [run_limit|tilt|nvs|nvs_custom|multi|full_single|full_multi] Specify which test tag to run."
|
||||
echo ""
|
||||
echo " If no profile is specified, it defaults to 'full_multi'."
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " -f, --file <path> Specify a custom sdkconfig file to use for the build."
|
||||
echo " Defaults to 'sdkconfig.defaults' if not provided."
|
||||
@@ -54,9 +58,8 @@ while [[ $# -gt 0 ]]; do
|
||||
arg_tag="$2"
|
||||
shift 2
|
||||
;;
|
||||
--file|-f)
|
||||
arg_sdkconfig_file="$2"
|
||||
flag_file=true
|
||||
--profile|-p)
|
||||
arg_profile="$2"
|
||||
shift 2
|
||||
;;
|
||||
--clean|-c)
|
||||
@@ -86,33 +89,25 @@ if [[ ! " ${valid_test_tags[*]} " =~ " $arg_tag " ]]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
if [[ ! " ${valid_test_profiles[*]} " =~ " $arg_profile " ]]; then
|
||||
echo "❌ Invalid profile: '$arg_profile'"
|
||||
usage
|
||||
fi
|
||||
|
||||
# ==== 5. Resolve Paths and Switch to Working Directory ====
|
||||
script_dir=$(dirname "$(readlink -f "$0")")
|
||||
project_root=$(dirname "$script_dir")
|
||||
|
||||
echo "🔍 Searching for 'test_apps' directory in '$project_root'..."
|
||||
test_apps_dir=$(find "$project_root" -type d -name "test_apps" | head -n 1)
|
||||
test_apps_dir="${project_root}/test_apps"
|
||||
|
||||
if [[ -z "$test_apps_dir" || ! -d "$test_apps_dir" ]]; then
|
||||
echo "❌ 'test_apps' directory not found within the project root: '$project_root'"
|
||||
echo " Please ensure the script is in a 'scripts' directory and 'test_apps' is a sibling."
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Found 'test_apps' at: $test_apps_dir"
|
||||
|
||||
if $flag_file; then
|
||||
if [[ -z "$arg_sdkconfig_file" || ! -f "$arg_sdkconfig_file" ]]; then
|
||||
echo "❌ Invalid or missing file: '$arg_sdkconfig_file'"
|
||||
usage
|
||||
fi
|
||||
# Resolve to an absolute path to work correctly after changing directory
|
||||
arg_sdkconfig_file=$(readlink -f "$arg_sdkconfig_file")
|
||||
else
|
||||
echo "⚠️ No SDK configuration file provided. Using default sdkconfig."
|
||||
arg_sdkconfig_file="$test_apps_dir/sdkconfig.defaults"
|
||||
fi
|
||||
|
||||
echo "🧪 Test mode: $arg_tag"
|
||||
echo "⏳ Current time is: $(date +"%Y-%m-%d %H:%M:%S")"
|
||||
echo "🧪 Test mode: $arg_tag | Profile: $arg_profile"
|
||||
echo "🧹 Clean: $arg_clean | 📄 Log: $arg_log"
|
||||
|
||||
echo "📂 Changing to working directory: $test_apps_dir"
|
||||
@@ -129,15 +124,14 @@ fi
|
||||
# In some locales, we can get errors like: "Error: unknown opcode or format name 'wsr.IBREAKA1'"
|
||||
# The 'LC_ALL=C' env variable is set to ensure consistent locale settings.
|
||||
LC_ALL=C \
|
||||
SDKCONFIG_DEFAULTS="$arg_sdkconfig_file" \
|
||||
RELAY_CHN_UNITY_TEST_GROUP_TAG="$arg_tag" \
|
||||
idf.py reconfigure build
|
||||
idf.py @profiles/"${arg_profile}" reconfigure build
|
||||
|
||||
echo "🚀 Running test with QEMU..."
|
||||
|
||||
if $arg_log; then
|
||||
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
|
||||
LOGFILE="test_log_${arg_tag}_$TIMESTAMP.txt"
|
||||
LOGFILE="test_log_${arg_profile}_${arg_tag}_$TIMESTAMP.txt"
|
||||
if $arg_dry_run; then
|
||||
echo "🔍 Dry run mode: Logging to $LOGFILE but not executing." | tee "$LOGFILE"
|
||||
echo "Command: idf.py qemu" | tee "$LOGFILE"
|
||||
|
||||
38
scripts/run_tests_all_profiles.sh
Executable file
38
scripts/run_tests_all_profiles.sh
Executable file
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
# Check tag argument
|
||||
arg_tag=$1
|
||||
if [[ -z "$arg_tag" ]]; then
|
||||
arg_tag="all"
|
||||
fi
|
||||
|
||||
# Resolve Paths and Switch to Working Directory
|
||||
script_dir=$(dirname "$(readlink -f "$0")")
|
||||
project_root=$(dirname "$script_dir")
|
||||
|
||||
echo "Script dir: ${script_dir}"
|
||||
echo "Project root: ${project_root}"
|
||||
|
||||
test_apps_dir="${project_root}/test_apps"
|
||||
echo "test_apps dir: ${test_apps_dir}"
|
||||
|
||||
if [[ -z "$test_apps_dir" || ! -d "$test_apps_dir" ]]; then
|
||||
echo "❌ 'test_apps' directory not found within the project root: '$project_root'"
|
||||
echo " Please ensure the script is in a 'scripts' directory and 'test_apps' is a sibling."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Execute tests for all profiles
|
||||
mapfile -t profiles < <(find "${test_apps_dir}/profiles" -maxdepth 1 -type f)
|
||||
|
||||
for profile in "${profiles[@]}"; do
|
||||
# Get only the name of the profile file
|
||||
profile=$(basename "${profile}")
|
||||
|
||||
echo "🔧 Running tests with profile: $profile"
|
||||
"${script_dir}"/run_tests.sh -c -p "$profile" -t "$arg_tag" || {
|
||||
echo "❌ Tests failed with profile: $profile"
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
1470
src/relay_chn.c
1470
src/relay_chn.c
File diff suppressed because it is too large
Load Diff
498
src/relay_chn_core.c
Normal file
498
src/relay_chn_core.c
Normal file
@@ -0,0 +1,498 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "esp_check.h"
|
||||
#include "esp_task.h"
|
||||
#include "relay_chn_output.h"
|
||||
#include "relay_chn_run_info.h"
|
||||
#include "relay_chn_ctl.h"
|
||||
#include "relay_chn_notify.h"
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
#include "relay_chn_tilt.h"
|
||||
#endif
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
#include "relay_chn_nvs.h"
|
||||
#endif
|
||||
|
||||
#include "relay_chn_core.h"
|
||||
|
||||
|
||||
static const char *TAG = "RELAY_CHN_CORE";
|
||||
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
/*
|
||||
* Run limit timer callback immediately dispatches a STOP command for the
|
||||
* relevant channel as soon as the run limit time times out
|
||||
*/
|
||||
static void relay_chn_run_limit_timer_cb(void* arg)
|
||||
{
|
||||
relay_chn_ctl_t* chn_ctl = (relay_chn_ctl_t*) arg;
|
||||
relay_chn_dispatch_cmd(chn_ctl, RELAY_CHN_CMD_STOP);
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_init_run_limit_timer(relay_chn_ctl_t *chn_ctl)
|
||||
{
|
||||
char timer_name[32];
|
||||
snprintf(timer_name, sizeof(timer_name), "ch_%d_rlimit_timer", chn_ctl->id);
|
||||
esp_timer_create_args_t timer_args = {
|
||||
.callback = relay_chn_run_limit_timer_cb,
|
||||
.arg = chn_ctl,
|
||||
.name = timer_name
|
||||
};
|
||||
return esp_timer_create(&timer_args, &chn_ctl->run_limit_timer);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Timer callback function for relay channel direction change inertia.
|
||||
static void relay_chn_timer_cb(void* arg)
|
||||
{
|
||||
relay_chn_ctl_t* chn_ctl = (relay_chn_ctl_t*) arg;
|
||||
// Does channel have a pending command?
|
||||
if (chn_ctl->pending_cmd != RELAY_CHN_CMD_NONE) {
|
||||
relay_chn_dispatch_cmd(chn_ctl, chn_ctl->pending_cmd);
|
||||
chn_ctl->pending_cmd = RELAY_CHN_CMD_NONE;
|
||||
}
|
||||
else {
|
||||
ESP_LOGE(TAG, "relay_chn_timer_cb: No pending cmd for relay channel %d!", chn_ctl->id);
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_init_timer(relay_chn_ctl_t *chn_ctl)
|
||||
{
|
||||
char timer_name[32];
|
||||
snprintf(timer_name, sizeof(timer_name), "relay_chn_%d_timer", chn_ctl->id);
|
||||
esp_timer_create_args_t timer_args = {
|
||||
.callback = relay_chn_timer_cb,
|
||||
.arg = chn_ctl,
|
||||
.name = timer_name
|
||||
};
|
||||
return esp_timer_create(&timer_args, &chn_ctl->inertia_timer);
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_create(const uint8_t* gpio_map, uint8_t gpio_count)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(gpio_map != NULL, ESP_ERR_INVALID_ARG, TAG, "gpio_map cannot be NULL");
|
||||
|
||||
esp_err_t ret;
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
ret = relay_chn_nvs_init();
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize NVS for relay channel");
|
||||
#endif
|
||||
|
||||
// Initialize the output
|
||||
ret = relay_chn_output_init(gpio_map, gpio_count);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize relay channel outputs");
|
||||
|
||||
// Initialize the run info
|
||||
relay_chn_run_info_init();
|
||||
|
||||
#if CONFIG_RELAY_CHN_COUNT > 1
|
||||
relay_chn_output_t *outputs = relay_chn_output_get_all();
|
||||
relay_chn_run_info_t *run_infos = relay_chn_run_info_get_all();
|
||||
#else
|
||||
relay_chn_output_t *outputs = relay_chn_output_get();
|
||||
relay_chn_run_info_t *run_infos = relay_chn_run_info_get();
|
||||
#endif
|
||||
|
||||
// Initialize the relay channel controls
|
||||
ret = relay_chn_ctl_init(outputs, run_infos);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize relay channel control");
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
// Initialize the tilt feature
|
||||
#if CONFIG_RELAY_CHN_COUNT > 1
|
||||
relay_chn_ctl_t *chn_ctls = relay_chn_ctl_get_all();
|
||||
#else
|
||||
relay_chn_ctl_t *chn_ctls = relay_chn_ctl_get();
|
||||
#endif // CONFIG_RELAY_CHN_COUNT > 1
|
||||
ret = relay_chn_tilt_init(chn_ctls); // Initialize tilt feature
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize tilt feature");
|
||||
#endif
|
||||
|
||||
// Initialize the notify feature
|
||||
ret = relay_chn_notify_init();
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize notify feature");
|
||||
return ret;
|
||||
}
|
||||
|
||||
void relay_chn_destroy(void)
|
||||
{
|
||||
#if CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
relay_chn_tilt_deinit();
|
||||
#endif
|
||||
relay_chn_notify_deinit();
|
||||
relay_chn_ctl_deinit();
|
||||
relay_chn_output_deinit();
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
relay_chn_nvs_deinit();
|
||||
#endif
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_start_esp_timer_once(esp_timer_handle_t esp_timer, uint32_t time_ms)
|
||||
{
|
||||
esp_err_t ret = esp_timer_start_once(esp_timer, time_ms * 1000);
|
||||
if (ret == ESP_ERR_INVALID_STATE) {
|
||||
// This timer is already running, stop the timer first
|
||||
ret = esp_timer_stop(esp_timer);
|
||||
if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) {
|
||||
return ret;
|
||||
}
|
||||
ret = esp_timer_start_once(esp_timer, time_ms * 1000);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void relay_chn_update_state(relay_chn_ctl_t *chn_ctl, relay_chn_state_t new_state)
|
||||
{
|
||||
relay_chn_state_t old_state = chn_ctl->state;
|
||||
|
||||
// Only update and notify if the state has actually changed.
|
||||
if (old_state == new_state) {
|
||||
return;
|
||||
}
|
||||
|
||||
chn_ctl->state = new_state;
|
||||
|
||||
relay_chn_notify_state_change(chn_ctl->id, old_state, new_state);
|
||||
}
|
||||
|
||||
static void relay_chn_execute_idle(relay_chn_ctl_t *chn_ctl);
|
||||
|
||||
static void relay_chn_start_timer_or_idle(relay_chn_ctl_t *chn_ctl, esp_timer_handle_t timer, uint32_t time_ms, const char* timer_name)
|
||||
{
|
||||
if (relay_chn_start_esp_timer_once(timer, time_ms) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to start %s timer for ch %d", timer_name, chn_ctl->id);
|
||||
// Attempt to go to a safe state.
|
||||
// relay_chn_execute_idle is safe to call, it stops timers and sets state.
|
||||
relay_chn_execute_idle(chn_ctl);
|
||||
}
|
||||
}
|
||||
|
||||
static void relay_chn_stop_prv(relay_chn_ctl_t *chn_ctl)
|
||||
{
|
||||
if (relay_chn_output_stop(chn_ctl->output) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "relay_chn_execute_stop: Failed to output stop for relay channel #%d!", chn_ctl->id);
|
||||
}
|
||||
relay_chn_state_t previous_state = chn_ctl->state;
|
||||
relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_STOPPED);
|
||||
|
||||
// Save the last run time only if the previous state was either STATE FORWARD
|
||||
// or STATE_REVERSE. Then schedule a free command.
|
||||
if (previous_state == RELAY_CHN_STATE_FORWARD || previous_state == RELAY_CHN_STATE_REVERSE) {
|
||||
// Record the command's last run time
|
||||
relay_chn_run_info_set_last_run_cmd_time_ms(chn_ctl->run_info, (uint32_t)(esp_timer_get_time() / 1000));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The command issuer function.
|
||||
*
|
||||
* This function is the deciding logic for issuing a command to a relay channel. It evaluates
|
||||
* the current state of the channel before issuing the command. Then it decides whether to run
|
||||
* the command immediately or wait for the opposite inertia time.
|
||||
*
|
||||
* The STOP command is an exception, it is always run immediately since it is safe in any case.
|
||||
*
|
||||
* Another special consideration is the FLIP command. If the channel is running, the FLIP command
|
||||
* is issued after the channel is stopped. If the channel is stopped, the FLIP command is issued
|
||||
* immediately.
|
||||
*
|
||||
* @param chn_ctl The relay channel to issue the command to.
|
||||
* @param cmd The command to issue.
|
||||
*/
|
||||
void relay_chn_issue_cmd(relay_chn_ctl_t* chn_ctl, relay_chn_cmd_t cmd)
|
||||
{
|
||||
if (cmd == RELAY_CHN_CMD_NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == RELAY_CHN_CMD_STOP) {
|
||||
if (chn_ctl->state == RELAY_CHN_STATE_STOPPED || chn_ctl->state == RELAY_CHN_STATE_IDLE) {
|
||||
return; // Do nothing if already stopped or idle
|
||||
}
|
||||
// If the command is STOP, issue it immediately
|
||||
relay_chn_dispatch_cmd(chn_ctl, cmd);
|
||||
return;
|
||||
}
|
||||
|
||||
relay_chn_cmd_t last_run_cmd = relay_chn_run_info_get_last_run_cmd(chn_ctl->run_info);
|
||||
// Evaluate the channel's next move depending on its status
|
||||
switch (chn_ctl->state)
|
||||
{
|
||||
case RELAY_CHN_STATE_IDLE:
|
||||
// If the channel is idle, run the command immediately
|
||||
relay_chn_dispatch_cmd(chn_ctl, cmd);
|
||||
break;
|
||||
|
||||
case RELAY_CHN_STATE_FORWARD_PENDING:
|
||||
case RELAY_CHN_STATE_REVERSE_PENDING:
|
||||
// The channel is already waiting for the opposite inertia time,
|
||||
// so do nothing unless the command is STOP
|
||||
if (cmd == RELAY_CHN_CMD_STOP) {
|
||||
relay_chn_dispatch_cmd(chn_ctl, cmd);
|
||||
}
|
||||
break;
|
||||
|
||||
case RELAY_CHN_STATE_STOPPED:
|
||||
if (last_run_cmd == cmd || last_run_cmd == RELAY_CHN_CMD_NONE) {
|
||||
// Since the state is STOPPED, the inertia timer should be running and must be invalidated
|
||||
// with the pending FREE command
|
||||
esp_timer_stop(chn_ctl->inertia_timer);
|
||||
chn_ctl->pending_cmd = RELAY_CHN_CMD_NONE;
|
||||
|
||||
// If this is the first run or the last run command is the same as the current command,
|
||||
// run the command immediately
|
||||
relay_chn_dispatch_cmd(chn_ctl, cmd);
|
||||
}
|
||||
else {
|
||||
// If the last run command is different from the current command, calculate the time passed
|
||||
// since the last run command stopped and decide whether to run the command immediately or wait
|
||||
uint32_t last_run_cmd_time_ms = relay_chn_run_info_get_last_run_cmd_time_ms(chn_ctl->run_info);
|
||||
uint32_t current_time_ms = (uint32_t)(esp_timer_get_time() / 1000);
|
||||
if (current_time_ms < last_run_cmd_time_ms) { // Timer overflow
|
||||
// If timer overflowed, it's been a long time. Run immediately.
|
||||
relay_chn_dispatch_cmd(chn_ctl, cmd);
|
||||
} else {
|
||||
uint32_t inertia_time_passed_ms = current_time_ms - last_run_cmd_time_ms;
|
||||
if (inertia_time_passed_ms < CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS) {
|
||||
uint32_t inertia_time_ms = CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS - inertia_time_passed_ms;
|
||||
chn_ctl->pending_cmd = cmd;
|
||||
relay_chn_state_t new_state = cmd == RELAY_CHN_CMD_FORWARD
|
||||
? RELAY_CHN_STATE_FORWARD_PENDING : RELAY_CHN_STATE_REVERSE_PENDING;
|
||||
relay_chn_update_state(chn_ctl, new_state);
|
||||
// If the time passed is less than the opposite inertia time, wait for the remaining time
|
||||
if (relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, inertia_time_ms) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to start inertia timer for ch %d", chn_ctl->id);
|
||||
relay_chn_execute_idle(chn_ctl);
|
||||
}
|
||||
} else {
|
||||
// If the time passed is more than the opposite inertia time, run the command immediately
|
||||
relay_chn_dispatch_cmd(chn_ctl, cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case RELAY_CHN_STATE_FORWARD:
|
||||
case RELAY_CHN_STATE_REVERSE:
|
||||
if (cmd == RELAY_CHN_CMD_FLIP) {
|
||||
// If the command is FLIP, stop the running channel first, then issue the FLIP command
|
||||
relay_chn_stop_prv(chn_ctl);
|
||||
relay_chn_dispatch_cmd(chn_ctl, cmd);
|
||||
return;
|
||||
}
|
||||
|
||||
if (last_run_cmd == cmd) {
|
||||
// If the last run command is the same as the current command, do nothing
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop the channel first before the schedule
|
||||
relay_chn_stop_prv(chn_ctl);
|
||||
|
||||
// If the last run command is different from the current command, wait for the opposite inertia time
|
||||
chn_ctl->pending_cmd = cmd;
|
||||
relay_chn_state_t new_state = cmd == RELAY_CHN_CMD_FORWARD
|
||||
? RELAY_CHN_STATE_FORWARD_PENDING : RELAY_CHN_STATE_REVERSE_PENDING;
|
||||
relay_chn_update_state(chn_ctl, new_state);
|
||||
relay_chn_start_timer_or_idle(chn_ctl, chn_ctl->inertia_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS, "inertia");
|
||||
break;
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
case RELAY_CHN_STATE_TILT_FORWARD:
|
||||
// Terminate tilting first
|
||||
relay_chn_tilt_dispatch_cmd(chn_ctl->tilt_ctl, RELAY_CHN_TILT_CMD_STOP);
|
||||
if (cmd == RELAY_CHN_CMD_FORWARD) {
|
||||
// Schedule for running forward
|
||||
chn_ctl->pending_cmd = cmd;
|
||||
relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_FORWARD_PENDING);
|
||||
relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS);
|
||||
} else if (cmd == RELAY_CHN_CMD_REVERSE) {
|
||||
// Run directly since it is the same direction
|
||||
relay_chn_dispatch_cmd(chn_ctl, cmd);
|
||||
relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_REVERSE);
|
||||
}
|
||||
break;
|
||||
case RELAY_CHN_STATE_TILT_REVERSE:
|
||||
// Terminate tilting first
|
||||
relay_chn_tilt_dispatch_cmd(chn_ctl->tilt_ctl, RELAY_CHN_TILT_CMD_STOP);
|
||||
if (cmd == RELAY_CHN_CMD_FORWARD) {
|
||||
// Run directly since it is the same direction
|
||||
relay_chn_dispatch_cmd(chn_ctl, cmd);
|
||||
relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_FORWARD);
|
||||
} else if (cmd == RELAY_CHN_CMD_REVERSE) {
|
||||
// Schedule for running reverse
|
||||
chn_ctl->pending_cmd = cmd;
|
||||
relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_REVERSE_PENDING);
|
||||
relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
default: ESP_LOGD(TAG, "relay_chn_evaluate: Unknown relay channel state!");
|
||||
}
|
||||
}
|
||||
|
||||
#if CONFIG_RELAY_CHN_COUNT > 1
|
||||
bool relay_chn_is_channel_id_valid(uint8_t chn_id)
|
||||
{
|
||||
bool valid = chn_id < CONFIG_RELAY_CHN_COUNT;
|
||||
if (!valid) {
|
||||
ESP_LOGE(TAG, "Invalid channel ID: %d", chn_id);
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
#endif // CONFIG_RELAY_CHN_COUNT > 1
|
||||
|
||||
|
||||
static void relay_chn_execute_idle(relay_chn_ctl_t *chn_ctl)
|
||||
{
|
||||
chn_ctl->pending_cmd = RELAY_CHN_CMD_NONE;
|
||||
// Invalidate the channel's timer if it is active
|
||||
esp_timer_stop(chn_ctl->inertia_timer);
|
||||
relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_IDLE);
|
||||
}
|
||||
|
||||
static void relay_chn_execute_stop(relay_chn_ctl_t *chn_ctl)
|
||||
{
|
||||
relay_chn_stop_prv(chn_ctl);
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
esp_timer_stop(chn_ctl->run_limit_timer);
|
||||
#endif
|
||||
|
||||
// Invalidate the channel's timer if it is active
|
||||
esp_timer_stop(chn_ctl->inertia_timer);
|
||||
|
||||
relay_chn_cmd_t last_run_cmd = relay_chn_run_info_get_last_run_cmd(chn_ctl->run_info);
|
||||
if (last_run_cmd == RELAY_CHN_CMD_FORWARD || last_run_cmd == RELAY_CHN_CMD_REVERSE ) {
|
||||
// Schedule IDLE
|
||||
chn_ctl->pending_cmd = RELAY_CHN_CMD_IDLE;
|
||||
relay_chn_start_timer_or_idle(chn_ctl, chn_ctl->inertia_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS, "idle");
|
||||
} else {
|
||||
// If the channel was not running forward or reverse, issue a free command immediately
|
||||
relay_chn_execute_idle(chn_ctl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void relay_chn_execute_forward(relay_chn_ctl_t *chn_ctl)
|
||||
{
|
||||
if (relay_chn_output_forward(chn_ctl->output) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "relay_chn_execute_forward: Failed to output forward for relay channel #%d!", chn_ctl->id);
|
||||
return;
|
||||
}
|
||||
relay_chn_run_info_set_last_run_cmd(chn_ctl->run_info, RELAY_CHN_CMD_FORWARD);
|
||||
relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_FORWARD);
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
relay_chn_start_timer_or_idle(chn_ctl, chn_ctl->run_limit_timer, chn_ctl->run_limit_sec * 1000, "run limit");
|
||||
#endif
|
||||
}
|
||||
|
||||
static void relay_chn_execute_reverse(relay_chn_ctl_t *chn_ctl)
|
||||
{
|
||||
if (relay_chn_output_reverse(chn_ctl->output) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "relay_chn_execute_reverse: Failed to output reverse for relay channel #%d!", chn_ctl->id);
|
||||
return;
|
||||
}
|
||||
relay_chn_run_info_set_last_run_cmd(chn_ctl->run_info, RELAY_CHN_CMD_REVERSE);
|
||||
relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_REVERSE);
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
relay_chn_start_timer_or_idle(chn_ctl, chn_ctl->run_limit_timer, chn_ctl->run_limit_sec * 1000, "run limit");
|
||||
#endif
|
||||
}
|
||||
|
||||
static void relay_chn_execute_flip(relay_chn_ctl_t *chn_ctl)
|
||||
{
|
||||
relay_chn_output_flip(chn_ctl->output);
|
||||
// Set an inertia on the channel to prevent any immediate movement
|
||||
chn_ctl->pending_cmd = RELAY_CHN_CMD_IDLE;
|
||||
relay_chn_start_timer_or_idle(chn_ctl, chn_ctl->inertia_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS, "flip inertia");
|
||||
}
|
||||
|
||||
// Dispatch relay channel command
|
||||
void relay_chn_dispatch_cmd(relay_chn_ctl_t *chn_ctl, relay_chn_cmd_t cmd) {
|
||||
ESP_LOGD(TAG, "relay_chn_dispatch_cmd: Command: %d", cmd);
|
||||
|
||||
switch (cmd) {
|
||||
case RELAY_CHN_CMD_STOP:
|
||||
relay_chn_execute_stop(chn_ctl);
|
||||
break;
|
||||
case RELAY_CHN_CMD_FORWARD:
|
||||
relay_chn_execute_forward(chn_ctl);
|
||||
break;
|
||||
case RELAY_CHN_CMD_REVERSE:
|
||||
relay_chn_execute_reverse(chn_ctl);
|
||||
break;
|
||||
case RELAY_CHN_CMD_FLIP:
|
||||
relay_chn_execute_flip(chn_ctl);
|
||||
break;
|
||||
case RELAY_CHN_CMD_IDLE:
|
||||
relay_chn_execute_idle(chn_ctl);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGD(TAG, "Unknown relay channel command!");
|
||||
}
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
// Reset the tilt counter when the command is either FORWARD or REVERSE
|
||||
if (cmd == RELAY_CHN_CMD_FORWARD || cmd == RELAY_CHN_CMD_REVERSE) {
|
||||
relay_chn_tilt_reset_count(chn_ctl->tilt_ctl);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
char *relay_chn_cmd_str(relay_chn_cmd_t cmd)
|
||||
{
|
||||
switch (cmd) {
|
||||
case RELAY_CHN_CMD_STOP:
|
||||
return "STOP";
|
||||
case RELAY_CHN_CMD_FORWARD:
|
||||
return "FORWARD";
|
||||
case RELAY_CHN_CMD_REVERSE:
|
||||
return "REVERSE";
|
||||
case RELAY_CHN_CMD_FLIP:
|
||||
return "FLIP";
|
||||
case RELAY_CHN_CMD_IDLE:
|
||||
return "IDLE";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
char *relay_chn_state_to_str(relay_chn_state_t state)
|
||||
{
|
||||
switch (state) {
|
||||
case RELAY_CHN_STATE_IDLE:
|
||||
return "IDLE";
|
||||
case RELAY_CHN_STATE_STOPPED:
|
||||
return "STOPPED";
|
||||
case RELAY_CHN_STATE_FORWARD:
|
||||
return "FORWARD";
|
||||
case RELAY_CHN_STATE_REVERSE:
|
||||
return "REVERSE";
|
||||
case RELAY_CHN_STATE_FORWARD_PENDING:
|
||||
return "FORWARD_PENDING";
|
||||
case RELAY_CHN_STATE_REVERSE_PENDING:
|
||||
return "REVERSE_PENDING";
|
||||
#if CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
case RELAY_CHN_STATE_TILT_FORWARD:
|
||||
return "TILT_FORWARD";
|
||||
case RELAY_CHN_STATE_TILT_REVERSE:
|
||||
return "TILT_REVERSE";
|
||||
#endif
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
251
src/relay_chn_ctl_multi.c
Normal file
251
src/relay_chn_ctl_multi.c
Normal file
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "esp_check.h"
|
||||
#include "relay_chn.h"
|
||||
#include "relay_chn_priv_types.h"
|
||||
#include "relay_chn_core.h"
|
||||
#include "relay_chn_ctl.h"
|
||||
#include "relay_chn_output.h"
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
#include "relay_chn_nvs.h"
|
||||
#endif
|
||||
|
||||
static const char *TAG = "RELAY_CHN_CTL";
|
||||
|
||||
static relay_chn_ctl_t s_chn_ctls[CONFIG_RELAY_CHN_COUNT];
|
||||
|
||||
|
||||
esp_err_t relay_chn_ctl_init(relay_chn_output_t *outputs, relay_chn_run_info_t *run_infos)
|
||||
{
|
||||
// Initialize all relay channels
|
||||
esp_err_t ret;
|
||||
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
relay_chn_ctl_t* chn_ctl = &s_chn_ctls[i];
|
||||
relay_chn_output_t* output = &outputs[i];
|
||||
relay_chn_run_info_t* run_info = &run_infos[i];
|
||||
|
||||
chn_ctl->id = i;
|
||||
chn_ctl->state = RELAY_CHN_STATE_IDLE;
|
||||
chn_ctl->pending_cmd = RELAY_CHN_CMD_NONE;
|
||||
|
||||
chn_ctl->output = output;
|
||||
chn_ctl->run_info = run_info;
|
||||
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
uint16_t run_limit_sec = CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC;
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
// Load run limit value from NVS
|
||||
ret = relay_chn_nvs_get_run_limit(chn_ctl->id, &run_limit_sec, CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load run limit from NVS for #%d with error: %s", i, esp_err_to_name(ret));
|
||||
#endif
|
||||
chn_ctl->run_limit_sec = run_limit_sec;
|
||||
ret = relay_chn_init_run_limit_timer(chn_ctl);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize run limit timer");
|
||||
#endif
|
||||
ret = relay_chn_init_timer(chn_ctl); // Create direction change inertia timer
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create relay channel timer for channel %d", i);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void relay_chn_ctl_deinit()
|
||||
{
|
||||
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
relay_chn_ctl_t* chn_ctl = &s_chn_ctls[i];
|
||||
if (chn_ctl->inertia_timer != NULL) {
|
||||
esp_timer_delete(chn_ctl->inertia_timer);
|
||||
chn_ctl->inertia_timer = NULL;
|
||||
}
|
||||
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
if (chn_ctl->run_limit_timer != NULL) {
|
||||
esp_timer_delete(chn_ctl->run_limit_timer);
|
||||
chn_ctl->run_limit_timer = NULL;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
relay_chn_state_t relay_chn_ctl_get_state(uint8_t chn_id)
|
||||
{
|
||||
return relay_chn_is_channel_id_valid(chn_id) ?
|
||||
s_chn_ctls[chn_id].state : RELAY_CHN_STATE_UNDEFINED;
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_ctl_get_state_all(relay_chn_state_t *states)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(states != NULL, ESP_ERR_INVALID_ARG, TAG, "states cannot be NULL");
|
||||
|
||||
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
relay_chn_state_t *dest_state = &states[i];
|
||||
if (dest_state == NULL) {
|
||||
ESP_LOGW(TAG, "get_state_all: States have been copied until channel %d since states[%d] is NULL", i, i);
|
||||
break;
|
||||
}
|
||||
*dest_state = s_chn_ctls[i].state;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
char *relay_chn_ctl_get_state_str(uint8_t chn_id)
|
||||
{
|
||||
return relay_chn_is_channel_id_valid(chn_id)
|
||||
? relay_chn_state_to_str(s_chn_ctls[chn_id].state)
|
||||
: relay_chn_state_to_str(RELAY_CHN_STATE_UNDEFINED);
|
||||
}
|
||||
|
||||
|
||||
static void relay_chn_ctl_issue_cmd_on_all_channels(relay_chn_cmd_t cmd)
|
||||
{
|
||||
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
relay_chn_issue_cmd(&s_chn_ctls[i], cmd);
|
||||
}
|
||||
}
|
||||
|
||||
void relay_chn_ctl_run_forward(uint8_t chn_id)
|
||||
{
|
||||
if (relay_chn_is_channel_id_valid(chn_id))
|
||||
relay_chn_issue_cmd(&s_chn_ctls[chn_id], RELAY_CHN_CMD_FORWARD);
|
||||
}
|
||||
|
||||
void relay_chn_ctl_run_forward_all()
|
||||
{
|
||||
relay_chn_ctl_issue_cmd_on_all_channels(RELAY_CHN_CMD_FORWARD);
|
||||
}
|
||||
|
||||
void relay_chn_ctl_run_reverse(uint8_t chn_id)
|
||||
{
|
||||
if (relay_chn_is_channel_id_valid(chn_id))
|
||||
relay_chn_issue_cmd(&s_chn_ctls[chn_id], RELAY_CHN_CMD_REVERSE);
|
||||
}
|
||||
|
||||
void relay_chn_ctl_run_reverse_all()
|
||||
{
|
||||
relay_chn_ctl_issue_cmd_on_all_channels(RELAY_CHN_CMD_REVERSE);
|
||||
}
|
||||
|
||||
void relay_chn_ctl_stop(uint8_t chn_id)
|
||||
{
|
||||
if (relay_chn_is_channel_id_valid(chn_id))
|
||||
relay_chn_issue_cmd(&s_chn_ctls[chn_id], RELAY_CHN_CMD_STOP);
|
||||
}
|
||||
|
||||
void relay_chn_ctl_stop_all()
|
||||
{
|
||||
relay_chn_ctl_issue_cmd_on_all_channels(RELAY_CHN_CMD_STOP);
|
||||
}
|
||||
|
||||
void relay_chn_ctl_flip_direction(uint8_t chn_id)
|
||||
{
|
||||
if (relay_chn_is_channel_id_valid(chn_id))
|
||||
relay_chn_issue_cmd(&s_chn_ctls[chn_id], RELAY_CHN_CMD_FLIP);
|
||||
}
|
||||
|
||||
void relay_chn_ctl_flip_direction_all()
|
||||
{
|
||||
relay_chn_ctl_issue_cmd_on_all_channels(RELAY_CHN_CMD_FLIP);
|
||||
}
|
||||
|
||||
relay_chn_direction_t relay_chn_ctl_get_direction(uint8_t chn_id)
|
||||
{
|
||||
return relay_chn_is_channel_id_valid(chn_id)
|
||||
? relay_chn_output_get_direction(s_chn_ctls[chn_id].output)
|
||||
: RELAY_CHN_DIRECTION_DEFAULT;
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_ctl_get_direction_all(relay_chn_direction_t *directions)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(directions != NULL, ESP_ERR_INVALID_ARG, TAG, "directions cannot be NULL");
|
||||
|
||||
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
relay_chn_direction_t *dest_direction = &directions[i];
|
||||
if (dest_direction == NULL) {
|
||||
ESP_LOGW(TAG, "get_direction_all: Directions have been copied until channel %d since directions[%d] is NULL", i, i);
|
||||
break;
|
||||
}
|
||||
*dest_direction = relay_chn_output_get_direction(s_chn_ctls[i].output);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
uint16_t relay_chn_ctl_get_run_limit(uint8_t chn_id)
|
||||
{
|
||||
return relay_chn_is_channel_id_valid(chn_id) ? s_chn_ctls[chn_id].run_limit_sec : 0;
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_ctl_get_run_limit_all(uint16_t *limits_sec)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(limits_sec != NULL, ESP_ERR_INVALID_ARG, TAG, "limits_sec cannot be NULL");
|
||||
|
||||
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
uint16_t *dest_limit_sec = &limits_sec[i];
|
||||
if (dest_limit_sec == NULL) {
|
||||
ESP_LOGW(TAG, "get_run_limit_all: Run limits have been copied until channel %d since limits_sec[%d] is NULL", i, i);
|
||||
break;
|
||||
}
|
||||
*dest_limit_sec = s_chn_ctls[i].run_limit_sec;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void relay_chn_ctl_set_run_limit_common(uint8_t chn_id, uint16_t limit_sec)
|
||||
{
|
||||
// Check for boundaries
|
||||
if (limit_sec > CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC)
|
||||
limit_sec = CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC;
|
||||
else if (limit_sec < CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC)
|
||||
limit_sec = CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC;
|
||||
|
||||
s_chn_ctls[chn_id].run_limit_sec = limit_sec;
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
relay_chn_nvs_set_run_limit(chn_id, limit_sec);
|
||||
#endif
|
||||
}
|
||||
|
||||
void relay_chn_ctl_set_run_limit(uint8_t chn_id, uint16_t limit_sec)
|
||||
{
|
||||
if (!relay_chn_is_channel_id_valid(chn_id)) {
|
||||
ESP_LOGE(TAG, "set_run_limit: Invalid channel ID: %d", chn_id);
|
||||
return;
|
||||
}
|
||||
relay_chn_ctl_set_run_limit_common(chn_id, limit_sec);
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_ctl_set_run_limit_all(uint16_t *limits_sec)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(limits_sec != NULL, ESP_ERR_INVALID_ARG, TAG, "limits_sec cannot be NULL");
|
||||
|
||||
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
uint16_t *src_limit_sec = &limits_sec[i];
|
||||
if (src_limit_sec == NULL) {
|
||||
ESP_LOGW(TAG, "set_run_limit_all: Run limits have been set until channel %d since limits_sec[%d] is NULL", i, i);
|
||||
break;
|
||||
}
|
||||
relay_chn_ctl_set_run_limit_common(i, *src_limit_sec);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_ctl_set_run_limit_all_with(uint16_t limit_sec)
|
||||
{
|
||||
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
relay_chn_ctl_set_run_limit_common(i, limit_sec);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
relay_chn_ctl_t *relay_chn_ctl_get(uint8_t chn_id)
|
||||
{
|
||||
return relay_chn_is_channel_id_valid(chn_id) ? &s_chn_ctls[chn_id] : NULL;
|
||||
}
|
||||
|
||||
relay_chn_ctl_t *relay_chn_ctl_get_all(void)
|
||||
{
|
||||
return s_chn_ctls;
|
||||
}
|
||||
123
src/relay_chn_ctl_single.c
Normal file
123
src/relay_chn_ctl_single.c
Normal file
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "esp_check.h"
|
||||
#include "relay_chn.h"
|
||||
#include "relay_chn_priv_types.h"
|
||||
#include "relay_chn_core.h"
|
||||
#include "relay_chn_ctl.h"
|
||||
#include "relay_chn_output.h"
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
#include "relay_chn_nvs.h"
|
||||
#endif
|
||||
|
||||
static const char *TAG __attribute__((unused)) = "RELAY_CHN_CTL";
|
||||
|
||||
static relay_chn_ctl_t s_chn_ctl;
|
||||
|
||||
|
||||
esp_err_t relay_chn_ctl_init(relay_chn_output_t *output, relay_chn_run_info_t *run_info)
|
||||
{
|
||||
// Initialize the relay channel
|
||||
s_chn_ctl.id = 0; // Single channel, so ID is 0
|
||||
s_chn_ctl.state = RELAY_CHN_STATE_IDLE;
|
||||
s_chn_ctl.pending_cmd = RELAY_CHN_CMD_NONE;
|
||||
s_chn_ctl.output = output;
|
||||
s_chn_ctl.run_info = run_info;
|
||||
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
uint16_t run_limit_sec = CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC;
|
||||
esp_err_t ret;
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
// Load run limit value from NVS
|
||||
ret = relay_chn_nvs_get_run_limit(s_chn_ctl.id, &run_limit_sec, CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load run limit from NVS with error: %s", esp_err_to_name(ret));
|
||||
#endif
|
||||
s_chn_ctl.run_limit_sec = run_limit_sec;
|
||||
ret = relay_chn_init_run_limit_timer(&s_chn_ctl);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize run limit timer");
|
||||
#endif
|
||||
return relay_chn_init_timer(&s_chn_ctl); // Create direction change inertia timer
|
||||
}
|
||||
|
||||
|
||||
void relay_chn_ctl_deinit()
|
||||
{
|
||||
if (s_chn_ctl.inertia_timer != NULL) {
|
||||
esp_timer_delete(s_chn_ctl.inertia_timer);
|
||||
s_chn_ctl.inertia_timer = NULL;
|
||||
}
|
||||
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
if (s_chn_ctl.run_limit_timer != NULL) {
|
||||
esp_timer_delete(s_chn_ctl.run_limit_timer);
|
||||
s_chn_ctl.run_limit_timer = NULL;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* relay_chn APIs */
|
||||
relay_chn_state_t relay_chn_ctl_get_state()
|
||||
{
|
||||
return s_chn_ctl.state;
|
||||
}
|
||||
|
||||
char *relay_chn_ctl_get_state_str()
|
||||
{
|
||||
return relay_chn_state_to_str(s_chn_ctl.state);
|
||||
}
|
||||
|
||||
void relay_chn_ctl_run_forward()
|
||||
{
|
||||
relay_chn_issue_cmd(&s_chn_ctl, RELAY_CHN_CMD_FORWARD);
|
||||
}
|
||||
|
||||
void relay_chn_ctl_run_reverse()
|
||||
{
|
||||
relay_chn_issue_cmd(&s_chn_ctl, RELAY_CHN_CMD_REVERSE);
|
||||
}
|
||||
|
||||
void relay_chn_ctl_stop()
|
||||
{
|
||||
relay_chn_issue_cmd(&s_chn_ctl, RELAY_CHN_CMD_STOP);
|
||||
}
|
||||
|
||||
void relay_chn_ctl_flip_direction()
|
||||
{
|
||||
relay_chn_issue_cmd(&s_chn_ctl, RELAY_CHN_CMD_FLIP);
|
||||
}
|
||||
|
||||
relay_chn_direction_t relay_chn_ctl_get_direction()
|
||||
{
|
||||
return relay_chn_output_get_direction(s_chn_ctl.output);
|
||||
}
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
uint16_t relay_chn_ctl_get_run_limit()
|
||||
{
|
||||
return s_chn_ctl.run_limit_sec;
|
||||
}
|
||||
|
||||
void relay_chn_ctl_set_run_limit(uint16_t limit_sec)
|
||||
{
|
||||
// Check for boundaries
|
||||
if (limit_sec > CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC)
|
||||
limit_sec = CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC;
|
||||
else if (limit_sec < CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC)
|
||||
limit_sec = CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC;
|
||||
|
||||
s_chn_ctl.run_limit_sec = limit_sec;
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
relay_chn_nvs_set_run_limit(s_chn_ctl.id, limit_sec);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
/* relay_chn APIs */
|
||||
|
||||
relay_chn_ctl_t *relay_chn_ctl_get()
|
||||
{
|
||||
return &s_chn_ctl;
|
||||
}
|
||||
268
src/relay_chn_notify.c
Normal file
268
src/relay_chn_notify.c
Normal file
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_check.h"
|
||||
#include "relay_chn_notify.h"
|
||||
|
||||
static const char *TAG = "RELAY_CHN_NOTIFY";
|
||||
|
||||
// --- Config ---
|
||||
#define RELAY_CHN_NOTIFY_QUEUE_LEN (16 + CONFIG_RELAY_CHN_COUNT * 4)
|
||||
#define RELAY_CHN_NOTIFY_TASK_STACK 2048
|
||||
#define RELAY_CHN_NOTIFY_TASK_PRIO (tskIDLE_PRIORITY + 5)
|
||||
|
||||
/// @brief Structure to hold a listener entry in the linked list.
|
||||
typedef struct relay_chn_listener_entry_type {
|
||||
relay_chn_state_listener_t listener; /*!< The listener function pointer */
|
||||
ListItem_t list_item; /*!< FreeRTOS list item */
|
||||
} relay_chn_listener_entry_t;
|
||||
|
||||
/// @brief Command types for the notification queue.
|
||||
typedef enum {
|
||||
RELAY_CHN_NOTIFY_CMD_BROADCAST, /*!< A relay channel state has changed. */
|
||||
RELAY_CHN_NOTIFY_CMD_ADD_LISTENER, /*!< Request to add a new listener. */
|
||||
RELAY_CHN_NOTIFY_CMD_REMOVE_LISTENER, /*!< Request to remove a listener. */
|
||||
} relay_chn_notify_cmd_t;
|
||||
|
||||
/// @brief Payload for a state change event.
|
||||
typedef struct {
|
||||
uint8_t chn_id;
|
||||
relay_chn_state_t old_state;
|
||||
relay_chn_state_t new_state;
|
||||
} relay_chn_notify_event_data_t;
|
||||
|
||||
/// @brief The command structure sent to the notification queue.
|
||||
typedef struct {
|
||||
relay_chn_notify_cmd_t cmd;
|
||||
union {
|
||||
relay_chn_notify_event_data_t event_data; /*!< Used for RELAY_CHN_NOTIFY_CMD_BROADCAST */
|
||||
relay_chn_state_listener_t listener; /*!< Used for ADD/REMOVE listener commands */
|
||||
} payload;
|
||||
} relay_chn_notify_msg_t;
|
||||
|
||||
// The list that holds references to the registered listeners.
|
||||
static List_t s_listeners;
|
||||
|
||||
static QueueHandle_t s_notify_queue = NULL;
|
||||
static TaskHandle_t s_notify_task = NULL;
|
||||
|
||||
static void relay_chn_notify_task(void *arg);
|
||||
|
||||
|
||||
esp_err_t relay_chn_notify_init(void)
|
||||
{
|
||||
if (s_notify_queue != NULL) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
s_notify_queue = xQueueCreate(RELAY_CHN_NOTIFY_QUEUE_LEN, sizeof(relay_chn_notify_msg_t));
|
||||
if (!s_notify_queue) {
|
||||
ESP_LOGE(TAG, "Failed to create notify queue");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
// Create the notify dispatcher task
|
||||
BaseType_t ret = xTaskCreate(relay_chn_notify_task, "task_rlch_ntfy",
|
||||
RELAY_CHN_NOTIFY_TASK_STACK, NULL,
|
||||
RELAY_CHN_NOTIFY_TASK_PRIO, &s_notify_task);
|
||||
if (ret != pdPASS) {
|
||||
ESP_LOGE(TAG, "Failed to create notify task");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
// Init the state listener list
|
||||
vListInitialise(&s_listeners);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void relay_chn_notify_deinit(void)
|
||||
{
|
||||
if (s_notify_task != NULL) {
|
||||
vTaskDelete(s_notify_task);
|
||||
s_notify_task = NULL;
|
||||
}
|
||||
|
||||
if (s_notify_queue != NULL) {
|
||||
vQueueDelete(s_notify_queue);
|
||||
s_notify_queue = NULL;
|
||||
}
|
||||
|
||||
if (!listLIST_IS_EMPTY(&s_listeners)) {
|
||||
// Free the listeners
|
||||
while (listCURRENT_LIST_LENGTH(&s_listeners) > 0) {
|
||||
ListItem_t *pxItem = listGET_HEAD_ENTRY(&s_listeners);
|
||||
relay_chn_listener_entry_t *entry = listGET_LIST_ITEM_OWNER(pxItem);
|
||||
uxListRemove(pxItem);
|
||||
free(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Find a listener entry in the list by its function pointer.
|
||||
*
|
||||
* This function replaces the old index-based search and is used to check
|
||||
* for the existence of a listener before registration or for finding it
|
||||
* during unregistration.
|
||||
*
|
||||
* @param listener The listener function pointer to find.
|
||||
* @return Pointer to the listener entry if found, otherwise NULL.
|
||||
*/
|
||||
static relay_chn_listener_entry_t* find_listener_entry(relay_chn_state_listener_t listener)
|
||||
{
|
||||
if (listLIST_IS_EMPTY(&s_listeners)) {
|
||||
ESP_LOGD(TAG, "No listeners registered");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Iterate through the linked list of listeners
|
||||
for (ListItem_t *pxListItem = listGET_HEAD_ENTRY(&s_listeners);
|
||||
pxListItem != listGET_END_MARKER(&s_listeners);
|
||||
pxListItem = listGET_NEXT(pxListItem)) {
|
||||
|
||||
relay_chn_listener_entry_t *entry = (relay_chn_listener_entry_t *) listGET_LIST_ITEM_OWNER(pxListItem);
|
||||
if (entry->listener == listener) {
|
||||
// Found the listener, return the entry
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
// Listener was not found in the list
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void do_add_listener(relay_chn_state_listener_t listener)
|
||||
{
|
||||
// This is now only called from the dispatcher task, so no mutex needed.
|
||||
if (find_listener_entry(listener) != NULL) {
|
||||
ESP_LOGD(TAG, "Listener %p already registered", listener);
|
||||
return;
|
||||
}
|
||||
relay_chn_listener_entry_t *entry = malloc(sizeof(relay_chn_listener_entry_t));
|
||||
if (!entry) {
|
||||
ESP_LOGE(TAG, "Failed to allocate memory for listener");
|
||||
return;
|
||||
}
|
||||
entry->listener = listener;
|
||||
vListInitialiseItem(&(entry->list_item));
|
||||
listSET_LIST_ITEM_OWNER(&(entry->list_item), (void *)entry);
|
||||
vListInsertEnd(&s_listeners, &(entry->list_item));
|
||||
ESP_LOGD(TAG, "Registered listener %p", listener);
|
||||
}
|
||||
|
||||
static void do_remove_listener(relay_chn_state_listener_t listener)
|
||||
{
|
||||
// This is now only called from the dispatcher task, so no mutex needed.
|
||||
relay_chn_listener_entry_t *entry = find_listener_entry(listener);
|
||||
if (entry != NULL) {
|
||||
uxListRemove(&(entry->list_item));
|
||||
free(entry);
|
||||
ESP_LOGD(TAG, "Unregistered listener %p", listener);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Listener %p not found for unregistration.", listener);
|
||||
}
|
||||
|
||||
if (listLIST_IS_EMPTY(&s_listeners)) {
|
||||
// Flush all pending notifications in the queue
|
||||
xQueueReset(s_notify_queue);
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_notify_add_listener(relay_chn_state_listener_t listener)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(listener, ESP_ERR_INVALID_ARG, TAG, "Listener cannot be NULL");
|
||||
ESP_RETURN_ON_FALSE(s_notify_queue, ESP_ERR_INVALID_STATE, TAG, "Notify module not initialized");
|
||||
|
||||
relay_chn_notify_msg_t msg = { .cmd = RELAY_CHN_NOTIFY_CMD_ADD_LISTENER, .payload.listener = listener };
|
||||
if (xQueueSend(s_notify_queue, &msg, 0) != pdTRUE) {
|
||||
ESP_LOGE(TAG, "Notify queue is full, failed to queue add_listener");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void relay_chn_notify_remove_listener(relay_chn_state_listener_t listener)
|
||||
{
|
||||
if (listener == NULL) {
|
||||
ESP_LOGD(TAG, "Cannot unregister a NULL listener.");
|
||||
return;
|
||||
}
|
||||
if (!s_notify_queue) {
|
||||
ESP_LOGE(TAG, "Notify module not initialized, cannot remove listener");
|
||||
return;
|
||||
}
|
||||
|
||||
relay_chn_notify_msg_t msg = { .cmd = RELAY_CHN_NOTIFY_CMD_REMOVE_LISTENER, .payload.listener = listener };
|
||||
if (xQueueSendToFront(s_notify_queue, &msg, 0) != pdTRUE) {
|
||||
ESP_LOGW(TAG, "Notify queue is full, failed to queue remove_listener");
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_notify_state_change(uint8_t chn_id, relay_chn_state_t old_state, relay_chn_state_t new_state)
|
||||
{
|
||||
if (!s_notify_queue) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
relay_chn_notify_msg_t msg = {
|
||||
.cmd = RELAY_CHN_NOTIFY_CMD_BROADCAST,
|
||||
.payload.event_data.chn_id = chn_id,
|
||||
.payload.event_data.old_state = old_state,
|
||||
.payload.event_data.new_state = new_state,
|
||||
};
|
||||
|
||||
// Try to send, do not wait if the queue is full
|
||||
if (xQueueSend(s_notify_queue, &msg, 0) != pdTRUE) {
|
||||
ESP_LOGW(TAG, "Notify queue is full, dropping event: %d -> %d for #%d", old_state, new_state, chn_id);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void do_notify(relay_chn_notify_event_data_t *event_data)
|
||||
{
|
||||
// Iterate through the linked list of listeners and notify them.
|
||||
// No mutex is needed as this is the only task accessing the list.
|
||||
for (ListItem_t *pxListItem = listGET_HEAD_ENTRY(&s_listeners);
|
||||
pxListItem != listGET_END_MARKER(&s_listeners);
|
||||
pxListItem = listGET_NEXT(pxListItem)) {
|
||||
relay_chn_listener_entry_t *entry = (relay_chn_listener_entry_t *) listGET_LIST_ITEM_OWNER(pxListItem);
|
||||
if (entry && entry->listener) {
|
||||
// Emit the state change to the listeners
|
||||
entry->listener(event_data->chn_id, event_data->old_state, event_data->new_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Notify Task ----
|
||||
static void relay_chn_notify_task(void *arg)
|
||||
{
|
||||
relay_chn_notify_msg_t msg;
|
||||
for (;;) {
|
||||
if (xQueueReceive(s_notify_queue, &msg, portMAX_DELAY) == pdTRUE) {
|
||||
switch (msg.cmd) {
|
||||
case RELAY_CHN_NOTIFY_CMD_BROADCAST: {
|
||||
do_notify(&msg.payload.event_data);
|
||||
break;
|
||||
}
|
||||
case RELAY_CHN_NOTIFY_CMD_ADD_LISTENER:
|
||||
do_add_listener(msg.payload.listener);
|
||||
break;
|
||||
case RELAY_CHN_NOTIFY_CMD_REMOVE_LISTENER:
|
||||
do_remove_listener(msg.payload.listener);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "Unknown command type in notify queue: %d", msg.cmd);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
452
src/relay_chn_nvs.c
Normal file
452
src/relay_chn_nvs.c
Normal file
@@ -0,0 +1,452 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs.h"
|
||||
#include "relay_chn_nvs.h"
|
||||
|
||||
#define RELAY_CHN_KEY_DIR "dir" /*!< Direction key */
|
||||
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
#if CONFIG_RELAY_CHN_COUNT > 1
|
||||
#define RELAY_CHN_KEY_RLIM_FMT "rlim_%d" /*!< Run limit key format for multi-channel */
|
||||
#else
|
||||
#define RELAY_CHN_KEY_RLIM "rlim_0" /*!< Run limit key for single-channel */
|
||||
#endif
|
||||
#endif
|
||||
#if CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
#if CONFIG_RELAY_CHN_COUNT > 1
|
||||
#define RELAY_CHN_KEY_TSENS_FMT "tsens_%d" /*!< Tilt sensitivity key format for multi-channel */
|
||||
#define RELAY_CHN_KEY_TCNT_FMT "tcnt_%d" /*!< Tilt count key format for multi-channel */
|
||||
#else
|
||||
#define RELAY_CHN_KEY_TSENS "tsens_0" /*!< Tilt sensitivity key for single-channel */
|
||||
#define RELAY_CHN_KEY_TCNT "tcnt_0" /*!< Tilt count key for single-channel */
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// --- Task and message queue config ---
|
||||
#define RELAY_CHN_NVS_QUEUE_LEN (8 + CONFIG_RELAY_CHN_COUNT * 8)
|
||||
#define RELAY_CHN_NVS_TASK_STACK 2048
|
||||
#define RELAY_CHN_NVS_COMMIT_TIMEOUT_MS 200
|
||||
#define RELAY_CHN_NVS_TASK_PRIO (tskIDLE_PRIORITY + 4)
|
||||
|
||||
typedef enum {
|
||||
RELAY_CHN_NVS_OP_ERASE_ALL,
|
||||
RELAY_CHN_NVS_OP_SET_DIRECTION,
|
||||
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
RELAY_CHN_NVS_OP_SET_RUN_LIMIT,
|
||||
#endif
|
||||
#if CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
RELAY_CHN_NVS_OP_SET_TILT_SENSITIVITY,
|
||||
RELAY_CHN_NVS_OP_SET_TILT_COUNT,
|
||||
#endif
|
||||
RELAY_CHN_NVS_OP_DEINIT,
|
||||
} relay_chn_nvs_op_t;
|
||||
|
||||
typedef struct {
|
||||
relay_chn_nvs_op_t op;
|
||||
uint8_t ch;
|
||||
union {
|
||||
uint16_t data_u16;
|
||||
uint8_t data_u8;
|
||||
} data;
|
||||
} relay_chn_nvs_msg_t;
|
||||
|
||||
static const char *TAG = "RELAY_CHN_NVS";
|
||||
|
||||
static nvs_handle_t s_relay_chn_nvs;
|
||||
static QueueHandle_t s_nvs_ops_queue = NULL;
|
||||
static TaskHandle_t s_nvs_ops_task = NULL;
|
||||
static SemaphoreHandle_t s_nvs_deinit_sem = NULL;
|
||||
|
||||
|
||||
static void relay_chn_nvs_task(void *arg);
|
||||
|
||||
esp_err_t relay_chn_nvs_init()
|
||||
{
|
||||
// Already initialized?
|
||||
if (s_nvs_ops_queue != NULL) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
s_nvs_deinit_sem = xSemaphoreCreateBinary();
|
||||
if (!s_nvs_deinit_sem) {
|
||||
ESP_LOGE(TAG, "Failed to create deinit semaphore");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
s_nvs_ops_queue = xQueueCreate(RELAY_CHN_NVS_QUEUE_LEN, sizeof(relay_chn_nvs_msg_t));
|
||||
if (!s_nvs_ops_queue) {
|
||||
ESP_LOGE(TAG, "Failed to create NVS queue");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
BaseType_t res = xTaskCreate(relay_chn_nvs_task, "task_rlch_nvs",
|
||||
RELAY_CHN_NVS_TASK_STACK, NULL,
|
||||
RELAY_CHN_NVS_TASK_PRIO, &s_nvs_ops_task);
|
||||
if (res != pdPASS) {
|
||||
ESP_LOGE(TAG, "Failed to create NVS task");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
esp_err_t ret;
|
||||
#if CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION
|
||||
ret = nvs_open_from_partition(CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME,
|
||||
CONFIG_RELAY_CHN_NVS_NAMESPACE,
|
||||
NVS_READWRITE,
|
||||
&s_relay_chn_nvs);
|
||||
|
||||
ESP_RETURN_ON_ERROR(ret,
|
||||
TAG,
|
||||
"Failed to open NVS namespace '%s' from partition '%s' with error %s",
|
||||
CONFIG_RELAY_CHN_NVS_NAMESPACE,
|
||||
CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME,
|
||||
esp_err_to_name(ret));
|
||||
#else
|
||||
ret = nvs_open(CONFIG_RELAY_CHN_NVS_NAMESPACE, NVS_READWRITE, &s_relay_chn_nvs);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to open NVS namespace '%s'", CONFIG_RELAY_CHN_NVS_NAMESPACE);
|
||||
#endif // CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t relay_chn_nvs_enqueue(relay_chn_nvs_msg_t *msg, const char *op_name)
|
||||
{
|
||||
if (!s_nvs_ops_queue) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (msg->op == RELAY_CHN_NVS_OP_DEINIT || msg->op == RELAY_CHN_NVS_OP_ERASE_ALL) {
|
||||
// Send DEINIT or ERASE_ALL to the front and wait up to 1 sec if needed
|
||||
if (xQueueSendToFront(s_nvs_ops_queue, msg, pdMS_TO_TICKS(1000)) != pdTRUE) {
|
||||
ESP_LOGW(TAG, "NVS queue is full, dropping %s for #%d", op_name, msg->ch);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
} else {
|
||||
// Send async
|
||||
if (xQueueSend(s_nvs_ops_queue, msg, 0) != pdTRUE) {
|
||||
ESP_LOGW(TAG, "NVS queue is full, dropping %s for #%d", op_name, msg->ch);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_nvs_set_direction(uint8_t ch, relay_chn_direction_t direction)
|
||||
{
|
||||
relay_chn_nvs_msg_t msg = {
|
||||
.op = RELAY_CHN_NVS_OP_SET_DIRECTION,
|
||||
.ch = ch,
|
||||
.data.data_u8 = (uint8_t) direction,
|
||||
};
|
||||
return relay_chn_nvs_enqueue(&msg, "SET_DIRECTION");
|
||||
}
|
||||
|
||||
static esp_err_t relay_chn_nvs_task_set_direction(uint8_t ch, uint8_t direction)
|
||||
{
|
||||
uint8_t direction_val = 0;
|
||||
esp_err_t ret = nvs_get_u8(s_relay_chn_nvs, RELAY_CHN_KEY_DIR, &direction_val);
|
||||
if (ret != ESP_OK && ret != ESP_ERR_NVS_NOT_FOUND) {
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to get direction from NVS with error: %s", esp_err_to_name(ret));
|
||||
}
|
||||
direction_val &= ~(1 << ch); // Clear the bit for the channel
|
||||
direction_val |= (((uint8_t) direction) << ch); // Set the new direction bit
|
||||
ret = nvs_set_u8(s_relay_chn_nvs, RELAY_CHN_KEY_DIR, direction_val);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set direction for channel %d", ch);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_nvs_get_direction(uint8_t ch, relay_chn_direction_t *direction, relay_chn_direction_t default_val)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(direction != NULL, ESP_ERR_INVALID_ARG, TAG, "Direction pointer is NULL");
|
||||
|
||||
uint8_t direction_val;
|
||||
esp_err_t ret = nvs_get_u8(s_relay_chn_nvs, RELAY_CHN_KEY_DIR, &direction_val);
|
||||
if (ret == ESP_ERR_NVS_NOT_FOUND) {
|
||||
*direction = default_val;
|
||||
return ESP_OK;
|
||||
} else if (ret != ESP_OK) {
|
||||
return ret; // A real error occurred, return it
|
||||
}
|
||||
// If ret is ESP_OK, direction_val has the stored value.
|
||||
*direction = (relay_chn_direction_t)((direction_val >> ch) & 0x01);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
esp_err_t relay_chn_nvs_set_run_limit(uint8_t ch, uint16_t limit_sec)
|
||||
{
|
||||
relay_chn_nvs_msg_t msg = {
|
||||
.op = RELAY_CHN_NVS_OP_SET_RUN_LIMIT,
|
||||
.ch = ch,
|
||||
.data.data_u16 = limit_sec,
|
||||
};
|
||||
return relay_chn_nvs_enqueue(&msg, "SET_RUN_LIMIT");
|
||||
}
|
||||
|
||||
static esp_err_t relay_chn_nvs_task_set_run_limit(uint8_t ch, uint16_t limit_sec)
|
||||
{
|
||||
esp_err_t ret;
|
||||
#if CONFIG_RELAY_CHN_COUNT > 1
|
||||
char key[NVS_KEY_NAME_MAX_SIZE];
|
||||
snprintf(key, sizeof(key), RELAY_CHN_KEY_RLIM_FMT, ch);
|
||||
ret = nvs_set_u16(s_relay_chn_nvs, key, limit_sec);
|
||||
#else
|
||||
ret = nvs_set_u16(s_relay_chn_nvs, RELAY_CHN_KEY_RLIM, limit_sec);
|
||||
#endif
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set run limit for channel %d", ch);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_nvs_get_run_limit(uint8_t ch, uint16_t *limit_sec, uint16_t default_val)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(limit_sec != NULL, ESP_ERR_INVALID_ARG, TAG, "Run limit value pointer is NULL");
|
||||
|
||||
esp_err_t ret;
|
||||
#if CONFIG_RELAY_CHN_COUNT > 1
|
||||
char key[NVS_KEY_NAME_MAX_SIZE];
|
||||
snprintf(key, sizeof(key), RELAY_CHN_KEY_RLIM_FMT, ch);
|
||||
ret = nvs_get_u16(s_relay_chn_nvs, key, limit_sec);
|
||||
#else
|
||||
ret = nvs_get_u16(s_relay_chn_nvs, RELAY_CHN_KEY_RLIM, limit_sec);
|
||||
#endif
|
||||
if (ret == ESP_ERR_NVS_NOT_FOUND) {
|
||||
*limit_sec = default_val;
|
||||
return ESP_OK;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
esp_err_t relay_chn_nvs_set_tilt_sensitivity(uint8_t ch, uint8_t sensitivity)
|
||||
{
|
||||
relay_chn_nvs_msg_t msg = {
|
||||
.op = RELAY_CHN_NVS_OP_SET_TILT_SENSITIVITY,
|
||||
.ch = ch,
|
||||
.data.data_u8 = sensitivity,
|
||||
};
|
||||
return relay_chn_nvs_enqueue(&msg, "SET_TILT_SENSITIVITY");
|
||||
}
|
||||
|
||||
static esp_err_t relay_chn_nvs_task_set_tilt_sensitivity(uint8_t ch, uint8_t sensitivity)
|
||||
{
|
||||
esp_err_t ret;
|
||||
#if CONFIG_RELAY_CHN_COUNT > 1
|
||||
char key[NVS_KEY_NAME_MAX_SIZE];
|
||||
snprintf(key, sizeof(key), RELAY_CHN_KEY_TSENS_FMT, ch);
|
||||
ret = nvs_set_u8(s_relay_chn_nvs, key, sensitivity);
|
||||
#else
|
||||
ret = nvs_set_u8(s_relay_chn_nvs, RELAY_CHN_KEY_TSENS, sensitivity);
|
||||
#endif
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set tilt sensitivity for channel %d", ch);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_nvs_get_tilt_sensitivity(uint8_t ch, uint8_t *sensitivity, uint8_t default_val)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(sensitivity != NULL, ESP_ERR_INVALID_ARG, TAG, "Sensitivity pointer is NULL");
|
||||
|
||||
esp_err_t ret;
|
||||
#if CONFIG_RELAY_CHN_COUNT > 1
|
||||
char key[NVS_KEY_NAME_MAX_SIZE];
|
||||
snprintf(key, sizeof(key), RELAY_CHN_KEY_TSENS_FMT, ch);
|
||||
ret = nvs_get_u8(s_relay_chn_nvs, key, sensitivity);
|
||||
#else
|
||||
ret = nvs_get_u8(s_relay_chn_nvs, RELAY_CHN_KEY_TSENS, sensitivity);
|
||||
#endif
|
||||
if (ret == ESP_ERR_NVS_NOT_FOUND) {
|
||||
*sensitivity = default_val;
|
||||
return ESP_OK;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_nvs_set_tilt_count(uint8_t ch, uint16_t tilt_count)
|
||||
{
|
||||
relay_chn_nvs_msg_t msg = {
|
||||
.op = RELAY_CHN_NVS_OP_SET_TILT_COUNT,
|
||||
.ch = ch,
|
||||
.data.data_u16 = tilt_count,
|
||||
};
|
||||
return relay_chn_nvs_enqueue(&msg, "SET_TILT_COUNT");
|
||||
}
|
||||
|
||||
static esp_err_t relay_chn_nvs_task_set_tilt_count(uint8_t ch, uint16_t tilt_count)
|
||||
{
|
||||
esp_err_t ret;
|
||||
#if CONFIG_RELAY_CHN_COUNT > 1
|
||||
char key[NVS_KEY_NAME_MAX_SIZE];
|
||||
snprintf(key, sizeof(key), RELAY_CHN_KEY_TCNT_FMT, ch);
|
||||
ret = nvs_set_u16(s_relay_chn_nvs, key, tilt_count);
|
||||
#else
|
||||
ret = nvs_set_u16(s_relay_chn_nvs, RELAY_CHN_KEY_TCNT, tilt_count);
|
||||
#endif
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to save tilt_count tilt counter");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_nvs_get_tilt_count(uint8_t ch, uint16_t *tilt_count, uint16_t default_val)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(tilt_count != NULL, ESP_ERR_INVALID_ARG, TAG, "Counter pointers are NULL");
|
||||
|
||||
esp_err_t ret;
|
||||
#if CONFIG_RELAY_CHN_COUNT > 1
|
||||
char key[NVS_KEY_NAME_MAX_SIZE];
|
||||
snprintf(key, sizeof(key), RELAY_CHN_KEY_TCNT_FMT, ch);
|
||||
ret = nvs_get_u16(s_relay_chn_nvs, key, tilt_count);
|
||||
#else
|
||||
ret = nvs_get_u16(s_relay_chn_nvs, RELAY_CHN_KEY_TCNT, tilt_count);
|
||||
#endif
|
||||
if (ret == ESP_ERR_NVS_NOT_FOUND) {
|
||||
*tilt_count = default_val;
|
||||
return ESP_OK;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#endif // CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
|
||||
esp_err_t relay_chn_nvs_erase_all()
|
||||
{
|
||||
relay_chn_nvs_msg_t msg = {
|
||||
.op = RELAY_CHN_NVS_OP_ERASE_ALL,
|
||||
};
|
||||
return relay_chn_nvs_enqueue(&msg, "ERASE_ALL");
|
||||
}
|
||||
|
||||
static esp_err_t do_nvs_deinit()
|
||||
{
|
||||
relay_chn_nvs_msg_t msg = {
|
||||
.op = RELAY_CHN_NVS_OP_DEINIT,
|
||||
};
|
||||
return relay_chn_nvs_enqueue(&msg, "DEINIT");
|
||||
}
|
||||
|
||||
static esp_err_t do_nvs_erase_all()
|
||||
{
|
||||
// Flush all pending SET operations since ERASE_ALL requested
|
||||
xQueueReset(s_nvs_ops_queue);
|
||||
// Erase all key-value pairs in the relay_chn NVS namespace
|
||||
esp_err_t ret = nvs_erase_all(s_relay_chn_nvs);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to erase all keys in NVS namespace '%s'", CONFIG_RELAY_CHN_NVS_NAMESPACE);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void relay_chn_nvs_deinit()
|
||||
{
|
||||
if (s_nvs_ops_task) {
|
||||
if (do_nvs_deinit() == ESP_OK) {
|
||||
if (s_nvs_deinit_sem && xSemaphoreTake(s_nvs_deinit_sem, pdMS_TO_TICKS(2000)) != pdTRUE) {
|
||||
ESP_LOGE(TAG, "Failed to get deinit confirmation from NVS task. Forcing deletion.");
|
||||
vTaskDelete(s_nvs_ops_task); // Last resort
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to send deinit message to NVS task. Forcing deletion.");
|
||||
vTaskDelete(s_nvs_ops_task);
|
||||
}
|
||||
}
|
||||
if (s_nvs_ops_queue) {
|
||||
vQueueDelete(s_nvs_ops_queue);
|
||||
s_nvs_ops_queue = NULL;
|
||||
}
|
||||
if (s_nvs_deinit_sem) {
|
||||
vSemaphoreDelete(s_nvs_deinit_sem);
|
||||
s_nvs_deinit_sem = NULL;
|
||||
}
|
||||
|
||||
// Close NVS handle here, after task has stopped and queue is deleted.
|
||||
nvs_close(s_relay_chn_nvs);
|
||||
s_nvs_ops_task = NULL;
|
||||
}
|
||||
|
||||
static esp_err_t relay_chn_nvs_task_process_message(const relay_chn_nvs_msg_t *msg, bool *running, bool *dirty)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
switch (msg->op) {
|
||||
case RELAY_CHN_NVS_OP_SET_DIRECTION:
|
||||
ret = relay_chn_nvs_task_set_direction(msg->ch, msg->data.data_u8);
|
||||
if (ret == ESP_OK) *dirty = true;
|
||||
break;
|
||||
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
case RELAY_CHN_NVS_OP_SET_RUN_LIMIT:
|
||||
ret = relay_chn_nvs_task_set_run_limit(msg->ch, msg->data.data_u16);
|
||||
if (ret == ESP_OK) *dirty = true;
|
||||
break;
|
||||
#endif
|
||||
#if CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
case RELAY_CHN_NVS_OP_SET_TILT_SENSITIVITY:
|
||||
ret = relay_chn_nvs_task_set_tilt_sensitivity(msg->ch, msg->data.data_u8);
|
||||
if (ret == ESP_OK) *dirty = true;
|
||||
break;
|
||||
case RELAY_CHN_NVS_OP_SET_TILT_COUNT:
|
||||
ret = relay_chn_nvs_task_set_tilt_count(msg->ch, msg->data.data_u16);
|
||||
if (ret == ESP_OK) *dirty = true;
|
||||
break;
|
||||
#endif
|
||||
case RELAY_CHN_NVS_OP_ERASE_ALL:
|
||||
ret = do_nvs_erase_all();
|
||||
if (ret == ESP_OK) *dirty = true;
|
||||
break;
|
||||
case RELAY_CHN_NVS_OP_DEINIT:
|
||||
*running = false;
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "Unknown operation in NVS queue: %d", msg->op);
|
||||
ret = ESP_ERR_INVALID_ARG;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* The ESP-IDF NVS functions are protected by an internal mutex. If this task is killed
|
||||
* while it's holding that mutex, the mutex is never released, which may result in
|
||||
* deadlocks. This is why this task must be terminated gracefully.
|
||||
*/
|
||||
static void relay_chn_nvs_task(void *arg)
|
||||
{
|
||||
relay_chn_nvs_msg_t msg;
|
||||
bool dirty = false;
|
||||
bool running = true;
|
||||
|
||||
while (running) {
|
||||
// Block indefinitely waiting for the first message of a potential batch.
|
||||
if (xQueueReceive(s_nvs_ops_queue, &msg, portMAX_DELAY) == pdTRUE) {
|
||||
// A batch of operations has started. Use a do-while to process the first message
|
||||
// and any subsequent messages that arrive within the timeout.
|
||||
do {
|
||||
esp_err_t ret = relay_chn_nvs_task_process_message(&msg, &running, &dirty);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to process operation %d for #%d with error %s", msg.op, msg.ch, esp_err_to_name(ret));
|
||||
}
|
||||
} while (running && xQueueReceive(s_nvs_ops_queue, &msg, pdMS_TO_TICKS(RELAY_CHN_NVS_COMMIT_TIMEOUT_MS)) == pdTRUE);
|
||||
|
||||
// The burst of messages is over (timeout occurred). Commit if anything changed.
|
||||
if (dirty) {
|
||||
esp_err_t commit_ret = nvs_commit(s_relay_chn_nvs);
|
||||
if (commit_ret == ESP_OK) {
|
||||
dirty = false;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "NVS batch commit failed");
|
||||
// Don't reset dirty flag, so we can try to commit again later.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Before exiting, do one final commit if there are pending changes.
|
||||
if (dirty) {
|
||||
if (nvs_commit(s_relay_chn_nvs) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Final NVS commit failed on deinit");
|
||||
}
|
||||
}
|
||||
xSemaphoreGive(s_nvs_deinit_sem);
|
||||
s_nvs_ops_task = NULL;
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
212
src/relay_chn_output.c
Normal file
212
src/relay_chn_output.c
Normal file
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "esp_check.h"
|
||||
#include "esp_log.h"
|
||||
#include "relay_chn_output.h"
|
||||
#include "relay_chn_core.h"
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
#include "relay_chn_nvs.h"
|
||||
#endif
|
||||
|
||||
|
||||
static const char *TAG = "RELAY_CHN_OUTPUT";
|
||||
|
||||
#if CONFIG_RELAY_CHN_COUNT > 1
|
||||
static relay_chn_output_t s_outputs[CONFIG_RELAY_CHN_COUNT];
|
||||
#else
|
||||
static relay_chn_output_t s_output;
|
||||
#endif
|
||||
|
||||
|
||||
static esp_err_t relay_chn_output_check_gpio_capabilities(uint8_t gpio_count)
|
||||
{
|
||||
// Check if the device's GPIOs are enough for the number of channels
|
||||
if (CONFIG_RELAY_CHN_COUNT > (GPIO_PIN_COUNT / 2)) {
|
||||
ESP_LOGE(TAG, "Not enough GPIOs for the number of channels!");
|
||||
ESP_LOGE(TAG, "Max available num of channels: %d, requested channels: %d", GPIO_PIN_COUNT / 2, CONFIG_RELAY_CHN_COUNT);
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// Check if the provided GPIOs correspond to the number of channels
|
||||
if (gpio_count != CONFIG_RELAY_CHN_COUNT * 2) {
|
||||
ESP_LOGE(TAG, "Invalid number of GPIOs provided: %d", gpio_count);
|
||||
ESP_LOGE(TAG, "Expected number of GPIOs: %d", CONFIG_RELAY_CHN_COUNT * 2);
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t relay_chn_output_ctl_init(relay_chn_output_t *output,
|
||||
gpio_num_t forward_pin,
|
||||
gpio_num_t reverse_pin,
|
||||
relay_chn_direction_t direction)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(GPIO_IS_VALID_OUTPUT_GPIO(forward_pin), ESP_ERR_INVALID_ARG, TAG,
|
||||
"Invalid GPIO pin number for forward_pin: %d", forward_pin);
|
||||
ESP_RETURN_ON_FALSE(GPIO_IS_VALID_OUTPUT_GPIO(reverse_pin), ESP_ERR_INVALID_ARG, TAG,
|
||||
"Invalid GPIO pin number for reverse_pin: %d", reverse_pin);
|
||||
|
||||
// Check if the GPIOs are valid
|
||||
esp_err_t ret;
|
||||
// Initialize the GPIOs
|
||||
ret = gpio_reset_pin(forward_pin);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to reset GPIO forward pin: %d", forward_pin);
|
||||
ret = gpio_set_direction(forward_pin, GPIO_MODE_OUTPUT);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set GPIO direction for forward pin: %d", forward_pin);
|
||||
|
||||
ret = gpio_reset_pin(reverse_pin);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to reset GPIO reverse pin: %d", reverse_pin);
|
||||
ret = gpio_set_direction(reverse_pin, GPIO_MODE_OUTPUT);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set GPIO direction for reverse pin: %d", reverse_pin);
|
||||
// Initialize the GPIOs
|
||||
|
||||
// Initialize the relay channel output
|
||||
output->forward_pin = direction == RELAY_CHN_DIRECTION_DEFAULT ? forward_pin : reverse_pin;
|
||||
output->reverse_pin = direction == RELAY_CHN_DIRECTION_DEFAULT ? reverse_pin : forward_pin;
|
||||
output->direction = direction;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
static esp_err_t relay_chn_output_load_direction(uint8_t ch, relay_chn_direction_t *direction)
|
||||
{
|
||||
// relay_chn_nvs_get_direction handles the NOT_FOUND case and returns the provided default value.
|
||||
esp_err_t ret = relay_chn_nvs_get_direction(ch, direction, RELAY_CHN_DIRECTION_DEFAULT);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to get direction from storage for channel %d: %s", ch, esp_err_to_name(ret));
|
||||
return ESP_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
esp_err_t relay_chn_output_init(const uint8_t* gpio_map, uint8_t gpio_count)
|
||||
{
|
||||
esp_err_t ret;
|
||||
ret = relay_chn_output_check_gpio_capabilities(gpio_count);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Device does not support the provided GPIOs");
|
||||
|
||||
#if CONFIG_RELAY_CHN_COUNT > 1
|
||||
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
relay_chn_output_t* output = &s_outputs[i];
|
||||
int gpio_index = i << 1; // gpio_index = i * 2
|
||||
gpio_num_t forward_pin = (gpio_num_t) gpio_map[gpio_index];
|
||||
gpio_num_t reverse_pin = (gpio_num_t) gpio_map[gpio_index + 1];
|
||||
|
||||
relay_chn_direction_t direction = RELAY_CHN_DIRECTION_DEFAULT;
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
// If NVS storage is enabled, retrieve the direction from storage
|
||||
ret = relay_chn_output_load_direction(i, &direction);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load direction from storage for channel %d", i);
|
||||
#endif
|
||||
ret = relay_chn_output_ctl_init(output, forward_pin, reverse_pin, direction);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize relay channel %d", i);
|
||||
}
|
||||
#else
|
||||
relay_chn_direction_t direction = RELAY_CHN_DIRECTION_DEFAULT;
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
// If NVS storage is enabled, retrieve the direction from storage
|
||||
ret = relay_chn_output_load_direction(0, &direction);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load direction from storage for channel %d", 0);
|
||||
#endif
|
||||
ret = relay_chn_output_ctl_init(&s_output, gpio_map[0], gpio_map[1], direction);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize relay channel");
|
||||
#endif
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void relay_chn_output_ctl_deinit(relay_chn_output_t *output)
|
||||
{
|
||||
gpio_reset_pin(output->forward_pin);
|
||||
gpio_reset_pin(output->reverse_pin);
|
||||
}
|
||||
|
||||
void relay_chn_output_deinit()
|
||||
{
|
||||
#if CONFIG_RELAY_CHN_COUNT > 1
|
||||
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
relay_chn_output_ctl_deinit(&s_outputs[i]);
|
||||
}
|
||||
#else
|
||||
relay_chn_output_ctl_deinit(&s_output);
|
||||
#endif // CONFIG_RELAY_CHN_COUNT > 1
|
||||
}
|
||||
|
||||
#if CONFIG_RELAY_CHN_COUNT > 1
|
||||
relay_chn_output_t *relay_chn_output_get(uint8_t chn_id)
|
||||
{
|
||||
if (!relay_chn_is_channel_id_valid(chn_id)) {
|
||||
return NULL;
|
||||
}
|
||||
return &s_outputs[chn_id];
|
||||
}
|
||||
|
||||
relay_chn_output_t *relay_chn_output_get_all(void)
|
||||
{
|
||||
return s_outputs;
|
||||
}
|
||||
#else
|
||||
relay_chn_output_t *relay_chn_output_get(void)
|
||||
{
|
||||
return &s_output;
|
||||
}
|
||||
#endif // CONFIG_RELAY_CHN_COUNT > 1
|
||||
|
||||
esp_err_t relay_chn_output_stop(relay_chn_output_t *output)
|
||||
{
|
||||
esp_err_t ret;
|
||||
ret = gpio_set_level(output->forward_pin, 0);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set forward pin to LOW");
|
||||
return gpio_set_level(output->reverse_pin, 0);
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_output_forward(relay_chn_output_t *output)
|
||||
{
|
||||
esp_err_t ret;
|
||||
ret = gpio_set_level(output->forward_pin, 1);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set forward pin to HIGH");
|
||||
return gpio_set_level(output->reverse_pin, 0);
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_output_reverse(relay_chn_output_t *output)
|
||||
{
|
||||
esp_err_t ret;
|
||||
ret = gpio_set_level(output->forward_pin, 0);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set forward pin to LOW");
|
||||
return gpio_set_level(output->reverse_pin, 1);
|
||||
}
|
||||
|
||||
void relay_chn_output_flip(relay_chn_output_t *output)
|
||||
{
|
||||
// Flip the output GPIO pins
|
||||
gpio_num_t temp = output->forward_pin;
|
||||
output->forward_pin = output->reverse_pin;
|
||||
output->reverse_pin = temp;
|
||||
// Flip the direction
|
||||
output->direction = (output->direction == RELAY_CHN_DIRECTION_DEFAULT)
|
||||
? RELAY_CHN_DIRECTION_FLIPPED
|
||||
: RELAY_CHN_DIRECTION_DEFAULT;
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
uint8_t ch = 0;
|
||||
#if CONFIG_RELAY_CHN_COUNT > 1
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
if (output == &s_outputs[i]) {
|
||||
ch = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
esp_err_t ret = relay_chn_nvs_set_direction(ch, output->direction);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to save flipped direction for channel %d: %s", ch, esp_err_to_name(ret));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
relay_chn_direction_t relay_chn_output_get_direction(relay_chn_output_t *output)
|
||||
{
|
||||
return output->direction;
|
||||
}
|
||||
74
src/relay_chn_run_info.c
Normal file
74
src/relay_chn_run_info.c
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "relay_chn_core.h"
|
||||
#include "relay_chn_run_info.h"
|
||||
|
||||
|
||||
#if CONFIG_RELAY_CHN_COUNT > 1
|
||||
static relay_chn_run_info_t s_run_infos[CONFIG_RELAY_CHN_COUNT];
|
||||
#else
|
||||
static relay_chn_run_info_t s_run_info;
|
||||
#endif
|
||||
|
||||
void relay_chn_run_info_init()
|
||||
{
|
||||
#if CONFIG_RELAY_CHN_COUNT > 1
|
||||
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
s_run_infos[i].last_run_cmd = RELAY_CHN_CMD_NONE;
|
||||
s_run_infos[i].last_run_cmd_time_ms = 0;
|
||||
}
|
||||
#else
|
||||
s_run_info.last_run_cmd = RELAY_CHN_CMD_NONE;
|
||||
s_run_info.last_run_cmd_time_ms = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if CONFIG_RELAY_CHN_COUNT > 1
|
||||
relay_chn_run_info_t *relay_chn_run_info_get(uint8_t chn_id)
|
||||
{
|
||||
if (!relay_chn_is_channel_id_valid(chn_id)) {
|
||||
return NULL;
|
||||
}
|
||||
return &s_run_infos[chn_id];
|
||||
}
|
||||
|
||||
relay_chn_run_info_t *relay_chn_run_info_get_all()
|
||||
{
|
||||
return s_run_infos;
|
||||
}
|
||||
#else
|
||||
relay_chn_run_info_t *relay_chn_run_info_get()
|
||||
{
|
||||
return &s_run_info;
|
||||
}
|
||||
#endif // CONFIG_RELAY_CHN_COUNT > 1
|
||||
|
||||
relay_chn_cmd_t relay_chn_run_info_get_last_run_cmd(relay_chn_run_info_t *run_info)
|
||||
{
|
||||
return run_info == NULL ? RELAY_CHN_CMD_NONE : run_info->last_run_cmd;
|
||||
}
|
||||
|
||||
void relay_chn_run_info_set_last_run_cmd(relay_chn_run_info_t *run_info, relay_chn_cmd_t cmd)
|
||||
{
|
||||
if (!run_info) {
|
||||
return;
|
||||
}
|
||||
run_info->last_run_cmd = cmd;
|
||||
}
|
||||
|
||||
uint32_t relay_chn_run_info_get_last_run_cmd_time_ms(relay_chn_run_info_t *run_info)
|
||||
{
|
||||
return run_info == NULL ? 0 : run_info->last_run_cmd_time_ms;
|
||||
}
|
||||
|
||||
void relay_chn_run_info_set_last_run_cmd_time_ms(relay_chn_run_info_t *run_info, uint32_t time_ms)
|
||||
{
|
||||
if (!run_info) {
|
||||
return;
|
||||
}
|
||||
run_info->last_run_cmd_time_ms = time_ms;
|
||||
}
|
||||
787
src/relay_chn_tilt.c
Normal file
787
src/relay_chn_tilt.c
Normal file
@@ -0,0 +1,787 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "esp_check.h"
|
||||
#include "relay_chn.h"
|
||||
#include "relay_chn_core.h"
|
||||
#include "relay_chn_output.h"
|
||||
#include "relay_chn_run_info.h"
|
||||
#include "relay_chn_tilt.h"
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
#include "relay_chn_nvs.h"
|
||||
|
||||
#define RELAY_CHN_TILT_FLUSH_DEBOUNCE_MS 3000
|
||||
#endif
|
||||
|
||||
|
||||
static const char *TAG = "RELAY_CHN_TILT";
|
||||
|
||||
/**@{*/
|
||||
/*
|
||||
* Tilt Pattern Timing Definitions
|
||||
*
|
||||
* The min and max timing definitions as well as the default timing definitions.
|
||||
* These definitions are used to define and adjust the tilt sensitivity.
|
||||
*/
|
||||
#define RELAY_CHN_TILT_RUN_MIN_MS 50
|
||||
#define RELAY_CHN_TILT_RUN_MAX_MS 10
|
||||
#define RELAY_CHN_TILT_PAUSE_MIN_MS 450
|
||||
#define RELAY_CHN_TILT_PAUSE_MAX_MS 90
|
||||
|
||||
#define RELAY_CHN_TILT_DEFAULT_RUN_MS 15
|
||||
#define RELAY_CHN_TILT_DEFAULT_PAUSE_MS 150
|
||||
|
||||
#define RELAY_CHN_TILT_DEFAULT_SENSITIVITY \
|
||||
( (RELAY_CHN_TILT_DEFAULT_RUN_MS - RELAY_CHN_TILT_RUN_MIN_MS) \
|
||||
* 100 / (RELAY_CHN_TILT_RUN_MAX_MS - RELAY_CHN_TILT_RUN_MIN_MS) )
|
||||
/**@}*/
|
||||
|
||||
#define ADJUST_TILT_SENS_BOUNDARIES(sens) if (sens > 100) sens = 100
|
||||
|
||||
/// @brief Tilt steps.
|
||||
typedef enum {
|
||||
RELAY_CHN_TILT_STEP_NONE, /*!< No step */
|
||||
RELAY_CHN_TILT_STEP_PENDING, /*!< Pending step */
|
||||
RELAY_CHN_TILT_STEP_MOVE, /*!< Move step. Tilt is driving either for forward or reverse */
|
||||
RELAY_CHN_TILT_STEP_PAUSE /*!< Pause step. Tilt is paused */
|
||||
} relay_chn_tilt_step_t;
|
||||
|
||||
/// @brief Tilt timing structure to manage tilt pattern timing.
|
||||
typedef struct {
|
||||
uint8_t sensitivity; /*!< Tilt sensitivity in percentage (%) */
|
||||
uint32_t move_time_ms; /*!< Move time in milliseconds */
|
||||
uint32_t pause_time_ms; /*!< Pause time in milliseconds */
|
||||
} relay_chn_tilt_timing_t;
|
||||
|
||||
/// @brief Tilt control structure to manage tilt operations.
|
||||
typedef struct relay_chn_tilt_ctl {
|
||||
relay_chn_ctl_t *chn_ctl; /*!< The relay channel control structure */
|
||||
relay_chn_tilt_cmd_t cmd; /*!< The tilt command in process */
|
||||
relay_chn_tilt_step_t step; /*!< Current tilt step */
|
||||
relay_chn_tilt_timing_t tilt_timing; /*!< Tilt timing structure */
|
||||
uint16_t tilt_count; /*!< Tilt count to manage forward and reverse tilts */
|
||||
esp_timer_handle_t tilt_timer; /*!< Tilt timer handle */
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
esp_timer_handle_t flush_timer; /*!< Flush timer to avoid frequent write of tilt counters */
|
||||
#endif
|
||||
} relay_chn_tilt_ctl_t;
|
||||
|
||||
|
||||
#if CONFIG_RELAY_CHN_COUNT > 1
|
||||
static relay_chn_tilt_ctl_t s_tilt_ctls[CONFIG_RELAY_CHN_COUNT];
|
||||
#else
|
||||
static relay_chn_tilt_ctl_t s_tilt_ctl;
|
||||
#endif
|
||||
|
||||
|
||||
// Returns the required timing before tilting depending on the last run.
|
||||
static uint32_t relay_chn_tilt_get_required_timing_before_tilting(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_tilt_cmd_t cmd)
|
||||
{
|
||||
relay_chn_cmd_t last_run_cmd = relay_chn_run_info_get_last_run_cmd(tilt_ctl->chn_ctl->run_info);
|
||||
if (cmd == RELAY_CHN_TILT_CMD_FORWARD && last_run_cmd == RELAY_CHN_CMD_REVERSE)
|
||||
return 0;
|
||||
else if (cmd == RELAY_CHN_TILT_CMD_REVERSE && last_run_cmd == RELAY_CHN_CMD_FORWARD)
|
||||
return 0;
|
||||
|
||||
uint32_t last_run_cmd_time_ms = relay_chn_run_info_get_last_run_cmd_time_ms(tilt_ctl->chn_ctl->run_info);
|
||||
uint32_t inertia_time_passed_ms = (uint32_t) (esp_timer_get_time() / 1000) - last_run_cmd_time_ms;
|
||||
return CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS - inertia_time_passed_ms;
|
||||
}
|
||||
|
||||
static void relay_chn_tilt_execute_stop(relay_chn_tilt_ctl_t *tilt_ctl);
|
||||
|
||||
static void relay_chn_tilt_start_timer_or_stop(relay_chn_tilt_ctl_t *tilt_ctl, esp_timer_handle_t timer, uint32_t time_ms, const char* timer_name)
|
||||
{
|
||||
if (relay_chn_start_esp_timer_once(timer, time_ms) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to start %s timer for ch %d", timer_name, tilt_ctl->chn_ctl->id);
|
||||
// Attempt to go to a safe state for tilt.
|
||||
// relay_chn_tilt_execute_stop is safe to call, it stops timers and sets state.
|
||||
relay_chn_tilt_execute_stop(tilt_ctl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if the relay channel can perform the current tilt command.
|
||||
*
|
||||
* This function evaluates whether a tilt command can be executed based on the
|
||||
* channel's history. The rules are as follows:
|
||||
* - Tilting in the same direction as the last full run command (e.g., TILT_FORWARD
|
||||
* after a FORWARD run) is always allowed.
|
||||
* - Tilting in the opposite direction of the last full run (e.g., TILT_REVERSE
|
||||
* after a FORWARD run) is only allowed if the tilt counter is greater than zero,
|
||||
* which indicates that the channel has previously tilted in the primary direction.
|
||||
* - If the channel has not been run before, tilting is not allowed.
|
||||
*
|
||||
* @param tilt_ctl Pointer to the tilt control structure for the channel.
|
||||
* @param tilt_cmd The tilt command to check against.
|
||||
*
|
||||
* @return true if the tilt command is allowed, false otherwise.
|
||||
*/
|
||||
static bool relay_chn_can_perform_tilt_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_tilt_cmd_t tilt_cmd)
|
||||
{
|
||||
relay_chn_cmd_t last_run_cmd = relay_chn_run_info_get_last_run_cmd(tilt_ctl->chn_ctl->run_info);
|
||||
if (last_run_cmd == RELAY_CHN_CMD_FORWARD) {
|
||||
return (tilt_cmd == RELAY_CHN_TILT_CMD_FORWARD) ||
|
||||
(tilt_cmd == RELAY_CHN_TILT_CMD_REVERSE && tilt_ctl->tilt_count > 0);
|
||||
} else if (last_run_cmd == RELAY_CHN_CMD_REVERSE) {
|
||||
return (tilt_cmd == RELAY_CHN_TILT_CMD_REVERSE) ||
|
||||
(tilt_cmd == RELAY_CHN_TILT_CMD_FORWARD && tilt_ctl->tilt_count > 0);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Issue a tilt command to a specific relay channel.
|
||||
static void relay_chn_tilt_issue_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_tilt_cmd_t cmd)
|
||||
{
|
||||
// TILT_STOP is safe and high priority
|
||||
if (cmd == RELAY_CHN_TILT_CMD_STOP) {
|
||||
relay_chn_state_t state = tilt_ctl->chn_ctl->state;
|
||||
if (state == RELAY_CHN_STATE_TILT_FORWARD || state == RELAY_CHN_STATE_TILT_REVERSE) {
|
||||
// If the command is TILT_STOP, issue it immediately
|
||||
relay_chn_tilt_dispatch_cmd(tilt_ctl, cmd);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (relay_chn_run_info_get_last_run_cmd(tilt_ctl->chn_ctl->run_info) == RELAY_CHN_CMD_NONE) {
|
||||
// Do not tilt if the channel hasn't been run before
|
||||
ESP_LOGD(TAG, "relay_chn_tilt_issue_cmd: Tilt will not be executed since the channel hasn't been run yet");
|
||||
return;
|
||||
}
|
||||
|
||||
if (tilt_ctl->cmd == cmd) {
|
||||
ESP_LOGD(TAG, "relay_chn_tilt_issue_cmd: There is already a tilt command in progress!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!relay_chn_can_perform_tilt_cmd(tilt_ctl, cmd)) {
|
||||
ESP_LOGD(TAG, "Cannot perform tilt command: %d for #%d", cmd, tilt_ctl->chn_ctl->id);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the command that will be processed
|
||||
tilt_ctl->cmd = cmd;
|
||||
switch (tilt_ctl->chn_ctl->state) {
|
||||
case RELAY_CHN_STATE_IDLE:
|
||||
// Relay channel is free, tilt can be issued immediately
|
||||
relay_chn_tilt_dispatch_cmd(tilt_ctl, cmd);
|
||||
break;
|
||||
|
||||
case RELAY_CHN_STATE_FORWARD_PENDING:
|
||||
case RELAY_CHN_STATE_REVERSE_PENDING:
|
||||
// Issue a stop command first so that the timer and pending cmd get cleared
|
||||
relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP);
|
||||
// FALLTHRU
|
||||
case RELAY_CHN_STATE_STOPPED: {
|
||||
// Check if channel needs timing before tilting
|
||||
uint32_t req_timing_ms = relay_chn_tilt_get_required_timing_before_tilting(tilt_ctl, cmd);
|
||||
if (req_timing_ms == 0) {
|
||||
relay_chn_tilt_dispatch_cmd(tilt_ctl, cmd);
|
||||
} else {
|
||||
// Channel needs timing before running tilting action, schedule it
|
||||
tilt_ctl->step = RELAY_CHN_TILT_STEP_PENDING;
|
||||
relay_chn_tilt_start_timer_or_stop(tilt_ctl, tilt_ctl->tilt_timer, req_timing_ms, "pending tilt");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case RELAY_CHN_STATE_FORWARD:
|
||||
if (cmd == RELAY_CHN_TILT_CMD_FORWARD) {
|
||||
// Stop the running channel first
|
||||
relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP);
|
||||
// Schedule for tilting
|
||||
tilt_ctl->step = RELAY_CHN_TILT_STEP_PENDING;
|
||||
relay_chn_tilt_start_timer_or_stop(tilt_ctl, tilt_ctl->tilt_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS, "tilt inertia");
|
||||
} else if (cmd == RELAY_CHN_TILT_CMD_REVERSE) {
|
||||
// Stop the running channel first
|
||||
relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP);
|
||||
// If the tilt cmd is TILT_REVERSE then dispatch it immediately
|
||||
relay_chn_tilt_dispatch_cmd(tilt_ctl, cmd);
|
||||
}
|
||||
break;
|
||||
|
||||
case RELAY_CHN_STATE_REVERSE:
|
||||
if (cmd == RELAY_CHN_TILT_CMD_REVERSE) {
|
||||
// Stop the running channel first
|
||||
relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP);
|
||||
// Schedule for tilting
|
||||
tilt_ctl->step = RELAY_CHN_TILT_STEP_PENDING;
|
||||
relay_chn_tilt_start_timer_or_stop(tilt_ctl, tilt_ctl->tilt_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS, "tilt inertia");
|
||||
} else if (cmd == RELAY_CHN_TILT_CMD_FORWARD) {
|
||||
// Stop the running channel first
|
||||
relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP);
|
||||
// If the tilt cmd is TILT_FORWARD then dispatch it immediately
|
||||
relay_chn_tilt_dispatch_cmd(tilt_ctl, cmd);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
ESP_LOGD(TAG, "relay_chn_tilt_issue_cmd: Unexpected relay channel state: %s!", relay_chn_state_to_str(tilt_ctl->chn_ctl->state));
|
||||
}
|
||||
}
|
||||
|
||||
static void relay_chn_tilt_issue_auto(relay_chn_tilt_ctl_t *tilt_ctl)
|
||||
{
|
||||
relay_chn_cmd_t last_run_cmd = relay_chn_run_info_get_last_run_cmd(tilt_ctl->chn_ctl->run_info);
|
||||
if (last_run_cmd == RELAY_CHN_CMD_FORWARD || tilt_ctl->chn_ctl->state == RELAY_CHN_STATE_FORWARD) {
|
||||
relay_chn_tilt_issue_cmd(tilt_ctl, RELAY_CHN_TILT_CMD_FORWARD);
|
||||
}
|
||||
else if (last_run_cmd == RELAY_CHN_CMD_REVERSE || tilt_ctl->chn_ctl->state == RELAY_CHN_STATE_REVERSE) {
|
||||
relay_chn_tilt_issue_cmd(tilt_ctl, RELAY_CHN_TILT_CMD_REVERSE);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t relay_chn_tilt_get_default_sensitivity()
|
||||
{
|
||||
return RELAY_CHN_TILT_DEFAULT_SENSITIVITY;
|
||||
}
|
||||
|
||||
|
||||
#if CONFIG_RELAY_CHN_COUNT > 1
|
||||
static void relay_chn_tilt_issue_cmd_on_all_channels(relay_chn_tilt_cmd_t cmd)
|
||||
{
|
||||
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
relay_chn_tilt_ctl_t* tilt_ctl = &s_tilt_ctls[i];
|
||||
relay_chn_tilt_issue_cmd(tilt_ctl, cmd);
|
||||
}
|
||||
}
|
||||
|
||||
void relay_chn_tilt_auto(uint8_t chn_id)
|
||||
{
|
||||
if (relay_chn_is_channel_id_valid(chn_id)) {
|
||||
relay_chn_tilt_issue_auto(&s_tilt_ctls[chn_id]);
|
||||
}
|
||||
}
|
||||
|
||||
void relay_chn_tilt_auto_all()
|
||||
{
|
||||
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
relay_chn_tilt_issue_auto(&s_tilt_ctls[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void relay_chn_tilt_forward(uint8_t chn_id)
|
||||
{
|
||||
if (relay_chn_is_channel_id_valid(chn_id)) {
|
||||
relay_chn_tilt_issue_cmd(&s_tilt_ctls[chn_id], RELAY_CHN_TILT_CMD_FORWARD);
|
||||
}
|
||||
}
|
||||
|
||||
void relay_chn_tilt_forward_all()
|
||||
{
|
||||
relay_chn_tilt_issue_cmd_on_all_channels(RELAY_CHN_TILT_CMD_FORWARD);
|
||||
}
|
||||
|
||||
void relay_chn_tilt_reverse(uint8_t chn_id)
|
||||
{
|
||||
if (relay_chn_is_channel_id_valid(chn_id)) {
|
||||
relay_chn_tilt_issue_cmd(&s_tilt_ctls[chn_id], RELAY_CHN_TILT_CMD_REVERSE);
|
||||
}
|
||||
}
|
||||
|
||||
void relay_chn_tilt_reverse_all()
|
||||
{
|
||||
relay_chn_tilt_issue_cmd_on_all_channels(RELAY_CHN_TILT_CMD_REVERSE);
|
||||
}
|
||||
|
||||
void relay_chn_tilt_stop(uint8_t chn_id)
|
||||
{
|
||||
if (relay_chn_is_channel_id_valid(chn_id)) {
|
||||
relay_chn_tilt_dispatch_cmd(&s_tilt_ctls[chn_id], RELAY_CHN_TILT_CMD_STOP);
|
||||
}
|
||||
}
|
||||
|
||||
void relay_chn_tilt_stop_all()
|
||||
{
|
||||
relay_chn_tilt_issue_cmd_on_all_channels(RELAY_CHN_TILT_CMD_STOP);
|
||||
}
|
||||
|
||||
#else // CONFIG_RELAY_CHN_COUNT > 1
|
||||
|
||||
void relay_chn_tilt_auto()
|
||||
{
|
||||
relay_chn_tilt_issue_auto(&s_tilt_ctl);
|
||||
}
|
||||
|
||||
void relay_chn_tilt_forward()
|
||||
{
|
||||
relay_chn_tilt_issue_cmd(&s_tilt_ctl, RELAY_CHN_TILT_CMD_FORWARD);
|
||||
}
|
||||
|
||||
void relay_chn_tilt_reverse()
|
||||
{
|
||||
relay_chn_tilt_issue_cmd(&s_tilt_ctl, RELAY_CHN_TILT_CMD_REVERSE);
|
||||
}
|
||||
|
||||
void relay_chn_tilt_stop()
|
||||
{
|
||||
relay_chn_tilt_issue_cmd(&s_tilt_ctl, RELAY_CHN_TILT_CMD_STOP);
|
||||
}
|
||||
#endif // CONFIG_RELAY_CHN_COUNT > 1
|
||||
|
||||
static void relay_chn_tilt_set_timing_values(relay_chn_tilt_timing_t *tilt_timing,
|
||||
uint8_t sensitivity,
|
||||
uint32_t run_time_ms,
|
||||
uint32_t pause_time_ms)
|
||||
{
|
||||
tilt_timing->sensitivity = sensitivity;
|
||||
tilt_timing->move_time_ms = run_time_ms;
|
||||
tilt_timing->pause_time_ms = pause_time_ms;
|
||||
}
|
||||
|
||||
static void relay_chn_tilt_compute_set_sensitivity(relay_chn_tilt_ctl_t *tilt_ctl, uint8_t sensitivity)
|
||||
{
|
||||
if (sensitivity >= 100) {
|
||||
relay_chn_tilt_set_timing_values(&tilt_ctl->tilt_timing,
|
||||
100,
|
||||
RELAY_CHN_TILT_RUN_MAX_MS,
|
||||
RELAY_CHN_TILT_PAUSE_MAX_MS);
|
||||
}
|
||||
else if (sensitivity == 0) {
|
||||
relay_chn_tilt_set_timing_values(&tilt_ctl->tilt_timing,
|
||||
0,
|
||||
RELAY_CHN_TILT_RUN_MIN_MS,
|
||||
RELAY_CHN_TILT_PAUSE_MIN_MS);
|
||||
}
|
||||
else if (sensitivity == RELAY_CHN_TILT_DEFAULT_SENSITIVITY) {
|
||||
relay_chn_tilt_set_timing_values(&tilt_ctl->tilt_timing,
|
||||
sensitivity,
|
||||
RELAY_CHN_TILT_DEFAULT_RUN_MS,
|
||||
RELAY_CHN_TILT_DEFAULT_PAUSE_MS);
|
||||
}
|
||||
else {
|
||||
// Compute the new timing values from the sensitivity percent value by using linear interpolation
|
||||
uint32_t tilt_run_time_ms = 0, tilt_pause_time_ms = 0;
|
||||
tilt_run_time_ms = RELAY_CHN_TILT_RUN_MIN_MS + (sensitivity * (RELAY_CHN_TILT_RUN_MAX_MS - RELAY_CHN_TILT_RUN_MIN_MS) / 100);
|
||||
tilt_pause_time_ms = RELAY_CHN_TILT_PAUSE_MIN_MS + (sensitivity * (RELAY_CHN_TILT_PAUSE_MAX_MS - RELAY_CHN_TILT_PAUSE_MIN_MS) / 100);
|
||||
|
||||
relay_chn_tilt_set_timing_values(&tilt_ctl->tilt_timing,
|
||||
sensitivity,
|
||||
tilt_run_time_ms,
|
||||
tilt_pause_time_ms);
|
||||
}
|
||||
}
|
||||
|
||||
#if CONFIG_RELAY_CHN_COUNT > 1
|
||||
void relay_chn_tilt_set_sensitivity(uint8_t chn_id, uint8_t sensitivity)
|
||||
{
|
||||
if (relay_chn_is_channel_id_valid(chn_id)) {
|
||||
ADJUST_TILT_SENS_BOUNDARIES(sensitivity);
|
||||
relay_chn_tilt_compute_set_sensitivity(&s_tilt_ctls[chn_id], sensitivity);
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
relay_chn_nvs_set_tilt_sensitivity(chn_id, sensitivity);
|
||||
#endif // CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_tilt_set_sensitivity_all(uint8_t *sensitivities)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(sensitivities != NULL, ESP_ERR_INVALID_ARG, TAG, "set_sensitivity_all: sensitivities is NULL");
|
||||
|
||||
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
uint8_t *src_sensitivity = &sensitivities[i];
|
||||
if (src_sensitivity == NULL) {
|
||||
ESP_LOGW(TAG, "set_sensitivity_all: Run limits have been set until channel %d since sensitivities[%d] is NULL", i, i);
|
||||
break;
|
||||
}
|
||||
ADJUST_TILT_SENS_BOUNDARIES(*src_sensitivity);
|
||||
relay_chn_tilt_compute_set_sensitivity(&s_tilt_ctls[i], *src_sensitivity);
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
relay_chn_nvs_set_tilt_sensitivity(i, *src_sensitivity);
|
||||
#endif // CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void relay_chn_tilt_set_sensitivity_all_with(uint8_t sensitivity)
|
||||
{
|
||||
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
ADJUST_TILT_SENS_BOUNDARIES(sensitivity);
|
||||
relay_chn_tilt_compute_set_sensitivity(&s_tilt_ctls[i], sensitivity);
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
relay_chn_nvs_set_tilt_sensitivity(i, sensitivity);
|
||||
#endif // CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t relay_chn_tilt_get_sensitivity(uint8_t chn_id)
|
||||
{
|
||||
return relay_chn_is_channel_id_valid(chn_id) ?
|
||||
s_tilt_ctls[chn_id].tilt_timing.sensitivity : 0;
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_tilt_get_sensitivity_all(uint8_t *sensitivities)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(sensitivities != NULL, ESP_ERR_INVALID_ARG, TAG, "get_sensitivity_all: sensitivities is NULL");
|
||||
|
||||
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
uint8_t *dest_sensitivity = &sensitivities[i];
|
||||
if (dest_sensitivity == NULL) {
|
||||
ESP_LOGW(TAG, "get_sensitivity_all: Sensitivites have been copied until channel %d since sensitivities[%d] is NULL", i, i);
|
||||
break;
|
||||
}
|
||||
*dest_sensitivity = s_tilt_ctls[i].tilt_timing.sensitivity;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void relay_chn_tilt_set_sensitivity(uint8_t sensitivity)
|
||||
{
|
||||
ADJUST_TILT_SENS_BOUNDARIES(sensitivity);
|
||||
relay_chn_tilt_compute_set_sensitivity(&s_tilt_ctl, sensitivity);
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
relay_chn_nvs_set_tilt_sensitivity(0, sensitivity);
|
||||
#endif // CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
}
|
||||
|
||||
uint8_t relay_chn_tilt_get_sensitivity()
|
||||
{
|
||||
return s_tilt_ctl.tilt_timing.sensitivity;
|
||||
}
|
||||
#endif // CONFIG_RELAY_CHN_COUNT > 1
|
||||
|
||||
void relay_chn_tilt_reset_count(relay_chn_tilt_ctl_t *tilt_ctl)
|
||||
{
|
||||
tilt_ctl->tilt_count = 0;
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
esp_timer_stop(tilt_ctl->flush_timer);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update tilt count automatically and return the current value.
|
||||
*
|
||||
* This helper function updates the relevant tilt count depending on the
|
||||
* last run info and helps the tilt module in deciding whether the requested
|
||||
* tilt should execute or not.
|
||||
*
|
||||
* This is useful to control reverse tilting for the same direction particularly.
|
||||
* For example:
|
||||
* - If the channel's last run was FORWARD and a TILT_FORWARD is requested,
|
||||
* then the tilt count will count up on the relay_chn_tilt_ctl_t::tilt_count
|
||||
* and the function will return the actual count.
|
||||
* - If the channel's last run was FORWARD and a TILT_REVERSE is requested,
|
||||
* then the relay_chn_tilt_ctl_t::tilt_count will be checked against zero first,
|
||||
* and then it will count down and return the actual count if it is greater
|
||||
* than 0, else the function will return 0.
|
||||
* - If the tilt command is irrelevant then the function will return 0.
|
||||
* - If the last run is irrelevant then the function will return 0.
|
||||
*
|
||||
* @param tilt_ctl The relay channel handle.
|
||||
*
|
||||
* @return The actual value of the relevant count.
|
||||
* @return 1 if the last tilt_count was 1 and decremented to 0.
|
||||
* @return 0 if:
|
||||
* - related count is already 0.
|
||||
* - tilt command is irrelevant.
|
||||
* - last run info is irrelevant.
|
||||
*/
|
||||
static uint16_t relay_chn_tilt_count_update(relay_chn_tilt_ctl_t *tilt_ctl)
|
||||
{
|
||||
relay_chn_cmd_t last_run_cmd = relay_chn_run_info_get_last_run_cmd(tilt_ctl->chn_ctl->run_info);
|
||||
if (last_run_cmd == RELAY_CHN_CMD_FORWARD) {
|
||||
if (tilt_ctl->cmd == RELAY_CHN_TILT_CMD_FORWARD) {
|
||||
return ++tilt_ctl->tilt_count;
|
||||
}
|
||||
else if (tilt_ctl->cmd == RELAY_CHN_TILT_CMD_REVERSE) {
|
||||
if (tilt_ctl->tilt_count > 0) {
|
||||
--tilt_ctl->tilt_count;
|
||||
// Still should do one more move, return non-zero value
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else if (last_run_cmd == RELAY_CHN_CMD_REVERSE) {
|
||||
if (tilt_ctl->cmd == RELAY_CHN_TILT_CMD_REVERSE) {
|
||||
return ++tilt_ctl->tilt_count;
|
||||
}
|
||||
else if (tilt_ctl->cmd == RELAY_CHN_TILT_CMD_FORWARD) {
|
||||
if (tilt_ctl->tilt_count > 0) {
|
||||
--tilt_ctl->tilt_count;
|
||||
// Still should do one more move, return non-zero value
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Irrelevant case -> reset
|
||||
tilt_ctl->tilt_count = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
static esp_err_t relay_chn_tilt_save_tilt_count(relay_chn_tilt_ctl_t *tilt_ctl)
|
||||
{
|
||||
// Save the tilt count to NVS storage
|
||||
esp_err_t ret = relay_chn_nvs_set_tilt_count(tilt_ctl->chn_ctl->id, tilt_ctl->tilt_count);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "relay_chn_tilt_execute_stop: Failed to save tilt count for channel #%d: %s", tilt_ctl->chn_ctl->id, esp_err_to_name(ret));
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void relay_chn_tilt_flush_timer_cb(void *arg)
|
||||
{
|
||||
relay_chn_tilt_ctl_t* tilt_ctl = (relay_chn_tilt_ctl_t*) arg;
|
||||
ESP_RETURN_VOID_ON_FALSE(tilt_ctl != NULL, TAG, "relay_chn_tilt_flush_timer_cb: timer arg is NULL");
|
||||
// Save the tilt count to storage
|
||||
esp_err_t ret = relay_chn_tilt_save_tilt_count(tilt_ctl);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "relay_chn_tilt_execute_stop: Failed to save tilt count for channel #%d: %s", tilt_ctl->chn_ctl->id, esp_err_to_name(ret));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void relay_chn_tilt_execute_stop(relay_chn_tilt_ctl_t *tilt_ctl)
|
||||
{
|
||||
// Stop the channel's timer if active
|
||||
esp_timer_stop(tilt_ctl->tilt_timer);
|
||||
// Invalidate tilt cmd and step
|
||||
tilt_ctl->cmd = RELAY_CHN_TILT_CMD_NONE;
|
||||
tilt_ctl->step = RELAY_CHN_TILT_STEP_NONE;
|
||||
// Stop the channel
|
||||
if (relay_chn_output_stop(tilt_ctl->chn_ctl->output) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "relay_chn_tilt_execute_stop: Failed to output stop for relay channel #%d!", tilt_ctl->chn_ctl->id);
|
||||
}
|
||||
relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_IDLE);
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
// Start the flush debounce timer
|
||||
if (relay_chn_start_esp_timer_once(tilt_ctl->flush_timer, RELAY_CHN_TILT_FLUSH_DEBOUNCE_MS) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to start tilt flush timer for ch %d", tilt_ctl->chn_ctl->id);
|
||||
// This is not a critical failure, just log it. The count will be saved on next stop.
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void relay_chn_tilt_execute_forward(relay_chn_tilt_ctl_t *tilt_ctl)
|
||||
{
|
||||
if (relay_chn_output_reverse(tilt_ctl->chn_ctl->output) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "relay_chn_tilt_execute_forward: Failed to output reverse for relay channel #%d!", tilt_ctl->chn_ctl->id);
|
||||
// Stop tilting because of the error
|
||||
relay_chn_tilt_execute_stop(tilt_ctl);
|
||||
return;
|
||||
}
|
||||
// Set the move time timer
|
||||
relay_chn_tilt_start_timer_or_stop(tilt_ctl, tilt_ctl->tilt_timer, tilt_ctl->tilt_timing.move_time_ms, "tilt move");
|
||||
// Set to pause step
|
||||
tilt_ctl->step = RELAY_CHN_TILT_STEP_PAUSE;
|
||||
}
|
||||
|
||||
static void relay_chn_tilt_execute_reverse(relay_chn_tilt_ctl_t *tilt_ctl)
|
||||
{
|
||||
if (relay_chn_output_forward(tilt_ctl->chn_ctl->output) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "relay_chn_tilt_execute_reverse: Failed to output forward for relay channel #%d!", tilt_ctl->chn_ctl->id);
|
||||
// Stop tilting because of the error
|
||||
relay_chn_tilt_execute_stop(tilt_ctl);
|
||||
return;
|
||||
}
|
||||
// Set the move time timer
|
||||
relay_chn_tilt_start_timer_or_stop(tilt_ctl, tilt_ctl->tilt_timer, tilt_ctl->tilt_timing.move_time_ms, "tilt move");
|
||||
// Set to pause step
|
||||
tilt_ctl->step = RELAY_CHN_TILT_STEP_PAUSE;
|
||||
}
|
||||
|
||||
static void relay_chn_tilt_execute_pause(relay_chn_tilt_ctl_t *tilt_ctl)
|
||||
{
|
||||
// Pause the channel
|
||||
if (relay_chn_output_stop(tilt_ctl->chn_ctl->output) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "relay_chn_tilt_execute_pause: Failed to output stop for relay channel #%d!", tilt_ctl->chn_ctl->id);
|
||||
// Stop tilting because of the error
|
||||
relay_chn_tilt_execute_stop(tilt_ctl);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the tilt count before the next move and expect the return value to be greater than 0
|
||||
if (relay_chn_tilt_count_update(tilt_ctl) == 0) {
|
||||
ESP_LOGD(TAG, "relay_chn_tilt_execute_pause: Relay channel cannot tilt anymore");
|
||||
// Stop tilting since the tilting limit has been reached
|
||||
relay_chn_tilt_execute_stop(tilt_ctl);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the pause time timer
|
||||
relay_chn_tilt_start_timer_or_stop(tilt_ctl, tilt_ctl->tilt_timer, tilt_ctl->tilt_timing.pause_time_ms, "tilt pause");
|
||||
// Set to move step
|
||||
tilt_ctl->step = RELAY_CHN_TILT_STEP_MOVE;
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_tilt_dispatch_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_tilt_cmd_t cmd)
|
||||
{
|
||||
ESP_LOGD(TAG, "relay_chn_tilt_dispatch_cmd: Command: %d", cmd);
|
||||
|
||||
switch(cmd) {
|
||||
case RELAY_CHN_TILT_CMD_STOP:
|
||||
relay_chn_tilt_execute_stop(tilt_ctl);
|
||||
break;
|
||||
case RELAY_CHN_TILT_CMD_FORWARD:
|
||||
relay_chn_tilt_execute_forward(tilt_ctl);
|
||||
// Update channel state
|
||||
relay_chn_update_state(tilt_ctl->chn_ctl, RELAY_CHN_STATE_TILT_FORWARD);
|
||||
break;
|
||||
case RELAY_CHN_TILT_CMD_REVERSE:
|
||||
relay_chn_tilt_execute_reverse(tilt_ctl);
|
||||
// Update channel state
|
||||
relay_chn_update_state(tilt_ctl->chn_ctl, RELAY_CHN_STATE_TILT_REVERSE);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unexpected relay channel tilt command: %d!", cmd);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Timer callback for the relay_chn_tilt_control_t::tilt_timer
|
||||
static void relay_chn_tilt_timer_cb(void *arg)
|
||||
{
|
||||
relay_chn_tilt_ctl_t* tilt_ctl = (relay_chn_tilt_ctl_t*) arg;
|
||||
ESP_RETURN_VOID_ON_FALSE(tilt_ctl != NULL, TAG, "relay_chn_tilt_timer_cb: timer arg is NULL");
|
||||
|
||||
switch (tilt_ctl->step)
|
||||
{
|
||||
case RELAY_CHN_TILT_STEP_MOVE:
|
||||
if (tilt_ctl->cmd == RELAY_CHN_TILT_CMD_FORWARD) {
|
||||
relay_chn_tilt_execute_forward(tilt_ctl);
|
||||
}
|
||||
else if (tilt_ctl->cmd == RELAY_CHN_TILT_CMD_REVERSE) {
|
||||
relay_chn_tilt_execute_reverse(tilt_ctl);
|
||||
}
|
||||
break;
|
||||
|
||||
case RELAY_CHN_TILT_STEP_PAUSE:
|
||||
relay_chn_tilt_execute_pause(tilt_ctl);
|
||||
break;
|
||||
|
||||
case RELAY_CHN_TILT_STEP_PENDING:
|
||||
// Just dispatch the pending tilt command
|
||||
relay_chn_tilt_dispatch_cmd(tilt_ctl, tilt_ctl->cmd);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
static esp_err_t relay_chn_tilt_load_sensitivity(uint8_t ch, uint8_t *sensitivity)
|
||||
{
|
||||
ESP_RETURN_ON_ERROR(relay_chn_nvs_get_tilt_sensitivity(ch, sensitivity, RELAY_CHN_TILT_DEFAULT_SENSITIVITY),
|
||||
TAG, "Failed to load tilt sensitivity for channel %d", ch);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t relay_chn_tilt_load_tilt_count(uint8_t ch, uint16_t *tilt_count)
|
||||
{
|
||||
ESP_RETURN_ON_ERROR(relay_chn_nvs_get_tilt_count(ch, tilt_count, 0),
|
||||
TAG, "Failed to load tilt counters for channel %d", ch);
|
||||
ESP_LOGD(TAG, "Loaded tilt count for channel %d: %d", ch, *tilt_count);
|
||||
return ESP_OK;
|
||||
}
|
||||
#endif // CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
|
||||
static esp_err_t relay_chn_tilt_ctl_init(relay_chn_tilt_ctl_t *tilt_ctl,
|
||||
relay_chn_ctl_t *chn_ctl,
|
||||
uint16_t tilt_count ,
|
||||
uint8_t sensitivity)
|
||||
{
|
||||
tilt_ctl->cmd = RELAY_CHN_TILT_CMD_NONE;
|
||||
tilt_ctl->step = RELAY_CHN_TILT_STEP_NONE;
|
||||
relay_chn_tilt_compute_set_sensitivity(tilt_ctl, sensitivity);
|
||||
tilt_ctl->tilt_count = tilt_count;
|
||||
|
||||
tilt_ctl->chn_ctl = chn_ctl;
|
||||
tilt_ctl->chn_ctl->tilt_ctl = tilt_ctl;
|
||||
|
||||
// Create tilt timer for the channel
|
||||
char timer_name[32];
|
||||
snprintf(timer_name, sizeof(timer_name), "relay_chn_%2d_tilt_timer", chn_ctl->id);
|
||||
esp_timer_create_args_t timer_args = {
|
||||
.callback = relay_chn_tilt_timer_cb,
|
||||
.arg = tilt_ctl,
|
||||
.name = timer_name
|
||||
};
|
||||
esp_err_t ret = esp_timer_create(&timer_args, &tilt_ctl->tilt_timer);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create tilt timer for channel %d", chn_ctl->id);
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
// Create flush timer for the tilt counters
|
||||
snprintf(timer_name, sizeof(timer_name), "relay_chn_%2d_tilt_flush_timer", chn_ctl->id);
|
||||
timer_args.callback = relay_chn_tilt_flush_timer_cb;
|
||||
timer_args.name = timer_name;
|
||||
ret = esp_timer_create(&timer_args, &tilt_ctl->flush_timer);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create tilt flush timer for channel %d", chn_ctl->id);
|
||||
#endif
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_tilt_init(relay_chn_ctl_t *chn_ctls)
|
||||
{
|
||||
uint8_t sensitivity;
|
||||
uint16_t tilt_count;
|
||||
|
||||
#if CONFIG_RELAY_CHN_COUNT > 1
|
||||
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
esp_err_t ret;
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
ret = relay_chn_tilt_load_sensitivity(i, &sensitivity);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load tilt sensitivity for channel %d", i);
|
||||
ret = relay_chn_tilt_load_tilt_count(i, &tilt_count);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load tilt count for channel %d", i);
|
||||
#else
|
||||
sensitivity = RELAY_CHN_TILT_DEFAULT_SENSITIVITY;
|
||||
tilt_count = 0;
|
||||
#endif // CONFIG_RELAY_CHN_ENABLE_NVS == 1
|
||||
ret = relay_chn_tilt_ctl_init(&s_tilt_ctls[i], &chn_ctls[i], tilt_count, sensitivity);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to init tilt control for channel %d", i);
|
||||
}
|
||||
return ESP_OK;
|
||||
#else
|
||||
sensitivity = RELAY_CHN_TILT_DEFAULT_SENSITIVITY;
|
||||
tilt_count = 0;
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
esp_err_t ret = relay_chn_tilt_load_sensitivity(0, &sensitivity);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load tilt sensitivity for channel %d", 0);
|
||||
ret = relay_chn_tilt_load_tilt_count(0, &tilt_count);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load tilt count for channel %d", 0);
|
||||
#endif // CONFIG_RELAY_CHN_ENABLE_NVS == 1
|
||||
return relay_chn_tilt_ctl_init(&s_tilt_ctl, chn_ctls, tilt_count, sensitivity);
|
||||
#endif // CONFIG_RELAY_CHN_COUNT > 1
|
||||
}
|
||||
|
||||
void relay_chn_tilt_ctl_deinit(relay_chn_tilt_ctl_t *tilt_ctl)
|
||||
{
|
||||
if (tilt_ctl->tilt_timer != NULL) {
|
||||
esp_timer_delete(tilt_ctl->tilt_timer);
|
||||
tilt_ctl->tilt_timer = NULL;
|
||||
}
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
if (tilt_ctl->flush_timer != NULL) {
|
||||
esp_timer_delete(tilt_ctl->flush_timer);
|
||||
tilt_ctl->flush_timer = NULL;
|
||||
}
|
||||
#endif // CONFIG_RELAY_CHN_ENABLE_NVS == 1
|
||||
}
|
||||
|
||||
void relay_chn_tilt_deinit()
|
||||
{
|
||||
#if CONFIG_RELAY_CHN_COUNT > 1
|
||||
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
relay_chn_tilt_ctl_deinit(&s_tilt_ctls[i]);
|
||||
}
|
||||
#else
|
||||
relay_chn_tilt_ctl_deinit(&s_tilt_ctl);
|
||||
#endif // CONFIG_RELAY_CHN_COUNT > 1
|
||||
}
|
||||
23
test_apps/.vscode/c_cpp_properties.json
vendored
Normal file
23
test_apps/.vscode/c_cpp_properties.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "ESP-IDF",
|
||||
"compilerPath": "${config:idf.toolsPath}/tools/xtensa-esp-elf/esp-14.2.0_20241119/xtensa-esp-elf/bin/xtensa-esp32-elf-gcc",
|
||||
"compileCommands": "${config:idf.buildPath}/compile_commands.json",
|
||||
"includePath": [
|
||||
"${config:idf.espIdfPath}/components/**",
|
||||
"${config:idf.espIdfPathWin}/components/**",
|
||||
"${workspaceFolder}/**"
|
||||
],
|
||||
"browse": {
|
||||
"path": [
|
||||
"${config:idf.espIdfPath}/components",
|
||||
"${config:idf.espIdfPathWin}/components",
|
||||
"${workspaceFolder}"
|
||||
],
|
||||
"limitSymbolsToIncludedHeaders": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
}
|
||||
15
test_apps/.vscode/launch.json
vendored
Normal file
15
test_apps/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "gdbtarget",
|
||||
"request": "attach",
|
||||
"name": "Eclipse CDT GDB Adapter"
|
||||
},
|
||||
{
|
||||
"type": "espidf",
|
||||
"name": "Launch",
|
||||
"request": "launch"
|
||||
}
|
||||
]
|
||||
}
|
||||
3
test_apps/.vscode/settings.json
vendored
Normal file
3
test_apps/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"C_Cpp.intelliSenseEngine": "default"
|
||||
}
|
||||
@@ -1,20 +1,43 @@
|
||||
# === These files must be included in any case ===
|
||||
set(srcs "test_common.c"
|
||||
"test_app_main.c"
|
||||
"test_relay_chn_core.c"
|
||||
"test_relay_chn_listener.c")
|
||||
"test_relay_chn_notify_common.c"
|
||||
"test_app_main.c")
|
||||
|
||||
if(CONFIG_RELAY_CHN_ENABLE_TILTING)
|
||||
list(APPEND srcs "test_relay_chn_tilt.c")
|
||||
set(incdirs "."
|
||||
"../../private_include")
|
||||
|
||||
# === Selective compilation based on channel count ===
|
||||
if(CONFIG_RELAY_CHN_COUNT GREATER 1)
|
||||
list(APPEND srcs "test_relay_chn_core_multi.c"
|
||||
"test_relay_chn_notify_multi.c")
|
||||
else()
|
||||
list(APPEND srcs "test_relay_chn_core_single.c"
|
||||
"test_relay_chn_notify_single.c")
|
||||
endif()
|
||||
|
||||
if(CONFIG_RELAY_CHN_ENABLE_TILTING)
|
||||
if(CONFIG_RELAY_CHN_COUNT GREATER 1)
|
||||
list(APPEND srcs "test_relay_chn_tilt_multi.c")
|
||||
else()
|
||||
list(APPEND srcs "test_relay_chn_tilt_single.c")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(CONFIG_RELAY_CHN_ENABLE_NVS)
|
||||
list(APPEND srcs "../../src/relay_chn_nvs.c")
|
||||
if(CONFIG_RELAY_CHN_COUNT GREATER 1)
|
||||
list(APPEND srcs "test_relay_chn_nvs_multi.c")
|
||||
else()
|
||||
list(APPEND srcs "test_relay_chn_nvs_single.c")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
message(STATUS "srcs=${srcs}")
|
||||
|
||||
# In order for the cases defined by `TEST_CASE` to be linked into the final elf,
|
||||
# the component can be registered as WHOLE_ARCHIVE
|
||||
idf_component_register(
|
||||
SRCS ${srcs}
|
||||
INCLUDE_DIRS "."
|
||||
INCLUDE_DIRS ${incdirs}
|
||||
REQUIRES unity relay_chn
|
||||
WHOLE_ARCHIVE
|
||||
)
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "esp_log.h"
|
||||
#include "esp_system.h"
|
||||
#include "test_common.h"
|
||||
#include "unity.h"
|
||||
#include "unity_internals.h"
|
||||
#include "unity_test_runner.h"
|
||||
#include <stdbool.h>
|
||||
#include "test_common.h"
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
#include "nvs_flash.h"
|
||||
#include "relay_chn_nvs.h"
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef RELAY_CHN_UNITY_TEST_GROUP_TAG
|
||||
@@ -13,22 +23,68 @@
|
||||
#define RELAY_CHN_UNITY_TEST_GROUP_TAG "relay_chn"
|
||||
#endif
|
||||
|
||||
void setUp()
|
||||
void setUp()
|
||||
{
|
||||
g_is_component_initialized = false;
|
||||
|
||||
}
|
||||
|
||||
void tearDown()
|
||||
{
|
||||
// Clean up after each test
|
||||
if (g_is_component_initialized) {
|
||||
relay_chn_destroy();
|
||||
g_is_component_initialized = false;
|
||||
}
|
||||
reset_channels_to_defaults();
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
static void test_nvs_flash_init(void)
|
||||
{
|
||||
esp_err_t ret;
|
||||
#if CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION
|
||||
ret = nvs_flash_init_partition(CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME);
|
||||
ESP_LOGI(TEST_TAG, "test_nvs_flash_init: NVS flash init partition return: %s", esp_err_to_name(ret));
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
// NVS partition is truncated and needs to be erased
|
||||
ret = nvs_flash_erase_partition(CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME);
|
||||
if (ret == ESP_OK) {
|
||||
ret = nvs_flash_init_partition(CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME);
|
||||
}
|
||||
}
|
||||
#else
|
||||
ret = nvs_flash_init();
|
||||
ESP_LOGI(TEST_TAG, "test_nvs_flash_init: NVS flash init return: %s", esp_err_to_name(ret));
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
// NVS partition is truncated and needs to be erased
|
||||
ret = nvs_flash_erase();
|
||||
if (ret == ESP_OK) {
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
TEST_ESP_OK(ret);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
static void test_nvs_flash_deinit(void)
|
||||
{
|
||||
esp_err_t ret;
|
||||
#if CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION
|
||||
ret = nvs_flash_deinit_partition(CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME);
|
||||
#else
|
||||
ret = nvs_flash_deinit();
|
||||
#endif
|
||||
TEST_ESP_OK(ret);
|
||||
}
|
||||
#endif
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
// Init NVS once for all tests
|
||||
test_nvs_flash_init();
|
||||
#endif
|
||||
|
||||
// Create relay_chn once for all tests
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
|
||||
UNITY_BEGIN();
|
||||
|
||||
// Log general test information
|
||||
@@ -43,7 +99,16 @@ void app_main(void)
|
||||
}
|
||||
|
||||
UNITY_END();
|
||||
|
||||
// Destroy relay_chn
|
||||
relay_chn_destroy();
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
// Deinit NVS
|
||||
test_nvs_flash_deinit();
|
||||
#endif
|
||||
|
||||
ESP_LOGI(TEST_TAG, "All tests complete.");
|
||||
|
||||
|
||||
esp_restart(); // Restart to invoke qemu exit
|
||||
}
|
||||
|
||||
@@ -1,17 +1,91 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "test_common.h"
|
||||
#include "relay_chn_ctl.h" // For resetting the channels
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
#include "relay_chn_tilt.h" // For resetting tilt count
|
||||
#endif
|
||||
|
||||
const char *TEST_TAG = "RELAY_CHN_TEST";
|
||||
|
||||
// GPIO eşlemesi (örn: GPIO_NUM_4 vs GPIO_NUM_5)
|
||||
const gpio_num_t gpio_map[] = {
|
||||
GPIO_NUM_4, GPIO_NUM_5, GPIO_NUM_18, GPIO_NUM_19
|
||||
// Test-wide GPIO map
|
||||
#if CONFIG_RELAY_CHN_COUNT > 1
|
||||
const uint8_t gpio_map[] = {
|
||||
0, 1,
|
||||
2, 3
|
||||
#if CONFIG_RELAY_CHN_COUNT > 2
|
||||
, 4, 5
|
||||
#if CONFIG_RELAY_CHN_COUNT > 3
|
||||
, 6, 7
|
||||
#if CONFIG_RELAY_CHN_COUNT > 4
|
||||
, 8, 9
|
||||
#if CONFIG_RELAY_CHN_COUNT > 5
|
||||
, 10, 11
|
||||
#if CONFIG_RELAY_CHN_COUNT > 6
|
||||
, 12, 13
|
||||
#if CONFIG_RELAY_CHN_COUNT > 7
|
||||
, 14, 15
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
#else
|
||||
const uint8_t gpio_map[] = {4, 5};
|
||||
#endif
|
||||
|
||||
const uint8_t gpio_count = sizeof(gpio_map) / sizeof(gpio_map[0]);
|
||||
const uint8_t relay_chn_count = gpio_count / 2;
|
||||
|
||||
// Konfigürasyon tabanlı inertia süresi
|
||||
const uint32_t opposite_inertia_ms = CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS;
|
||||
const uint32_t test_delay_margin_ms = 50; // ms toleransı
|
||||
static void reset_channel(relay_chn_ctl_t *ctl)
|
||||
{
|
||||
ctl->pending_cmd = RELAY_CHN_CMD_NONE;
|
||||
ctl->state = RELAY_CHN_STATE_IDLE;
|
||||
ctl->output->direction = RELAY_CHN_DIRECTION_DEFAULT;
|
||||
ctl->run_info->last_run_cmd = RELAY_CHN_CMD_NONE;
|
||||
ctl->run_info->last_run_cmd_time_ms = 0;
|
||||
esp_timer_stop(ctl->inertia_timer);
|
||||
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
esp_timer_stop(ctl->run_limit_timer);
|
||||
ctl->run_limit_sec = CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC;
|
||||
#endif
|
||||
#if CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
relay_chn_tilt_reset_count(ctl->tilt_ctl);
|
||||
#endif
|
||||
#if CONFIG_RELAY_CHN_COUNT > 1
|
||||
#else
|
||||
#endif
|
||||
}
|
||||
|
||||
bool g_is_component_initialized = false;
|
||||
void reset_channels_to_defaults()
|
||||
{
|
||||
#if CONFIG_RELAY_CHN_COUNT > 1
|
||||
relay_chn_ctl_t *ctls = relay_chn_ctl_get_all();
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(ctls, "reset_channels_to_defaults: relay_chn_ctl_get_all() returned NULL");
|
||||
|
||||
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
relay_chn_ctl_t *ctl = &ctls[i];
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(ctl, "ctl is NULL");
|
||||
reset_channel(ctl);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(i));
|
||||
}
|
||||
#else
|
||||
relay_chn_ctl_t *ctl = relay_chn_ctl_get();
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(ctl, "reset_channels_to_defaults: relay_chn_ctl_get() returned NULL");
|
||||
reset_channel(ctl);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state());
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction());
|
||||
#endif
|
||||
}
|
||||
|
||||
void test_state_listener(uint8_t id, relay_chn_state_t old_state, relay_chn_state_t new_state)
|
||||
{
|
||||
ESP_LOGI(TEST_TAG, "test_state_listener: id: %d, old_state: %d, new_state: %d", id, old_state, new_state);
|
||||
}
|
||||
@@ -1,25 +1,30 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string.h> // For memset
|
||||
#include "unity.h"
|
||||
#include "relay_chn.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_log.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
// Test log tag
|
||||
extern const char *TEST_TAG;
|
||||
|
||||
// GPIO konfigürasyonları
|
||||
extern const gpio_num_t gpio_map[];
|
||||
// GPIO configurations
|
||||
extern const uint8_t gpio_map[];
|
||||
extern const uint8_t gpio_count;
|
||||
extern const uint8_t relay_chn_count;
|
||||
|
||||
// Config parametreleri
|
||||
extern const uint32_t opposite_inertia_ms;
|
||||
extern const uint32_t test_delay_margin_ms;
|
||||
// Config variables for tests
|
||||
#define TEST_DELAY_MARGIN_MS 50
|
||||
|
||||
// Init durumu
|
||||
extern bool g_is_component_initialized;
|
||||
// Reset channels to Idle state
|
||||
void reset_channels_to_defaults(void);
|
||||
|
||||
// Relay channel state listener for tests
|
||||
void test_state_listener(uint8_t id, relay_chn_state_t old_state, relay_chn_state_t new_state);
|
||||
@@ -1,403 +0,0 @@
|
||||
#include "test_common.h"
|
||||
|
||||
|
||||
// --- Initialization Tests ---
|
||||
|
||||
TEST_CASE("relay_chn_create handles invalid arguments", "[relay_chn][core]")
|
||||
{
|
||||
// 1. Test with NULL gpio_map
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(NULL, gpio_count));
|
||||
|
||||
// 2. Test with incorrect gpio_count (must be RELAY_CHN_COUNT * 2)
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(gpio_map, gpio_count - 1));
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(gpio_map, 1));
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(gpio_map, 0));
|
||||
|
||||
// 3. Test with invalid GPIO numbers (GPIO_NUM_MAX is an invalid GPIO for output)
|
||||
gpio_num_t invalid_gpio_map[] = {GPIO_NUM_4, GPIO_NUM_MAX, GPIO_NUM_18, GPIO_NUM_19};
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(invalid_gpio_map, gpio_count));
|
||||
}
|
||||
|
||||
// --- Basic Functionality Tests ---
|
||||
|
||||
// TEST_CASE: Test that relay channels initialize correctly to RELAY_CHN_STATE_FREE
|
||||
TEST_CASE("Relay channels initialize correctly to FREE state", "[relay_chn][core]") {
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
// TEST_CASE: Test that relays do nothing when an invlid channel id given
|
||||
TEST_CASE("Run forward does nothing if channel id is invalid", "[relay_chn][core]") {
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
int invalid_id = relay_chn_count * 2 + i;
|
||||
relay_chn_run_forward(invalid_id); // relay_chn_run_forward returns void
|
||||
// Short delay for state to update
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
// TEST_CASE: Test that relays run in the forward direction and update their state
|
||||
TEST_CASE("Relay channels run forward and update state", "[relay_chn][core]") {
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
relay_chn_run_forward(i); // relay_chn_run_forward returns void
|
||||
// Short delay for state to update
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
// TEST_CASE: Test that relays do nothing when an invlid channel id given
|
||||
TEST_CASE("Run reverse does nothing if channel id is invalid", "[relay_chn][core]") {
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
// Verify that no valid channels were affected
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
int invalid_id = relay_chn_count * 2 + i;
|
||||
// Call run_reverse with an invalid ID
|
||||
relay_chn_run_reverse(invalid_id);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
// TEST_CASE: Test that relays run in the reverse direction and update their state
|
||||
TEST_CASE("Relay channels run reverse and update state", "[relay_chn][core]") {
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
relay_chn_run_reverse(i); // relay_chn_run_reverse returns void
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ### Broadcast Command (RELAY_CHN_ID_ALL) Tests
|
||||
|
||||
TEST_CASE("run_forward with ID_ALL sets all channels to FORWARD", "[relay_chn][core][id_all]")
|
||||
{
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
relay_chn_run_forward(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("run_reverse with ID_ALL sets all channels to REVERSE", "[relay_chn][core][id_all]")
|
||||
{
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
relay_chn_run_reverse(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("stop with ID_ALL stops all running channels", "[relay_chn][core][id_all]")
|
||||
{
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
// 1. Start all channels forward to ensure they are in a known running state
|
||||
relay_chn_run_forward(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
|
||||
// 2. Stop all channels using the broadcast command
|
||||
relay_chn_stop(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
|
||||
// 3. Verify all channels have transitioned to the FREE state
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// TEST_CASE: Test that relays stop and transition to RELAY_CHN_STATE_FREE
|
||||
// This test also verifies the transition to FREE state after a STOP command.
|
||||
TEST_CASE("Relay channels stop and update to FREE state", "[relay_chn][core]") {
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
// First, run forward to test stopping and transitioning to FREE state
|
||||
relay_chn_run_forward(i); // relay_chn_run_forward returns void
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(i));
|
||||
|
||||
// Now, issue the stop command
|
||||
relay_chn_stop(i); // relay_chn_stop returns void
|
||||
// Immediately after stop, state should be STOPPED
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(i));
|
||||
|
||||
// Then, wait for the inertia period for it to transition to RELAY_CHN_STATE_FREE
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
// TEST_CASE: Get state should return UNDEFINED when id is not valid
|
||||
TEST_CASE("Get state returns UNDEFINED when id is invalid", "[relay_chn][core]") {
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
int invalid_id = relay_chn_count * 2 + i;
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_UNDEFINED, relay_chn_get_state(invalid_id));
|
||||
}
|
||||
// Test for running states also
|
||||
relay_chn_run_forward(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
int invalid_id = relay_chn_count * 2 + i;
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_UNDEFINED, relay_chn_get_state(invalid_id));
|
||||
}
|
||||
}
|
||||
|
||||
// TEST_CASE: Get state string should return "UNKNOWN" when id is not valid
|
||||
TEST_CASE("Get state string returns UNKNOWN when id is invalid", "[relay_chn][core]") {
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
int invalid_id = relay_chn_count * 2 + i;
|
||||
TEST_ASSERT_EQUAL_STRING("UNKNOWN", relay_chn_get_state_str(invalid_id));
|
||||
}
|
||||
// Test for running states also
|
||||
relay_chn_run_forward(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
int invalid_id = relay_chn_count * 2 + i;
|
||||
TEST_ASSERT_EQUAL_STRING("UNKNOWN", relay_chn_get_state_str(invalid_id));
|
||||
}
|
||||
}
|
||||
|
||||
// TEST_CASE: Test independent operation of multiple relay channels
|
||||
TEST_CASE("Multiple channels can operate independently", "[relay_chn][core]") {
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
if (relay_chn_count >= 2) {
|
||||
// Start Channel 0 in forward direction
|
||||
relay_chn_run_forward(0); // relay_chn_run_forward returns void
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(0));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(1)); // Other channel should not be affected
|
||||
|
||||
// Start Channel 1 in reverse direction
|
||||
relay_chn_run_reverse(1); // relay_chn_run_reverse returns void
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(0));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(1));
|
||||
|
||||
// Stop Channel 0 and wait for it to become FREE
|
||||
relay_chn_stop(0); // relay_chn_stop returns void
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(0));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(1)); // Other channel should continue running
|
||||
|
||||
// Stop Channel 1 and wait for it to become FREE
|
||||
relay_chn_stop(1); // relay_chn_stop returns void
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(0));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(1));
|
||||
} else {
|
||||
ESP_LOGW("TEST", "Skipping 'Multiple channels can operate independently' test: Not enough channels available.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ### Inertia and State Transition Tests
|
||||
|
||||
// This section specifically targets the inertia periods and complex state transitions as per the component's logic.
|
||||
|
||||
// TEST_CASE: Test transition from forward to reverse with inertia and state checks
|
||||
// Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_REVERSE
|
||||
TEST_CASE("Forward to Reverse transition with opposite inertia", "[relay_chn][core][inertia]") {
|
||||
uint8_t ch = 0; // Channel to test
|
||||
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
// 1. Start in forward direction
|
||||
relay_chn_run_forward(ch); // relay_chn_run_forward returns void
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Short delay for state stabilization
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch));
|
||||
|
||||
// 2. Issue reverse command
|
||||
relay_chn_run_reverse(ch); // relay_chn_run_reverse returns void
|
||||
// Immediately after the command, the motor should be stopped
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE_PENDING, relay_chn_get_state(ch));
|
||||
|
||||
// Wait for the inertia period (after which the reverse command will be dispatched)
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch)); // Should now be in reverse state
|
||||
}
|
||||
|
||||
// TEST_CASE: Test transition from reverse to forward with inertia and state checks
|
||||
// Scenario: RELAY_CHN_STATE_REVERSE -> (relay_chn_run_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_FORWARD
|
||||
TEST_CASE("Reverse to Forward transition with opposite inertia", "[relay_chn][core][inertia]") {
|
||||
uint8_t ch = 0;
|
||||
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
// 1. Start in reverse direction
|
||||
relay_chn_run_reverse(ch); // relay_chn_run_reverse returns void
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch));
|
||||
|
||||
// 2. Issue forward command
|
||||
relay_chn_run_forward(ch); // relay_chn_run_forward returns void
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD_PENDING, relay_chn_get_state(ch));
|
||||
|
||||
// Wait for inertia
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch));
|
||||
}
|
||||
|
||||
// TEST_CASE: Test issuing the same run command while already running (no inertia expected)
|
||||
// Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD
|
||||
TEST_CASE("Running in same direction does not incur inertia", "[relay_chn][core][inertia]") {
|
||||
uint8_t ch = 0;
|
||||
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
// 1. Start in forward direction
|
||||
relay_chn_run_forward(ch); // relay_chn_run_forward returns void
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch));
|
||||
|
||||
// 2. Issue the same forward command again
|
||||
relay_chn_run_forward(ch); // relay_chn_run_forward returns void
|
||||
// As per the code, is_direction_opposite_to_current_motion should return false, so no inertia.
|
||||
// Just a short delay to check state remains the same.
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch));
|
||||
}
|
||||
|
||||
// TEST_CASE: Test transition from FREE state to running (no inertia expected)
|
||||
// Scenario: RELAY_CHN_STATE_FREE -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD
|
||||
TEST_CASE("FREE to Running transition without inertia", "[relay_chn][core][inertia]") {
|
||||
uint8_t ch = 0;
|
||||
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
// setUp() should have already brought the channel to FREE state
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(ch));
|
||||
|
||||
// Start in forward direction
|
||||
relay_chn_run_forward(ch); // relay_chn_run_forward returns void
|
||||
// No inertia is expected when starting from FREE state.
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch));
|
||||
}
|
||||
|
||||
// ### Direction Flipping Tests
|
||||
|
||||
TEST_CASE("Single channel direction can be flipped", "[relay_chn][core][direction]")
|
||||
{
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
const uint8_t ch = 0;
|
||||
|
||||
// 1. Initial direction should be default
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(ch));
|
||||
|
||||
// 2. Flip the direction
|
||||
relay_chn_flip_direction(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); // Wait for flip inertia
|
||||
|
||||
// 3. Verify direction is flipped
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction(ch));
|
||||
|
||||
// 4. Flip back
|
||||
relay_chn_flip_direction(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); // Wait for flip inertia
|
||||
|
||||
// 5. Verify direction is back to default
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(ch));
|
||||
}
|
||||
|
||||
TEST_CASE("All channels direction can be flipped simultaneously", "[relay_chn][core][direction][id_all]")
|
||||
{
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
// 1. Flip all channels
|
||||
relay_chn_flip_direction(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
|
||||
// 2. Verify all channels are flipped
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction(i));
|
||||
}
|
||||
|
||||
// 3. Flip all back
|
||||
relay_chn_flip_direction(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
|
||||
// 4. Verify all channels are back to default
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Flipping a running channel stops it and flips direction", "[relay_chn][core][direction]")
|
||||
{
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
const uint8_t ch = 0;
|
||||
|
||||
// 1. Start channel running and verify state
|
||||
relay_chn_run_forward(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch));
|
||||
|
||||
// 2. Flip the direction while running
|
||||
relay_chn_flip_direction(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Give time for events to process
|
||||
|
||||
// 3. The channel should stop as part of the flip process
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(ch));
|
||||
|
||||
// 4. Wait for the flip inertia to pass, after which it should be FREE and FLIPPED
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(ch));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction(ch));
|
||||
}
|
||||
|
||||
TEST_CASE("Direction flip handles invalid channel ID gracefully", "[relay_chn][core][direction]")
|
||||
{
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
const uint8_t invalid_ch = relay_chn_count + 5;
|
||||
|
||||
relay_chn_flip_direction(invalid_ch); // Call with an invalid ID
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(invalid_ch));
|
||||
}
|
||||
593
test_apps/main/test_relay_chn_core_multi.c
Normal file
593
test_apps/main/test_relay_chn_core_multi.c
Normal file
@@ -0,0 +1,593 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "test_common.h"
|
||||
|
||||
static relay_chn_state_t s_states[CONFIG_RELAY_CHN_COUNT], s_expect_states[CONFIG_RELAY_CHN_COUNT];
|
||||
static relay_chn_direction_t s_directions[CONFIG_RELAY_CHN_COUNT], s_expect_directions[CONFIG_RELAY_CHN_COUNT];
|
||||
|
||||
static void test_set_expected_state_all(relay_chn_state_t state)
|
||||
{
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
s_expect_states[i] = state;
|
||||
}
|
||||
}
|
||||
|
||||
static void test_set_expected_direction_all(relay_chn_direction_t direction)
|
||||
{
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
s_expect_directions[i] = direction;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- Initialization Tests ---
|
||||
|
||||
TEST_CASE("relay_chn_create handles invalid arguments", "[relay_chn][core]")
|
||||
{
|
||||
// 1. Test with NULL gpio_map
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(NULL, gpio_count));
|
||||
|
||||
// 2. Test with incorrect gpio_count (must be CONFIG_RELAY_CHN_COUNT * 2)
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(gpio_map, gpio_count - 1));
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(gpio_map, 1));
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(gpio_map, 0));
|
||||
|
||||
// 3. Test with invalid GPIO numbers (127 is an invalid GPIO for output)
|
||||
uint8_t invalid_gpio_map[] = {4, 127, 18, 19};
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(invalid_gpio_map, gpio_count));
|
||||
}
|
||||
|
||||
// --- Basic Functionality Tests ---
|
||||
|
||||
// TEST_CASE: Test that relay channels initialize correctly to RELAY_CHN_STATE_IDLE
|
||||
TEST_CASE("Relay channels initialize correctly to FREE state", "[relay_chn][core]")
|
||||
{
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
// TEST_CASE: Test that relays do nothing when an invlid channel id given
|
||||
TEST_CASE("Run forward does nothing if channel id is invalid", "[relay_chn][core]")
|
||||
{
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
int invalid_id = CONFIG_RELAY_CHN_COUNT * 2 + i;
|
||||
relay_chn_run_forward(invalid_id);
|
||||
// Short delay for state to update
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
// TEST_CASE: Test that relays run in the forward direction and update their state
|
||||
TEST_CASE("Relay channels run forward and update state", "[relay_chn][core]")
|
||||
{
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
relay_chn_run_forward(i);
|
||||
// Short delay for state to update
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
// TEST_CASE: Test that relays do nothing when an invlid channel id given
|
||||
TEST_CASE("Run reverse does nothing if channel id is invalid", "[relay_chn][core]")
|
||||
{
|
||||
// Verify that no valid channels were affected
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
int invalid_id = CONFIG_RELAY_CHN_COUNT * 2 + i;
|
||||
// Call run_reverse with an invalid ID
|
||||
relay_chn_run_reverse(invalid_id);
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
// TEST_CASE: Test that relays run in the reverse direction and update their state
|
||||
TEST_CASE("Relay channels run reverse and update state", "[relay_chn][core]")
|
||||
{
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
relay_chn_run_reverse(i);
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ### Batch Control Tests
|
||||
TEST_CASE("run_forward_all sets all channels to FORWARD", "[relay_chn][core][batch]")
|
||||
{
|
||||
relay_chn_run_forward_all();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("run_reverse_all sets all channels to REVERSE", "[relay_chn][core][batch]")
|
||||
{
|
||||
relay_chn_run_reverse_all();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("stop_all stops all running channels", "[relay_chn][core][batch]")
|
||||
{
|
||||
// 1. Start all channels forward to ensure they are in a known running state
|
||||
relay_chn_run_forward_all();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
|
||||
// 2. Stop all channels
|
||||
relay_chn_stop_all();
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
|
||||
// 3. Verify all channels have transitioned to the FREE state
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// TEST_CASE: Test that relays stop and transition to RELAY_CHN_STATE_IDLE
|
||||
// This test also verifies the transition to FREE state after a STOP command.
|
||||
TEST_CASE("Relay channels stop and update to FREE state", "[relay_chn][core]")
|
||||
{
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
// First, run forward to test stopping and transitioning to FREE state
|
||||
relay_chn_run_forward(i);
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(i));
|
||||
|
||||
// Now, issue the stop command
|
||||
relay_chn_stop(i);
|
||||
// Immediately after stop, state should be STOPPED
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(i));
|
||||
|
||||
// Then, wait for the inertia period for it to transition to RELAY_CHN_STATE_IDLE
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
// TEST_CASE: Get state should return UNDEFINED when id is not valid
|
||||
TEST_CASE("Get state returns UNDEFINED when id is invalid", "[relay_chn][core]")
|
||||
{
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
int invalid_id = CONFIG_RELAY_CHN_COUNT * 2 + i;
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_UNDEFINED, relay_chn_get_state(invalid_id));
|
||||
}
|
||||
// Test for running states also
|
||||
relay_chn_run_forward_all();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
int invalid_id = CONFIG_RELAY_CHN_COUNT * 2 + i;
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_UNDEFINED, relay_chn_get_state(invalid_id));
|
||||
}
|
||||
}
|
||||
|
||||
// TEST_CASE: Get state string should return "UNKNOWN" when id is not valid
|
||||
TEST_CASE("Get state string returns UNKNOWN when id is invalid", "[relay_chn][core]")
|
||||
{
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
int invalid_id = CONFIG_RELAY_CHN_COUNT * 2 + i;
|
||||
TEST_ASSERT_EQUAL_STRING("UNKNOWN", relay_chn_get_state_str(invalid_id));
|
||||
}
|
||||
// Test for running states also
|
||||
relay_chn_run_forward_all();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
int invalid_id = CONFIG_RELAY_CHN_COUNT * 2 + i;
|
||||
TEST_ASSERT_EQUAL_STRING("UNKNOWN", relay_chn_get_state_str(invalid_id));
|
||||
}
|
||||
}
|
||||
|
||||
// TEST_CASE: Test independent operation of multiple relay channels
|
||||
TEST_CASE("Multiple channels can operate independently", "[relay_chn][core]")
|
||||
{
|
||||
// Start Channel 0 in forward direction
|
||||
relay_chn_run_forward(0);
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(0));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(1)); // Other channel should not be affected
|
||||
|
||||
// Start Channel 1 in reverse direction
|
||||
relay_chn_run_reverse(1);
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(0));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(1));
|
||||
|
||||
// Stop Channel 0 and wait for it to become FREE
|
||||
relay_chn_stop(0);
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(0));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(1)); // Other channel should continue running
|
||||
|
||||
// Stop Channel 1 and wait for it to become FREE
|
||||
relay_chn_stop(1);
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(0));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(1));
|
||||
}
|
||||
|
||||
|
||||
// ### Inertia and State Transition Tests
|
||||
|
||||
// This section specifically targets the inertia periods and complex state transitions as per the component's logic.
|
||||
|
||||
// TEST_CASE: Test transition from forward to reverse with inertia and state checks
|
||||
// Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_REVERSE
|
||||
TEST_CASE("Forward to Reverse transition with opposite inertia", "[relay_chn][core][inertia]")
|
||||
{
|
||||
// 1. Start in forward direction
|
||||
relay_chn_run_forward_all();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Short delay for state stabilization
|
||||
TEST_ESP_OK(relay_chn_get_state_all(s_states));
|
||||
test_set_expected_state_all(RELAY_CHN_STATE_FORWARD);
|
||||
TEST_ASSERT_EQUAL_UINT_ARRAY(s_expect_states, s_states, CONFIG_RELAY_CHN_COUNT);
|
||||
|
||||
// 2. Issue reverse command
|
||||
relay_chn_run_reverse_all();
|
||||
// Immediately after the command, the motor should be stopped
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ESP_OK(relay_chn_get_state_all(s_states));
|
||||
test_set_expected_state_all(RELAY_CHN_STATE_REVERSE_PENDING);
|
||||
TEST_ASSERT_EQUAL_UINT_ARRAY(s_expect_states, s_states, CONFIG_RELAY_CHN_COUNT);
|
||||
|
||||
// Wait for the inertia period (after which the reverse command will be dispatched)
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
// Should now be in reverse state
|
||||
TEST_ESP_OK(relay_chn_get_state_all(s_states));
|
||||
test_set_expected_state_all(RELAY_CHN_STATE_REVERSE);
|
||||
TEST_ASSERT_EQUAL_UINT_ARRAY(s_expect_states, s_states, CONFIG_RELAY_CHN_COUNT);
|
||||
}
|
||||
|
||||
// TEST_CASE: Test transition from reverse to forward with inertia and state checks
|
||||
// Scenario: RELAY_CHN_STATE_REVERSE -> (relay_chn_run_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_FORWARD
|
||||
TEST_CASE("Reverse to Forward transition with opposite inertia", "[relay_chn][core][inertia]")
|
||||
{
|
||||
// 1. Start in reverse direction
|
||||
relay_chn_run_reverse_all();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ESP_OK(relay_chn_get_state_all(s_states));
|
||||
test_set_expected_state_all(RELAY_CHN_STATE_REVERSE);
|
||||
TEST_ASSERT_EQUAL_UINT_ARRAY(s_expect_states, s_states, CONFIG_RELAY_CHN_COUNT);
|
||||
|
||||
// 2. Issue forward command
|
||||
relay_chn_run_forward_all();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ESP_OK(relay_chn_get_state_all(s_states));
|
||||
test_set_expected_state_all(RELAY_CHN_STATE_FORWARD_PENDING);
|
||||
TEST_ASSERT_EQUAL_UINT_ARRAY(s_expect_states, s_states, CONFIG_RELAY_CHN_COUNT);
|
||||
|
||||
// Wait for inertia
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
TEST_ESP_OK(relay_chn_get_state_all(s_states));
|
||||
test_set_expected_state_all(RELAY_CHN_STATE_FORWARD);
|
||||
TEST_ASSERT_EQUAL_UINT_ARRAY(s_expect_states, s_states, CONFIG_RELAY_CHN_COUNT);
|
||||
}
|
||||
|
||||
// TEST_CASE: Test issuing the same run command while already running (no inertia expected)
|
||||
// Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD
|
||||
TEST_CASE("Running in same direction does not incur inertia", "[relay_chn][core][inertia]")
|
||||
{
|
||||
// 1. Start in forward direction
|
||||
relay_chn_run_forward_all();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ESP_OK(relay_chn_get_state_all(s_states));
|
||||
test_set_expected_state_all(RELAY_CHN_STATE_FORWARD);
|
||||
TEST_ASSERT_EQUAL_UINT_ARRAY(s_expect_states, s_states, CONFIG_RELAY_CHN_COUNT);
|
||||
|
||||
// 2. Issue the same forward command again
|
||||
relay_chn_run_forward_all();
|
||||
// As per the code, is_direction_opposite_to_current_motion should return false, so no inertia.
|
||||
// Just a short delay to check state remains the same.
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ESP_OK(relay_chn_get_state_all(s_states));
|
||||
test_set_expected_state_all(RELAY_CHN_STATE_FORWARD);
|
||||
TEST_ASSERT_EQUAL_UINT_ARRAY(s_expect_states, s_states, CONFIG_RELAY_CHN_COUNT);
|
||||
}
|
||||
|
||||
// ### Direction Flipping Tests
|
||||
|
||||
TEST_CASE("Direction can be flipped for each channel independently", "[relay_chn][core][direction]")
|
||||
{
|
||||
// 1. Initial direction should be default
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(i));
|
||||
}
|
||||
|
||||
// 2. Flip the direction
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
relay_chn_flip_direction(i);
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); // Wait for flip inertia
|
||||
|
||||
// 3. Verify direction is flipped
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction(i));
|
||||
}
|
||||
|
||||
// 4. Flip back
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
relay_chn_flip_direction(i);
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); // Wait for flip inertia
|
||||
|
||||
// 5. Verify direction is back to default
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("All channels direction can be flipped simultaneously", "[relay_chn][core][direction][batch]")
|
||||
{
|
||||
// 1. Flip all channels
|
||||
relay_chn_flip_direction_all();
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
|
||||
// 2. Verify all channels are flipped
|
||||
TEST_ESP_OK(relay_chn_get_direction_all(s_directions));
|
||||
test_set_expected_direction_all(RELAY_CHN_DIRECTION_FLIPPED);
|
||||
TEST_ASSERT_EQUAL_UINT_ARRAY(s_expect_directions, s_directions, CONFIG_RELAY_CHN_COUNT);
|
||||
|
||||
// 3. Flip all back
|
||||
relay_chn_flip_direction_all();
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
|
||||
// 4. Verify all channels are back to default
|
||||
TEST_ESP_OK(relay_chn_get_direction_all(s_directions));
|
||||
test_set_expected_direction_all(RELAY_CHN_DIRECTION_DEFAULT);
|
||||
TEST_ASSERT_EQUAL_UINT_ARRAY(s_expect_directions, s_directions, CONFIG_RELAY_CHN_COUNT);
|
||||
}
|
||||
|
||||
TEST_CASE("Flipping a running channel stops it and flips direction", "[relay_chn][core][direction]")
|
||||
{
|
||||
// 1. Start channel running and verify state
|
||||
relay_chn_run_forward_all();
|
||||
TEST_ESP_OK(relay_chn_get_state_all(s_states));
|
||||
test_set_expected_state_all(RELAY_CHN_STATE_FORWARD);
|
||||
TEST_ASSERT_EQUAL_UINT_ARRAY(s_expect_states, s_states, CONFIG_RELAY_CHN_COUNT);
|
||||
// 2. Flip the direction while running
|
||||
relay_chn_flip_direction_all();
|
||||
// 3. The channel should stop as part of the flip process
|
||||
TEST_ESP_OK(relay_chn_get_state_all(s_states));
|
||||
test_set_expected_state_all(RELAY_CHN_STATE_STOPPED);
|
||||
TEST_ASSERT_EQUAL_UINT_ARRAY(s_expect_states, s_states, CONFIG_RELAY_CHN_COUNT);
|
||||
|
||||
// 4. Wait for the flip inertia to pass, after which it should be idle and FLIPPED
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
|
||||
TEST_ESP_OK(relay_chn_get_state_all(s_states));
|
||||
test_set_expected_state_all(RELAY_CHN_STATE_IDLE);
|
||||
TEST_ASSERT_EQUAL_UINT_ARRAY(s_expect_states, s_states, CONFIG_RELAY_CHN_COUNT);
|
||||
|
||||
TEST_ESP_OK(relay_chn_get_direction_all(s_directions));
|
||||
test_set_expected_direction_all(RELAY_CHN_DIRECTION_FLIPPED);
|
||||
TEST_ASSERT_EQUAL_UINT_ARRAY(s_expect_directions, s_directions, CONFIG_RELAY_CHN_COUNT);
|
||||
}
|
||||
|
||||
TEST_CASE("Direction flip handles invalid channel ID gracefully", "[relay_chn][core][direction]")
|
||||
{
|
||||
const uint8_t invalid_ch = CONFIG_RELAY_CHN_COUNT + 5;
|
||||
|
||||
relay_chn_flip_direction(invalid_ch); // Call with an invalid ID
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(invalid_ch));
|
||||
}
|
||||
|
||||
TEST_CASE("get_state_all retrieves all channel states", "[relay_chn][core][batch]")
|
||||
{
|
||||
// 1. All should be IDLE initially
|
||||
TEST_ESP_OK(relay_chn_get_state_all(s_states));
|
||||
test_set_expected_state_all(RELAY_CHN_STATE_IDLE);
|
||||
|
||||
// 2. Set some states
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
if (i % 2 == 0) {
|
||||
relay_chn_run_forward(i);
|
||||
} else {
|
||||
relay_chn_run_reverse(i);
|
||||
}
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
|
||||
// 3. Get all states and verify
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
if (i % 2 == 0) {
|
||||
s_expect_states[i] = RELAY_CHN_STATE_FORWARD;
|
||||
} else {
|
||||
s_expect_states[i] = RELAY_CHN_STATE_REVERSE;
|
||||
}
|
||||
}
|
||||
TEST_ESP_OK(relay_chn_get_state_all(s_states));
|
||||
TEST_ASSERT_EQUAL_UINT_ARRAY(s_expect_states, s_states, CONFIG_RELAY_CHN_COUNT);
|
||||
}
|
||||
|
||||
TEST_CASE("get_direction_all retrieves all channel directions", "[relay_chn][core][direction][batch]")
|
||||
{
|
||||
// 1. All should be default initially
|
||||
TEST_ESP_OK(relay_chn_get_direction_all(s_directions));
|
||||
test_set_expected_direction_all(RELAY_CHN_DIRECTION_DEFAULT);
|
||||
TEST_ASSERT_EQUAL_UINT_ARRAY(s_expect_directions, s_directions, CONFIG_RELAY_CHN_COUNT);
|
||||
|
||||
// 2. Flip all
|
||||
relay_chn_flip_direction_all();
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
|
||||
// 3. Get all directions and verify
|
||||
TEST_ESP_OK(relay_chn_get_direction_all(s_directions));
|
||||
test_set_expected_direction_all(RELAY_CHN_DIRECTION_FLIPPED);
|
||||
TEST_ASSERT_EQUAL_UINT_ARRAY(s_expect_directions, s_directions, CONFIG_RELAY_CHN_COUNT);
|
||||
}
|
||||
|
||||
TEST_CASE("get_all functions handle NULL arguments", "[relay_chn][core][batch]")
|
||||
{
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_get_state_all(NULL));
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_get_direction_all(NULL));
|
||||
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_get_run_limit_all(NULL));
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_set_run_limit_all(NULL));
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
#define TEST_RUN_LIMIT_SEC 5
|
||||
#define TEST_SHORT_RUN_LIMIT_SEC 2
|
||||
// ### Run Limit Tests
|
||||
TEST_CASE("Test run limit initialization", "[relay_chn][run_limit]")
|
||||
{
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
// Should initialize with default value
|
||||
TEST_ASSERT_EQUAL(CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC, relay_chn_get_run_limit(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test run limit setting boundaries", "[relay_chn][run_limit]")
|
||||
{
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
// Test minimum boundary
|
||||
relay_chn_set_run_limit(i, CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC - 1);
|
||||
TEST_ASSERT_EQUAL(CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC, relay_chn_get_run_limit(i));
|
||||
|
||||
// Test maximum boundary
|
||||
relay_chn_set_run_limit(i, CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC + 1);
|
||||
TEST_ASSERT_EQUAL(CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC, relay_chn_get_run_limit(i));
|
||||
|
||||
// Test valid value
|
||||
relay_chn_set_run_limit(i, TEST_RUN_LIMIT_SEC);
|
||||
TEST_ASSERT_EQUAL(TEST_RUN_LIMIT_SEC, relay_chn_get_run_limit(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test run limit stops channel after timeout", "[relay_chn][run_limit]")
|
||||
{
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
// Set a short run limit for testing
|
||||
relay_chn_set_run_limit(i, TEST_SHORT_RUN_LIMIT_SEC);
|
||||
}
|
||||
|
||||
relay_chn_run_forward_all();
|
||||
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
// Check running forward
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(i));
|
||||
}
|
||||
|
||||
// Wait for run limit timeout
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_SHORT_RUN_LIMIT_SEC * 1000 + TEST_DELAY_MARGIN_MS));
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test run limit reset on direction change and time out finally", "[relay_chn][run_limit]")
|
||||
{
|
||||
relay_chn_set_run_limit_all_with(TEST_SHORT_RUN_LIMIT_SEC);
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
// Wait for the NVS module task to process operations
|
||||
vTaskDelay(300 / portTICK_PERIOD_MS); // Wait 1 second
|
||||
#endif
|
||||
|
||||
// Start running forward
|
||||
relay_chn_run_forward_all();
|
||||
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS); // Wait 1 second
|
||||
|
||||
// Change direction before timeout
|
||||
relay_chn_run_reverse_all();
|
||||
|
||||
// Wait for the inertia period (after which the reverse command will be dispatched)
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(i));
|
||||
}
|
||||
|
||||
// Timer should time out and stop the channel after the run limit time
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_SHORT_RUN_LIMIT_SEC * 1000 + TEST_DELAY_MARGIN_MS));
|
||||
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test run limit persistence across stop/start", "[relay_chn][run_limit]")
|
||||
{
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
// Set initial run limit
|
||||
relay_chn_set_run_limit(i, TEST_RUN_LIMIT_SEC);
|
||||
|
||||
// Stop and start channel
|
||||
relay_chn_stop(i);
|
||||
relay_chn_run_forward(i);
|
||||
|
||||
// Run limit should persist
|
||||
TEST_ASSERT_EQUAL(TEST_RUN_LIMIT_SEC, relay_chn_get_run_limit(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Run limit functions handle invalid channel ID", "[relay_chn][run_limit]")
|
||||
{
|
||||
const uint8_t invalid_ch = CONFIG_RELAY_CHN_COUNT + 5;
|
||||
const uint16_t original_limit = relay_chn_get_run_limit(0);
|
||||
|
||||
// get_run_limit with invalid ID should return 0
|
||||
TEST_ASSERT_EQUAL(0, relay_chn_get_run_limit(invalid_ch));
|
||||
|
||||
// set_run_limit with invalid ID should not crash or affect other channels
|
||||
relay_chn_set_run_limit(invalid_ch, 999);
|
||||
TEST_ASSERT_EQUAL(original_limit, relay_chn_get_run_limit(0));
|
||||
}
|
||||
|
||||
TEST_CASE("Run limit _all functions work correctly", "[relay_chn][run_limit][batch]")
|
||||
{
|
||||
// 1. Test set_run_limit_all_with
|
||||
relay_chn_set_run_limit_all_with(TEST_RUN_LIMIT_SEC);
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
TEST_ASSERT_EQUAL(TEST_RUN_LIMIT_SEC, relay_chn_get_run_limit(i));
|
||||
}
|
||||
|
||||
// 2. Test get_run_limit_all
|
||||
uint16_t limits[CONFIG_RELAY_CHN_COUNT];
|
||||
TEST_ESP_OK(relay_chn_get_run_limit_all(limits));
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
TEST_ASSERT_EQUAL(TEST_RUN_LIMIT_SEC, limits[i]);
|
||||
}
|
||||
|
||||
// 3. Test set_run_limit_all
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
limits[i] = TEST_RUN_LIMIT_SEC + i;
|
||||
}
|
||||
TEST_ESP_OK(relay_chn_set_run_limit_all(limits));
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
TEST_ASSERT_EQUAL(TEST_RUN_LIMIT_SEC + i, relay_chn_get_run_limit(i));
|
||||
}
|
||||
}
|
||||
#endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
|
||||
TEST_CASE("relay_chn_destroy allows clean-up and re-creation", "[relay_chn][core]")
|
||||
{
|
||||
relay_chn_run_forward_all();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(i));
|
||||
}
|
||||
|
||||
relay_chn_destroy();
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
320
test_apps/main/test_relay_chn_core_single.c
Normal file
320
test_apps/main/test_relay_chn_core_single.c
Normal file
@@ -0,0 +1,320 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "test_common.h"
|
||||
|
||||
|
||||
// --- Initialization Tests ---
|
||||
|
||||
TEST_CASE("relay_chn_create handles invalid arguments", "[relay_chn][core]")
|
||||
{
|
||||
// 1. Test with NULL gpio_map
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(NULL, gpio_count));
|
||||
|
||||
// 2. Test with incorrect gpio_count (must be CONFIG_RELAY_CHN_COUNT * 2)
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(gpio_map, gpio_count - 1));
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(gpio_map, 1));
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(gpio_map, 0));
|
||||
|
||||
// 3. Test with invalid GPIO numbers (GPIO_NUM_MAX is an invalid GPIO for output)
|
||||
uint8_t invalid_gpio_map[] = {4, 127};
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_create(invalid_gpio_map, gpio_count));
|
||||
}
|
||||
|
||||
// --- Basic Functionality Tests ---
|
||||
|
||||
// TEST_CASE: Test that relay channels initialize correctly to RELAY_CHN_STATE_IDLE
|
||||
TEST_CASE("Relay channels initialize correctly to IDLE state", "[relay_chn][core]")
|
||||
{
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state());
|
||||
}
|
||||
|
||||
// TEST_CASE: Test that relays run in the forward direction and update their state
|
||||
TEST_CASE("Relay channels run forward and update state", "[relay_chn][core]")
|
||||
{
|
||||
relay_chn_run_forward();
|
||||
// Short delay for state to update
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state());
|
||||
}
|
||||
|
||||
// TEST_CASE: Test that relays run in the reverse direction and update their state
|
||||
TEST_CASE("Relay channels run reverse and update state", "[relay_chn][core]")
|
||||
{
|
||||
relay_chn_run_reverse();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state());
|
||||
}
|
||||
|
||||
|
||||
// TEST_CASE: Test that relays stop and transition to RELAY_CHN_STATE_IDLE
|
||||
// This test also verifies the transition to IDLE state after a STOP command.
|
||||
TEST_CASE("Relay channels stop and update to IDLE state", "[relay_chn][core]")
|
||||
{
|
||||
// First, run forward to test stopping and transitioning to IDLE state
|
||||
relay_chn_run_forward();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state());
|
||||
|
||||
// Now, issue the stop command
|
||||
relay_chn_stop();
|
||||
// Immediately after stop, state should be STOPPED
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state());
|
||||
|
||||
// Then, wait for the inertia period for it to transition to RELAY_CHN_STATE_IDLE
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state());
|
||||
}
|
||||
|
||||
|
||||
// ### Inertia and State Transition Tests
|
||||
|
||||
// This section specifically targets the inertia periods and complex state transitions as per the component's logic.
|
||||
|
||||
// TEST_CASE: Test transition from forward to reverse with inertia and state checks
|
||||
// Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_REVERSE
|
||||
TEST_CASE("Forward to Reverse transition with opposite inertia", "[relay_chn][core][inertia]")
|
||||
{
|
||||
// 1. Start in forward direction
|
||||
relay_chn_run_forward();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Short delay for state stabilization
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state());
|
||||
|
||||
// 2. Issue reverse command
|
||||
relay_chn_run_reverse();
|
||||
// Immediately after the command, the motor should be stopped
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE_PENDING, relay_chn_get_state());
|
||||
|
||||
// Wait for the inertia period (after which the reverse command will be dispatched)
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state()); // Should now be in reverse state
|
||||
}
|
||||
|
||||
// TEST_CASE: Test transition from reverse to forward with inertia and state checks
|
||||
// Scenario: RELAY_CHN_STATE_REVERSE -> (relay_chn_run_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_FORWARD
|
||||
TEST_CASE("Reverse to Forward transition with opposite inertia", "[relay_chn][core][inertia]")
|
||||
{
|
||||
// 1. Start in reverse direction
|
||||
relay_chn_run_reverse();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state());
|
||||
|
||||
// 2. Issue forward command
|
||||
relay_chn_run_forward();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD_PENDING, relay_chn_get_state());
|
||||
|
||||
// Wait for inertia
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state());
|
||||
}
|
||||
|
||||
// TEST_CASE: Test issuing the same run command while already running (no inertia expected)
|
||||
// Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD
|
||||
TEST_CASE("Running in same direction does not incur inertia", "[relay_chn][core][inertia]")
|
||||
{
|
||||
// 1. Start in forward direction
|
||||
relay_chn_run_forward();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state());
|
||||
|
||||
// 2. Issue the same forward command again
|
||||
relay_chn_run_forward();
|
||||
// As per the code, is_direction_opposite_to_current_motion should return false, so no inertia.
|
||||
// Just a short delay to check state remains the same.
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state());
|
||||
}
|
||||
|
||||
// TEST_CASE: Test transition from IDLE state to running (no inertia expected)
|
||||
// Scenario: RELAY_CHN_STATE_IDLE -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD
|
||||
TEST_CASE("IDLE to Running transition without inertia", "[relay_chn][core][inertia]")
|
||||
{
|
||||
// setUp() should have already brought the channel to IDLE state
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state());
|
||||
|
||||
// Start in forward direction
|
||||
relay_chn_run_forward();
|
||||
// No inertia is expected when starting from IDLE state.
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state());
|
||||
}
|
||||
|
||||
// ### Direction Flipping Tests
|
||||
|
||||
TEST_CASE("Single channel direction can be flipped", "[relay_chn][core][direction]")
|
||||
{
|
||||
// 1. Initial direction should be default
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction());
|
||||
|
||||
// 2. Flip the direction
|
||||
relay_chn_flip_direction();
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); // Wait for flip inertia
|
||||
|
||||
// 3. Verify direction is flipped
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction());
|
||||
|
||||
// 4. Flip back
|
||||
relay_chn_flip_direction();
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); // Wait for flip inertia
|
||||
|
||||
// 5. Verify direction is back to default
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction());
|
||||
}
|
||||
|
||||
TEST_CASE("Flipping a running channel stops it and flips direction", "[relay_chn][core][direction]")
|
||||
{
|
||||
// 1. Start channel running and verify state
|
||||
relay_chn_run_forward();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state());
|
||||
|
||||
// 2. Flip the direction while running
|
||||
relay_chn_flip_direction();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Give time for events to process
|
||||
|
||||
// 3. The channel should stop as part of the flip process
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state());
|
||||
|
||||
// 4. Wait for the flip inertia to pass, after which it should be IDLE and FLIPPED
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state());
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction());
|
||||
}
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
#define TEST_RUN_LIMIT_SEC 5
|
||||
#define TEST_SHORT_RUN_LIMIT_SEC 2
|
||||
// ### Run Limit Tests
|
||||
TEST_CASE("Test run limit initialization", "[relay_chn][run_limit]")
|
||||
{
|
||||
// Should initialize with default value
|
||||
TEST_ASSERT_EQUAL(CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC, relay_chn_get_run_limit());
|
||||
}
|
||||
|
||||
TEST_CASE("Test run limit setting boundaries", "[relay_chn][run_limit]")
|
||||
{
|
||||
// Test minimum boundary
|
||||
relay_chn_set_run_limit(CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC - 1);
|
||||
TEST_ASSERT_EQUAL(CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC, relay_chn_get_run_limit());
|
||||
|
||||
// Test maximum boundary
|
||||
relay_chn_set_run_limit(CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC + 1);
|
||||
TEST_ASSERT_EQUAL(CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC, relay_chn_get_run_limit());
|
||||
|
||||
// Test valid value
|
||||
relay_chn_set_run_limit(TEST_RUN_LIMIT_SEC);
|
||||
TEST_ASSERT_EQUAL(TEST_RUN_LIMIT_SEC, relay_chn_get_run_limit());
|
||||
}
|
||||
|
||||
TEST_CASE("Test run limit stops channel after timeout", "[relay_chn][run_limit]")
|
||||
{
|
||||
// Set a short run limit for testing
|
||||
relay_chn_set_run_limit(TEST_SHORT_RUN_LIMIT_SEC);
|
||||
|
||||
// Start running forward
|
||||
relay_chn_run_forward();
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state());
|
||||
|
||||
// Wait for run limit timeout
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_SHORT_RUN_LIMIT_SEC * 1000 + TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state());
|
||||
}
|
||||
|
||||
TEST_CASE("Test run limit reset on direction change and time out finally", "[relay_chn][run_limit]")
|
||||
{
|
||||
// Set a short run limit
|
||||
relay_chn_set_run_limit(TEST_SHORT_RUN_LIMIT_SEC);
|
||||
|
||||
// Start running forward
|
||||
relay_chn_run_forward();
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS); // Wait 1 second
|
||||
|
||||
// Change direction before timeout
|
||||
relay_chn_run_reverse();
|
||||
|
||||
// Wait for the inertia period (after which the reverse command will be dispatched)
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state());
|
||||
|
||||
// Timer should time out and stop the channel after the run limit time
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_SHORT_RUN_LIMIT_SEC * 1000 + TEST_DELAY_MARGIN_MS));
|
||||
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state());
|
||||
}
|
||||
|
||||
TEST_CASE("Test run limit persistence across stop/start", "[relay_chn][run_limit]")
|
||||
{
|
||||
// Set initial run limit
|
||||
relay_chn_set_run_limit(TEST_RUN_LIMIT_SEC);
|
||||
|
||||
// Stop and start channel
|
||||
relay_chn_stop();
|
||||
relay_chn_run_forward();
|
||||
|
||||
// Run limit should persist
|
||||
TEST_ASSERT_EQUAL(TEST_RUN_LIMIT_SEC, relay_chn_get_run_limit());
|
||||
}
|
||||
#endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1
|
||||
|
||||
TEST_CASE("relay_chn_get_state_str returns correct strings", "[relay_chn][core]")
|
||||
{
|
||||
// This test is a bit contrived as it's hard to force every state
|
||||
// without complex sequences. We will test the most common ones.
|
||||
TEST_ASSERT_EQUAL_STRING("IDLE", relay_chn_get_state_str());
|
||||
|
||||
relay_chn_run_forward();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL_STRING("FORWARD", relay_chn_get_state_str());
|
||||
|
||||
relay_chn_run_reverse();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL_STRING("REVERSE_PENDING", relay_chn_get_state_str());
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS));
|
||||
TEST_ASSERT_EQUAL_STRING("REVERSE", relay_chn_get_state_str());
|
||||
|
||||
relay_chn_stop();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL_STRING("STOPPED", relay_chn_get_state_str());
|
||||
}
|
||||
|
||||
TEST_CASE("Stop command interrupts pending commands", "[relay_chn][core][inertia]")
|
||||
{
|
||||
// 1. Start running forward
|
||||
relay_chn_run_forward();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state());
|
||||
|
||||
// 2. Issue a reverse command, which will make the state REVERSE_PENDING
|
||||
relay_chn_run_reverse();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE_PENDING, relay_chn_get_state());
|
||||
|
||||
// 3. Before the inertia timer fires, issue a stop command
|
||||
relay_chn_stop();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state());
|
||||
|
||||
// 4. Wait for more than the inertia period
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
// The channel should transition to IDLE, not REVERSE, because stop cancelled the pending command.
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state());
|
||||
}
|
||||
|
||||
TEST_CASE("relay_chn_destroy allows clean-up and re-creation", "[relay_chn][core]")
|
||||
{
|
||||
relay_chn_run_forward();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state());
|
||||
|
||||
relay_chn_destroy();
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state());
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
#include "test_common.h"
|
||||
|
||||
|
||||
// --- Listener Test Globals ---
|
||||
typedef struct {
|
||||
uint8_t chn_id;
|
||||
relay_chn_state_t old_state;
|
||||
relay_chn_state_t new_state;
|
||||
int call_count;
|
||||
} listener_callback_info_t;
|
||||
|
||||
static listener_callback_info_t listener1_info;
|
||||
static listener_callback_info_t listener2_info;
|
||||
|
||||
// --- Listener Test Helper Functions ---
|
||||
|
||||
// Clear the memory from possible garbage values
|
||||
static void reset_listener_info(listener_callback_info_t* info) {
|
||||
memset(info, 0, sizeof(listener_callback_info_t));
|
||||
}
|
||||
|
||||
static void test_listener_1(uint8_t chn_id, relay_chn_state_t old_state, relay_chn_state_t new_state) {
|
||||
listener1_info.chn_id = chn_id;
|
||||
listener1_info.old_state = old_state;
|
||||
listener1_info.new_state = new_state;
|
||||
listener1_info.call_count++;
|
||||
}
|
||||
|
||||
static void test_listener_2(uint8_t chn_id, relay_chn_state_t old_state, relay_chn_state_t new_state) {
|
||||
listener2_info.chn_id = chn_id;
|
||||
listener2_info.old_state = old_state;
|
||||
listener2_info.new_state = new_state;
|
||||
listener2_info.call_count++;
|
||||
}
|
||||
|
||||
// ### Listener Functionality Tests
|
||||
|
||||
TEST_CASE("Listener is called on state change", "[relay_chn][listener]") {
|
||||
uint8_t ch = 0;
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
reset_listener_info(&listener1_info);
|
||||
|
||||
// 1. Register the listener
|
||||
TEST_ESP_OK(relay_chn_register_listener(test_listener_1));
|
||||
|
||||
// 2. Trigger a state change
|
||||
relay_chn_run_forward(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Allow event to be processed
|
||||
|
||||
// 3. Verify the listener was called with correct parameters
|
||||
TEST_ASSERT_EQUAL(1, listener1_info.call_count);
|
||||
TEST_ASSERT_EQUAL(ch, listener1_info.chn_id);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, listener1_info.old_state);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, listener1_info.new_state);
|
||||
|
||||
// 4. Unregister to clean up
|
||||
relay_chn_unregister_listener(test_listener_1);
|
||||
}
|
||||
|
||||
TEST_CASE("Unregistered listener is not called", "[relay_chn][listener]") {
|
||||
uint8_t ch = 0;
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
reset_listener_info(&listener1_info);
|
||||
|
||||
// 1. Register and then immediately unregister the listener
|
||||
TEST_ESP_OK(relay_chn_register_listener(test_listener_1));
|
||||
relay_chn_unregister_listener(test_listener_1);
|
||||
|
||||
// 2. Trigger a state change
|
||||
relay_chn_run_forward(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
|
||||
// 3. Verify the listener was NOT called
|
||||
TEST_ASSERT_EQUAL(0, listener1_info.call_count);
|
||||
}
|
||||
|
||||
TEST_CASE("Multiple listeners are called on state change", "[relay_chn][listener]") {
|
||||
uint8_t ch = 0;
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
reset_listener_info(&listener1_info);
|
||||
reset_listener_info(&listener2_info);
|
||||
|
||||
// 1. Register two different listeners
|
||||
TEST_ESP_OK(relay_chn_register_listener(test_listener_1));
|
||||
TEST_ESP_OK(relay_chn_register_listener(test_listener_2));
|
||||
|
||||
// 2. Trigger a state change
|
||||
relay_chn_run_forward(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
|
||||
// 3. Verify listener 1 was called correctly
|
||||
TEST_ASSERT_EQUAL(1, listener1_info.call_count);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, listener1_info.old_state);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, listener1_info.new_state);
|
||||
|
||||
// 4. Verify listener 2 was also called correctly
|
||||
TEST_ASSERT_EQUAL(1, listener2_info.call_count);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, listener2_info.old_state);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, listener2_info.new_state);
|
||||
|
||||
// 5. Clean up
|
||||
relay_chn_unregister_listener(test_listener_1);
|
||||
relay_chn_unregister_listener(test_listener_2);
|
||||
}
|
||||
|
||||
TEST_CASE("Listener registration handles invalid arguments and duplicates", "[relay_chn][listener]") {
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
reset_listener_info(&listener1_info);
|
||||
|
||||
// 1. Registering a NULL listener should fail
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_register_listener(NULL));
|
||||
|
||||
// 2. Unregistering a NULL listener should not crash
|
||||
relay_chn_unregister_listener(NULL);
|
||||
|
||||
// 3. Registering the same listener twice should be handled gracefully
|
||||
TEST_ESP_OK(relay_chn_register_listener(test_listener_1));
|
||||
TEST_ESP_OK(relay_chn_register_listener(test_listener_1)); // Second call should be a no-op
|
||||
|
||||
// 4. Trigger a state change and verify the listener is only called ONCE
|
||||
relay_chn_run_forward(0);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(1, listener1_info.call_count);
|
||||
|
||||
// 5. Clean up
|
||||
relay_chn_unregister_listener(test_listener_1);
|
||||
}
|
||||
55
test_apps/main/test_relay_chn_notify_common.c
Normal file
55
test_apps/main/test_relay_chn_notify_common.c
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "test_relay_chn_notify_common.h"
|
||||
|
||||
listener_callback_info_t listener1_info;
|
||||
listener_callback_info_t listener2_info;
|
||||
|
||||
// --- Globals for Advanced Tests ---
|
||||
SemaphoreHandle_t blocking_listener_sem = NULL;
|
||||
SemaphoreHandle_t log_check_sem = NULL;
|
||||
volatile int blocking_listener_call_count = 0;
|
||||
vprintf_like_t original_vprintf = NULL;
|
||||
|
||||
// --- Listener Test Helper Functions ---
|
||||
|
||||
// Clear the memory from possible garbage values
|
||||
void reset_listener_info(listener_callback_info_t* info) {
|
||||
memset(info, 0, sizeof(listener_callback_info_t));
|
||||
}
|
||||
|
||||
void test_listener_1(uint8_t chn_id, relay_chn_state_t old_state, relay_chn_state_t new_state) {
|
||||
listener1_info.chn_id = chn_id;
|
||||
listener1_info.old_state = old_state;
|
||||
listener1_info.new_state = new_state;
|
||||
listener1_info.call_count++;
|
||||
}
|
||||
|
||||
void test_listener_2(uint8_t chn_id, relay_chn_state_t old_state, relay_chn_state_t new_state) {
|
||||
listener2_info.chn_id = chn_id;
|
||||
listener2_info.old_state = old_state;
|
||||
listener2_info.new_state = new_state;
|
||||
listener2_info.call_count++;
|
||||
}
|
||||
|
||||
void blocking_listener(uint8_t chn_id, relay_chn_state_t old_state, relay_chn_state_t new_state) {
|
||||
blocking_listener_call_count++;
|
||||
// Block until the main test task unblocks us
|
||||
xSemaphoreTake(blocking_listener_sem, portMAX_DELAY);
|
||||
}
|
||||
|
||||
int log_check_vprintf(const char *format, va_list args) {
|
||||
// Buffer to hold the formatted log message
|
||||
char buffer[256];
|
||||
vsnprintf(buffer, sizeof(buffer), format, args);
|
||||
|
||||
if (strstr(buffer, "Notify queue is full")) {
|
||||
xSemaphoreGive(log_check_sem);
|
||||
}
|
||||
|
||||
return original_vprintf(format, args);
|
||||
}
|
||||
54
test_apps/main/test_relay_chn_notify_common.h
Normal file
54
test_apps/main/test_relay_chn_notify_common.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "test_common.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include <stdarg.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// This is defined in the source file, we redefine it here for the test.
|
||||
// The test build must have the same CONFIG_RELAY_CHN_COUNT.
|
||||
#define TEST_RELAY_CHN_NOTIFY_QUEUE_LEN (16 + CONFIG_RELAY_CHN_COUNT * 4)
|
||||
|
||||
// --- Listener Test Globals ---
|
||||
typedef struct {
|
||||
uint8_t chn_id;
|
||||
relay_chn_state_t old_state;
|
||||
relay_chn_state_t new_state;
|
||||
int call_count;
|
||||
} listener_callback_info_t;
|
||||
|
||||
// --- Listener callback infos to be defined ---
|
||||
extern listener_callback_info_t listener1_info;
|
||||
extern listener_callback_info_t listener2_info;
|
||||
|
||||
// --- Globals for Advanced Tests ---
|
||||
extern SemaphoreHandle_t blocking_listener_sem;
|
||||
extern SemaphoreHandle_t log_check_sem;
|
||||
extern volatile int blocking_listener_call_count;
|
||||
extern vprintf_like_t original_vprintf;
|
||||
|
||||
|
||||
// --- Listener Test Helper Functions ---
|
||||
|
||||
// Clear the memory from possible garbage values
|
||||
void reset_listener_info(listener_callback_info_t* info);
|
||||
|
||||
// State listeners for notify module testing
|
||||
void test_listener_1(uint8_t chn_id, relay_chn_state_t old_state, relay_chn_state_t new_state);
|
||||
void test_listener_2(uint8_t chn_id, relay_chn_state_t old_state, relay_chn_state_t new_state);
|
||||
void blocking_listener(uint8_t chn_id, relay_chn_state_t old_state, relay_chn_state_t new_state);
|
||||
|
||||
int log_check_vprintf(const char *format, va_list args);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
172
test_apps/main/test_relay_chn_notify_multi.c
Normal file
172
test_apps/main/test_relay_chn_notify_multi.c
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "test_relay_chn_notify_common.h"
|
||||
|
||||
// This is a private header, but we need it for direct notification calls and queue length.
|
||||
// It's included conditionally in the build via CMakeLists.txt when NVS is enabled.
|
||||
#include "relay_chn_notify.h"
|
||||
|
||||
// ### Listener Functionality Tests
|
||||
|
||||
TEST_CASE("Listener is called on state change for each channel", "[relay_chn][notify]")
|
||||
{
|
||||
// 1. Register the listener and reset info
|
||||
reset_listener_info(&listener1_info);
|
||||
TEST_ESP_OK(relay_chn_register_listener(test_listener_1));
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow registration to be processed
|
||||
|
||||
// Loop through each channel
|
||||
for (uint8_t ch = 0; ch < CONFIG_RELAY_CHN_COUNT; ch++) {
|
||||
// 2. Trigger a state change on the current channel.
|
||||
// tearDown() ensures each channel starts as IDLE.
|
||||
relay_chn_run_forward(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow event to be processed
|
||||
|
||||
// 3. Verify the listener was called with correct parameters for this channel.
|
||||
// The listener_info struct is overwritten each time, but we check it before the next iteration.
|
||||
TEST_ASSERT_EQUAL(ch, listener1_info.chn_id);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, listener1_info.old_state);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, listener1_info.new_state);
|
||||
}
|
||||
|
||||
// 4. Verify the total call count after the loop
|
||||
TEST_ASSERT_EQUAL(CONFIG_RELAY_CHN_COUNT, listener1_info.call_count);
|
||||
|
||||
// 5. Unregister to clean up
|
||||
relay_chn_unregister_listener(test_listener_1);
|
||||
}
|
||||
|
||||
TEST_CASE("Unregistered listener is not called for any channel", "[relay_chn][notify]")
|
||||
{
|
||||
reset_listener_info(&listener1_info);
|
||||
|
||||
// 1. Register and then immediately unregister the listener
|
||||
TEST_ESP_OK(relay_chn_register_listener(test_listener_1));
|
||||
relay_chn_unregister_listener(test_listener_1);
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow commands to process
|
||||
|
||||
// 2. Trigger a state change on all channels
|
||||
relay_chn_run_forward_all();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS * CONFIG_RELAY_CHN_COUNT)); // Allow all events to be processed
|
||||
|
||||
// 3. Verify the listener was NOT called
|
||||
TEST_ASSERT_EQUAL(0, listener1_info.call_count);
|
||||
}
|
||||
|
||||
TEST_CASE("Multiple listeners are called on state change for each channel", "[relay_chn][notify]")
|
||||
{
|
||||
// 1. Register listeners and reset info
|
||||
reset_listener_info(&listener1_info);
|
||||
reset_listener_info(&listener2_info);
|
||||
TEST_ESP_OK(relay_chn_register_listener(test_listener_1));
|
||||
TEST_ESP_OK(relay_chn_register_listener(test_listener_2));
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow registration commands to be processed
|
||||
|
||||
// Loop through each channel
|
||||
for (uint8_t ch = 0; ch < CONFIG_RELAY_CHN_COUNT; ch++) {
|
||||
// 2. Trigger a state change on the current channel
|
||||
relay_chn_run_forward(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
|
||||
// 3. Verify listener 1 was called correctly for this channel
|
||||
TEST_ASSERT_EQUAL(ch, listener1_info.chn_id);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, listener1_info.old_state);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, listener1_info.new_state);
|
||||
|
||||
// 4. Verify listener 2 was also called correctly for this channel
|
||||
TEST_ASSERT_EQUAL(ch, listener2_info.chn_id);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, listener2_info.old_state);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, listener2_info.new_state);
|
||||
}
|
||||
|
||||
// 5. Verify total call counts
|
||||
TEST_ASSERT_EQUAL(CONFIG_RELAY_CHN_COUNT, listener1_info.call_count);
|
||||
TEST_ASSERT_EQUAL(CONFIG_RELAY_CHN_COUNT, listener2_info.call_count);
|
||||
|
||||
// 6. Clean up
|
||||
relay_chn_unregister_listener(test_listener_1);
|
||||
relay_chn_unregister_listener(test_listener_2);
|
||||
}
|
||||
|
||||
TEST_CASE("Listener registration handles invalid arguments and duplicates", "[relay_chn][notify]")
|
||||
{
|
||||
reset_listener_info(&listener1_info);
|
||||
|
||||
// 1. Registering a NULL listener should fail
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_register_listener(NULL));
|
||||
|
||||
// 2. Unregistering a NULL listener should not crash
|
||||
relay_chn_unregister_listener(NULL);
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow commands to process
|
||||
|
||||
// 3. Registering the same listener twice should be handled gracefully
|
||||
TEST_ESP_OK(relay_chn_register_listener(test_listener_1));
|
||||
TEST_ESP_OK(relay_chn_register_listener(test_listener_1)); // Second call should be a no-op
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow registration commands to be processed
|
||||
|
||||
// 4. Trigger a state change on all channels and verify the listener is only called ONCE per channel
|
||||
relay_chn_run_forward_all();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS * CONFIG_RELAY_CHN_COUNT)); // Allow all events to be processed
|
||||
TEST_ASSERT_EQUAL(CONFIG_RELAY_CHN_COUNT, listener1_info.call_count);
|
||||
|
||||
// 5. Clean up
|
||||
relay_chn_unregister_listener(test_listener_1);
|
||||
}
|
||||
|
||||
TEST_CASE("Notify queue full scenario is handled gracefully", "[relay_chn][notify]")
|
||||
{
|
||||
// 1. Setup
|
||||
blocking_listener_sem = xSemaphoreCreateBinary();
|
||||
log_check_sem = xSemaphoreCreateBinary();
|
||||
blocking_listener_call_count = 0;
|
||||
|
||||
// Intercept logs to check for the "queue full" warning
|
||||
original_vprintf = esp_log_set_vprintf(log_check_vprintf);
|
||||
|
||||
// 2. Register a listener that will block, allowing the queue to fill up
|
||||
TEST_ESP_OK(relay_chn_register_listener(blocking_listener));
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow task to start
|
||||
|
||||
// 3. Fill the queue. The first event will be consumed immediately by the dispatcher,
|
||||
// which will then call the blocking_listener and block. The remaining (LEN - 1)
|
||||
// events will sit in the queue, leaving one empty slot.
|
||||
// Use different channel IDs to make the test more robust.
|
||||
for (int i = 0; i < TEST_RELAY_CHN_NOTIFY_QUEUE_LEN; i++) {
|
||||
uint8_t ch = i % CONFIG_RELAY_CHN_COUNT;
|
||||
TEST_ESP_OK(relay_chn_notify_state_change(ch, RELAY_CHN_STATE_IDLE, RELAY_CHN_STATE_FORWARD));
|
||||
}
|
||||
|
||||
// 4. Send one more event to fill the last slot in the queue. This should succeed.
|
||||
TEST_ESP_OK(relay_chn_notify_state_change(0, RELAY_CHN_STATE_IDLE, RELAY_CHN_STATE_FORWARD)); // Use any valid channel
|
||||
|
||||
// 5. Now the queue is full. Trigger one more event to cause an overflow.
|
||||
// This call should fail and log the warning.
|
||||
TEST_ASSERT_EQUAL(ESP_FAIL, relay_chn_notify_state_change(1 % CONFIG_RELAY_CHN_COUNT, RELAY_CHN_STATE_IDLE, RELAY_CHN_STATE_FORWARD));
|
||||
|
||||
// 6. Wait for the "queue full" log message to be captured by our vprintf hook
|
||||
TEST_ASSERT_TRUE_MESSAGE(xSemaphoreTake(log_check_sem, pdMS_TO_TICKS(1000)) == pdTRUE, "Did not receive 'queue full' log message");
|
||||
|
||||
// 7. Unblock the listener so it can process all queued items.
|
||||
// There was 1 initial event + QUEUE_LEN events that were successfully queued.
|
||||
for (int i = 0; i < TEST_RELAY_CHN_NOTIFY_QUEUE_LEN + 1; i++) {
|
||||
xSemaphoreGive(blocking_listener_sem);
|
||||
// Give the dispatcher task a moment to process one item from the queue
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
|
||||
// 8. Verify the listener was called exactly QUEUE_LEN + 1 times
|
||||
TEST_ASSERT_EQUAL_INT(TEST_RELAY_CHN_NOTIFY_QUEUE_LEN + 1, blocking_listener_call_count);
|
||||
|
||||
// 9. Cleanup
|
||||
esp_log_set_vprintf(original_vprintf);
|
||||
relay_chn_unregister_listener(blocking_listener);
|
||||
vSemaphoreDelete(blocking_listener_sem);
|
||||
vSemaphoreDelete(log_check_sem);
|
||||
blocking_listener_sem = NULL;
|
||||
log_check_sem = NULL;
|
||||
original_vprintf = NULL;
|
||||
}
|
||||
152
test_apps/main/test_relay_chn_notify_single.c
Normal file
152
test_apps/main/test_relay_chn_notify_single.c
Normal file
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "test_relay_chn_notify_common.h"
|
||||
|
||||
// This is a private header, but we need it for direct notification calls and queue length.
|
||||
// It's included conditionally in the build via CMakeLists.txt when NVS is enabled.
|
||||
#include "relay_chn_notify.h"
|
||||
|
||||
// ### Listener Functionality Tests
|
||||
|
||||
TEST_CASE("Listener is called on state change", "[relay_chn][notify]")
|
||||
{
|
||||
reset_listener_info(&listener1_info);
|
||||
|
||||
// 1. Register the listener
|
||||
TEST_ESP_OK(relay_chn_register_listener(test_listener_1));
|
||||
|
||||
// 2. Trigger a state change
|
||||
relay_chn_run_forward();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow event to be processed
|
||||
|
||||
// 3. Verify the listener was called with correct parameters
|
||||
TEST_ASSERT_EQUAL(1, listener1_info.call_count);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, listener1_info.old_state);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, listener1_info.new_state);
|
||||
|
||||
// 4. Unregister to clean up
|
||||
relay_chn_unregister_listener(test_listener_1);
|
||||
}
|
||||
|
||||
TEST_CASE("Unregistered listener is not called", "[relay_chn][notify]")
|
||||
{
|
||||
reset_listener_info(&listener1_info);
|
||||
|
||||
// 1. Register and then immediately unregister the listener
|
||||
TEST_ESP_OK(relay_chn_register_listener(test_listener_1));
|
||||
relay_chn_unregister_listener(test_listener_1);
|
||||
|
||||
// 2. Trigger a state change
|
||||
relay_chn_run_forward();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
|
||||
// 3. Verify the listener was NOT called
|
||||
TEST_ASSERT_EQUAL(0, listener1_info.call_count);
|
||||
}
|
||||
|
||||
TEST_CASE("Multiple listeners are called on state change", "[relay_chn][notify]")
|
||||
{
|
||||
reset_listener_info(&listener1_info);
|
||||
reset_listener_info(&listener2_info);
|
||||
|
||||
// 1. Register two different listeners
|
||||
TEST_ESP_OK(relay_chn_register_listener(test_listener_1));
|
||||
TEST_ESP_OK(relay_chn_register_listener(test_listener_2));
|
||||
|
||||
// 2. Trigger a state change
|
||||
relay_chn_run_forward();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
|
||||
// 3. Verify listener 1 was called correctly
|
||||
TEST_ASSERT_EQUAL(1, listener1_info.call_count);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, listener1_info.old_state);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, listener1_info.new_state);
|
||||
|
||||
// 4. Verify listener 2 was also called correctly
|
||||
TEST_ASSERT_EQUAL(1, listener2_info.call_count);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, listener2_info.old_state);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, listener2_info.new_state);
|
||||
|
||||
// 5. Clean up
|
||||
relay_chn_unregister_listener(test_listener_1);
|
||||
relay_chn_unregister_listener(test_listener_2);
|
||||
}
|
||||
|
||||
TEST_CASE("Listener registration handles invalid arguments and duplicates", "[relay_chn][notify]")
|
||||
{
|
||||
reset_listener_info(&listener1_info);
|
||||
|
||||
// 1. Registering a NULL listener should fail
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_register_listener(NULL));
|
||||
|
||||
// 2. Unregistering a NULL listener should not crash
|
||||
relay_chn_unregister_listener(NULL);
|
||||
|
||||
// 3. Registering the same listener twice should be handled gracefully
|
||||
TEST_ESP_OK(relay_chn_register_listener(test_listener_1));
|
||||
TEST_ESP_OK(relay_chn_register_listener(test_listener_1)); // Second call should be a no-op
|
||||
|
||||
// 4. Trigger a state change and verify the listener is only called ONCE
|
||||
relay_chn_run_forward();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(1, listener1_info.call_count);
|
||||
|
||||
// 5. Clean up
|
||||
relay_chn_unregister_listener(test_listener_1);
|
||||
}
|
||||
|
||||
TEST_CASE("Notify queue full scenario is handled gracefully", "[relay_chn][notify]")
|
||||
{
|
||||
// 1. Setup
|
||||
blocking_listener_sem = xSemaphoreCreateBinary();
|
||||
log_check_sem = xSemaphoreCreateBinary();
|
||||
blocking_listener_call_count = 0;
|
||||
|
||||
// Intercept logs to check for the "queue full" warning
|
||||
original_vprintf = esp_log_set_vprintf(log_check_vprintf);
|
||||
|
||||
// 2. Register a listener that will block, allowing the queue to fill up
|
||||
TEST_ESP_OK(relay_chn_register_listener(blocking_listener));
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Allow task to start
|
||||
|
||||
// 3. Fill the queue. The first event will be consumed immediately by the dispatcher,
|
||||
// which will then call the blocking_listener and block. The remaining (LEN - 1)
|
||||
// events will sit in the queue, leaving one empty slot.
|
||||
for (int i = 0; i < TEST_RELAY_CHN_NOTIFY_QUEUE_LEN; i++) {
|
||||
TEST_ESP_OK(relay_chn_notify_state_change(0, RELAY_CHN_STATE_IDLE, RELAY_CHN_STATE_FORWARD));
|
||||
}
|
||||
|
||||
// 4. Send one more event to fill the last slot in the queue. This should succeed.
|
||||
TEST_ESP_OK(relay_chn_notify_state_change(0, RELAY_CHN_STATE_IDLE, RELAY_CHN_STATE_FORWARD));
|
||||
|
||||
// 5. Now the queue is full. Trigger one more event to cause an overflow.
|
||||
// This call should fail and log the warning.
|
||||
TEST_ASSERT_EQUAL(ESP_FAIL, relay_chn_notify_state_change(0, RELAY_CHN_STATE_IDLE, RELAY_CHN_STATE_FORWARD));
|
||||
|
||||
// 6. Wait for the "queue full" log message to be captured by our vprintf hook
|
||||
TEST_ASSERT_TRUE_MESSAGE(xSemaphoreTake(log_check_sem, pdMS_TO_TICKS(1000)) == pdTRUE, "Did not receive 'queue full' log message");
|
||||
|
||||
// 7. Unblock the listener so it can process all queued items.
|
||||
// There was 1 initial event + QUEUE_LEN events that were successfully queued.
|
||||
for (int i = 0; i < TEST_RELAY_CHN_NOTIFY_QUEUE_LEN + 1; i++) {
|
||||
xSemaphoreGive(blocking_listener_sem);
|
||||
// Give the dispatcher task a moment to process one item from the queue
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
|
||||
// 8. Verify the listener was called exactly QUEUE_LEN + 1 times
|
||||
TEST_ASSERT_EQUAL_INT(TEST_RELAY_CHN_NOTIFY_QUEUE_LEN + 1, blocking_listener_call_count);
|
||||
|
||||
// 9. Cleanup
|
||||
esp_log_set_vprintf(original_vprintf);
|
||||
relay_chn_unregister_listener(blocking_listener);
|
||||
vSemaphoreDelete(blocking_listener_sem);
|
||||
vSemaphoreDelete(log_check_sem);
|
||||
blocking_listener_sem = NULL;
|
||||
log_check_sem = NULL;
|
||||
original_vprintf = NULL;
|
||||
}
|
||||
189
test_apps/main/test_relay_chn_nvs_multi.c
Normal file
189
test_apps/main/test_relay_chn_nvs_multi.c
Normal file
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "unity.h"
|
||||
#include "esp_system.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "relay_chn_nvs.h"
|
||||
#include "test_common.h"
|
||||
|
||||
#define TEST_NVS_TASK_TIME_OUT_MS 300
|
||||
|
||||
TEST_CASE("Test direction setting and getting", "[relay_chn][nvs]")
|
||||
{
|
||||
// Test all channels
|
||||
relay_chn_direction_t dir, expect;
|
||||
|
||||
for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) {
|
||||
dir = channel % 2 == 0 ? RELAY_CHN_DIRECTION_DEFAULT : RELAY_CHN_DIRECTION_FLIPPED;
|
||||
TEST_ESP_OK(relay_chn_nvs_set_direction(channel, dir));
|
||||
}
|
||||
|
||||
// Wait for the batch commit timeout to ensure the value is written
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_NVS_TASK_TIME_OUT_MS));
|
||||
|
||||
for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) {
|
||||
expect = channel % 2 == 0 ? RELAY_CHN_DIRECTION_DEFAULT : RELAY_CHN_DIRECTION_FLIPPED;
|
||||
TEST_ESP_OK(relay_chn_nvs_get_direction(channel, &dir, RELAY_CHN_DIRECTION_DEFAULT));
|
||||
TEST_ASSERT_EQUAL(expect, dir);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test invalid parameters", "[relay_chn][nvs]")
|
||||
{
|
||||
// Test NULL pointer for all channels
|
||||
for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) {
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_direction(channel, NULL, RELAY_CHN_DIRECTION_DEFAULT));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test relay_chn_nvs_erase_all", "[relay_chn][nvs]")
|
||||
{
|
||||
// Store some test data first
|
||||
for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) {
|
||||
// Set direction for all channels
|
||||
TEST_ESP_OK(relay_chn_nvs_set_direction(channel, RELAY_CHN_DIRECTION_FLIPPED));
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
TEST_ESP_OK(relay_chn_nvs_set_run_limit(channel, 100 + channel));
|
||||
#endif
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
TEST_ESP_OK(relay_chn_nvs_set_tilt_sensitivity(channel, 50));
|
||||
TEST_ESP_OK(relay_chn_nvs_set_tilt_count(channel, 100 + channel));
|
||||
}
|
||||
#endif
|
||||
|
||||
// Wait for the set operations and subsequent commits to complete
|
||||
// Wait 4 times more since 4 x 8 = 32 operations to process
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_NVS_TASK_TIME_OUT_MS * 8));
|
||||
|
||||
// Test erase all
|
||||
TEST_ESP_OK(relay_chn_nvs_erase_all());
|
||||
|
||||
// Wait for the erase operation and subsequent commit to complete
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_NVS_TASK_TIME_OUT_MS));
|
||||
// Verify data was erased by trying to read it back
|
||||
for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) {
|
||||
relay_chn_direction_t read_direction;
|
||||
TEST_ESP_OK(relay_chn_nvs_get_direction(0, &read_direction, RELAY_CHN_DIRECTION_DEFAULT));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, read_direction);
|
||||
}
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) {
|
||||
uint16_t read_run_limit;
|
||||
TEST_ESP_OK(relay_chn_nvs_get_run_limit(channel, &read_run_limit, CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC));
|
||||
TEST_ASSERT_EQUAL(CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC, read_run_limit);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
const uint8_t default_sensitivity_for_test = 42;
|
||||
for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) {
|
||||
uint8_t read_sensitivity;
|
||||
TEST_ESP_OK(relay_chn_nvs_get_tilt_sensitivity(channel, &read_sensitivity, default_sensitivity_for_test));
|
||||
TEST_ASSERT_EQUAL(default_sensitivity_for_test, read_sensitivity);
|
||||
uint16_t tilt_count;
|
||||
TEST_ESP_OK(relay_chn_nvs_get_tilt_count(channel, &tilt_count, 0));
|
||||
TEST_ASSERT_EQUAL(0, tilt_count);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
TEST_CASE("Test run limit setting and getting", "[relay_chn][nvs][run_limit]")
|
||||
{
|
||||
// Use different values for each channel to detect overwrites
|
||||
uint16_t test_limits[CONFIG_RELAY_CHN_COUNT];
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
test_limits[i] = 30 + i; // e.g., 30, 31, 32...
|
||||
}
|
||||
|
||||
// 1. Set all values first
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
TEST_ESP_OK(relay_chn_nvs_set_run_limit(i, test_limits[i]));
|
||||
}
|
||||
|
||||
// Allow the NVS task to process the batch and commit
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_NVS_TASK_TIME_OUT_MS));
|
||||
|
||||
// 2. Then, read them all back and verify
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
uint16_t run_limit_read;
|
||||
TEST_ESP_OK(relay_chn_nvs_get_run_limit(i, &run_limit_read, CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC));
|
||||
TEST_ASSERT_EQUAL_UINT16(test_limits[i], run_limit_read);
|
||||
}
|
||||
|
||||
// 3. Verify that changing one channel doesn't affect another
|
||||
uint16_t new_limit_ch0 = 99;
|
||||
TEST_ESP_OK(relay_chn_nvs_set_run_limit(0, new_limit_ch0));
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_NVS_TASK_TIME_OUT_MS)); // Allow NVS task to write
|
||||
uint16_t read_val_ch0, read_val_ch1;
|
||||
TEST_ESP_OK(relay_chn_nvs_get_run_limit(0, &read_val_ch0, CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC));
|
||||
TEST_ESP_OK(relay_chn_nvs_get_run_limit(1, &read_val_ch1, CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC));
|
||||
TEST_ASSERT_EQUAL_UINT16(new_limit_ch0, read_val_ch0);
|
||||
TEST_ASSERT_EQUAL_UINT16(test_limits[1], read_val_ch1); // Should still be the old value
|
||||
}
|
||||
#endif
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
TEST_CASE("Test sensitivity setting and getting", "[relay_chn][nvs][tilt]")
|
||||
{
|
||||
uint8_t test_sensitivities[CONFIG_RELAY_CHN_COUNT];
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
test_sensitivities[i] = 70 + i; // e.g., 70, 71, 72...
|
||||
}
|
||||
|
||||
// 1. Set all values first
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
TEST_ESP_OK(relay_chn_nvs_set_tilt_sensitivity(i, test_sensitivities[i]));
|
||||
}
|
||||
|
||||
// Allow the NVS task to process the batch and commit
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_NVS_TASK_TIME_OUT_MS));
|
||||
|
||||
// 2. Then, read them all back and verify
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
uint8_t sensitivity_read;
|
||||
TEST_ESP_OK(relay_chn_nvs_get_tilt_sensitivity(i, &sensitivity_read, 0));
|
||||
TEST_ASSERT_EQUAL_UINT8(test_sensitivities[i], sensitivity_read);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test tilt counter operations", "[relay_chn][nvs][tilt]")
|
||||
{
|
||||
uint16_t test_counts[CONFIG_RELAY_CHN_COUNT];
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
test_counts[i] = 100 + i; // e.g., 100, 101, 102...
|
||||
}
|
||||
|
||||
// 1. Set all values first
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
TEST_ESP_OK(relay_chn_nvs_set_tilt_count(i, test_counts[i]));
|
||||
}
|
||||
|
||||
// Allow the NVS task to process the batch and commit
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_NVS_TASK_TIME_OUT_MS));
|
||||
|
||||
// 2. Then, read them all back and verify
|
||||
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
uint16_t count_read;
|
||||
TEST_ESP_OK(relay_chn_nvs_get_tilt_count(i, &count_read, 0));
|
||||
TEST_ASSERT_EQUAL_UINT16(test_counts[i], count_read);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test tilting invalid parameters", "[relay_chn][nvs][tilt]")
|
||||
{
|
||||
// Test NULL pointers for all channels
|
||||
for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) {
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_tilt_sensitivity(channel, NULL, 0));
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_tilt_count(channel, NULL, 0));
|
||||
}
|
||||
}
|
||||
#endif // CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
125
test_apps/main/test_relay_chn_nvs_single.c
Normal file
125
test_apps/main/test_relay_chn_nvs_single.c
Normal file
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "unity.h"
|
||||
#include "esp_system.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "relay_chn_nvs.h"
|
||||
#include "test_common.h"
|
||||
|
||||
#define TEST_NVS_TASK_TIME_OUT_MS 300
|
||||
|
||||
TEST_CASE("Test direction setting and getting", "[relay_chn][nvs]")
|
||||
{
|
||||
// Test channel 0
|
||||
TEST_ESP_OK(relay_chn_nvs_set_direction(0, RELAY_CHN_DIRECTION_DEFAULT));
|
||||
relay_chn_direction_t dir;
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_NVS_TASK_TIME_OUT_MS)); // Allow NVS task to write and commit
|
||||
TEST_ESP_OK(relay_chn_nvs_get_direction(0, &dir, RELAY_CHN_DIRECTION_DEFAULT));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, dir);
|
||||
|
||||
// Test channel 1
|
||||
TEST_ESP_OK(relay_chn_nvs_set_direction(0, RELAY_CHN_DIRECTION_FLIPPED));
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_NVS_TASK_TIME_OUT_MS)); // Allow NVS task to write and commit
|
||||
TEST_ESP_OK(relay_chn_nvs_get_direction(0, &dir, RELAY_CHN_DIRECTION_DEFAULT));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, dir);
|
||||
}
|
||||
|
||||
TEST_CASE("Test invalid parameters", "[relay_chn][nvs]")
|
||||
{
|
||||
// Test NULL pointer
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_direction(0, NULL, RELAY_CHN_DIRECTION_DEFAULT));
|
||||
}
|
||||
|
||||
TEST_CASE("Test relay_chn_nvs_erase_all", "[relay_chn][nvs]")
|
||||
{
|
||||
// Store some test data first
|
||||
relay_chn_direction_t direction = RELAY_CHN_DIRECTION_FLIPPED;
|
||||
TEST_ESP_OK(relay_chn_nvs_set_direction(0, direction));
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
uint16_t run_limit = 123;
|
||||
TEST_ESP_OK(relay_chn_nvs_set_run_limit(0, run_limit));
|
||||
#endif
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
uint8_t sensitivity = 50;
|
||||
TEST_ESP_OK(relay_chn_nvs_set_tilt_sensitivity(0, sensitivity));
|
||||
TEST_ESP_OK(relay_chn_nvs_set_tilt_count(0, 100));
|
||||
#endif
|
||||
|
||||
// Test erase all
|
||||
TEST_ESP_OK(relay_chn_nvs_erase_all());
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_NVS_TASK_TIME_OUT_MS)); // Allow NVS task to write and commit
|
||||
// Verify data was erased by trying to read it back
|
||||
relay_chn_direction_t read_direction;
|
||||
TEST_ESP_OK(relay_chn_nvs_get_direction(0, &read_direction, RELAY_CHN_DIRECTION_DEFAULT));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, read_direction);
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
uint16_t read_run_limit;
|
||||
TEST_ESP_OK(relay_chn_nvs_get_run_limit(0, &read_run_limit, CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC));
|
||||
TEST_ASSERT_EQUAL(CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC, read_run_limit);
|
||||
#endif
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
const uint8_t default_sensitivity_for_test = 42;
|
||||
uint8_t read_sensitivity;
|
||||
TEST_ESP_OK(relay_chn_nvs_get_tilt_sensitivity(0, &read_sensitivity, default_sensitivity_for_test));
|
||||
TEST_ASSERT_EQUAL(default_sensitivity_for_test, read_sensitivity);
|
||||
uint16_t tilt_count;
|
||||
TEST_ESP_OK(relay_chn_nvs_get_tilt_count(0, &tilt_count, 0));
|
||||
TEST_ASSERT_EQUAL(0, tilt_count);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
|
||||
TEST_CASE("Test run limit setting and getting", "[relay_chn][nvs][run_limit]")
|
||||
{
|
||||
const uint16_t run_limit_sec = 32;
|
||||
TEST_ESP_OK(relay_chn_nvs_set_run_limit(0, run_limit_sec));
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_NVS_TASK_TIME_OUT_MS)); // Allow NVS task to write and commit
|
||||
uint16_t run_limit_read;
|
||||
TEST_ESP_OK(relay_chn_nvs_get_run_limit(0, &run_limit_read, CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC));
|
||||
TEST_ASSERT_EQUAL(run_limit_sec, run_limit_read);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
TEST_CASE("Test sensitivity setting and getting", "[relay_chn][nvs][tilt]")
|
||||
{
|
||||
const uint8_t test_sensitivity = 75;
|
||||
TEST_ESP_OK(relay_chn_nvs_set_tilt_sensitivity(0, test_sensitivity));
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_NVS_TASK_TIME_OUT_MS)); // Allow NVS task to write and commit
|
||||
uint8_t sensitivity;
|
||||
TEST_ESP_OK(relay_chn_nvs_get_tilt_sensitivity(0, &sensitivity, 0));
|
||||
TEST_ASSERT_EQUAL(test_sensitivity, sensitivity);
|
||||
}
|
||||
|
||||
TEST_CASE("Test tilt counter operations", "[relay_chn][nvs][tilt]")
|
||||
{
|
||||
const uint16_t tilt_count = 100;
|
||||
|
||||
// Test setting counters
|
||||
TEST_ESP_OK(relay_chn_nvs_set_tilt_count(0, tilt_count));
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_NVS_TASK_TIME_OUT_MS)); // Allow NVS task to write and commit
|
||||
uint16_t tilt_count_read;
|
||||
TEST_ESP_OK(relay_chn_nvs_get_tilt_count(0, &tilt_count_read, 0));
|
||||
TEST_ASSERT_EQUAL(tilt_count, tilt_count_read);
|
||||
}
|
||||
|
||||
TEST_CASE("Test tilting invalid parameters", "[relay_chn][nvs][tilt]")
|
||||
{
|
||||
// Test NULL pointers
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_tilt_sensitivity(0, NULL, 0));
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_tilt_count(0, NULL, 0));
|
||||
}
|
||||
#endif // CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
@@ -1,389 +0,0 @@
|
||||
#include "test_common.h"
|
||||
|
||||
|
||||
// ### Tilt Functionality Tests (Conditional)
|
||||
|
||||
// This section will only be compiled if **`CONFIG_RELAY_CHN_ENABLE_TILTING`** is defined as **`1`** in `sdkconfig`.
|
||||
|
||||
#ifndef CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
#error "This test requires CONFIG_RELAY_CHN_ENABLE_TILTING"
|
||||
#endif
|
||||
|
||||
#define RELAY_CHN_CMD_FORWARD 1
|
||||
#define RELAY_CHN_CMD_REVERSE 2
|
||||
|
||||
// Helper function to prepare channel for tilt tests
|
||||
void prepare_channel_for_tilt(uint8_t chn_id, int initial_cmd) {
|
||||
// Ensure the channel has had a 'last_run_cmd'
|
||||
if (initial_cmd == RELAY_CHN_CMD_FORWARD) {
|
||||
relay_chn_run_forward(chn_id);
|
||||
} else { // Assuming initial_cmd is RELAY_CHN_CMD_REVERSE
|
||||
relay_chn_run_reverse(chn_id);
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Allow command to process
|
||||
relay_chn_stop(chn_id); // Stop it to set last_run_cmd but return to FREE for next test
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(chn_id));
|
||||
}
|
||||
|
||||
// TEST_CASE: Test transition from running forward to tilt forward
|
||||
// Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_tilt_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_FORWARD
|
||||
TEST_CASE("Run Forward to Tilt Forward transition with inertia", "[relay_chn][tilt][inertia]") {
|
||||
uint8_t ch = 0;
|
||||
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
// Prepare channel by running forward first to set last_run_cmd
|
||||
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD);
|
||||
|
||||
// 1. Start in forward direction
|
||||
relay_chn_run_forward(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch));
|
||||
|
||||
// 2. Issue tilt forward command
|
||||
relay_chn_tilt_forward(ch);
|
||||
// After tilt command, it should immediately stop and then trigger inertia.
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(ch));
|
||||
|
||||
// Wait for the inertia period (after which the tilt command will be dispatched)
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch));
|
||||
}
|
||||
|
||||
// TEST_CASE: Test transition from running reverse to tilt reverse
|
||||
// Scenario: RELAY_CHN_STATE_REVERSE -> (relay_chn_tilt_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_REVERSE
|
||||
TEST_CASE("Run Reverse to Tilt Reverse transition with inertia", "[relay_chn][tilt][inertia]") {
|
||||
uint8_t ch = 0;
|
||||
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
// Prepare channel by running reverse first to set last_run_cmd
|
||||
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_REVERSE);
|
||||
|
||||
// 1. Start in reverse direction
|
||||
relay_chn_run_reverse(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch));
|
||||
|
||||
// 2. Issue tilt reverse command
|
||||
relay_chn_tilt_reverse(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(ch));
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(ch));
|
||||
}
|
||||
|
||||
// TEST_CASE: Test transition from FREE state to tilt forward (now with preparation)
|
||||
// Scenario: RELAY_CHN_STATE_FREE -> (prepare) -> RELAY_CHN_STATE_FREE -> (relay_chn_tilt_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_FORWARD
|
||||
TEST_CASE("FREE to Tilt Forward transition with inertia (prepared)", "[relay_chn][tilt][inertia]") {
|
||||
uint8_t ch = 0;
|
||||
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
// Prepare channel by running forward first to set last_run_cmd
|
||||
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(ch)); // Ensure we are back to FREE
|
||||
|
||||
// Issue tilt forward command
|
||||
relay_chn_tilt_forward(ch);
|
||||
// From FREE state, tilt command should still incur the inertia due to the internal timer logic
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch));
|
||||
}
|
||||
|
||||
// TEST_CASE: Test transition from FREE state to tilt reverse (now with preparation)
|
||||
// Scenario: RELAY_CHN_STATE_FREE -> (prepare) -> RELAY_CHN_STATE_FREE -> (relay_chn_tilt_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_REVERSE
|
||||
TEST_CASE("FREE to Tilt Reverse transition with inertia (prepared)", "[relay_chn][tilt][inertia]") {
|
||||
uint8_t ch = 0;
|
||||
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
// Prepare channel by running reverse first to set last_run_cmd
|
||||
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_REVERSE);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(ch)); // Ensure we are back to FREE
|
||||
|
||||
// Issue tilt reverse command
|
||||
relay_chn_tilt_reverse(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(ch));
|
||||
}
|
||||
|
||||
// TEST_CASE: Test transition from tilt forward to run forward (inertia expected for run)
|
||||
// Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD
|
||||
TEST_CASE("Tilt Forward to Run Forward transition with inertia", "[relay_chn][tilt][inertia]") {
|
||||
uint8_t ch = 0;
|
||||
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
// Prepare channel by running forward first to set last_run_cmd, then tilt
|
||||
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD);
|
||||
relay_chn_tilt_forward(ch); // Go to tilt state
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch));
|
||||
|
||||
// 2. Issue run forward command
|
||||
relay_chn_run_forward(ch);
|
||||
// From Tilt to Run in the same logical name but in the opposite direction, inertia is expected.
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD_PENDING, relay_chn_get_state(ch));
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch));
|
||||
}
|
||||
|
||||
// TEST_CASE: Test transition from tilt reverse to run reverse (no inertia expected for run)
|
||||
// Scenario: RELAY_CHN_STATE_TILT_REVERSE -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_REVERSE
|
||||
TEST_CASE("Tilt Reverse to Run Reverse transition with inertia", "[relay_chn][tilt][inertia]") {
|
||||
uint8_t ch = 0;
|
||||
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
// Prepare channel by running reverse first to set last_run_cmd, then tilt
|
||||
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_REVERSE);
|
||||
relay_chn_tilt_reverse(ch); // Go to tilt state
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(ch));
|
||||
|
||||
// 2. Issue run reverse command
|
||||
relay_chn_run_reverse(ch);
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE_PENDING, relay_chn_get_state(ch));
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch));
|
||||
}
|
||||
|
||||
// TEST_CASE: Test transition from tilt forward to run reverse (without inertia)
|
||||
// Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_REVERSE
|
||||
TEST_CASE("Tilt Forward to Run Reverse transition without inertia", "[relay_chn][tilt][inertia]") {
|
||||
uint8_t ch = 0;
|
||||
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
// Prepare channel by running forward first to set last_run_cmd, then tilt
|
||||
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD);
|
||||
relay_chn_tilt_forward(ch); // Go to tilt state
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch));
|
||||
|
||||
// 2. Issue run reverse command (opposite direction)
|
||||
relay_chn_run_reverse(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch));
|
||||
}
|
||||
|
||||
// TEST_CASE: Test stopping from a tilt state (no inertia for stop command itself)
|
||||
// Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_stop) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_FREE
|
||||
TEST_CASE("Tilt to Stop transition without immediate inertia for stop", "[relay_chn][tilt][inertia]") {
|
||||
uint8_t ch = 0;
|
||||
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
// Prepare channel by running forward first to set last_run_cmd, then tilt
|
||||
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD);
|
||||
relay_chn_tilt_forward(ch); // Go to tilt state
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch));
|
||||
|
||||
// 2. Issue stop command
|
||||
relay_chn_stop(ch);
|
||||
// Stop command should apply immediately, setting state to FREE since last state was tilt.
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(ch));
|
||||
}
|
||||
|
||||
// ### Tilt Broadcast Command (RELAY_CHN_ID_ALL) Tests
|
||||
|
||||
TEST_CASE("tilt_forward with ID_ALL sets all channels to TILT_FORWARD", "[relay_chn][tilt][id_all]")
|
||||
{
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
// 1. Prepare all channels.
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
prepare_channel_for_tilt(i, RELAY_CHN_CMD_FORWARD);
|
||||
}
|
||||
|
||||
// 2. Issue tilt forward to all channels
|
||||
relay_chn_tilt_forward(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Tilt from FREE doesn't have stop-inertia
|
||||
|
||||
// 3. Verify all channels are tilting forward
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("tilt_reverse with ID_ALL sets all channels to TILT_REVERSE", "[relay_chn][tilt][id_all]")
|
||||
{
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
// 1. Prepare all channels.
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
prepare_channel_for_tilt(i, RELAY_CHN_CMD_REVERSE);
|
||||
}
|
||||
|
||||
// 2. Issue tilt reverse to all channels
|
||||
relay_chn_tilt_reverse(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
|
||||
// 3. Verify all channels are tilting reverse
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("tilt_stop with ID_ALL stops all tilting channels", "[relay_chn][tilt][id_all]")
|
||||
{
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
// 1. Prepare and start all channels tilting forward
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
prepare_channel_for_tilt(i, RELAY_CHN_CMD_REVERSE);
|
||||
}
|
||||
relay_chn_tilt_forward(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
|
||||
// 2. Stop tilting on all channels
|
||||
relay_chn_tilt_stop(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
|
||||
// 3. Verify all channels are free
|
||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("tilt_auto with ID_ALL tilts channels based on last run direction", "[relay_chn][tilt][id_all]")
|
||||
{
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
// This test requires at least 2 channels to demonstrate different behaviors
|
||||
TEST_ASSERT_GREATER_OR_EQUAL_MESSAGE(2, relay_chn_count, "Test requires at least 2 channels");
|
||||
|
||||
// 1. Prepare channel 0 with last run FORWARD and channel 1 with last run REVERSE
|
||||
prepare_channel_for_tilt(0, RELAY_CHN_CMD_FORWARD);
|
||||
prepare_channel_for_tilt(1, RELAY_CHN_CMD_REVERSE);
|
||||
|
||||
// 2. Issue auto tilt command to all channels
|
||||
relay_chn_tilt_auto(RELAY_CHN_ID_ALL);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Tilt from FREE state is dispatched immediately
|
||||
|
||||
// 3. Verify channel 0 tilts forward (last run was forward) and channel 1 tilts reverse (last run was reverse)
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(0));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(1));
|
||||
}
|
||||
|
||||
// Test relay_chn_tilt_auto() chooses correct tilt direction
|
||||
TEST_CASE("relay_chn_tilt_auto chooses correct direction", "[relay_chn][tilt][auto]") {
|
||||
uint8_t ch = 0;
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
// Prepare FORWARD
|
||||
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD);
|
||||
relay_chn_tilt_auto(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch));
|
||||
relay_chn_tilt_stop(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
|
||||
// Prepare REVERSE
|
||||
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_REVERSE);
|
||||
relay_chn_tilt_auto(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(ch));
|
||||
}
|
||||
|
||||
// Test sensitivity set/get
|
||||
TEST_CASE("relay_chn_tilt_sensitivity_set and get", "[relay_chn][tilt][sensitivity]") {
|
||||
uint8_t ch = 0;
|
||||
uint8_t val = 0;
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
relay_chn_tilt_sensitivity_set(ch, 0);
|
||||
TEST_ESP_OK(relay_chn_tilt_sensitivity_get(ch, &val, 1));
|
||||
TEST_ASSERT_EQUAL_UINT8(0, val);
|
||||
|
||||
relay_chn_tilt_sensitivity_set(ch, 50);
|
||||
TEST_ESP_OK(relay_chn_tilt_sensitivity_get(ch, &val, 1));
|
||||
TEST_ASSERT_EQUAL_UINT8(50, val);
|
||||
|
||||
relay_chn_tilt_sensitivity_set(ch, 100);
|
||||
TEST_ESP_OK(relay_chn_tilt_sensitivity_get(ch, &val, 1));
|
||||
TEST_ASSERT_EQUAL_UINT8(100, val);
|
||||
|
||||
// Set all channels
|
||||
relay_chn_tilt_sensitivity_set(RELAY_CHN_ID_ALL, 42);
|
||||
uint8_t vals[CONFIG_RELAY_CHN_COUNT] = {0};
|
||||
TEST_ESP_OK(relay_chn_tilt_sensitivity_get(RELAY_CHN_ID_ALL, vals, relay_chn_count));
|
||||
for (int i = 0; i < relay_chn_count; ++i) {
|
||||
TEST_ASSERT_EQUAL_UINT8(42, vals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Test tilt counter logic: forward x3, reverse x3, extra reverse fails
|
||||
TEST_CASE("tilt counter logic: forward and reverse consumption", "[relay_chn][tilt][counter]") {
|
||||
uint8_t ch = 0;
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD);
|
||||
|
||||
// Tilt forward 3 times
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
relay_chn_tilt_forward(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch));
|
||||
relay_chn_tilt_stop(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
}
|
||||
|
||||
// Now tilt reverse 3 times (should succeed)
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
relay_chn_tilt_reverse(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
if (i < 3) {
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(ch));
|
||||
relay_chn_tilt_stop(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
}
|
||||
}
|
||||
|
||||
// Extra reverse tilt should fail (counter exhausted)
|
||||
relay_chn_tilt_reverse(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
// Should not enter TILT_REVERSE, should remain FREE or STOPPED
|
||||
relay_chn_state_t state = relay_chn_get_state(ch);
|
||||
TEST_ASSERT(state != RELAY_CHN_STATE_TILT_REVERSE);
|
||||
}
|
||||
|
||||
// Test run command during TILT state
|
||||
TEST_CASE("run command during TILT state transitions correctly", "[relay_chn][tilt][run-during-tilt]") {
|
||||
uint8_t ch = 0;
|
||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||
g_is_component_initialized = true;
|
||||
|
||||
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD);
|
||||
relay_chn_tilt_forward(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch));
|
||||
|
||||
// Issue run reverse while in TILT_FORWARD
|
||||
relay_chn_run_reverse(ch);
|
||||
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||
// Should transition to REVERSE or REVERSE_PENDING depending on inertia logic
|
||||
relay_chn_state_t state = relay_chn_get_state(ch);
|
||||
TEST_ASSERT(state == RELAY_CHN_STATE_REVERSE || state == RELAY_CHN_STATE_REVERSE_PENDING);
|
||||
}
|
||||
504
test_apps/main/test_relay_chn_tilt_multi.c
Normal file
504
test_apps/main/test_relay_chn_tilt_multi.c
Normal file
@@ -0,0 +1,504 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "test_common.h"
|
||||
|
||||
|
||||
// ### Tilt Functionality Tests (Conditional)
|
||||
|
||||
// This section will only be compiled if **`CONFIG_RELAY_CHN_ENABLE_TILTING`** is defined as **`1`** in `sdkconfig`.
|
||||
|
||||
#ifndef CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
#error "This test requires CONFIG_RELAY_CHN_ENABLE_TILTING"
|
||||
#endif
|
||||
|
||||
#define RELAY_CHN_CMD_FORWARD 1
|
||||
#define RELAY_CHN_CMD_REVERSE 2
|
||||
|
||||
void check_all_channels_for_state(relay_chn_state_t state)
|
||||
{
|
||||
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
// ESP_LOGI(TEST_TAG, "Checking channel %d for state %d", i, state);
|
||||
TEST_ASSERT_EQUAL(state, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to prepare channel for tilt tests
|
||||
void prepare_channels_for_tilt_with_mixed_runs() {
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
|
||||
// Ensure the channel has had a 'last_run_cmd'
|
||||
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
if (i % 2 == 0) {
|
||||
relay_chn_run_forward(i);
|
||||
} else { // Assuming initial_cmd is RELAY_CHN_CMD_REVERSE
|
||||
relay_chn_run_reverse(i);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
relay_chn_state_t expect_state;
|
||||
if (i % 2 == 0) {
|
||||
expect_state = RELAY_CHN_STATE_FORWARD;
|
||||
} else { // Assuming initial_cmd is RELAY_CHN_CMD_REVERSE
|
||||
expect_state = RELAY_CHN_STATE_REVERSE;
|
||||
}
|
||||
TEST_ASSERT_EQUAL(expect_state, relay_chn_get_state(i));
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to prepare channel for tilt tests
|
||||
void prepare_all_channels_for_tilt(int initial_cmd) {
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
|
||||
// If the channels are not IDLE yet, wait more
|
||||
bool not_idle = false;
|
||||
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
if (relay_chn_get_state(i) != RELAY_CHN_STATE_IDLE) {
|
||||
not_idle = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (not_idle) {
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS));
|
||||
}
|
||||
// Ensure all channels are IDLE
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_IDLE);
|
||||
|
||||
// Ensure the channel has had a 'last_run_cmd'
|
||||
if (initial_cmd == RELAY_CHN_CMD_FORWARD) {
|
||||
relay_chn_run_forward_all();
|
||||
} else { // Assuming initial_cmd is RELAY_CHN_CMD_REVERSE
|
||||
relay_chn_run_reverse_all();
|
||||
}
|
||||
|
||||
relay_chn_state_t expect_state = initial_cmd == RELAY_CHN_CMD_FORWARD
|
||||
? RELAY_CHN_STATE_FORWARD : RELAY_CHN_STATE_REVERSE;
|
||||
check_all_channels_for_state(expect_state);
|
||||
ESP_LOGI(TEST_TAG, "All channels prepared for tilt test");
|
||||
}
|
||||
|
||||
// TEST_CASE: Test transition from running forward to tilt forward
|
||||
// Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_tilt_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_FORWARD
|
||||
TEST_CASE("Run Forward to Tilt Forward transition with inertia", "[relay_chn][tilt][inertia]")
|
||||
{
|
||||
// 1. Start in forward direction
|
||||
relay_chn_run_forward_all();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_FORWARD);
|
||||
|
||||
// 2. Issue tilt forward command
|
||||
relay_chn_tilt_forward_all();
|
||||
// After tilt command, it should immediately stop and then trigger inertia.
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_STOPPED);
|
||||
|
||||
// Wait for the inertia period (after which the tilt command will be dispatched)
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS));
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD);
|
||||
|
||||
// Ensure the channel reset tilt control
|
||||
relay_chn_tilt_stop_all();
|
||||
}
|
||||
|
||||
// TEST_CASE: Test transition from running reverse to tilt reverse
|
||||
// Scenario: RELAY_CHN_STATE_REVERSE -> (relay_chn_tilt_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_REVERSE
|
||||
TEST_CASE("Run Reverse to Tilt Reverse transition with inertia", "[relay_chn][tilt][inertia]")
|
||||
{
|
||||
// 1. Start in reverse direction
|
||||
relay_chn_run_reverse_all();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_REVERSE);
|
||||
|
||||
// 2. Issue tilt reverse command
|
||||
relay_chn_tilt_reverse_all();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_STOPPED);
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_TILT_REVERSE);
|
||||
|
||||
// Ensure the channel reset tilt control
|
||||
relay_chn_tilt_stop_all();
|
||||
}
|
||||
|
||||
// TEST_CASE: Test transition from FREE state to tilt forward (now with preparation)
|
||||
// Scenario: RELAY_CHN_STATE_IDLE -> (prepare) -> RELAY_CHN_STATE_IDLE -> (relay_chn_tilt_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_FORWARD
|
||||
TEST_CASE("FREE to Tilt Forward transition with inertia (prepared)", "[relay_chn][tilt][inertia]")
|
||||
{
|
||||
// Prepare channel by running forward first to set last_run_cmd
|
||||
prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD);
|
||||
relay_chn_stop_all(); // Stop to trigger IDLE
|
||||
// Wait for the channel to transition to IDLE
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_IDLE); // Ensure we are back to IDLE
|
||||
|
||||
// Issue tilt forward command
|
||||
relay_chn_tilt_forward_all();
|
||||
// From FREE state, tilt command should still incur the inertia due to the internal timer logic
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD);
|
||||
|
||||
// Ensure the channel reset tilt control
|
||||
relay_chn_tilt_stop_all();
|
||||
}
|
||||
|
||||
// TEST_CASE: Test transition from FREE state to tilt reverse (now with preparation)
|
||||
// Scenario: RELAY_CHN_STATE_IDLE -> (prepare) -> RELAY_CHN_STATE_IDLE -> (relay_chn_tilt_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_REVERSE
|
||||
TEST_CASE("FREE to Tilt Reverse transition with inertia (prepared)", "[relay_chn][tilt][inertia]")
|
||||
{
|
||||
// Prepare channel by running reverse first to set last_run_cmd
|
||||
prepare_all_channels_for_tilt(RELAY_CHN_CMD_REVERSE);
|
||||
relay_chn_stop_all(); // Stop to trigger IDLE
|
||||
// Wait for the channel to transition to IDLE
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_IDLE); // Ensure we are back to IDLE
|
||||
|
||||
// Issue tilt reverse command
|
||||
relay_chn_tilt_reverse_all();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_TILT_REVERSE);
|
||||
|
||||
// Ensure the channel reset tilt control
|
||||
relay_chn_tilt_stop_all();
|
||||
}
|
||||
|
||||
// TEST_CASE: Test transition from tilt forward to run forward (inertia expected for run)
|
||||
// Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD
|
||||
TEST_CASE("Tilt Forward to Run Forward transition with inertia", "[relay_chn][tilt][inertia]")
|
||||
{
|
||||
// Prepare channel by running forward first to set last_run_cmd, then tilt
|
||||
prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD);
|
||||
relay_chn_tilt_forward_all(); // Go to tilt state
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD);
|
||||
|
||||
// 2. Issue run forward command
|
||||
relay_chn_run_forward_all();
|
||||
// From Tilt to Run in the same logical name but in the opposite direction, inertia is expected.
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_FORWARD_PENDING);
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_FORWARD);
|
||||
|
||||
// Ensure the channel reset tilt control
|
||||
relay_chn_tilt_stop_all();
|
||||
}
|
||||
|
||||
// TEST_CASE: Test transition from tilt reverse to run reverse (no inertia expected for run)
|
||||
// Scenario: RELAY_CHN_STATE_TILT_REVERSE -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_REVERSE
|
||||
TEST_CASE("Tilt Reverse to Run Reverse transition with inertia", "[relay_chn][tilt][inertia]")
|
||||
{
|
||||
// Prepare channel by running reverse first to set last_run_cmd, then tilt
|
||||
prepare_all_channels_for_tilt(RELAY_CHN_CMD_REVERSE);
|
||||
relay_chn_tilt_reverse_all(); // Go to tilt state
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_TILT_REVERSE);
|
||||
|
||||
// 2. Issue run reverse command
|
||||
relay_chn_run_reverse_all();
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_REVERSE_PENDING);
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_REVERSE);
|
||||
|
||||
// Ensure the channel reset tilt control
|
||||
relay_chn_tilt_stop_all();
|
||||
}
|
||||
|
||||
// TEST_CASE: Test transition from tilt forward to run reverse (without inertia)
|
||||
// Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_REVERSE
|
||||
TEST_CASE("Tilt Forward to Run Reverse transition without inertia", "[relay_chn][tilt][inertia]")
|
||||
{
|
||||
// Prepare channel by running forward first to set last_run_cmd, then tilt
|
||||
prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD);
|
||||
relay_chn_tilt_forward_all(); // Go to tilt state
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD);
|
||||
|
||||
// 2. Issue run reverse command (opposite direction)
|
||||
relay_chn_run_reverse_all();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_REVERSE);
|
||||
|
||||
// Ensure the channel reset tilt control
|
||||
relay_chn_tilt_stop_all();
|
||||
}
|
||||
|
||||
// TEST_CASE: Test stopping from a tilt state (no inertia for stop command itself)
|
||||
// Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_tilt_stop) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_IDLE
|
||||
TEST_CASE("Tilt to Stop transition without immediate inertia for stop", "[relay_chn][tilt][inertia]")
|
||||
{
|
||||
// Prepare all channels by running forward first to set last_run_cmd, then tilt
|
||||
prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD);
|
||||
relay_chn_tilt_forward_all(); // Go to tilt state
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
// Verify all channels are tilting forward
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD);
|
||||
|
||||
// 2. Issue stop command
|
||||
relay_chn_tilt_stop_all();
|
||||
// Stop command should apply immediately, setting state to FREE since last state was tilt.
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
// Verify all channels are IDLE
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_IDLE);
|
||||
}
|
||||
|
||||
// ### Batch Tilt Control Tests
|
||||
|
||||
TEST_CASE("tilt_forward_all sets all channels to TILT_FORWARD", "[relay_chn][tilt][batch]")
|
||||
{
|
||||
// 1. Prepare all channels.
|
||||
prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD);
|
||||
|
||||
// 2. Issue tilt forward to all channels
|
||||
relay_chn_tilt_forward_all();
|
||||
// Should incur inertia timer
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
|
||||
// 3. Verify all channels are tilting forward
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD);
|
||||
|
||||
// Ensure the channel reset tilt control
|
||||
relay_chn_tilt_stop_all();
|
||||
}
|
||||
|
||||
TEST_CASE("tilt_reverse_all sets all channels to TILT_REVERSE", "[relay_chn][tilt][batch]")
|
||||
{
|
||||
// 1. Prepare all channels.
|
||||
prepare_all_channels_for_tilt(RELAY_CHN_CMD_REVERSE);
|
||||
|
||||
// 2. Issue tilt reverse to all channels
|
||||
relay_chn_tilt_reverse_all();
|
||||
// Should incur inertia timer
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
|
||||
// 3. Verify all channels are tilting reverse
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_TILT_REVERSE);
|
||||
|
||||
// Ensure the channel reset tilt control
|
||||
relay_chn_tilt_stop_all();
|
||||
}
|
||||
|
||||
TEST_CASE("tilt_stop_all stops all tilting channels", "[relay_chn][tilt][batch]")
|
||||
{
|
||||
// 1. Prepare and start all channels tilting forward
|
||||
prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD);
|
||||
|
||||
relay_chn_tilt_forward_all();
|
||||
// Should incur inertia timer
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
// 3. Verify all channels are tilting forward
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD);
|
||||
|
||||
// 2. Stop tilting on all channels
|
||||
relay_chn_tilt_stop_all();
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
|
||||
// 3. Verify all channels are free
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_IDLE);
|
||||
}
|
||||
|
||||
TEST_CASE("tilt_auto_all tilts channels based on last run direction", "[relay_chn][tilt][batch]")
|
||||
{
|
||||
// 1. Prepare channel 0 with last run FORWARD and channel 1 with last run REVERSE
|
||||
prepare_channels_for_tilt_with_mixed_runs();
|
||||
|
||||
// 2. Issue auto tilt command to all channels
|
||||
relay_chn_tilt_auto_all();
|
||||
// Should incur inertia timer
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
|
||||
// 3. Verify even channels tilt forward (last run was forward) and odd channels tilt reverse (last run was reverse)
|
||||
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
relay_chn_state_t state = i % 2 == 0 ?
|
||||
RELAY_CHN_STATE_TILT_FORWARD : RELAY_CHN_STATE_TILT_REVERSE;
|
||||
|
||||
TEST_ASSERT_EQUAL(state, relay_chn_get_state(i));
|
||||
}
|
||||
|
||||
// Ensure the channel reset tilt control
|
||||
relay_chn_tilt_stop_all();
|
||||
}
|
||||
|
||||
// Test relay_chn_tilt_auto() chooses correct tilt direction
|
||||
TEST_CASE("relay_chn_tilt_auto chooses correct direction", "[relay_chn][tilt][auto]")
|
||||
{
|
||||
// Prepare FORWARD
|
||||
prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD);
|
||||
relay_chn_tilt_auto_all();
|
||||
// Should incur inertia timer
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
// Verify all tilt forward
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD);
|
||||
|
||||
// Ensure the channel reset tilt control
|
||||
relay_chn_tilt_stop_all();
|
||||
|
||||
// Prepare REVERSE
|
||||
prepare_all_channels_for_tilt(RELAY_CHN_CMD_REVERSE);
|
||||
relay_chn_tilt_auto_all();
|
||||
// Should incur inertia timer
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
// Verify all tilt reverse
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_TILT_REVERSE);
|
||||
|
||||
// Ensure the channel reset tilt control
|
||||
relay_chn_tilt_stop_all();
|
||||
}
|
||||
|
||||
// Test sensitivity set/get
|
||||
TEST_CASE("relay_chn_tilt_set_sensitivity and get", "[relay_chn][tilt][sensitivity]")
|
||||
{
|
||||
uint8_t ch = 0;
|
||||
relay_chn_tilt_set_sensitivity(ch, 0);
|
||||
TEST_ASSERT_EQUAL_UINT8(0, relay_chn_tilt_get_sensitivity(ch));
|
||||
|
||||
relay_chn_tilt_set_sensitivity(ch, 50);
|
||||
TEST_ASSERT_EQUAL_UINT8(50, relay_chn_tilt_get_sensitivity(ch));
|
||||
|
||||
relay_chn_tilt_set_sensitivity(ch, 100);
|
||||
TEST_ASSERT_EQUAL_UINT8(100, relay_chn_tilt_get_sensitivity(ch));
|
||||
// Set all channels
|
||||
relay_chn_tilt_set_sensitivity_all_with(42);
|
||||
|
||||
uint8_t vals[CONFIG_RELAY_CHN_COUNT] = {0};
|
||||
uint8_t expect[CONFIG_RELAY_CHN_COUNT];
|
||||
memset(expect, 42, CONFIG_RELAY_CHN_COUNT);
|
||||
|
||||
TEST_ESP_OK(relay_chn_tilt_get_sensitivity_all(vals));
|
||||
TEST_ASSERT_EQUAL_UINT8_ARRAY(expect, vals, CONFIG_RELAY_CHN_COUNT);
|
||||
}
|
||||
|
||||
TEST_CASE("relay_chn_tilt_get_default_sensitivity returns correct value", "[relay_chn][tilt][sensitivity]")
|
||||
{
|
||||
// The default sensitivity is calculated from default timing values.
|
||||
// Default run time: 15ms, Min run time: 50ms, Max run time: 10ms.
|
||||
// Formula: ( (DEFAULT_RUN - MIN_RUN) * 100 ) / (MAX_RUN - MIN_RUN)
|
||||
// ( (15 - 50) * 100 ) / (10 - 50) = (-35 * 100) / -40 = -3500 / -40 = 87.5
|
||||
// As integer arithmetic, this is 87.
|
||||
uint8_t expected_sensitivity = 87;
|
||||
TEST_ASSERT_EQUAL_UINT8(expected_sensitivity, relay_chn_tilt_get_default_sensitivity());
|
||||
}
|
||||
|
||||
// Test sensitivity upper boundary for all set functions
|
||||
TEST_CASE("relay_chn_tilt_set_sensitivity functions handle upper boundary", "[relay_chn][tilt][sensitivity]")
|
||||
{
|
||||
// 1. Test relay_chn_tilt_set_sensitivity() for each channel
|
||||
for (uint8_t ch = 0; ch < CONFIG_RELAY_CHN_COUNT; ch++) {
|
||||
relay_chn_tilt_set_sensitivity(ch, 101);
|
||||
TEST_ASSERT_EQUAL_UINT8(100, relay_chn_tilt_get_sensitivity(ch));
|
||||
|
||||
relay_chn_tilt_set_sensitivity(ch, 255);
|
||||
TEST_ASSERT_EQUAL_UINT8(100, relay_chn_tilt_get_sensitivity(ch));
|
||||
}
|
||||
|
||||
// 2. Test relay_chn_tilt_set_sensitivity_all_with()
|
||||
relay_chn_tilt_set_sensitivity_all_with(150);
|
||||
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
TEST_ASSERT_EQUAL_UINT8(100, relay_chn_tilt_get_sensitivity(i));
|
||||
}
|
||||
|
||||
// 3. Test relay_chn_tilt_set_sensitivity_all()
|
||||
uint8_t sensitivities[CONFIG_RELAY_CHN_COUNT];
|
||||
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
sensitivities[i] = 100 + i * 10; // Values like 100, 110, 120...
|
||||
}
|
||||
TEST_ESP_OK(relay_chn_tilt_set_sensitivity_all(sensitivities));
|
||||
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
|
||||
TEST_ASSERT_EQUAL_UINT8(100, relay_chn_tilt_get_sensitivity(i));
|
||||
}
|
||||
}
|
||||
|
||||
// Test tilt counter logic: forward x3, reverse x3, extra reverse fails
|
||||
TEST_CASE("tilt counter logic: forward and reverse consumption", "[relay_chn][tilt][counter]")
|
||||
{
|
||||
// Tilt execution time at 100% sensitivity in milliseconds (10 + 90)
|
||||
#define TEST_TILT_EXECUTION_TIME_MS 100
|
||||
|
||||
prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD);
|
||||
|
||||
relay_chn_tilt_set_sensitivity_all_with(100); // Set sentivity to max for fastest execution
|
||||
// Ensure sensitivities are set correctly
|
||||
uint8_t sensitivities[CONFIG_RELAY_CHN_COUNT];
|
||||
uint8_t expect[CONFIG_RELAY_CHN_COUNT];
|
||||
memset(expect, 100, CONFIG_RELAY_CHN_COUNT);
|
||||
relay_chn_tilt_get_sensitivity_all(sensitivities);
|
||||
TEST_ASSERT_EQUAL_UINT8_ARRAY(expect, sensitivities, CONFIG_RELAY_CHN_COUNT);
|
||||
|
||||
// Tilt forward 3 times
|
||||
relay_chn_tilt_forward_all();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_TILT_EXECUTION_TIME_MS * 3 + TEST_DELAY_MARGIN_MS));
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD);
|
||||
|
||||
// Stop tilt on all channels
|
||||
relay_chn_tilt_stop_all();
|
||||
#if CONFIG_RELAY_CHN_ENABLE_NVS
|
||||
// Tilt stop should save the latest tilt count to the NVS
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS + 300));
|
||||
#else
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
#endif
|
||||
|
||||
// Now tilt reverse 3 times (should succeed)
|
||||
relay_chn_tilt_reverse_all();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_TILT_EXECUTION_TIME_MS));
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_TILT_REVERSE);
|
||||
// Let it execute 2 at least, or more
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_TILT_EXECUTION_TIME_MS * 3));
|
||||
|
||||
// More reverse tilt should fail (counter exhausted)
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_IDLE);
|
||||
}
|
||||
|
||||
// Test run command during TILT state
|
||||
TEST_CASE("run command during TILT state transitions correctly", "[relay_chn][tilt][run-during-tilt]")
|
||||
{
|
||||
prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD);
|
||||
relay_chn_tilt_forward_all();
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD);
|
||||
|
||||
// Issue run reverse while in TILT_FORWARD
|
||||
relay_chn_run_reverse_all();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
// Should transition to REVERSE
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_REVERSE);
|
||||
}
|
||||
|
||||
// Test run command during active tilt cycle (move/pause)
|
||||
TEST_CASE("run_all command during active tilt cycle stops tilt", "[relay_chn][tilt][interrupt]")
|
||||
{
|
||||
// Set a known sensitivity for predictable timing.
|
||||
// For sensitivity=50, move_time=30ms, pause_time=270ms.
|
||||
relay_chn_tilt_set_sensitivity_all_with(50);
|
||||
|
||||
// --- Test interrupting during MOVE step ---
|
||||
prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD);
|
||||
relay_chn_tilt_forward_all();
|
||||
// Should incur inertia timer
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD);
|
||||
|
||||
// Interrupt with run_reverse_all while in the MOVE part of the cycle
|
||||
relay_chn_run_reverse_all();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
// Should stop tilting and go to REVERSE immediately (no inertia from TILT_FORWARD)
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_REVERSE);
|
||||
|
||||
// --- Test interrupting during PAUSE step ---
|
||||
relay_chn_stop_all(); // Stop the reverse runs
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD);
|
||||
relay_chn_tilt_forward_all();
|
||||
// Should incur inertia timer
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); // Wait past MOVE, into PAUSE
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD);
|
||||
|
||||
// Interrupt with run_forward_all while in the PAUSE part of the cycle
|
||||
relay_chn_run_forward_all();
|
||||
// Should stop tilting and go to FORWARD_PENDING (inertia from TILT_FORWARD)
|
||||
check_all_channels_for_state(RELAY_CHN_STATE_FORWARD_PENDING);
|
||||
}
|
||||
316
test_apps/main/test_relay_chn_tilt_single.c
Normal file
316
test_apps/main/test_relay_chn_tilt_single.c
Normal file
@@ -0,0 +1,316 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "test_common.h"
|
||||
|
||||
|
||||
// ### Tilt Functionality Tests (Conditional)
|
||||
|
||||
// This section will only be compiled if **`CONFIG_RELAY_CHN_ENABLE_TILTING`** is defined as **`1`** in `sdkconfig`.
|
||||
|
||||
#ifndef CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
#error "This test requires CONFIG_RELAY_CHN_ENABLE_TILTING"
|
||||
#endif
|
||||
|
||||
#define RELAY_CHN_CMD_FORWARD 1
|
||||
#define RELAY_CHN_CMD_REVERSE 2
|
||||
|
||||
// Helper function to prepare channel for tilt tests
|
||||
void prepare_channel_for_tilt(int initial_cmd) {
|
||||
// Ensure the channel reset tilt control
|
||||
relay_chn_tilt_stop();
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
|
||||
// Ensure the channel has had a 'last_run_cmd'
|
||||
if (initial_cmd == RELAY_CHN_CMD_FORWARD) {
|
||||
relay_chn_run_forward();
|
||||
} else { // Assuming initial_cmd is RELAY_CHN_CMD_REVERSE
|
||||
relay_chn_run_reverse();
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
relay_chn_state_t expect_state = initial_cmd == RELAY_CHN_CMD_FORWARD ? RELAY_CHN_STATE_FORWARD : RELAY_CHN_STATE_REVERSE;
|
||||
TEST_ASSERT_EQUAL(expect_state, relay_chn_get_state());
|
||||
}
|
||||
|
||||
// Test transition from running forward to tilt forward
|
||||
// Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_tilt_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_FORWARD
|
||||
TEST_CASE("Run Forward to Tilt Forward transition with inertia", "[relay_chn][tilt][inertia]")
|
||||
{
|
||||
// 1. Start in forward direction
|
||||
relay_chn_run_forward();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state());
|
||||
|
||||
// 2. Issue tilt forward command
|
||||
relay_chn_tilt_forward();
|
||||
// After tilt command, it should immediately stop and then trigger inertia.
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state());
|
||||
|
||||
// Wait for the inertia period (after which the tilt command will be dispatched)
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state());
|
||||
}
|
||||
|
||||
// Test transition from running reverse to tilt reverse
|
||||
// Scenario: RELAY_CHN_STATE_REVERSE -> (relay_chn_tilt_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_REVERSE
|
||||
TEST_CASE("Run Reverse to Tilt Reverse transition with inertia", "[relay_chn][tilt][inertia]")
|
||||
{
|
||||
// 1. Start in reverse direction
|
||||
relay_chn_run_reverse();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state());
|
||||
|
||||
// 2. Issue tilt reverse command
|
||||
relay_chn_tilt_reverse();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state());
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state());
|
||||
}
|
||||
|
||||
// Test transition from FREE state to tilt forward (now with preparation)
|
||||
// Scenario: RELAY_CHN_STATE_IDLE -> (prepare) -> RELAY_CHN_STATE_IDLE -> (relay_chn_tilt_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_FORWARD
|
||||
TEST_CASE("FREE to Tilt Forward transition with inertia (prepared)", "[relay_chn][tilt][inertia]")
|
||||
{
|
||||
// Prepare channel by running forward first to set last_run_cmd
|
||||
prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD);
|
||||
relay_chn_stop(); // Stop to trigger IDLE
|
||||
// Wait for the channel to transition to IDLE
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); // Ensure we are back to FREE
|
||||
|
||||
// Issue tilt forward command
|
||||
relay_chn_tilt_forward();
|
||||
// From FREE state, tilt command should still incur the inertia due to the internal timer logic
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state());
|
||||
}
|
||||
|
||||
// Test transition from FREE state to tilt reverse (now with preparation)
|
||||
// Scenario: RELAY_CHN_STATE_IDLE -> (prepare) -> RELAY_CHN_STATE_IDLE -> (relay_chn_tilt_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_REVERSE
|
||||
TEST_CASE("FREE to Tilt Reverse transition with inertia (prepared)", "[relay_chn][tilt][inertia]")
|
||||
{
|
||||
// Prepare channel by running reverse first to set last_run_cmd
|
||||
prepare_channel_for_tilt(RELAY_CHN_CMD_REVERSE);
|
||||
relay_chn_stop(); // Stop to trigger IDLE
|
||||
// Wait for the channel to transition to IDLE
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); // Ensure we are back to FREE
|
||||
|
||||
// Issue tilt reverse command
|
||||
relay_chn_tilt_reverse();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state());
|
||||
}
|
||||
|
||||
// Test transition from tilt forward to run forward (inertia expected for run)
|
||||
// Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD
|
||||
TEST_CASE("Tilt Forward to Run Forward transition with inertia", "[relay_chn][tilt][inertia]")
|
||||
{
|
||||
// Prepare channel by running forward first to set last_run_cmd, then tilt
|
||||
prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD);
|
||||
relay_chn_tilt_forward(); // Go to tilt state
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state());
|
||||
|
||||
// 2. Issue run forward command
|
||||
relay_chn_run_forward();
|
||||
// From Tilt to Run in the same logical name but in the opposite direction, inertia is expected.
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD_PENDING, relay_chn_get_state());
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state());
|
||||
}
|
||||
|
||||
// Test transition from tilt reverse to run reverse (no inertia expected for run)
|
||||
// Scenario: RELAY_CHN_STATE_TILT_REVERSE -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_REVERSE
|
||||
TEST_CASE("Tilt Reverse to Run Reverse transition with inertia", "[relay_chn][tilt][inertia]")
|
||||
{
|
||||
// Prepare channel by running reverse first to set last_run_cmd, then tilt
|
||||
prepare_channel_for_tilt(RELAY_CHN_CMD_REVERSE);
|
||||
relay_chn_tilt_reverse(); // Go to tilt state
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state());
|
||||
|
||||
// 2. Issue run reverse command
|
||||
relay_chn_run_reverse();
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE_PENDING, relay_chn_get_state());
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state());
|
||||
}
|
||||
|
||||
// Test transition from tilt forward to run reverse (without inertia)
|
||||
// Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_REVERSE
|
||||
TEST_CASE("Tilt Forward to Run Reverse transition without inertia", "[relay_chn][tilt][inertia]")
|
||||
{
|
||||
// Prepare channel by running forward first to set last_run_cmd, then tilt
|
||||
prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD);
|
||||
relay_chn_tilt_forward(); // Go to tilt state
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state());
|
||||
|
||||
// 2. Issue run reverse command (opposite direction)
|
||||
relay_chn_run_reverse();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state());
|
||||
}
|
||||
|
||||
// Test stopping from a tilt state (no inertia for stop command itself)
|
||||
// Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_tilt_stop) -> RELAY_CHN_STATE_IDLE
|
||||
TEST_CASE("Tilt to Stop transition without immediate inertia for stop", "[relay_chn][tilt][inertia]")
|
||||
{
|
||||
// Prepare channel by running forward first to set last_run_cmd, then tilt
|
||||
prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD);
|
||||
relay_chn_tilt_forward(); // Go to tilt state
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state());
|
||||
|
||||
// 2. Issue stop command
|
||||
relay_chn_tilt_stop();
|
||||
// Stop command should apply immediately, setting state to FREE since last state was tilt.
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state());
|
||||
}
|
||||
|
||||
// Test relay_chn_tilt_auto() chooses correct tilt direction
|
||||
TEST_CASE("relay_chn_tilt_auto chooses correct direction", "[relay_chn][tilt][auto]")
|
||||
{
|
||||
// Prepare FORWARD
|
||||
prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD);
|
||||
relay_chn_tilt_auto();
|
||||
// Should incur inertia timer
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state());
|
||||
relay_chn_tilt_stop();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
|
||||
// Prepare REVERSE
|
||||
prepare_channel_for_tilt(RELAY_CHN_CMD_REVERSE);
|
||||
relay_chn_tilt_auto();
|
||||
// Should incur inertia timer
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state());
|
||||
}
|
||||
|
||||
// Test sensitivity set/get
|
||||
TEST_CASE("relay_chn_tilt_set_sensitivity and get", "[relay_chn][tilt][sensitivity]")
|
||||
{
|
||||
relay_chn_tilt_set_sensitivity(0);
|
||||
TEST_ASSERT_EQUAL_UINT8(0, relay_chn_tilt_get_sensitivity());
|
||||
|
||||
relay_chn_tilt_set_sensitivity(50);
|
||||
TEST_ASSERT_EQUAL_UINT8(50, relay_chn_tilt_get_sensitivity());
|
||||
|
||||
relay_chn_tilt_set_sensitivity(100);
|
||||
TEST_ASSERT_EQUAL_UINT8(100, relay_chn_tilt_get_sensitivity());
|
||||
|
||||
relay_chn_tilt_set_sensitivity(42);
|
||||
TEST_ASSERT_EQUAL_UINT8(42, relay_chn_tilt_get_sensitivity());
|
||||
}
|
||||
|
||||
TEST_CASE("relay_chn_tilt_get_default_sensitivity returns correct value", "[relay_chn][tilt][sensitivity]")
|
||||
{
|
||||
// The default sensitivity is calculated from default timing values.
|
||||
// Default run time: 15ms, Min run time: 50ms, Max run time: 10ms.
|
||||
// Formula: ( (DEFAULT_RUN - MIN_RUN) * 100 ) / (MAX_RUN - MIN_RUN)
|
||||
// ( (15 - 50) * 100 ) / (10 - 50) = (-35 * 100) / -40 = -3500 / -40 = 87.5
|
||||
// As integer arithmetic, this is 87.
|
||||
uint8_t expected_sensitivity = 87;
|
||||
TEST_ASSERT_EQUAL_UINT8(expected_sensitivity, relay_chn_tilt_get_default_sensitivity());
|
||||
}
|
||||
|
||||
// Test sensitivity upper boundary
|
||||
TEST_CASE("relay_chn_tilt_set_sensitivity handles upper boundary", "[relay_chn][tilt][sensitivity]")
|
||||
{
|
||||
// Set sensitivity to a value greater than 100
|
||||
relay_chn_tilt_set_sensitivity(101);
|
||||
// It should be capped at 100
|
||||
TEST_ASSERT_EQUAL_UINT8(100, relay_chn_tilt_get_sensitivity());
|
||||
|
||||
relay_chn_tilt_set_sensitivity(200);
|
||||
TEST_ASSERT_EQUAL_UINT8(100, relay_chn_tilt_get_sensitivity());
|
||||
}
|
||||
|
||||
// Test tilt counter logic: forward x3, reverse x3, extra reverse fails
|
||||
TEST_CASE("tilt counter logic: forward and reverse consumption", "[relay_chn][tilt][counter]")
|
||||
{
|
||||
// Tilt execution time at 100% sensitivity in milliseconds (10 + 90)
|
||||
#define TEST_TILT_EXECUTION_TIME_MS 100
|
||||
|
||||
prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD);
|
||||
|
||||
// Tilt forward 3 times
|
||||
relay_chn_tilt_forward();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_TILT_EXECUTION_TIME_MS * 3 + TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state());
|
||||
relay_chn_tilt_stop();
|
||||
|
||||
// Now tilt reverse 3 times (should succeed)
|
||||
relay_chn_tilt_reverse();
|
||||
// Let it execute one time
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_TILT_EXECUTION_TIME_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state());
|
||||
// Let it execute 2 at least, or more
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_TILT_EXECUTION_TIME_MS * 3));
|
||||
|
||||
// Should not enter TILT_REVERSE, should remain FREE or STOPPED
|
||||
relay_chn_state_t state = relay_chn_get_state();
|
||||
TEST_ASSERT(state != RELAY_CHN_STATE_TILT_REVERSE);
|
||||
}
|
||||
|
||||
// Test run command during TILT state
|
||||
TEST_CASE("run command during TILT state transitions correctly", "[relay_chn][tilt][run-during-tilt]")
|
||||
{
|
||||
prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD);
|
||||
relay_chn_tilt_forward();
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state());
|
||||
|
||||
// Issue run reverse while in TILT_FORWARD
|
||||
relay_chn_run_reverse();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
// Should transition to REVERSE or REVERSE_PENDING depending on inertia logic
|
||||
relay_chn_state_t state = relay_chn_get_state();
|
||||
TEST_ASSERT(state == RELAY_CHN_STATE_REVERSE || state == RELAY_CHN_STATE_REVERSE_PENDING);
|
||||
}
|
||||
|
||||
// Test run command during active tilt cycle (move/pause)
|
||||
TEST_CASE("run command during active tilt cycle stops tilt", "[relay_chn][tilt][interrupt]")
|
||||
{
|
||||
// Set a known sensitivity for predictable timing.
|
||||
// For sensitivity=50, move_time=30ms, pause_time=270ms.
|
||||
relay_chn_tilt_set_sensitivity(50);
|
||||
|
||||
// --- Test interrupting during MOVE step ---
|
||||
prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD);
|
||||
relay_chn_tilt_forward();
|
||||
// Should incur inertia timer
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state());
|
||||
|
||||
// Interrupt with run_reverse while in the MOVE part of the cycle
|
||||
relay_chn_run_reverse();
|
||||
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
|
||||
// Should stop tilting and go to REVERSE immediately (no inertia from TILT_FORWARD)
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state());
|
||||
|
||||
// --- Test interrupting during PAUSE step ---
|
||||
relay_chn_stop(); // Stop the reverse run
|
||||
// Wait the channel to be IDLE
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
|
||||
prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); // Prepare channel again
|
||||
relay_chn_tilt_forward();
|
||||
// Should incur inertia timer
|
||||
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state());
|
||||
|
||||
// Interrupt with run_forward while in the PAUSE part of the cycle
|
||||
relay_chn_run_forward();
|
||||
// Should stop tilting and go to FORWARD_PENDING (inertia from TILT_FORWARD)
|
||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD_PENDING, relay_chn_get_state());
|
||||
}
|
||||
6
test_apps/partitions/part_nvs.csv
Normal file
6
test_apps/partitions/part_nvs.csv
Normal file
@@ -0,0 +1,6 @@
|
||||
# ESP-IDF Partition Table
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs,data,nvs,0xa000,24K,,
|
||||
phy_init,data,phy,0x10000,4K,,
|
||||
factory,app,factory,0x20000,1M,,
|
||||
app_data,data,nvs,,8K,,
|
||||
|
1
test_apps/profiles/full_multi
Normal file
1
test_apps/profiles/full_multi
Normal file
@@ -0,0 +1 @@
|
||||
-DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.multi;sdkconfig.defaults.run_limit;sdkconfig.defaults.tilt;sdkconfig.defaults.nvs;sdkconfig.defaults.nvs_custom"
|
||||
1
test_apps/profiles/full_single
Normal file
1
test_apps/profiles/full_single
Normal file
@@ -0,0 +1 @@
|
||||
-DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.run_limit;sdkconfig.defaults.tilt;sdkconfig.defaults.nvs;sdkconfig.defaults.nvs_custom"
|
||||
1
test_apps/profiles/multi
Normal file
1
test_apps/profiles/multi
Normal file
@@ -0,0 +1 @@
|
||||
-DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.multi"
|
||||
1
test_apps/profiles/nvs
Normal file
1
test_apps/profiles/nvs
Normal file
@@ -0,0 +1 @@
|
||||
-DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.nvs"
|
||||
1
test_apps/profiles/nvs_custom
Normal file
1
test_apps/profiles/nvs_custom
Normal file
@@ -0,0 +1 @@
|
||||
-DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.nvs;sdkconfig.defaults.nvs_custom"
|
||||
1
test_apps/profiles/run_limit
Normal file
1
test_apps/profiles/run_limit
Normal file
@@ -0,0 +1 @@
|
||||
-DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.run_limit"
|
||||
1
test_apps/profiles/tilt
Normal file
1
test_apps/profiles/tilt
Normal file
@@ -0,0 +1 @@
|
||||
-DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.tilt"
|
||||
1400
test_apps/sdkconfig
1400
test_apps/sdkconfig
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,9 @@
|
||||
# Disable task WDT for tests
|
||||
CONFIG_ESP_TASK_WDT_INIT=n
|
||||
|
||||
CONFIG_LOG_MAXIMUM_LEVEL_DEBUG=y
|
||||
CONFIG_LOG_MAXIMUM_LEVEL=4
|
||||
|
||||
# Relay Channel Driver Default Configuration for Testing
|
||||
# Keep this as short as possible for tests
|
||||
CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200
|
||||
CONFIG_RELAY_CHN_COUNT=2
|
||||
CONFIG_RELAY_CHN_ENABLE_TILTING=y
|
||||
CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200
|
||||
1
test_apps/sdkconfig.defaults.multi
Normal file
1
test_apps/sdkconfig.defaults.multi
Normal file
@@ -0,0 +1 @@
|
||||
CONFIG_RELAY_CHN_COUNT=8
|
||||
1
test_apps/sdkconfig.defaults.nvs
Normal file
1
test_apps/sdkconfig.defaults.nvs
Normal file
@@ -0,0 +1 @@
|
||||
CONFIG_RELAY_CHN_ENABLE_NVS=y
|
||||
6
test_apps/sdkconfig.defaults.nvs_custom
Normal file
6
test_apps/sdkconfig.defaults.nvs_custom
Normal file
@@ -0,0 +1,6 @@
|
||||
# Partition configuration
|
||||
CONFIG_PARTITION_TABLE_SINGLE_APP=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/part_nvs.csv"
|
||||
|
||||
CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION=y
|
||||
3
test_apps/sdkconfig.defaults.run_limit
Normal file
3
test_apps/sdkconfig.defaults.run_limit
Normal file
@@ -0,0 +1,3 @@
|
||||
CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT=y
|
||||
# Keep this as short as possible for tests
|
||||
CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC=1
|
||||
1
test_apps/sdkconfig.defaults.tilt
Normal file
1
test_apps/sdkconfig.defaults.tilt
Normal file
@@ -0,0 +1 @@
|
||||
CONFIG_RELAY_CHN_ENABLE_TILTING=y
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user