52 Commits

Author SHA1 Message Date
5e92e0477b Merge pull request 'Merge main with the latest changes and its tag into dev' (!40) from main into dev
Reviewed-on: #40
2025-09-13 12:04:40 +03:00
fafc23591a Merge pull request 'release-1.0.0' (!39) from release-1.0.0 into main
Reviewed-on: #39
2025-09-13 11:55:48 +03:00
497f45e336 Fix and optimize tilt test case issues
Tilt tests started to fail after the latest changes. Had to make
some fixes and optimizations so that the test code resets the
tilt controls correctly and aligns with relay_chn's timing requirements.

Fixes #1115
2025-09-13 09:54:12 +03:00
95ca976bc6 Allow changing log level dynamically 2025-09-13 09:46:10 +03:00
300f9a1317 Fix unused variable warning
TAG constant is only used when run limit is enabled. As a result,
the compiler generates an "unused variable" warning for other cases.
Fixed this warning by adding the unused attribute.
2025-09-13 09:45:13 +03:00
5440440c4d Untrack autogenerated sdkconfig.old file 2025-09-13 09:39:24 +03:00
8416187d86 Fix test_apps directory issue
Fixed test_apps directory locating issue by fixing the path to
"project_root/test_apps" and removed find command since it
searches recursively and founds the similar directories in
managed_components directory. Also added current time to the output.
2025-09-12 15:10:37 +03:00
9f45a2310d Update and enrich the manifest file 2025-09-12 10:27:46 +03:00
a1ff54b6e9 Bump versions to 1.0.0 2025-09-12 09:39:16 +03:00
c718a1380f Merge pull request 'feat/1104-add-examples' (!38) from feat/1104-add-examples into dev
Reviewed-on: #38
2025-09-11 16:44:19 +03:00
6caa4f1bd5 Ignore session specific settings files 2025-09-11 16:03:29 +03:00
4edebf206e Add a multi channel example
Added a multi channel example with run limit, tilting and channel selection
features. Refs #1104 and closes #1111.
2025-09-11 15:50:34 +03:00
e30b445b91 Fix channel validity check
Fixed mistaken channel validity check in `tilt_stop` funciton
in multi mode. Refs #1111 and fixes #1114.
2025-09-11 14:21:24 +03:00
9ee974e677 Add a single channel example
Added a single channel example with run limit and tilting
features. Refs #1104 and closes #1105.
2025-09-09 18:15:32 +03:00
31e351a129 Fix tilting opposite direction when running
Fixed TILT_STOP command issuing chain that was causing a running channel
to be stopped when an opposite direction tilting requested. For exeample:
RUN_FORWARD > TILT_REVERSE. Refs #1105 and fixes #1110.
2025-09-09 17:50:02 +03:00
3ce079c2e8 Fix unwanted reverse tilts
Added a helper function to determine if the channel may perform
the requested tilt command. Refs #1105 and fixes #1109.
2025-09-09 17:09:08 +03:00
a5b320c152 Add state to string API function
Added a state to string public API function. There was already
a private function (`relay_chn_state_str`) that provides
this functionality. So this function has been renamed to
`relay_chn_state_to_str` and made publicly available.

Refs #1104, #1105 and closes #1108.
2025-09-09 10:53:56 +03:00
087deb338e Fix STOP command issuing when idle
Fixed almost unconditional STOP command issuing when the
channel is idle. Refs #1104, #1105 and closes #1107.
2025-09-09 09:26:24 +03:00
fbf8b5dfc8 Fix mispelled config parameter
Fixed a mispelled configuration parameter. Refs #1105
2025-09-09 09:14:28 +03:00
a3f83eaaee Merge pull request 'opt/1085-optimization-and-cleanup' (!37) from opt/1085-optimization-and-cleanup into dev
Reviewed-on: #37
2025-09-05 11:05:41 +03:00
61ca2197e1 Fix conditional compilation issues.
Fixed include directory settings and conditional includes in CMakeLists and test_common.c. Refs #1085.
2025-09-04 18:23:05 +03:00
86cc29a33b Fix static variable names
Fixed static variable names according to the ESP-IDF C code formatting guide.

Refs #1085 and fixes #1103
2025-09-04 18:20:51 +03:00
bf5e3a4426 General code and comment cleanup 2025-09-04 16:59:00 +03:00
7244b57061 Add SPDX license headers to multiple source files
Refs #1085 and closes #1099
2025-09-04 16:23:13 +03:00
7bafc4845f Add default tilt sensitivity get function
Added `relay_chn_tilt_get_default_sensitivity` function and test cases.

Refs #1085 and fixes #1101
2025-09-04 16:16:10 +03:00
ad377ebfc8 Fix the operation of unregistering listener
- Add check for empty listener list in find_listener_entry function.
- Reset notification queue when all listeners are removed.
- Change queue operation to send remove_listener message to the front of the queue.

Refs #1085, #1096 and fixes #1102
2025-09-04 15:46:57 +03:00
a1a54e2ca0 Stop tracking auto generated sdkconfig file 2025-09-04 15:44:26 +03:00
639533cbb6 Refactor and improve unit tests
- Refactored and improved NVS tests to accomodate latest changes in NVS module. See #1098
- Improved channel reset function that is called from `tearDown` to minimize public API calls that trigger a chain of internal calls that involve NVS and timer operations.
- Fixed test case bugs that make some test cases to fail.

Refs #1096, #1098
2025-09-04 15:06:17 +03:00
2c9ee40ff4 Enhance NVS module with a dedicated background task
- Implemented a dedicated background task to decouple long-running code from the main application task.
- Improved the NVS commit code logic, especially for batch writes to minimize flash wear.
- Updated NVS functions to support asynchronous writes and synchronous reads.
- Added default value parameters to `get` functions for better usability.
- Improved error handling and logging in NVS operations.
- Refactored related code in multiple source files to accommodate these changes.

Refs #1085, #1096 and closes #1098
2025-09-04 14:50:38 +03:00
0122ef0803 Fix NVS key bug and optimize for single mode
- Fixed a critical NVS key generation bug that would cause overwriting the values for all channels.
- Optimized the code for single channel mode since no formatting required.
- Improved multi-channel test coverage to cover that each value for each channel stored correctly.

Refs #1096, #1098 and fixes #1100
2025-09-02 17:02:05 +03:00
5e8e5a4cab Add notification system for relay channel state changes
- Introduced a new notification module to handle state change listeners.
- Added functions to register and unregister listeners for relay channel state changes.
- Implemented a queue-based system to manage notifications and listener callbacks.
- Updated core relay channel logic to utilize the new notification system.
- Removed old listener management code from relay channel core.
- Refactored the former listener tests to notify tests and added tests for the notification system, including handling of multiple listeners and queue overflow scenarios.
- Updated CMakeLists.txt to include new source files and headers for the notification module.
- Revised README.md to include warnings about callback execution context and performance considerations.

Refs #1096, #1085 and closes #1097
2025-09-02 15:46:48 +03:00
d2b38a5b4e Ignore autogenerated sdkconfig file 2025-09-02 15:31:53 +03:00
db55c0b7e4 Fix single channel tests in multi mode tests
Some test cases that were testing for only one channel in multi-channel mode are fixed to test all available channels. Fixes #1094
2025-08-29 17:41:42 +03:00
dea9f1e986 Add missing test tags 2025-08-29 14:10:18 +03:00
a2e8e3c120 Implement relay_chn_stop_prv function
Implemented `relay_chn_stop_prv` to streamline stop command handling
and avoid unnecessary code execution. Fixes #1095.
2025-08-28 17:43:14 +03:00
a6d38327b7 Add test cases for *all and *all_with functions
Added test cases for the recently implemented `*all` and `*all_with`
functions. Closes #1089.
2025-08-28 09:34:19 +03:00
0cd6b4e263 Refactor and enhance reset_channels_to_idle_state
- Refactor reset_channels_to_idle_state to reset_channels_to_defaults
and enhance functionality with direction reset logic. This is because
some tilt test cases were failing due to modified run limit values in some
of the previous core test cases. See #1089-3.
- A relay channel listener has been added to diagnose channel
states during tests.

Refs #1089
2025-08-28 09:30:43 +03:00
ec1b25d489 Add missing test cases
Some missing test cases for the public API have been added.

Closes #1090
2025-08-27 17:26:32 +03:00
4eb1bb03a0 Fix function call
Refs #1090
2025-08-27 17:16:06 +03:00
7d597f3725 Fix STOP command does not interrupt
An if statement has been added to handle the STOP command properly
when the `previous_state` is one of the `*PENDING` states.

Fixes #1093
2025-08-27 17:02:02 +03:00
71b632737e Fix esp_timer_start error handling
A helper function added for each module so that each module
handles errors by itself.

Fixes #1092
2025-08-27 15:56:24 +03:00
374647732c Fix possible integer underflow bug
Fixed a possible integer underflow bug that may cause the timer
to be set for an unexpectedly long duration.

Fixes #1091
2025-08-27 14:06:37 +03:00
ae33204a87 Optimize internal stop calls for output errors 2025-08-27 11:03:55 +03:00
79a66c19d7 Rename time_sec to limit_sec in relay channel functions for clarity
Fixes #1087
2025-08-26 18:08:05 +03:00
9a6b8c9f80 Add upper boundary checks for tilt sensitivity settings
The tilt sensitivity values were passed to the NVS module without
the boundaries being checked, even though the maximum
percent value is 100. This commit fixes this issue.
Also test cases are added to cover the upper boundary checks
for the tilt sensitivity settings.

Fixes #1086
2025-08-26 17:43:19 +03:00
0b75df35d1 Use build profiles instead of providing sdkconfig.defaults manually
ESP-IDF provides a more efficient way of handling and combining
multi-configurations for a project than the way currently used:
Build profiles.

So I switched to this method instead of providing the sdkconfig.defaults.*
files manually. This reduced the number of sdkconfig.defaults.* files
as well as the configuration parameters defined in them.

Refs #1085
2025-08-26 17:03:36 +03:00
da953846c9 Refactor function brackets
Refactor the brackets of the test case functions to align with ESP-IDF style.

Refs #1085
2025-08-26 15:19:09 +03:00
15d1673e77 Refactor and improve for batch operations
Refactor tilt test functions to use batch operations for all channels and improve state checks.

Refs #1085
2025-08-26 15:03:16 +03:00
329812aecc Enhance relay command handling
- `TILT_STOP` command is prioritized in `relay_chn_tilt_issue_cmd()` because it should override any other tilt commands.
- The debug logs are cleaned up.

Fixes #1088
Refs #1085
2025-08-26 13:37:48 +03:00
54c8dc26fc Add .ESP-IDF vscode configuration files 2025-08-26 12:10:02 +03:00
396a02b5ae Cleanup and replace constants
- Delete unused declaration of `g_is_component_initialized`.
- Replace the following constants with approprite config options:
  + `relay_chn_count` > `CONFIG_RELAY_CHN_COUNT`
  + `opposite_inerta_ms` > `CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS`
- Replace the definition of the `test_delay_margin_ms` constant with  `#define TEST_DELAY_MARGIN_MS 50` for preprocessor calculations.
2025-08-26 09:23:06 +03:00
3831384169 Implement specific *all functions
Specific `_*all` and `_*all_with` functions were implemented to make
the API more concise and clean, and to replace the use of
`RELAY_CHN_ID_ALL`, which caused confusion within the API.

Refs #1085
2025-08-26 08:42:49 +03:00
91 changed files with 4160 additions and 4323 deletions

8
.gitignore vendored
View File

@@ -56,6 +56,11 @@ tools/test_apps/**/build_*_*/
tools/test_apps/**/sdkconfig tools/test_apps/**/sdkconfig
tools/test_apps/**/sdkconfig.old tools/test_apps/**/sdkconfig.old
# autogenerated config files
sdkconfig
test_apps/sdkconfig
test_apps/sdkconfig.old
TEST_LOGS/ TEST_LOGS/
build_summary_*.xml build_summary_*.xml
@@ -69,6 +74,7 @@ test_multi_heap_host
# VS Code Settings # VS Code Settings
# .vscode/ # .vscode/
settings.json
# VIM files # VIM files
*.swp *.swp
@@ -108,4 +114,4 @@ XUNIT_RESULT*.xml
.clangd .clangd
# Vale # Vale
.vale/styles/* .vale/styles/*

View File

@@ -1,3 +0,0 @@
{
"C_Cpp.intelliSenseEngine": "default"
}

View File

@@ -3,7 +3,8 @@ set(priv_include_dirs "private_include")
set(srcs "src/relay_chn_core.c" set(srcs "src/relay_chn_core.c"
"src/relay_chn_output.c" "src/relay_chn_output.c"
"src/relay_chn_run_info.c") "src/relay_chn_run_info.c"
"src/relay_chn_notify.c")
if(CONFIG_RELAY_CHN_ENABLE_TILTING) if(CONFIG_RELAY_CHN_ENABLE_TILTING)
list(APPEND srcs "src/relay_chn_tilt.c") list(APPEND srcs "src/relay_chn_tilt.c")
@@ -15,7 +16,7 @@ else()
list(APPEND srcs "src/relay_chn_ctl_single.c") list(APPEND srcs "src/relay_chn_ctl_single.c")
endif() endif()
if(CONFIG_RELAY_CHN_NVS) if(CONFIG_RELAY_CHN_ENABLE_NVS)
list(APPEND srcs "src/relay_chn_nvs.c") list(APPEND srcs "src/relay_chn_nvs.c")
endif() endif()

10
Kconfig
View File

@@ -9,14 +9,14 @@ menu "Relay Channel Driver Configuration"
starting the output. This is useful for the motors or some other starting the output. This is useful for the motors or some other
mechanical actuators to allow them to stop and settle before mechanical actuators to allow them to stop and settle before
changing the direction. changing the direction.
config RELAY_CHN_COUNT config RELAY_CHN_COUNT
int "Number of relay channels" int "Number of relay channels"
range 1 8 range 1 8
default 1 default 1
help help
Number of relay channels between 1 and 8. Number of relay channels between 1 and 8.
config RELAY_CHN_ENABLE_RUN_LIMIT config RELAY_CHN_ENABLE_RUN_LIMIT
bool "Enable run limit for channels" bool "Enable run limit for channels"
default n default n
@@ -32,7 +32,7 @@ menu "Relay Channel Driver Configuration"
at a time. Tilting is specifically designed for controlling some at a time. Tilting is specifically designed for controlling some
types of curtains that need to be adjusted to let enter specific types of curtains that need to be adjusted to let enter specific
amount of day light. amount of day light.
config RELAY_CHN_ENABLE_NVS config RELAY_CHN_ENABLE_NVS
bool "Enable persistent NVS storage for relay channel" bool "Enable persistent NVS storage for relay channel"
default n default n
@@ -58,7 +58,7 @@ menu "Relay Channel NVS Storage Configuration"
If enabled, a custom NVS partition will be used for storing If enabled, a custom NVS partition will be used for storing
relay channel configuration. If disabled, the default NVS relay channel configuration. If disabled, the default NVS
partition will be used. partition will be used.
config RELAY_CHN_NVS_CUSTOM_PARTITION_NAME config RELAY_CHN_NVS_CUSTOM_PARTITION_NAME
string "Custom NVS partition name" string "Custom NVS partition name"
depends on RELAY_CHN_NVS_CUSTOM_PARTITION depends on RELAY_CHN_NVS_CUSTOM_PARTITION
@@ -92,5 +92,5 @@ menu "Relay Channel Run Limit Configuration"
default 60 default 60
help help
Default run limit in seconds for channels. Default run limit in seconds for channels.
endmenu endmenu

View File

@@ -21,8 +21,8 @@ Each relay channel consists of 2 output relays controlled by 2 GPIO pins. The co
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. 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. 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. 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: Another optional feature is NVS storage, which saves the configuration permanently across reboots of the device. These configurations are:
@@ -31,6 +31,16 @@ Another optional feature is NVS storage, which saves the configuration permanent
- Tilt sensitivity - Tilt sensitivity
- Last tilt position - 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.
## Configuration ## Configuration
Configure the component through menuconfig under "Relay Channel Driver Configuration": Configure the component through menuconfig under "Relay Channel Driver Configuration":
@@ -120,7 +130,7 @@ dependencies:
# Add as a custom component from git repository # Add as a custom component from git repository
relay_chn: relay_chn:
git: https://git.kozmotronik.com.tr/KozmotronikTech/relay_chn.git git: https://git.kozmotronik.com.tr/KozmotronikTech/relay_chn.git
version: '>=0.5.0' version: '>=1.0.0'
``` ```
## Usage ## Usage
@@ -135,7 +145,7 @@ Depending on the mode, the component will be built selectively, so the signature
```c ```c
relay_chn_run_forward(); // No channel ID parameter for single channel mode relay_chn_run_forward(); // No channel ID parameter for single channel mode
// or // or
relay_chn_run_forward(2); // Channel ID parameters will be needed in multi channel mode relay_chn_run_forward(2); // Channel ID parameters will be needed in multi channel mode
``` ```
See the examples for further reference See the examples for further reference
@@ -187,9 +197,9 @@ relay_chn_run_reverse(1);
// Run all channels reverse // Run all channels reverse
relay_chn_run_reverse_all(); relay_chn_run_reverse_all();
// Stop channel #1 // Stop channel #1
relay_chn_stop(1); relay_chn_stop(1);
// Stop all channels // Stop all channels
relay_chn_stop_all(); relay_chn_stop_all();
// Flip direction of channel #0 // Flip direction of channel #0
@@ -200,6 +210,9 @@ relay_chn_flip_direction_all();
### 3. Monitor channel state ### 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: For single mode:
```c ```c
@@ -332,7 +345,7 @@ relay_chn_tilt_set_sensitivity_all_with(90);
// Assuming the CONFIG_RELAY_CHN_COUNT is 4 // Assuming the CONFIG_RELAY_CHN_COUNT is 4
uint8_t sensitivities[CONFIG_RELAY_CHN_COUNT] = { 90, 85, 80, 75 }; uint8_t sensitivities[CONFIG_RELAY_CHN_COUNT] = { 90, 85, 80, 75 };
relay_chn_set_sensitivity_all(sensitivity); // Set all channels according to the array relay_chn_tilt_set_sensitivity_all(sensitivity); // Set all channels according to the array
// Get tilt sensitivity for channel #0 // Get tilt sensitivity for channel #0
uint8_t sensitivity = relay_chn_tilt_get_sensitivity(0); uint8_t sensitivity = relay_chn_tilt_get_sensitivity(0);

6
examples/relay_chn_multi/.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
build/
sdkconfig
sdkconfig.old
# Exclude auto-populated settings file
settings.json

View 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
}

View 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"
}
]
}

View 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)

View 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.
![Hardware Schematic](example_schematic.png)
### 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
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "relay_chn_multi_main.c"
PRIV_REQUIRES button led_indicator relay_chn)

View 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

View 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: ../../../

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

View 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
View File

@@ -0,0 +1,6 @@
build/
sdkconfig
sdkconfig.old
# Exclude auto-populated settings file
settings.json

View 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
}

View 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"
}
]
}

View 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)

View 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
![Hardware Schematic](example_schematic.png)
### 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
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "relay_chn_single_main.c"
PRIV_REQUIRES button led_indicator relay_chn)

View 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

View 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: ../../../

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

View 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

View File

@@ -1,6 +1,12 @@
name: relay_chn version: "1.0.0"
version: "0.5.0" description: "Relay channel driver for bipolar motors."
description: "Custom component for relay channel control"
license: "MIT" license: "MIT"
url: "https://git.kozmotronik.com.tr/KozmotronikTech/relay_chn" 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

View File

@@ -6,7 +6,7 @@
* One relay channel consists of 2 output relays, hence 2 GPIO pins are required for each relay channel. * 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. * 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. * 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. * 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 STOP command overrides any other command and clears the pending command if any.
*/ */
@@ -45,7 +45,7 @@ void relay_chn_destroy(void);
/** /**
* @brief Register a channel state change listener. * @brief Register a channel state change listener.
* *
* @param listener A function that implements relay_chn_state_listener_t interface. * @param listener A function that implements relay_chn_state_listener_t interface.
* *
* @return * @return
@@ -58,11 +58,31 @@ esp_err_t relay_chn_register_listener(relay_chn_state_listener_t listener);
/** /**
* @brief Unregister a channel state change listener. * @brief Unregister a channel state change listener.
* *
* @param listener A function that implements relay_chn_state_listener_t interface. * @param listener A function that implements relay_chn_state_listener_t interface.
*/ */
void relay_chn_unregister_listener(relay_chn_state_listener_t listener); 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 #if CONFIG_RELAY_CHN_COUNT > 1
/** /**
* @brief Get the state of the specified relay channel. * @brief Get the state of the specified relay channel.
@@ -139,8 +159,8 @@ void relay_chn_ctl_run_reverse_all(void);
/** /**
* @brief Stops the relay channel specified by the channel ID. * @brief Stops the relay channel specified by the channel ID.
* *
* This function stops the operation of the relay channel identified by the * 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 * provided channel ID. It is typically used to turn off or disable the relay
* channel. * channel.
* *
* @param chn_id The ID of the relay channel to stop. * @param chn_id The ID of the relay channel to stop.
@@ -150,7 +170,7 @@ void relay_chn_stop(uint8_t chn_id);
/** /**
* @brief Commands all configured relay channels to stop. * @brief Commands all configured relay channels to stop.
* *
* This function iterates through all configured relay channels and issues a command * This function iterates through all configured relay channels and issues a command
* to each to stop any ongoing movement. * to each to stop any ongoing movement.
*/ */
void relay_chn_ctl_stop_all(void); void relay_chn_ctl_stop_all(void);
@@ -169,7 +189,7 @@ void relay_chn_flip_direction(uint8_t chn_id);
/** /**
* @brief Flips the logical direction of all configured relay channels. * @brief Flips the logical direction of all configured relay channels.
* *
* This function iterates through all configured relay channels and swaps the * This function iterates through all configured relay channels and swaps the
* physical GPIO pins assigned to the forward and reverse directions for each. * physical GPIO pins assigned to the forward and reverse directions for each.
*/ */
void relay_chn_ctl_flip_direction_all(void); void relay_chn_ctl_flip_direction_all(void);
@@ -203,7 +223,7 @@ esp_err_t relay_chn_ctl_get_direction_all(relay_chn_direction_t *directions);
* @brief Get the run limit for the specified channel * @brief Get the run limit for the specified channel
* *
* @param chn_id The ID of the relay channel to query. * @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. * @return The run limit value for the relevant channel if the channel ID is valid.
* 0 if the channel ID is invalid. * 0 if the channel ID is invalid.
*/ */
@@ -223,18 +243,18 @@ esp_err_t relay_chn_ctl_get_run_limit_all(uint16_t *limits_sec);
/** /**
* @brief Set the run limit for the specified channel * @brief Set the run limit for the specified channel
* *
* Sets the time limit in seconds for the specified channel. It will not proceed * Sets the time limit in seconds for the specified channel. It will not proceed
* if the channel ID is invalid. * if the channel ID is invalid.
* If the time_sec value is lesser than the CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC, * 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. * the value will be set to CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC.
* If the time_sec value is greater than the CONFIG_RELAY_CHN_RUN_LIMIT_MAX_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. * 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 chn_id The ID of the relay channel to query.
* @param time_sec The run limit time in seconds. * @param limit_sec The run limit time in seconds.
*/ */
void relay_chn_set_run_limit(uint8_t chn_id, uint16_t time_sec); void relay_chn_set_run_limit(uint8_t chn_id, uint16_t limit_sec);
/** /**
* @brief Sets the run limits for all configured relay channels. * @brief Sets the run limits for all configured relay channels.
@@ -346,8 +366,8 @@ void relay_chn_tilt_set_sensitivity(uint8_t chn_id, uint8_t sensitivity);
* The new sensitivities are persisted in NVS if enabled. * The new sensitivities are persisted in NVS if enabled.
* *
* @param sensitivities Pointer to an array containing the desired tilt sensitivities. * @param sensitivities Pointer to an array containing the desired tilt sensitivities.
* *
* @return * @return
* - ESP_OK: Success * - ESP_OK: Success
* - ESP_ERR_INVALID_ARG: When sensitivities parameter is NULL * - ESP_ERR_INVALID_ARG: When sensitivities parameter is NULL
*/ */
@@ -447,7 +467,7 @@ void relay_chn_flip_direction(void);
* @brief Get the direction of the relay channel. * @brief Get the direction of the relay channel.
* *
* This function retrieves the direction configuration of a relay channel. * This function retrieves the direction configuration of a relay channel.
* *
* @return The direction of the relay channel as a value of type * @return The direction of the relay channel as a value of type
* relay_chn_direction_t. * relay_chn_direction_t.
*/ */
@@ -456,24 +476,24 @@ relay_chn_direction_t relay_chn_get_direction(void);
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
/** /**
* @brief Get the run limit for the channel * @brief Get the run limit for the channel
* *
* @return The run limit value for the channel. * @return The run limit value for the channel.
*/ */
uint16_t relay_chn_get_run_limit(void); uint16_t relay_chn_get_run_limit(void);
/** /**
* @brief Set the run limit for the channel * @brief Set the run limit for the channel
* *
* Sets the time limit in seconds for the channel. It will not proceed * Sets the time limit in seconds for the channel. It will not proceed
* if the channel ID is invalid. * if the channel ID is invalid.
* If the time_sec value is lesser than the CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC, * 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. * the value will be set to CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC.
* If the time_sec value is greater than the CONFIG_RELAY_CHN_RUN_LIMIT_MAX_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. * the value will be set to CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC.
* *
* @param time_sec The run limit time in seconds. * @param limit_sec The run limit time in seconds.
*/ */
void relay_chn_set_run_limit(uint16_t time_sec); void relay_chn_set_run_limit(uint16_t limit_sec);
#endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 #endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1

View File

@@ -2,7 +2,7 @@
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech * SPDX-FileCopyrightText: 2025 Kozmotronik Tech
* *
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
* *
* An adapter header to expose the appropriate API functions to the public API * 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. * depending on the CONFIG_RELAY_CHN_COUNT value which determines single or multi mode.
*/ */
@@ -13,6 +13,36 @@
extern "C" { extern "C" {
#endif #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 #if CONFIG_RELAY_CHN_COUNT > 1
/** /**
* @brief Get the current state of a relay channel. * @brief Get the current state of a relay channel.
@@ -30,7 +60,7 @@ extern relay_chn_state_t relay_chn_ctl_get_state(uint8_t chn_id);
* to hold `CONFIG_RELAY_CHN_COUNT` elements. * to hold `CONFIG_RELAY_CHN_COUNT` elements.
* *
* @param states Pointer to an array where the states will be stored. * @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. * @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); extern esp_err_t relay_chn_ctl_get_state_all(relay_chn_state_t *states);
@@ -97,7 +127,7 @@ extern void relay_chn_ctl_flip_direction(uint8_t chn_id);
/** /**
* @brief Flips the logical direction of all configured relay channels. * @brief Flips the logical direction of all configured relay channels.
* *
* This function iterates through all configured relay channels and swaps the * This function iterates through all configured relay channels and swaps the
* physical GPIO pins assigned to the forward and reverse directions for each. * physical GPIO pins assigned to the forward and reverse directions for each.
*/ */
extern void relay_chn_ctl_flip_direction_all(void); extern void relay_chn_ctl_flip_direction_all(void);
@@ -192,7 +222,7 @@ static inline esp_err_t relay_chn_get_direction_all(relay_chn_direction_t *direc
* @brief Get the run limit for the specified channel * @brief Get the run limit for the specified channel
* *
* @param chn_id The ID of the relay channel to query. * @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. * @return The run limit value for the relevant channel if the channel ID is valid.
* 0 if the channel ID is invalid. * 0 if the channel ID is invalid.
*/ */
@@ -214,9 +244,9 @@ esp_err_t relay_chn_ctl_get_run_limit_all(uint16_t *limits_sec);
* @brief Set the run limit for the specified channel * @brief Set the run limit for the specified channel
* *
* @param chn_id The ID of the relay channel to query. * @param chn_id The ID of the relay channel to query.
* @param time_sec The run limit time in seconds. * @param limit_sec The run limit time in seconds.
*/ */
extern void relay_chn_ctl_set_run_limit(uint8_t chn_id, uint16_t time_sec); 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. * @brief Sets the run limits for all configured relay channels.
@@ -253,9 +283,9 @@ 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); return relay_chn_ctl_get_run_limit_all(limits_sec);
} }
static inline void relay_chn_set_run_limit(uint8_t chn_id, uint16_t time_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, time_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) static inline esp_err_t relay_chn_set_run_limit_all(uint16_t *limits_sec)
@@ -350,7 +380,7 @@ static inline relay_chn_direction_t relay_chn_get_direction(void)
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
/** /**
* @brief Get the run limit for the channel * @brief Get the run limit for the channel
* *
* @return The run limit value for the channel. * @return The run limit value for the channel.
*/ */
extern uint16_t relay_chn_ctl_get_run_limit(void); extern uint16_t relay_chn_ctl_get_run_limit(void);
@@ -358,18 +388,18 @@ extern uint16_t relay_chn_ctl_get_run_limit(void);
/** /**
* @brief Set the run limit for the channel * @brief Set the run limit for the channel
* *
* @param time_sec The run limit time in seconds. * @param limit_sec The run limit time in seconds.
*/ */
extern void relay_chn_ctl_set_run_limit(uint16_t time_sec); extern void relay_chn_ctl_set_run_limit(uint16_t limit_sec);
static inline uint16_t relay_chn_get_run_limit(void) static inline uint16_t relay_chn_get_run_limit(void)
{ {
return relay_chn_ctl_get_run_limit(); return relay_chn_ctl_get_run_limit();
} }
static inline void relay_chn_set_run_limit(uint16_t time_sec) static inline void relay_chn_set_run_limit(uint16_t limit_sec)
{ {
relay_chn_ctl_set_run_limit(time_sec); relay_chn_ctl_set_run_limit(limit_sec);
} }
#endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 #endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1

View File

@@ -18,10 +18,10 @@ extern "C" {
/** /**
* @brief Initializes the relay channel timer. * @brief Initializes the relay channel timer.
* *
* This function creates a timer for the relay channel to handle direction change inertia. * This function creates a timer for the relay channel to handle direction change inertia.
* Required by *_ctl_* module. * Required by *_ctl_* module.
* *
* @param chn_ctl Pointer to the relay channel control structure. * @param chn_ctl Pointer to the relay channel control structure.
* @return esp_err_t ESP_OK on success, or an error code on failure. * @return esp_err_t ESP_OK on success, or an error code on failure.
*/ */
@@ -30,12 +30,12 @@ esp_err_t relay_chn_init_timer(relay_chn_ctl_t *chn_ctl);
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
/** /**
* @brief Initializes the relay channel run limit timer. * @brief Initializes the relay channel run limit timer.
* *
* This function creates a timer for the relay channel to handle run time limit. * This function creates a timer for the relay channel to handle run time limit.
* Required by *_ctl_* module. * Required by *_ctl_* module.
* *
* @param chn_ctl Pointer to the relay channel control structure. * @param chn_ctl Pointer to the relay channel control structure.
* *
* @return esp_err_t ESP_OK on success, or an error code on failure. * @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); esp_err_t relay_chn_init_run_limit_timer(relay_chn_ctl_t *chn_ctl);
@@ -43,10 +43,10 @@ esp_err_t relay_chn_init_run_limit_timer(relay_chn_ctl_t *chn_ctl);
/** /**
* @brief Issues a command to the relay channel. * @brief Issues a command to the relay channel.
* *
* Evaluates the current state of the relay channel and issues the command accordingly. * Evaluates the current state of the relay channel and issues the command accordingly.
* Required by *_core, *_ctl_* and *_tilt modules. * Required by *_core, *_ctl_* and *_tilt modules.
* *
* @param chn_ctl Pointer to the relay channel control structure. * @param chn_ctl Pointer to the relay channel control structure.
* @param cmd The command to issue. * @param cmd The command to issue.
*/ */
@@ -54,7 +54,7 @@ void relay_chn_issue_cmd(relay_chn_ctl_t* chn_ctl, relay_chn_cmd_t cmd);
/** /**
* @brief Dispatches a relay channel command. * @brief Dispatches a relay channel command.
* *
* @param chn_ctl Pointer to the relay channel control structure. * @param chn_ctl Pointer to the relay channel control structure.
* @param cmd The command to dispatch. * @param cmd The command to dispatch.
*/ */
@@ -62,7 +62,7 @@ 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. * @brief Returns the string representation of a relay channel command.
* *
* @param cmd The relay channel command. * @param cmd The relay channel command.
* @return char* The string representation of the command. * @return char* The string representation of the command.
*/ */
@@ -70,11 +70,11 @@ char *relay_chn_cmd_str(relay_chn_cmd_t cmd);
/** /**
* @brief Starts the ESP timer once with the specified time in milliseconds. * @brief Starts the ESP timer once with the specified time in milliseconds.
* *
* Starts the ESP timer to run once after the specified time. * 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. * If the timer is already running, it stops it first and then starts it again.
* Required by *_ctl_* and *_tilt modules. * Required by *_ctl_* and *_tilt modules.
* *
* @param esp_timer The ESP timer handle. * @param esp_timer The ESP timer handle.
* @param time_ms The time in milliseconds to wait before the timer expires. * @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. * @return esp_err_t ESP_OK on success, or an error code on failure.
@@ -83,28 +83,20 @@ esp_err_t relay_chn_start_esp_timer_once(esp_timer_handle_t esp_timer, uint32_t
/** /**
* @brief Updates the state of the relay channel and notifies listeners. * @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 * This function updates the state of the relay channel and notifies all registered listeners
* about the state change. * about the state change.
* Required by *_ctl_* and *_tilt modules. * Required by *_ctl_* and *_tilt modules.
* *
* @param chn_ctl Pointer to the relay channel control structure. * @param chn_ctl Pointer to the relay channel control structure.
* @param new_state The new state to set for the relay channel. * @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); void relay_chn_update_state(relay_chn_ctl_t *chn_ctl, relay_chn_state_t new_state);
/**
* @brief Return the text presentation of an state.
*
* @param state A state with type of relay_chn_state_t.
* @return char* The text presentation of the state. "UNKNOWN" if the state is not known.
*/
char *relay_chn_state_str(relay_chn_state_t state);
#if CONFIG_RELAY_CHN_COUNT > 1 #if CONFIG_RELAY_CHN_COUNT > 1
/** /**
* @brief Check if the provided channel ID is valid. * @brief Check if the provided channel ID is valid.
* *
* @param chn_id Channel ID to check. * @param chn_id Channel ID to check.
* @return true Channel ID is valid. * @return true Channel ID is valid.
* @return false Channel ID is invalid. * @return false Channel ID is invalid.

View File

@@ -16,17 +16,17 @@ extern "C" {
/** /**
* @brief Initialize the relay channel control. * @brief Initialize the relay channel control.
* *
* @param output Pointer to the output object(s). * @param output Pointer to the output object(s).
* @param run_info Pointer to the runtime information 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. * @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); 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. * @brief Deinitialize the relay channel control.
* *
* This function cleans up resources used by the relay channel control. * This function cleans up resources used by the relay channel control.
*/ */
void relay_chn_ctl_deinit(void); void relay_chn_ctl_deinit(void);
@@ -34,16 +34,16 @@ void relay_chn_ctl_deinit(void);
#if CONFIG_RELAY_CHN_COUNT > 1 #if CONFIG_RELAY_CHN_COUNT > 1
/** /**
* @brief Get the control structure for a specific relay channel. * @brief Get the control structure for a specific relay channel.
* *
* @param chn_id The ID of the relay channel to retrieve. * @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. * @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); relay_chn_ctl_t *relay_chn_ctl_get(uint8_t chn_id);
/** /**
* @brief Get the control structures for all relay channels. * @brief Get the control structures for all relay channels.
* *
* @return relay_chn_ctl_t* Pointer to the array of control structures for all 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); relay_chn_ctl_t *relay_chn_ctl_get_all(void);

View 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

View File

@@ -18,9 +18,9 @@ extern "C" {
/** /**
* @brief Initialize NVS storage for relay channels. * @brief Initialize NVS storage for relay channels.
* *
* @attention Before calling this function, make sure the NVS flash is initialised * @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 * using either the nvs_flash_init() function for the default NVS partition or the
* nvs_flash_init_partition() function for a custom partition. * nvs_flash_init_partition() function for a custom partition.
* *
* @return ESP_OK on success, error code otherwise. * @return ESP_OK on success, error code otherwise.
@@ -32,6 +32,10 @@ esp_err_t relay_chn_nvs_init(void);
* *
* @param[in] ch Channel number. * @param[in] ch Channel number.
* @param[in] direction Direction to store. * @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. * @return ESP_OK on success, error code otherwise.
*/ */
esp_err_t relay_chn_nvs_set_direction(uint8_t ch, relay_chn_direction_t direction); esp_err_t relay_chn_nvs_set_direction(uint8_t ch, relay_chn_direction_t direction);
@@ -41,28 +45,34 @@ esp_err_t relay_chn_nvs_set_direction(uint8_t ch, relay_chn_direction_t directio
* *
* @param[in] ch Channel number. * @param[in] ch Channel number.
* @param[out] direction Pointer to store retrieved direction. * @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. * @return ESP_OK on success, error code otherwise.
*/ */
esp_err_t relay_chn_nvs_get_direction(uint8_t ch, relay_chn_direction_t *direction); 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 #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
/** /**
* @brief Store relay channel run limit in NVS. * @brief Store relay channel run limit in NVS.
* *
* @param[in] ch Channel number. * @param[in] ch Channel number.
* @param[in] direction Run limit value to store. * @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. * @return ESP_OK on success, error code otherwise.
*/ */
esp_err_t relay_chn_nvs_set_run_limit(uint8_t ch, uint16_t time_sec); esp_err_t relay_chn_nvs_set_run_limit(uint8_t ch, uint16_t limit_sec);
/** /**
* @brief Retrieve relay channel run limit from NVS. * @brief Retrieve relay channel run limit from NVS.
* *
* @param[in] ch Channel number. * @param[in] ch Channel number.
* @param[out] direction Pointer to store retrieved run limit value. * @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. * @return ESP_OK on success, error code otherwise.
*/ */
esp_err_t relay_chn_nvs_get_run_limit(uint8_t ch, uint16_t *time_sec); 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 #endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1
#if CONFIG_RELAY_CHN_ENABLE_TILTING #if CONFIG_RELAY_CHN_ENABLE_TILTING
@@ -71,6 +81,10 @@ esp_err_t relay_chn_nvs_get_run_limit(uint8_t ch, uint16_t *time_sec);
* *
* @param[in] ch Channel number. * @param[in] ch Channel number.
* @param[in] sensitivity Sensitivity value to store. * @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. * @return ESP_OK on success, error code otherwise.
*/ */
esp_err_t relay_chn_nvs_set_tilt_sensitivity(uint8_t ch, uint8_t sensitivity); esp_err_t relay_chn_nvs_set_tilt_sensitivity(uint8_t ch, uint8_t sensitivity);
@@ -80,15 +94,20 @@ esp_err_t relay_chn_nvs_set_tilt_sensitivity(uint8_t ch, uint8_t sensitivity);
* *
* @param[in] ch Channel number. * @param[in] ch Channel number.
* @param[out] sensitivity Pointer to store retrieved sensitivity. * @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. * @return ESP_OK on success, error code otherwise.
*/ */
esp_err_t relay_chn_nvs_get_tilt_sensitivity(uint8_t ch, uint8_t *sensitivity); 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. * @brief Store tilt counters in NVS.
* *
* @param[in] ch Channel number. * @param[in] ch Channel number.
* @param[in] tilt_count Tilt count value. * @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. * @return ESP_OK on success, error code otherwise.
*/ */
esp_err_t relay_chn_nvs_set_tilt_count(uint8_t ch, uint16_t tilt_count); esp_err_t relay_chn_nvs_set_tilt_count(uint8_t ch, uint16_t tilt_count);
@@ -98,15 +117,17 @@ esp_err_t relay_chn_nvs_set_tilt_count(uint8_t ch, uint16_t tilt_count);
* *
* @param[in] ch Channel number. * @param[in] ch Channel number.
* @param[out] tilt_count Pointer to store tilt count. * @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. * @return ESP_OK on success, error code otherwise.
*/ */
esp_err_t relay_chn_nvs_get_tilt_count(uint8_t ch, uint16_t *tilt_count); 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 #endif // CONFIG_RELAY_CHN_ENABLE_TILTING
/** /**
* @brief Erase all keys in the NVS namespace. * @brief Erase all keys in the NVS namespace.
* *
* This function will erase all key-value pairs in the NVS namespace used by relay channels. * 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. * @return ESP_OK on success, error code otherwise.
*/ */
@@ -114,10 +135,8 @@ esp_err_t relay_chn_nvs_erase_all(void);
/** /**
* @brief Deinitialize NVS storage for relay channels. * @brief Deinitialize NVS storage for relay channels.
*
* @return ESP_OK on success, error code otherwise.
*/ */
esp_err_t relay_chn_nvs_deinit(void); void relay_chn_nvs_deinit(void);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@@ -24,7 +24,7 @@ extern "C" {
* *
* @param[in] gpio_map Array of GPIO pin numbers for each relay channel. * @param[in] gpio_map Array of GPIO pin numbers for each relay channel.
* @param[in] gpio_count Number of GPIO pins (relay channels). * @param[in] gpio_count Number of GPIO pins (relay channels).
* *
* @return ESP_OK on success, error code otherwise. * @return ESP_OK on success, error code otherwise.
*/ */
esp_err_t relay_chn_output_init(const uint8_t* gpio_map, uint8_t gpio_count); esp_err_t relay_chn_output_init(const uint8_t* gpio_map, uint8_t gpio_count);
@@ -41,7 +41,7 @@ void relay_chn_output_deinit(void);
* @brief Get the relay channel output object for a specific channel. * @brief Get the relay channel output object for a specific channel.
* *
* @param[in] chn_id Channel ID. * @param[in] chn_id Channel ID.
* *
* @return Pointer to relay channel output object, or NULL if invalid. * @return Pointer to relay channel output object, or NULL if invalid.
*/ */
relay_chn_output_t *relay_chn_output_get(uint8_t chn_id); relay_chn_output_t *relay_chn_output_get(uint8_t chn_id);
@@ -67,7 +67,7 @@ relay_chn_output_t *relay_chn_output_get(void);
* Sets the relay channel to the stop state. * Sets the relay channel to the stop state.
* *
* @param[in] output Pointer to relay channel output object. * @param[in] output Pointer to relay channel output object.
* *
* @return ESP_OK on success, error code otherwise. * @return ESP_OK on success, error code otherwise.
*/ */
esp_err_t relay_chn_output_stop(relay_chn_output_t *output); esp_err_t relay_chn_output_stop(relay_chn_output_t *output);
@@ -76,7 +76,7 @@ esp_err_t relay_chn_output_stop(relay_chn_output_t *output);
* @brief Set relay channel output to forward direction. * @brief Set relay channel output to forward direction.
* *
* @param[in] output Pointer to relay channel output object. * @param[in] output Pointer to relay channel output object.
* *
* @return ESP_OK on success, error code otherwise. * @return ESP_OK on success, error code otherwise.
*/ */
esp_err_t relay_chn_output_forward(relay_chn_output_t *output); esp_err_t relay_chn_output_forward(relay_chn_output_t *output);
@@ -85,7 +85,7 @@ esp_err_t relay_chn_output_forward(relay_chn_output_t *output);
* @brief Set relay channel output to reverse direction. * @brief Set relay channel output to reverse direction.
* *
* @param[in] output Pointer to relay channel output object. * @param[in] output Pointer to relay channel output object.
* *
* @return ESP_OK on success, error code otherwise. * @return ESP_OK on success, error code otherwise.
*/ */
esp_err_t relay_chn_output_reverse(relay_chn_output_t *output); esp_err_t relay_chn_output_reverse(relay_chn_output_t *output);
@@ -103,7 +103,7 @@ void relay_chn_output_flip(relay_chn_output_t *output);
* @brief Get the current direction of the relay channel output. * @brief Get the current direction of the relay channel output.
* *
* @param[in] output Pointer to relay channel output object. * @param[in] output Pointer to relay channel output object.
* *
* @return Current direction of the relay channel. * @return Current direction of the relay channel.
*/ */
relay_chn_direction_t relay_chn_output_get_direction(relay_chn_output_t *output); relay_chn_direction_t relay_chn_output_get_direction(relay_chn_output_t *output);

View File

@@ -49,7 +49,7 @@ relay_chn_run_info_t *relay_chn_run_info_get(void);
* @brief Get the last run command for a relay channel. * @brief Get the last run command for a relay channel.
* *
* @param[in] run_info Pointer to run information structure. * @param[in] run_info Pointer to run information structure.
* *
* @return Last command that was executed, or RELAY_CHN_CMD_NONE if invalid. * @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); relay_chn_cmd_t relay_chn_run_info_get_last_run_cmd(relay_chn_run_info_t *run_info);
@@ -66,7 +66,7 @@ void relay_chn_run_info_set_last_run_cmd(relay_chn_run_info_t *run_info, relay_c
* @brief Get the timestamp of the last run command. * @brief Get the timestamp of the last run command.
* *
* @param[in] run_info Pointer to run information structure. * @param[in] run_info Pointer to run information structure.
* *
* @return Timestamp in milliseconds of last command, or 0 if invalid. * @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); uint32_t relay_chn_run_info_get_last_run_cmd_time_ms(relay_chn_run_info_t *run_info);

View File

@@ -19,7 +19,7 @@ extern "C" {
* Must be called before using any other tilt functions. * Must be called before using any other tilt functions.
* *
* @param[in] chn_ctls Array of relay channel control structures. * @param[in] chn_ctls Array of relay channel control structures.
* *
* @return ESP_OK on success, error code otherwise. * @return ESP_OK on success, error code otherwise.
*/ */
esp_err_t relay_chn_tilt_init(relay_chn_ctl_t *chn_ctls); esp_err_t relay_chn_tilt_init(relay_chn_ctl_t *chn_ctls);
@@ -34,12 +34,12 @@ void relay_chn_tilt_deinit(void);
/** /**
* @brief Dispatch a tilt command to a relay channel. * @brief Dispatch a tilt command to a relay channel.
* *
* Queues a tilt command for execution on the specified channel. * Queues a tilt command for execution on the specified channel.
* *
* @param[in] tilt_ctl Pointer to tilt control structure. * @param[in] tilt_ctl Pointer to tilt control structure.
* @param[in] cmd Tilt command to execute. * @param[in] cmd Tilt command to execute.
* *
* @return ESP_OK on success, error code otherwise. * @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); esp_err_t relay_chn_tilt_dispatch_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_tilt_cmd_t cmd);

View File

@@ -10,13 +10,13 @@ if [[ -z "$IDF_PATH" ]]; then
fi fi
# ==== 2. Valid Modes and Defaults ==== # ==== 2. Valid Modes and Defaults ====
valid_test_tags=("core" "tilt" "listener" "all" "relay_chn" "nvs" "run_limit" "batch") 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_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_clean=false
arg_log=false arg_log=false
arg_dry_run=false arg_dry_run=false
arg_sdkconfig_file=""
flag_file=false
print_help() { print_help() {
echo "Usage: $0 -t <tags> [OPTIONS]" 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 "This script builds and runs tests for the relay_chn component using QEMU."
echo "" echo ""
echo "Arguments:" echo "Arguments:"
echo " -t, --tag [relay_chn|core|tilt|listener|nvs|run_limit|batch|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 ""
echo " If no tag is specified, it defaults to 'all'." echo " If no tag is specified, it defaults to 'all'."
echo "" 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 "Options:"
echo " -f, --file <path> Specify a custom sdkconfig file to use for the build." echo " -f, --file <path> Specify a custom sdkconfig file to use for the build."
echo " Defaults to 'sdkconfig.defaults' if not provided." echo " Defaults to 'sdkconfig.defaults' if not provided."
@@ -54,9 +58,8 @@ while [[ $# -gt 0 ]]; do
arg_tag="$2" arg_tag="$2"
shift 2 shift 2
;; ;;
--file|-f) --profile|-p)
arg_sdkconfig_file="$2" arg_profile="$2"
flag_file=true
shift 2 shift 2
;; ;;
--clean|-c) --clean|-c)
@@ -86,33 +89,25 @@ if [[ ! " ${valid_test_tags[*]} " =~ " $arg_tag " ]]; then
usage usage
fi fi
if [[ ! " ${valid_test_profiles[*]} " =~ " $arg_profile " ]]; then
echo "❌ Invalid profile: '$arg_profile'"
usage
fi
# ==== 5. Resolve Paths and Switch to Working Directory ==== # ==== 5. Resolve Paths and Switch to Working Directory ====
script_dir=$(dirname "$(readlink -f "$0")") script_dir=$(dirname "$(readlink -f "$0")")
project_root=$(dirname "$script_dir") project_root=$(dirname "$script_dir")
echo "🔍 Searching for 'test_apps' directory in '$project_root'..." test_apps_dir="${project_root}/test_apps"
test_apps_dir=$(find "$project_root" -type d -name "test_apps" | head -n 1)
if [[ -z "$test_apps_dir" || ! -d "$test_apps_dir" ]]; then if [[ -z "$test_apps_dir" || ! -d "$test_apps_dir" ]]; then
echo "❌ 'test_apps' directory not found within the project root: '$project_root'" 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." echo " Please ensure the script is in a 'scripts' directory and 'test_apps' is a sibling."
exit 1 exit 1
fi fi
echo "✅ Found 'test_apps' at: $test_apps_dir"
if $flag_file; then echo "⏳ Current time is: $(date +"%Y-%m-%d %H:%M:%S")"
if [[ -z "$arg_sdkconfig_file" || ! -f "$arg_sdkconfig_file" ]]; then echo "🧪 Test mode: $arg_tag | Profile: $arg_profile"
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 "🧹 Clean: $arg_clean | 📄 Log: $arg_log" echo "🧹 Clean: $arg_clean | 📄 Log: $arg_log"
echo "📂 Changing to working directory: $test_apps_dir" 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'" # 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. # The 'LC_ALL=C' env variable is set to ensure consistent locale settings.
LC_ALL=C \ LC_ALL=C \
SDKCONFIG_DEFAULTS="$arg_sdkconfig_file" \
RELAY_CHN_UNITY_TEST_GROUP_TAG="$arg_tag" \ RELAY_CHN_UNITY_TEST_GROUP_TAG="$arg_tag" \
idf.py reconfigure build idf.py @profiles/"${arg_profile}" reconfigure build
echo "🚀 Running test with QEMU..." echo "🚀 Running test with QEMU..."
if $arg_log; then if $arg_log; then
TIMESTAMP=$(date +"%Y%m%d_%H%M%S") 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 if $arg_dry_run; then
echo "🔍 Dry run mode: Logging to $LOGFILE but not executing." | tee "$LOGFILE" echo "🔍 Dry run mode: Logging to $LOGFILE but not executing." | tee "$LOGFILE"
echo "Command: idf.py qemu" | tee "$LOGFILE" echo "Command: idf.py qemu" | tee "$LOGFILE"

View File

@@ -1,30 +0,0 @@
#!/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}"
echo "🔍 Searching for 'test_apps' directory in '$project_root'..."
test_apps_dir=$(find "$project_root" -type d -name "test_apps" | head -n 1)
echo "test_apps dir: ${test_apps_dir}"
# Execute tests for all configs
mapfile -t sdkcfg_files < <(find "$test_apps_dir" -maxdepth 1 -type f -name "sdkconfig.defaults*")
for sdkcfg_file in "${sdkcfg_files[@]}"; do
echo "🔧 Running tests with config: $sdkcfg_file"
"${script_dir}"/run_tests.sh -c -f "$sdkcfg_file" -t "$arg_tag" || {
echo "❌ Tests failed with config: $sdkcfg_file"
exit 1
}
done

View 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

View File

@@ -11,6 +11,7 @@
#include "relay_chn_output.h" #include "relay_chn_output.h"
#include "relay_chn_run_info.h" #include "relay_chn_run_info.h"
#include "relay_chn_ctl.h" #include "relay_chn_ctl.h"
#include "relay_chn_notify.h"
#if CONFIG_RELAY_CHN_ENABLE_TILTING #if CONFIG_RELAY_CHN_ENABLE_TILTING
#include "relay_chn_tilt.h" #include "relay_chn_tilt.h"
@@ -26,18 +27,9 @@
static const char *TAG = "RELAY_CHN_CORE"; static const char *TAG = "RELAY_CHN_CORE";
// Structure to hold a listener entry in the linked list.
typedef struct relay_chn_listener_entry_type {
relay_chn_state_listener_t listener; /*!< The listener function pointer */
ListItem_t list_item; /*!< FreeRTOS list item */
} relay_chn_listener_entry_t;
// The list that holds references to the registered listeners.
static List_t relay_chn_listener_list;
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
/* /*
* Run limit timer callback immediately dispatches a STOP command for the * Run limit timer callback immediately dispatches a STOP command for the
* relevant channel as soon as the run limit time times out * relevant channel as soon as the run limit time times out
*/ */
static void relay_chn_run_limit_timer_cb(void* arg) static void relay_chn_run_limit_timer_cb(void* arg)
@@ -125,9 +117,9 @@ esp_err_t relay_chn_create(const uint8_t* gpio_map, uint8_t gpio_count)
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize tilt feature"); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize tilt feature");
#endif #endif
// Init the state listener list // Initialize the notify feature
vListInitialise(&relay_chn_listener_list); ret = relay_chn_notify_init();
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize notify feature");
return ret; return ret;
} }
@@ -136,96 +128,15 @@ void relay_chn_destroy(void)
#if CONFIG_RELAY_CHN_ENABLE_TILTING #if CONFIG_RELAY_CHN_ENABLE_TILTING
relay_chn_tilt_deinit(); relay_chn_tilt_deinit();
#endif #endif
relay_chn_notify_deinit();
relay_chn_ctl_deinit(); relay_chn_ctl_deinit();
relay_chn_output_deinit(); relay_chn_output_deinit();
#if CONFIG_RELAY_CHN_ENABLE_NVS #if CONFIG_RELAY_CHN_ENABLE_NVS
relay_chn_nvs_deinit(); relay_chn_nvs_deinit();
#endif #endif
// Free the listeners
while (listCURRENT_LIST_LENGTH(&relay_chn_listener_list) > 0) {
ListItem_t *pxItem = listGET_HEAD_ENTRY(&relay_chn_listener_list);
relay_chn_listener_entry_t *entry = listGET_LIST_ITEM_OWNER(pxItem);
uxListRemove(pxItem);
free(entry);
}
} }
/**
* @brief Find a listener entry in the list by its function pointer.
*
* This function replaces the old index-based search and is used to check
* for the existence of a listener before registration or for finding it
* during unregistration.
*
* @param listener The listener function pointer to find.
* @return Pointer to the listener entry if found, otherwise NULL.
*/
static relay_chn_listener_entry_t* find_listener_entry(relay_chn_state_listener_t listener)
{
// Iterate through the linked list of listeners
for (ListItem_t *pxListItem = listGET_HEAD_ENTRY(&relay_chn_listener_list);
pxListItem != listGET_END_MARKER(&relay_chn_listener_list);
pxListItem = listGET_NEXT(pxListItem)) {
relay_chn_listener_entry_t *entry = (relay_chn_listener_entry_t *) listGET_LIST_ITEM_OWNER(pxListItem);
if (entry->listener == listener) {
// Found the listener, return the entry
return entry;
}
}
// Listener was not found in the list
return NULL;
}
esp_err_t relay_chn_register_listener(relay_chn_state_listener_t listener)
{
ESP_RETURN_ON_FALSE(listener, ESP_ERR_INVALID_ARG, TAG, "Listener cannot be NULL");
// Check for duplicates
if (find_listener_entry(listener) != NULL) {
ESP_LOGD(TAG, "Listener %p already registered", listener);
return ESP_OK;
}
// Allocate memory for the new listener entry
relay_chn_listener_entry_t *entry = malloc(sizeof(relay_chn_listener_entry_t));
ESP_RETURN_ON_FALSE(entry, ESP_ERR_NO_MEM, TAG, "Failed to allocate memory for listener");
// Initialize and insert the new listener
entry->listener = listener;
vListInitialiseItem(&(entry->list_item));
listSET_LIST_ITEM_OWNER(&(entry->list_item), (void *)entry);
vListInsertEnd(&relay_chn_listener_list, &(entry->list_item));
ESP_LOGD(TAG, "Registered listener %p", listener);
return ESP_OK;
}
void relay_chn_unregister_listener(relay_chn_state_listener_t listener)
{
if (listener == NULL)
{
ESP_LOGD(TAG, "Cannot unregister a NULL listener.");
return;
}
// Find the listener entry in the list
relay_chn_listener_entry_t *entry = find_listener_entry(listener);
if (entry != NULL) {
// Remove the item from the list and free the allocated memory
uxListRemove(&(entry->list_item));
free(entry);
ESP_LOGD(TAG, "Unregistered listener %p", listener);
} else {
ESP_LOGD(TAG, "Listener %p not found for unregistration.", listener);
}
}
esp_err_t relay_chn_start_esp_timer_once(esp_timer_handle_t esp_timer, uint32_t time_ms) 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); esp_err_t ret = esp_timer_start_once(esp_timer, time_ms * 1000);
@@ -251,31 +162,50 @@ void relay_chn_update_state(relay_chn_ctl_t *chn_ctl, relay_chn_state_t new_stat
chn_ctl->state = new_state; chn_ctl->state = new_state;
// Iterate through the linked list of listeners and notify them. relay_chn_notify_state_change(chn_ctl->id, old_state, new_state);
for (ListItem_t *pxListItem = listGET_HEAD_ENTRY(&relay_chn_listener_list); }
pxListItem != listGET_END_MARKER(&relay_chn_listener_list);
pxListItem = listGET_NEXT(pxListItem)) { static void relay_chn_execute_idle(relay_chn_ctl_t *chn_ctl);
relay_chn_listener_entry_t *entry = (relay_chn_listener_entry_t *) listGET_LIST_ITEM_OWNER(pxListItem);
if (entry && entry->listener) { 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)
// Emit the state change to the listeners {
entry->listener(chn_ctl->id, old_state, new_state); 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. * @brief The command issuer function.
* *
* This function is the deciding logic for issuing a command to a relay channel. It evaluates * 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 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 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. * 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 * 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 * is issued after the channel is stopped. If the channel is stopped, the FLIP command is issued
* immediately. * immediately.
* *
* @param chn_ctl The relay channel to issue the command to. * @param chn_ctl The relay channel to issue the command to.
* @param cmd The command to issue. * @param cmd The command to issue.
*/ */
@@ -284,10 +214,10 @@ void relay_chn_issue_cmd(relay_chn_ctl_t* chn_ctl, relay_chn_cmd_t cmd)
if (cmd == RELAY_CHN_CMD_NONE) { if (cmd == RELAY_CHN_CMD_NONE) {
return; return;
} }
if (cmd == RELAY_CHN_CMD_STOP) { if (cmd == RELAY_CHN_CMD_STOP) {
if (chn_ctl->state == RELAY_CHN_STATE_STOPPED) { if (chn_ctl->state == RELAY_CHN_STATE_STOPPED || chn_ctl->state == RELAY_CHN_STATE_IDLE) {
return; // Do nothing if already stopped return; // Do nothing if already stopped or idle
} }
// If the command is STOP, issue it immediately // If the command is STOP, issue it immediately
relay_chn_dispatch_cmd(chn_ctl, cmd); relay_chn_dispatch_cmd(chn_ctl, cmd);
@@ -302,7 +232,7 @@ void relay_chn_issue_cmd(relay_chn_ctl_t* chn_ctl, relay_chn_cmd_t cmd)
// If the channel is idle, run the command immediately // If the channel is idle, run the command immediately
relay_chn_dispatch_cmd(chn_ctl, cmd); relay_chn_dispatch_cmd(chn_ctl, cmd);
break; break;
case RELAY_CHN_STATE_FORWARD_PENDING: case RELAY_CHN_STATE_FORWARD_PENDING:
case RELAY_CHN_STATE_REVERSE_PENDING: case RELAY_CHN_STATE_REVERSE_PENDING:
// The channel is already waiting for the opposite inertia time, // The channel is already waiting for the opposite inertia time,
@@ -311,7 +241,7 @@ void relay_chn_issue_cmd(relay_chn_ctl_t* chn_ctl, relay_chn_cmd_t cmd)
relay_chn_dispatch_cmd(chn_ctl, cmd); relay_chn_dispatch_cmd(chn_ctl, cmd);
} }
break; break;
case RELAY_CHN_STATE_STOPPED: case RELAY_CHN_STATE_STOPPED:
if (last_run_cmd == cmd || last_run_cmd == RELAY_CHN_CMD_NONE) { 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 // Since the state is STOPPED, the inertia timer should be running and must be invalidated
@@ -324,49 +254,57 @@ void relay_chn_issue_cmd(relay_chn_ctl_t* chn_ctl, relay_chn_cmd_t cmd)
relay_chn_dispatch_cmd(chn_ctl, cmd); relay_chn_dispatch_cmd(chn_ctl, cmd);
} }
else { else {
// If the last run command is different from the current command, calculate the time passed // 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 // 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 last_run_cmd_time_ms = relay_chn_run_info_get_last_run_cmd_time_ms(chn_ctl->run_info);
uint32_t inertia_time_passed_ms = (uint32_t) (esp_timer_get_time() / 1000) - last_run_cmd_time_ms; uint32_t current_time_ms = (uint32_t)(esp_timer_get_time() / 1000);
uint32_t inertia_time_ms = CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS - inertia_time_passed_ms; if (current_time_ms < last_run_cmd_time_ms) { // Timer overflow
if (inertia_time_ms > 0) { // If timer overflowed, it's been a long time. Run immediately.
chn_ctl->pending_cmd = cmd;
relay_chn_state_t new_state = cmd == RELAY_CHN_CMD_FORWARD
? RELAY_CHN_STATE_FORWARD_PENDING : RELAY_CHN_STATE_REVERSE_PENDING;
relay_chn_update_state(chn_ctl, new_state);
// If the time passed is less than the opposite inertia time, wait for the remaining time
relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, inertia_time_ms);
}
else {
// If the time passed is more than the opposite inertia time, run the command immediately
relay_chn_dispatch_cmd(chn_ctl, cmd); 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; break;
case RELAY_CHN_STATE_FORWARD: case RELAY_CHN_STATE_FORWARD:
case RELAY_CHN_STATE_REVERSE: case RELAY_CHN_STATE_REVERSE:
if (cmd == RELAY_CHN_CMD_FLIP) { if (cmd == RELAY_CHN_CMD_FLIP) {
// If the command is FLIP, stop the running channel first, then issue the FLIP command // If the command is FLIP, stop the running channel first, then issue the FLIP command
relay_chn_dispatch_cmd(chn_ctl, RELAY_CHN_CMD_STOP); relay_chn_stop_prv(chn_ctl);
relay_chn_dispatch_cmd(chn_ctl, cmd); relay_chn_dispatch_cmd(chn_ctl, cmd);
return; return;
} }
if (last_run_cmd == cmd) { if (last_run_cmd == cmd) {
// If the last run command is the same as the current command, do nothing // If the last run command is the same as the current command, do nothing
return; return;
} }
// Stop the channel first before the schedule // Stop the channel first before the schedule
relay_chn_dispatch_cmd(chn_ctl, RELAY_CHN_CMD_STOP); relay_chn_stop_prv(chn_ctl);
// If the last run command is different from the current command, wait for the opposite inertia time // If the last run command is different from the current command, wait for the opposite inertia time
chn_ctl->pending_cmd = cmd; chn_ctl->pending_cmd = cmd;
relay_chn_state_t new_state = cmd == RELAY_CHN_CMD_FORWARD relay_chn_state_t new_state = cmd == RELAY_CHN_CMD_FORWARD
? RELAY_CHN_STATE_FORWARD_PENDING : RELAY_CHN_STATE_REVERSE_PENDING; ? RELAY_CHN_STATE_FORWARD_PENDING : RELAY_CHN_STATE_REVERSE_PENDING;
relay_chn_update_state(chn_ctl, new_state); relay_chn_update_state(chn_ctl, new_state);
relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS); relay_chn_start_timer_or_idle(chn_ctl, chn_ctl->inertia_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS, "inertia");
break; break;
#if CONFIG_RELAY_CHN_ENABLE_TILTING #if CONFIG_RELAY_CHN_ENABLE_TILTING
@@ -426,36 +364,27 @@ static void relay_chn_execute_idle(relay_chn_ctl_t *chn_ctl)
static void relay_chn_execute_stop(relay_chn_ctl_t *chn_ctl) static void relay_chn_execute_stop(relay_chn_ctl_t *chn_ctl)
{ {
if (relay_chn_output_stop(chn_ctl->output) != ESP_OK) { relay_chn_stop_prv(chn_ctl);
ESP_LOGE(TAG, "relay_chn_execute_stop: Failed to output stop for relay channel #%d!", chn_ctl->id);
}
relay_chn_state_t previous_state = chn_ctl->state;
relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_STOPPED);
// If there is any pending command, cancel it since the STOP command is issued right after it
chn_ctl->pending_cmd = RELAY_CHN_CMD_NONE;
// Invalidate the channel's timer if it is active
esp_timer_stop(chn_ctl->inertia_timer);
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
esp_timer_stop(chn_ctl->run_limit_timer); esp_timer_stop(chn_ctl->run_limit_timer);
#endif #endif
// Save the last run time only if the previous state was either STATE FORWARD // Invalidate the channel's timer if it is active
// or STATE_REVERSE. Then schedule a free command. esp_timer_stop(chn_ctl->inertia_timer);
if (previous_state == RELAY_CHN_STATE_FORWARD || previous_state == RELAY_CHN_STATE_REVERSE) {
// Record the command's last run time relay_chn_cmd_t last_run_cmd = relay_chn_run_info_get_last_run_cmd(chn_ctl->run_info);
relay_chn_run_info_set_last_run_cmd_time_ms(chn_ctl->run_info, (uint32_t)(esp_timer_get_time() / 1000)); if (last_run_cmd == RELAY_CHN_CMD_FORWARD || last_run_cmd == RELAY_CHN_CMD_REVERSE ) {
// Schedule a free command for the channel // Schedule IDLE
chn_ctl->pending_cmd = RELAY_CHN_CMD_IDLE; chn_ctl->pending_cmd = RELAY_CHN_CMD_IDLE;
relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS); relay_chn_start_timer_or_idle(chn_ctl, chn_ctl->inertia_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS, "idle");
} else { } else {
// If the channel was not running one of the run or fwd, issue a free command immediately // If the channel was not running forward or reverse, issue a free command immediately
// relay_chn_dispatch_cmd(chn_ctl, RELAY_CHN_CMD_IDLE);
relay_chn_execute_idle(chn_ctl); relay_chn_execute_idle(chn_ctl);
} }
} }
static void relay_chn_execute_forward(relay_chn_ctl_t *chn_ctl) static void relay_chn_execute_forward(relay_chn_ctl_t *chn_ctl)
{ {
if (relay_chn_output_forward(chn_ctl->output) != ESP_OK) { if (relay_chn_output_forward(chn_ctl->output) != ESP_OK) {
@@ -466,7 +395,7 @@ static void relay_chn_execute_forward(relay_chn_ctl_t *chn_ctl)
relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_FORWARD); relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_FORWARD);
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
relay_chn_start_esp_timer_once(chn_ctl->run_limit_timer, chn_ctl->run_limit_sec * 1000); relay_chn_start_timer_or_idle(chn_ctl, chn_ctl->run_limit_timer, chn_ctl->run_limit_sec * 1000, "run limit");
#endif #endif
} }
@@ -480,7 +409,7 @@ static void relay_chn_execute_reverse(relay_chn_ctl_t *chn_ctl)
relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_REVERSE); relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_REVERSE);
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
relay_chn_start_esp_timer_once(chn_ctl->run_limit_timer, chn_ctl->run_limit_sec * 1000); relay_chn_start_timer_or_idle(chn_ctl, chn_ctl->run_limit_timer, chn_ctl->run_limit_sec * 1000, "run limit");
#endif #endif
} }
@@ -489,7 +418,7 @@ static void relay_chn_execute_flip(relay_chn_ctl_t *chn_ctl)
relay_chn_output_flip(chn_ctl->output); relay_chn_output_flip(chn_ctl->output);
// Set an inertia on the channel to prevent any immediate movement // Set an inertia on the channel to prevent any immediate movement
chn_ctl->pending_cmd = RELAY_CHN_CMD_IDLE; chn_ctl->pending_cmd = RELAY_CHN_CMD_IDLE;
relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS); relay_chn_start_timer_or_idle(chn_ctl, chn_ctl->inertia_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS, "flip inertia");
} }
// Dispatch relay channel command // Dispatch relay channel command
@@ -542,7 +471,7 @@ char *relay_chn_cmd_str(relay_chn_cmd_t cmd)
} }
} }
char *relay_chn_state_str(relay_chn_state_t state) char *relay_chn_state_to_str(relay_chn_state_t state)
{ {
switch (state) { switch (state) {
case RELAY_CHN_STATE_IDLE: case RELAY_CHN_STATE_IDLE:

View File

@@ -5,6 +5,7 @@
*/ */
#include "esp_check.h" #include "esp_check.h"
#include "relay_chn.h"
#include "relay_chn_priv_types.h" #include "relay_chn_priv_types.h"
#include "relay_chn_core.h" #include "relay_chn_core.h"
#include "relay_chn_ctl.h" #include "relay_chn_ctl.h"
@@ -16,7 +17,7 @@
static const char *TAG = "RELAY_CHN_CTL"; static const char *TAG = "RELAY_CHN_CTL";
static relay_chn_ctl_t chn_ctls[CONFIG_RELAY_CHN_COUNT]; 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) esp_err_t relay_chn_ctl_init(relay_chn_output_t *outputs, relay_chn_run_info_t *run_infos)
@@ -24,7 +25,7 @@ esp_err_t relay_chn_ctl_init(relay_chn_output_t *outputs, relay_chn_run_info_t *
// Initialize all relay channels // Initialize all relay channels
esp_err_t ret; esp_err_t ret;
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
relay_chn_ctl_t* chn_ctl = &chn_ctls[i]; relay_chn_ctl_t* chn_ctl = &s_chn_ctls[i];
relay_chn_output_t* output = &outputs[i]; relay_chn_output_t* output = &outputs[i];
relay_chn_run_info_t* run_info = &run_infos[i]; relay_chn_run_info_t* run_info = &run_infos[i];
@@ -38,10 +39,8 @@ esp_err_t relay_chn_ctl_init(relay_chn_output_t *outputs, relay_chn_run_info_t *
uint16_t run_limit_sec = CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC; uint16_t run_limit_sec = CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC;
#if CONFIG_RELAY_CHN_ENABLE_NVS #if CONFIG_RELAY_CHN_ENABLE_NVS
// Load run limit value from NVS // Load run limit value from NVS
ret = relay_chn_nvs_get_run_limit(chn_ctl->id, &run_limit_sec); ret = relay_chn_nvs_get_run_limit(chn_ctl->id, &run_limit_sec, CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC);
if (ret != ESP_OK && ret != ESP_ERR_NVS_NOT_FOUND) { ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load run limit from NVS for #%d with error: %s", i, esp_err_to_name(ret));
ESP_LOGE(TAG, "Failed to load run limit from NVS for channel %d with error: %s", i, esp_err_to_name(ret));
}
#endif #endif
chn_ctl->run_limit_sec = run_limit_sec; chn_ctl->run_limit_sec = run_limit_sec;
ret = relay_chn_init_run_limit_timer(chn_ctl); ret = relay_chn_init_run_limit_timer(chn_ctl);
@@ -56,7 +55,7 @@ esp_err_t relay_chn_ctl_init(relay_chn_output_t *outputs, relay_chn_run_info_t *
void relay_chn_ctl_deinit() void relay_chn_ctl_deinit()
{ {
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
relay_chn_ctl_t* chn_ctl = &chn_ctls[i]; relay_chn_ctl_t* chn_ctl = &s_chn_ctls[i];
if (chn_ctl->inertia_timer != NULL) { if (chn_ctl->inertia_timer != NULL) {
esp_timer_delete(chn_ctl->inertia_timer); esp_timer_delete(chn_ctl->inertia_timer);
chn_ctl->inertia_timer = NULL; chn_ctl->inertia_timer = NULL;
@@ -73,7 +72,7 @@ void relay_chn_ctl_deinit()
relay_chn_state_t relay_chn_ctl_get_state(uint8_t chn_id) relay_chn_state_t relay_chn_ctl_get_state(uint8_t chn_id)
{ {
return relay_chn_is_channel_id_valid(chn_id) ? return relay_chn_is_channel_id_valid(chn_id) ?
chn_ctls[chn_id].state : RELAY_CHN_STATE_UNDEFINED; 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_err_t relay_chn_ctl_get_state_all(relay_chn_state_t *states)
@@ -86,7 +85,7 @@ esp_err_t relay_chn_ctl_get_state_all(relay_chn_state_t *states)
ESP_LOGW(TAG, "get_state_all: States have been copied until channel %d since states[%d] is NULL", i, i); ESP_LOGW(TAG, "get_state_all: States have been copied until channel %d since states[%d] is NULL", i, i);
break; break;
} }
*dest_state = chn_ctls[i].state; *dest_state = s_chn_ctls[i].state;
} }
return ESP_OK; return ESP_OK;
} }
@@ -94,22 +93,22 @@ esp_err_t relay_chn_ctl_get_state_all(relay_chn_state_t *states)
char *relay_chn_ctl_get_state_str(uint8_t chn_id) char *relay_chn_ctl_get_state_str(uint8_t chn_id)
{ {
return relay_chn_is_channel_id_valid(chn_id) return relay_chn_is_channel_id_valid(chn_id)
? relay_chn_state_str(chn_ctls[chn_id].state) ? relay_chn_state_to_str(s_chn_ctls[chn_id].state)
: relay_chn_state_str(RELAY_CHN_STATE_UNDEFINED); : relay_chn_state_to_str(RELAY_CHN_STATE_UNDEFINED);
} }
static void relay_chn_ctl_issue_cmd_on_all_channels(relay_chn_cmd_t cmd) 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++) { for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
relay_chn_issue_cmd(&chn_ctls[i], cmd); relay_chn_issue_cmd(&s_chn_ctls[i], cmd);
} }
} }
void relay_chn_ctl_run_forward(uint8_t chn_id) void relay_chn_ctl_run_forward(uint8_t chn_id)
{ {
if (relay_chn_is_channel_id_valid(chn_id)) if (relay_chn_is_channel_id_valid(chn_id))
relay_chn_issue_cmd(&chn_ctls[chn_id], RELAY_CHN_CMD_FORWARD); relay_chn_issue_cmd(&s_chn_ctls[chn_id], RELAY_CHN_CMD_FORWARD);
} }
void relay_chn_ctl_run_forward_all() void relay_chn_ctl_run_forward_all()
@@ -120,7 +119,7 @@ void relay_chn_ctl_run_forward_all()
void relay_chn_ctl_run_reverse(uint8_t chn_id) void relay_chn_ctl_run_reverse(uint8_t chn_id)
{ {
if (relay_chn_is_channel_id_valid(chn_id)) if (relay_chn_is_channel_id_valid(chn_id))
relay_chn_issue_cmd(&chn_ctls[chn_id], RELAY_CHN_CMD_REVERSE); relay_chn_issue_cmd(&s_chn_ctls[chn_id], RELAY_CHN_CMD_REVERSE);
} }
void relay_chn_ctl_run_reverse_all() void relay_chn_ctl_run_reverse_all()
@@ -131,7 +130,7 @@ void relay_chn_ctl_run_reverse_all()
void relay_chn_ctl_stop(uint8_t chn_id) void relay_chn_ctl_stop(uint8_t chn_id)
{ {
if (relay_chn_is_channel_id_valid(chn_id)) if (relay_chn_is_channel_id_valid(chn_id))
relay_chn_issue_cmd(&chn_ctls[chn_id], RELAY_CHN_CMD_STOP); relay_chn_issue_cmd(&s_chn_ctls[chn_id], RELAY_CHN_CMD_STOP);
} }
void relay_chn_ctl_stop_all() void relay_chn_ctl_stop_all()
@@ -142,7 +141,7 @@ void relay_chn_ctl_stop_all()
void relay_chn_ctl_flip_direction(uint8_t chn_id) void relay_chn_ctl_flip_direction(uint8_t chn_id)
{ {
if (relay_chn_is_channel_id_valid(chn_id)) if (relay_chn_is_channel_id_valid(chn_id))
relay_chn_issue_cmd(&chn_ctls[chn_id], RELAY_CHN_CMD_FLIP); relay_chn_issue_cmd(&s_chn_ctls[chn_id], RELAY_CHN_CMD_FLIP);
} }
void relay_chn_ctl_flip_direction_all() void relay_chn_ctl_flip_direction_all()
@@ -153,7 +152,7 @@ void relay_chn_ctl_flip_direction_all()
relay_chn_direction_t relay_chn_ctl_get_direction(uint8_t chn_id) relay_chn_direction_t relay_chn_ctl_get_direction(uint8_t chn_id)
{ {
return relay_chn_is_channel_id_valid(chn_id) return relay_chn_is_channel_id_valid(chn_id)
? relay_chn_output_get_direction(chn_ctls[chn_id].output) ? relay_chn_output_get_direction(s_chn_ctls[chn_id].output)
: RELAY_CHN_DIRECTION_DEFAULT; : RELAY_CHN_DIRECTION_DEFAULT;
} }
@@ -167,7 +166,7 @@ esp_err_t relay_chn_ctl_get_direction_all(relay_chn_direction_t *directions)
ESP_LOGW(TAG, "get_direction_all: Directions have been copied until channel %d since directions[%d] is NULL", i, i); ESP_LOGW(TAG, "get_direction_all: Directions have been copied until channel %d since directions[%d] is NULL", i, i);
break; break;
} }
*dest_direction = relay_chn_output_get_direction(chn_ctls[i].output); *dest_direction = relay_chn_output_get_direction(s_chn_ctls[i].output);
} }
return ESP_OK; return ESP_OK;
} }
@@ -175,7 +174,7 @@ esp_err_t relay_chn_ctl_get_direction_all(relay_chn_direction_t *directions)
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
uint16_t relay_chn_ctl_get_run_limit(uint8_t chn_id) uint16_t relay_chn_ctl_get_run_limit(uint8_t chn_id)
{ {
return relay_chn_is_channel_id_valid(chn_id) ? chn_ctls[chn_id].run_limit_sec : 0; 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_err_t relay_chn_ctl_get_run_limit_all(uint16_t *limits_sec)
@@ -188,7 +187,7 @@ esp_err_t relay_chn_ctl_get_run_limit_all(uint16_t *limits_sec)
ESP_LOGW(TAG, "get_run_limit_all: Run limits have been copied until channel %d since limits_sec[%d] is NULL", i, i); ESP_LOGW(TAG, "get_run_limit_all: Run limits have been copied until channel %d since limits_sec[%d] is NULL", i, i);
break; break;
} }
*dest_limit_sec = chn_ctls[i].run_limit_sec; *dest_limit_sec = s_chn_ctls[i].run_limit_sec;
} }
return ESP_OK; return ESP_OK;
} }
@@ -201,7 +200,7 @@ static void relay_chn_ctl_set_run_limit_common(uint8_t chn_id, uint16_t limit_se
else if (limit_sec < CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC) else if (limit_sec < CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC)
limit_sec = CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC; limit_sec = CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC;
chn_ctls[chn_id].run_limit_sec = limit_sec; s_chn_ctls[chn_id].run_limit_sec = limit_sec;
#if CONFIG_RELAY_CHN_ENABLE_NVS #if CONFIG_RELAY_CHN_ENABLE_NVS
relay_chn_nvs_set_run_limit(chn_id, limit_sec); relay_chn_nvs_set_run_limit(chn_id, limit_sec);
@@ -243,10 +242,10 @@ esp_err_t relay_chn_ctl_set_run_limit_all_with(uint16_t limit_sec)
relay_chn_ctl_t *relay_chn_ctl_get(uint8_t chn_id) relay_chn_ctl_t *relay_chn_ctl_get(uint8_t chn_id)
{ {
return relay_chn_is_channel_id_valid(chn_id) ? &chn_ctls[chn_id] : NULL; 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) relay_chn_ctl_t *relay_chn_ctl_get_all(void)
{ {
return chn_ctls; return s_chn_ctls;
} }

View File

@@ -5,6 +5,7 @@
*/ */
#include "esp_check.h" #include "esp_check.h"
#include "relay_chn.h"
#include "relay_chn_priv_types.h" #include "relay_chn_priv_types.h"
#include "relay_chn_core.h" #include "relay_chn_core.h"
#include "relay_chn_ctl.h" #include "relay_chn_ctl.h"
@@ -14,47 +15,45 @@
#include "relay_chn_nvs.h" #include "relay_chn_nvs.h"
#endif #endif
static const char *TAG = "RELAY_CHN_CTL"; static const char *TAG __attribute__((unused)) = "RELAY_CHN_CTL";
static relay_chn_ctl_t 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) esp_err_t relay_chn_ctl_init(relay_chn_output_t *output, relay_chn_run_info_t *run_info)
{ {
// Initialize the relay channel // Initialize the relay channel
chn_ctl.id = 0; // Single channel, so ID is 0 s_chn_ctl.id = 0; // Single channel, so ID is 0
chn_ctl.state = RELAY_CHN_STATE_IDLE; s_chn_ctl.state = RELAY_CHN_STATE_IDLE;
chn_ctl.pending_cmd = RELAY_CHN_CMD_NONE; s_chn_ctl.pending_cmd = RELAY_CHN_CMD_NONE;
chn_ctl.output = output; s_chn_ctl.output = output;
chn_ctl.run_info = run_info; s_chn_ctl.run_info = run_info;
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
uint16_t run_limit_sec = CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC; uint16_t run_limit_sec = CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC;
esp_err_t ret; esp_err_t ret;
#if CONFIG_RELAY_CHN_ENABLE_NVS #if CONFIG_RELAY_CHN_ENABLE_NVS
// Load run limit value from NVS // Load run limit value from NVS
ret = relay_chn_nvs_get_run_limit(chn_ctl.id, &run_limit_sec); ret = relay_chn_nvs_get_run_limit(s_chn_ctl.id, &run_limit_sec, CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC);
if (ret != ESP_OK && ret != ESP_ERR_NVS_NOT_FOUND) { ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load run limit from NVS with error: %s", esp_err_to_name(ret));
ESP_LOGE(TAG, "Failed to load run limit from NVS with error: %s", esp_err_to_name(ret));
}
#endif #endif
chn_ctl.run_limit_sec = run_limit_sec; s_chn_ctl.run_limit_sec = run_limit_sec;
ret = relay_chn_init_run_limit_timer(&chn_ctl); ret = relay_chn_init_run_limit_timer(&s_chn_ctl);
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize run limit timer"); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize run limit timer");
#endif #endif
return relay_chn_init_timer(&chn_ctl); // Create direction change inertia timer return relay_chn_init_timer(&s_chn_ctl); // Create direction change inertia timer
} }
void relay_chn_ctl_deinit() void relay_chn_ctl_deinit()
{ {
if (chn_ctl.inertia_timer != NULL) { if (s_chn_ctl.inertia_timer != NULL) {
esp_timer_delete(chn_ctl.inertia_timer); esp_timer_delete(s_chn_ctl.inertia_timer);
chn_ctl.inertia_timer = NULL; s_chn_ctl.inertia_timer = NULL;
} }
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
if (chn_ctl.run_limit_timer != NULL) { if (s_chn_ctl.run_limit_timer != NULL) {
esp_timer_delete(chn_ctl.run_limit_timer); esp_timer_delete(s_chn_ctl.run_limit_timer);
chn_ctl.run_limit_timer = NULL; s_chn_ctl.run_limit_timer = NULL;
} }
#endif #endif
} }
@@ -62,63 +61,63 @@ void relay_chn_ctl_deinit()
/* relay_chn APIs */ /* relay_chn APIs */
relay_chn_state_t relay_chn_ctl_get_state() relay_chn_state_t relay_chn_ctl_get_state()
{ {
return chn_ctl.state; return s_chn_ctl.state;
} }
char *relay_chn_ctl_get_state_str() char *relay_chn_ctl_get_state_str()
{ {
return relay_chn_state_str(chn_ctl.state); return relay_chn_state_to_str(s_chn_ctl.state);
} }
void relay_chn_ctl_run_forward() void relay_chn_ctl_run_forward()
{ {
relay_chn_issue_cmd(&chn_ctl, RELAY_CHN_CMD_FORWARD); relay_chn_issue_cmd(&s_chn_ctl, RELAY_CHN_CMD_FORWARD);
} }
void relay_chn_ctl_run_reverse() void relay_chn_ctl_run_reverse()
{ {
relay_chn_issue_cmd(&chn_ctl, RELAY_CHN_CMD_REVERSE); relay_chn_issue_cmd(&s_chn_ctl, RELAY_CHN_CMD_REVERSE);
} }
void relay_chn_ctl_stop() void relay_chn_ctl_stop()
{ {
relay_chn_issue_cmd(&chn_ctl, RELAY_CHN_CMD_STOP); relay_chn_issue_cmd(&s_chn_ctl, RELAY_CHN_CMD_STOP);
} }
void relay_chn_ctl_flip_direction() void relay_chn_ctl_flip_direction()
{ {
relay_chn_issue_cmd(&chn_ctl, RELAY_CHN_CMD_FLIP); relay_chn_issue_cmd(&s_chn_ctl, RELAY_CHN_CMD_FLIP);
} }
relay_chn_direction_t relay_chn_ctl_get_direction() relay_chn_direction_t relay_chn_ctl_get_direction()
{ {
return relay_chn_output_get_direction(chn_ctl.output); return relay_chn_output_get_direction(s_chn_ctl.output);
} }
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
uint16_t relay_chn_ctl_get_run_limit() uint16_t relay_chn_ctl_get_run_limit()
{ {
return chn_ctl.run_limit_sec; return s_chn_ctl.run_limit_sec;
} }
void relay_chn_ctl_set_run_limit(uint16_t time_sec) void relay_chn_ctl_set_run_limit(uint16_t limit_sec)
{ {
// Check for boundaries // Check for boundaries
if (time_sec > CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC) if (limit_sec > CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC)
time_sec = CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC; limit_sec = CONFIG_RELAY_CHN_RUN_LIMIT_MAX_SEC;
else if (time_sec < CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC) else if (limit_sec < CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC)
time_sec = CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC; limit_sec = CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC;
chn_ctl.run_limit_sec = time_sec; s_chn_ctl.run_limit_sec = limit_sec;
#if CONFIG_RELAY_CHN_ENABLE_NVS #if CONFIG_RELAY_CHN_ENABLE_NVS
relay_chn_nvs_set_run_limit(chn_ctl.id, time_sec); relay_chn_nvs_set_run_limit(s_chn_ctl.id, limit_sec);
#endif #endif
} }
#endif #endif
/* relay_chn APIs */ /* relay_chn APIs */
relay_chn_ctl_t *relay_chn_ctl_get() relay_chn_ctl_t *relay_chn_ctl_get()
{ {
return &chn_ctl; return &s_chn_ctl;
} }

268
src/relay_chn_notify.c Normal file
View 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;
}
}
}
}

View File

@@ -4,30 +4,104 @@
* SPDX-License-Identifier: MIT * 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_check.h"
#include "esp_log.h"
#include "nvs.h"
#include "relay_chn_nvs.h" #include "relay_chn_nvs.h"
#define RELAY_CHN_KEY_DIR "dir" /*!< Direction key */ #define RELAY_CHN_KEY_DIR "dir" /*!< Direction key */
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
#define RELAY_CHN_KEY_RLIM(ch) "rlim_%d" /*!< Run limit key */ #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 #endif
#if CONFIG_RELAY_CHN_ENABLE_TILTING #if CONFIG_RELAY_CHN_ENABLE_TILTING
#define RELAY_CHN_KEY_TSENS(ch) "tsens_%d" /*!< Tilt sensitivity key */ #if CONFIG_RELAY_CHN_COUNT > 1
#define RELAY_CHN_KEY_TCNT(ch) "tcnt_%d" /*!< Tilt count key */ #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
#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 const char *TAG = "RELAY_CHN_NVS";
static nvs_handle_t 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() 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; esp_err_t ret;
#if CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION #if CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION
ret = nvs_open_from_partition(CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME, ret = nvs_open_from_partition(CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME,
CONFIG_RELAY_CHN_NVS_NAMESPACE, CONFIG_RELAY_CHN_NVS_NAMESPACE,
NVS_READWRITE, NVS_READWRITE,
&relay_chn_nvs); &s_relay_chn_nvs);
ESP_RETURN_ON_ERROR(ret, ESP_RETURN_ON_ERROR(ret,
TAG, TAG,
@@ -36,101 +110,343 @@ esp_err_t relay_chn_nvs_init()
CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME, CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION_NAME,
esp_err_to_name(ret)); esp_err_to_name(ret));
#else #else
ret = nvs_open(CONFIG_RELAY_CHN_NVS_NAMESPACE, NVS_READWRITE, &relay_chn_nvs); 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); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to open NVS namespace '%s'", CONFIG_RELAY_CHN_NVS_NAMESPACE);
#endif // CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION #endif // CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION
return ESP_OK; 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) esp_err_t relay_chn_nvs_set_direction(uint8_t ch, relay_chn_direction_t direction)
{ {
uint8_t direction_val; relay_chn_nvs_msg_t msg = {
esp_err_t ret = nvs_get_u8(relay_chn_nvs, RELAY_CHN_KEY_DIR, &direction_val); .op = RELAY_CHN_NVS_OP_SET_DIRECTION,
if (ret == ESP_ERR_NVS_NOT_FOUND) { .ch = ch,
// The key does not exist yet, set it to zero which is the default direction .data.data_u8 = (uint8_t) direction,
direction_val = RELAY_CHN_DIRECTION_DEFAULT; };
} else if (ret != ESP_OK) { 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)); 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 &= ~(1 << ch); // Clear the bit for the channel
direction_val |= (((uint8_t) direction) << ch); // Set the new direction bit direction_val |= (((uint8_t) direction) << ch); // Set the new direction bit
ret = nvs_set_u8(relay_chn_nvs, RELAY_CHN_KEY_DIR, direction_val); 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); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set direction for channel %d", ch);
return nvs_commit(relay_chn_nvs); return ESP_OK;
} }
esp_err_t relay_chn_nvs_get_direction(uint8_t ch, relay_chn_direction_t *direction) 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"); ESP_RETURN_ON_FALSE(direction != NULL, ESP_ERR_INVALID_ARG, TAG, "Direction pointer is NULL");
uint8_t direction_val; uint8_t direction_val;
esp_err_t ret = nvs_get_u8(relay_chn_nvs, RELAY_CHN_KEY_DIR, &direction_val); esp_err_t ret = nvs_get_u8(s_relay_chn_nvs, RELAY_CHN_KEY_DIR, &direction_val);
if (ret != ESP_OK) { if (ret == ESP_ERR_NVS_NOT_FOUND) {
return ret; // Return error if the key does not exist *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); *direction = (relay_chn_direction_t)((direction_val >> ch) & 0x01);
return ESP_OK; return ESP_OK;
} }
#if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
esp_err_t relay_chn_nvs_set_run_limit(uint8_t ch, uint16_t time_sec) esp_err_t relay_chn_nvs_set_run_limit(uint8_t ch, uint16_t limit_sec)
{ {
esp_err_t ret = nvs_set_u16(relay_chn_nvs, RELAY_CHN_KEY_RLIM(ch), time_sec); relay_chn_nvs_msg_t msg = {
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set run limit for channel %d", ch); .op = RELAY_CHN_NVS_OP_SET_RUN_LIMIT,
return nvs_commit(relay_chn_nvs); .ch = ch,
.data.data_u16 = limit_sec,
};
return relay_chn_nvs_enqueue(&msg, "SET_RUN_LIMIT");
} }
esp_err_t relay_chn_nvs_get_run_limit(uint8_t ch, uint16_t *time_sec) static esp_err_t relay_chn_nvs_task_set_run_limit(uint8_t ch, uint16_t limit_sec)
{ {
ESP_RETURN_ON_FALSE(time_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_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;
}
return nvs_get_u16(relay_chn_nvs, RELAY_CHN_KEY_RLIM(ch), time_sec); 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 #endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1
#if CONFIG_RELAY_CHN_ENABLE_TILTING #if CONFIG_RELAY_CHN_ENABLE_TILTING
esp_err_t relay_chn_nvs_set_tilt_sensitivity(uint8_t ch, uint8_t sensitivity) esp_err_t relay_chn_nvs_set_tilt_sensitivity(uint8_t ch, uint8_t sensitivity)
{ {
esp_err_t ret = nvs_set_u8(relay_chn_nvs, RELAY_CHN_KEY_TSENS(ch), sensitivity); relay_chn_nvs_msg_t msg = {
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set tilt sensitivity for channel %d", ch); .op = RELAY_CHN_NVS_OP_SET_TILT_SENSITIVITY,
return nvs_commit(relay_chn_nvs); .ch = ch,
.data.data_u8 = sensitivity,
};
return relay_chn_nvs_enqueue(&msg, "SET_TILT_SENSITIVITY");
} }
esp_err_t relay_chn_nvs_get_tilt_sensitivity(uint8_t ch, uint8_t *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_RETURN_ON_FALSE(sensitivity != NULL, ESP_ERR_INVALID_ARG, TAG, "Sensitivity pointer is NULL");
return nvs_get_u8(relay_chn_nvs, RELAY_CHN_KEY_TSENS(ch), 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_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) esp_err_t relay_chn_nvs_set_tilt_count(uint8_t ch, uint16_t tilt_count)
{ {
esp_err_t ret; relay_chn_nvs_msg_t msg = {
ret = nvs_set_u16(relay_chn_nvs, RELAY_CHN_KEY_TCNT(ch), tilt_count); .op = RELAY_CHN_NVS_OP_SET_TILT_COUNT,
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to save tilt_count tilt counter"); .ch = ch,
return nvs_commit(relay_chn_nvs); .data.data_u16 = tilt_count,
};
return relay_chn_nvs_enqueue(&msg, "SET_TILT_COUNT");
} }
esp_err_t relay_chn_nvs_get_tilt_count(uint8_t ch, uint16_t *tilt_count) static esp_err_t relay_chn_nvs_task_set_tilt_count(uint8_t ch, uint16_t tilt_count)
{ {
ESP_RETURN_ON_FALSE(tilt_count != NULL, esp_err_t ret;
ESP_ERR_INVALID_ARG, TAG, "Counter pointers are NULL"); #if CONFIG_RELAY_CHN_COUNT > 1
return nvs_get_u16(relay_chn_nvs, RELAY_CHN_KEY_TCNT(ch), tilt_count); 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 #endif // CONFIG_RELAY_CHN_ENABLE_TILTING
esp_err_t relay_chn_nvs_erase_all() esp_err_t relay_chn_nvs_erase_all()
{ {
// Erase all key-value pairs in the relay_chn NVS namespace relay_chn_nvs_msg_t msg = {
esp_err_t ret = nvs_erase_all(relay_chn_nvs); .op = RELAY_CHN_NVS_OP_ERASE_ALL,
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to erase all keys in NVS namespace '%s'", CONFIG_RELAY_CHN_NVS_NAMESPACE); };
return relay_chn_nvs_enqueue(&msg, "ERASE_ALL");
// Commit the changes
return nvs_commit(relay_chn_nvs);
} }
esp_err_t relay_chn_nvs_deinit() static esp_err_t do_nvs_deinit()
{ {
nvs_close(relay_chn_nvs); 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; 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);
}

View File

@@ -17,9 +17,9 @@
static const char *TAG = "RELAY_CHN_OUTPUT"; static const char *TAG = "RELAY_CHN_OUTPUT";
#if CONFIG_RELAY_CHN_COUNT > 1 #if CONFIG_RELAY_CHN_COUNT > 1
static relay_chn_output_t outputs[CONFIG_RELAY_CHN_COUNT]; static relay_chn_output_t s_outputs[CONFIG_RELAY_CHN_COUNT];
#else #else
static relay_chn_output_t output; static relay_chn_output_t s_output;
#endif #endif
@@ -50,7 +50,7 @@ static esp_err_t relay_chn_output_ctl_init(relay_chn_output_t *output,
"Invalid GPIO pin number for forward_pin: %d", forward_pin); "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, 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); "Invalid GPIO pin number for reverse_pin: %d", reverse_pin);
// Check if the GPIOs are valid // Check if the GPIOs are valid
esp_err_t ret; esp_err_t ret;
// Initialize the GPIOs // Initialize the GPIOs
@@ -58,7 +58,7 @@ static esp_err_t relay_chn_output_ctl_init(relay_chn_output_t *output,
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to reset GPIO forward pin: %d", 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); 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); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set GPIO direction for forward pin: %d", forward_pin);
ret = gpio_reset_pin(reverse_pin); ret = gpio_reset_pin(reverse_pin);
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to reset GPIO reverse pin: %d", 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); ret = gpio_set_direction(reverse_pin, GPIO_MODE_OUTPUT);
@@ -75,13 +75,9 @@ static esp_err_t relay_chn_output_ctl_init(relay_chn_output_t *output,
#if CONFIG_RELAY_CHN_ENABLE_NVS #if CONFIG_RELAY_CHN_ENABLE_NVS
static esp_err_t relay_chn_output_load_direction(uint8_t ch, relay_chn_direction_t *direction) static esp_err_t relay_chn_output_load_direction(uint8_t ch, relay_chn_direction_t *direction)
{ {
esp_err_t ret = relay_chn_nvs_get_direction(ch, direction); // relay_chn_nvs_get_direction handles the NOT_FOUND case and returns the provided default value.
if (ret == ESP_ERR_NVS_NOT_FOUND) { esp_err_t ret = relay_chn_nvs_get_direction(ch, direction, RELAY_CHN_DIRECTION_DEFAULT);
// If the key does not exist, use the default direction ESP_RETURN_ON_ERROR(ret, TAG, "Failed to get direction from storage for channel %d: %s", ch, esp_err_to_name(ret));
*direction = RELAY_CHN_DIRECTION_DEFAULT;
} else if (ret != ESP_OK) {
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; return ESP_OK;
} }
#endif #endif
@@ -91,10 +87,10 @@ esp_err_t relay_chn_output_init(const uint8_t* gpio_map, uint8_t gpio_count)
esp_err_t ret; esp_err_t ret;
ret = relay_chn_output_check_gpio_capabilities(gpio_count); ret = relay_chn_output_check_gpio_capabilities(gpio_count);
ESP_RETURN_ON_ERROR(ret, TAG, "Device does not support the provided GPIOs"); ESP_RETURN_ON_ERROR(ret, TAG, "Device does not support the provided GPIOs");
#if CONFIG_RELAY_CHN_COUNT > 1 #if CONFIG_RELAY_CHN_COUNT > 1
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
relay_chn_output_t* output = &outputs[i]; relay_chn_output_t* output = &s_outputs[i];
int gpio_index = i << 1; // gpio_index = i * 2 int gpio_index = i << 1; // gpio_index = i * 2
gpio_num_t forward_pin = (gpio_num_t) gpio_map[gpio_index]; 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]; gpio_num_t reverse_pin = (gpio_num_t) gpio_map[gpio_index + 1];
@@ -115,7 +111,7 @@ esp_err_t relay_chn_output_init(const uint8_t* gpio_map, uint8_t gpio_count)
ret = relay_chn_output_load_direction(0, &direction); ret = relay_chn_output_load_direction(0, &direction);
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load direction from storage for channel %d", 0); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load direction from storage for channel %d", 0);
#endif #endif
ret = relay_chn_output_ctl_init(&output, gpio_map[0], gpio_map[1], direction); 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"); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize relay channel");
#endif #endif
return ESP_OK; return ESP_OK;
@@ -131,10 +127,10 @@ void relay_chn_output_deinit()
{ {
#if CONFIG_RELAY_CHN_COUNT > 1 #if CONFIG_RELAY_CHN_COUNT > 1
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
relay_chn_output_ctl_deinit(&outputs[i]); relay_chn_output_ctl_deinit(&s_outputs[i]);
} }
#else #else
relay_chn_output_ctl_deinit(&output); relay_chn_output_ctl_deinit(&s_output);
#endif // CONFIG_RELAY_CHN_COUNT > 1 #endif // CONFIG_RELAY_CHN_COUNT > 1
} }
@@ -144,17 +140,17 @@ relay_chn_output_t *relay_chn_output_get(uint8_t chn_id)
if (!relay_chn_is_channel_id_valid(chn_id)) { if (!relay_chn_is_channel_id_valid(chn_id)) {
return NULL; return NULL;
} }
return &outputs[chn_id]; return &s_outputs[chn_id];
} }
relay_chn_output_t *relay_chn_output_get_all(void) relay_chn_output_t *relay_chn_output_get_all(void)
{ {
return outputs; return s_outputs;
} }
#else #else
relay_chn_output_t *relay_chn_output_get(void) relay_chn_output_t *relay_chn_output_get(void)
{ {
return &output; return &s_output;
} }
#endif // CONFIG_RELAY_CHN_COUNT > 1 #endif // CONFIG_RELAY_CHN_COUNT > 1
@@ -189,15 +185,15 @@ void relay_chn_output_flip(relay_chn_output_t *output)
output->forward_pin = output->reverse_pin; output->forward_pin = output->reverse_pin;
output->reverse_pin = temp; output->reverse_pin = temp;
// Flip the direction // Flip the direction
output->direction = (output->direction == RELAY_CHN_DIRECTION_DEFAULT) output->direction = (output->direction == RELAY_CHN_DIRECTION_DEFAULT)
? RELAY_CHN_DIRECTION_FLIPPED ? RELAY_CHN_DIRECTION_FLIPPED
: RELAY_CHN_DIRECTION_DEFAULT; : RELAY_CHN_DIRECTION_DEFAULT;
#if CONFIG_RELAY_CHN_ENABLE_NVS #if CONFIG_RELAY_CHN_ENABLE_NVS
uint8_t ch = 0; uint8_t ch = 0;
#if CONFIG_RELAY_CHN_COUNT > 1 #if CONFIG_RELAY_CHN_COUNT > 1
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
if (output == &outputs[i]) { if (output == &s_outputs[i]) {
ch = i; ch = i;
break; break;
} }

View File

@@ -9,21 +9,21 @@
#if CONFIG_RELAY_CHN_COUNT > 1 #if CONFIG_RELAY_CHN_COUNT > 1
static relay_chn_run_info_t run_infos[CONFIG_RELAY_CHN_COUNT]; static relay_chn_run_info_t s_run_infos[CONFIG_RELAY_CHN_COUNT];
#else #else
static relay_chn_run_info_t run_info; static relay_chn_run_info_t s_run_info;
#endif #endif
void relay_chn_run_info_init() void relay_chn_run_info_init()
{ {
#if CONFIG_RELAY_CHN_COUNT > 1 #if CONFIG_RELAY_CHN_COUNT > 1
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
run_infos[i].last_run_cmd = RELAY_CHN_CMD_NONE; s_run_infos[i].last_run_cmd = RELAY_CHN_CMD_NONE;
run_infos[i].last_run_cmd_time_ms = 0; s_run_infos[i].last_run_cmd_time_ms = 0;
} }
#else #else
run_info.last_run_cmd = RELAY_CHN_CMD_NONE; s_run_info.last_run_cmd = RELAY_CHN_CMD_NONE;
run_info.last_run_cmd_time_ms = 0; s_run_info.last_run_cmd_time_ms = 0;
#endif #endif
} }
@@ -33,17 +33,17 @@ relay_chn_run_info_t *relay_chn_run_info_get(uint8_t chn_id)
if (!relay_chn_is_channel_id_valid(chn_id)) { if (!relay_chn_is_channel_id_valid(chn_id)) {
return NULL; return NULL;
} }
return &run_infos[chn_id]; return &s_run_infos[chn_id];
} }
relay_chn_run_info_t *relay_chn_run_info_get_all() relay_chn_run_info_t *relay_chn_run_info_get_all()
{ {
return run_infos; return s_run_infos;
} }
#else #else
relay_chn_run_info_t *relay_chn_run_info_get() relay_chn_run_info_t *relay_chn_run_info_get()
{ {
return &run_info; return &s_run_info;
} }
#endif // CONFIG_RELAY_CHN_COUNT > 1 #endif // CONFIG_RELAY_CHN_COUNT > 1

View File

@@ -5,6 +5,7 @@
*/ */
#include "esp_check.h" #include "esp_check.h"
#include "relay_chn.h"
#include "relay_chn_core.h" #include "relay_chn_core.h"
#include "relay_chn_output.h" #include "relay_chn_output.h"
#include "relay_chn_run_info.h" #include "relay_chn_run_info.h"
@@ -22,7 +23,7 @@ static const char *TAG = "RELAY_CHN_TILT";
/**@{*/ /**@{*/
/* /*
* Tilt Pattern Timing Definitions * Tilt Pattern Timing Definitions
* *
* The min and max timing definitions as well as the default 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. * These definitions are used to define and adjust the tilt sensitivity.
*/ */
@@ -39,6 +40,7 @@ static const char *TAG = "RELAY_CHN_TILT";
* 100 / (RELAY_CHN_TILT_RUN_MAX_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. /// @brief Tilt steps.
typedef enum { typedef enum {
@@ -70,9 +72,9 @@ typedef struct relay_chn_tilt_ctl {
#if CONFIG_RELAY_CHN_COUNT > 1 #if CONFIG_RELAY_CHN_COUNT > 1
static relay_chn_tilt_ctl_t tilt_ctls[CONFIG_RELAY_CHN_COUNT]; static relay_chn_tilt_ctl_t s_tilt_ctls[CONFIG_RELAY_CHN_COUNT];
#else #else
static relay_chn_tilt_ctl_t tilt_ctl; static relay_chn_tilt_ctl_t s_tilt_ctl;
#endif #endif
@@ -84,35 +86,92 @@ static uint32_t relay_chn_tilt_get_required_timing_before_tilting(relay_chn_tilt
return 0; return 0;
else if (cmd == RELAY_CHN_TILT_CMD_REVERSE && last_run_cmd == RELAY_CHN_CMD_FORWARD) else if (cmd == RELAY_CHN_TILT_CMD_REVERSE && last_run_cmd == RELAY_CHN_CMD_FORWARD)
return 0; 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 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; 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; 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. // 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) 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) { 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 // 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"); ESP_LOGD(TAG, "relay_chn_tilt_issue_cmd: Tilt will not be executed since the channel hasn't been run yet");
return; return;
} }
if (tilt_ctl->cmd == cmd) { if (tilt_ctl->cmd == cmd) {
ESP_LOGD(TAG, "relay_chn_tilt_issue_cmd: There is already a tilt command in progress!"); ESP_LOGD(TAG, "relay_chn_tilt_issue_cmd: There is already a tilt command in progress!");
return; 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 // Set the command that will be processed
tilt_ctl->cmd = cmd; tilt_ctl->cmd = cmd;
ESP_LOGI(TAG, "relay_chn_tilt_issue_cmd: Command-chn: %d-%d", cmd, tilt_ctl->chn_ctl->id); // TODO delete
switch (tilt_ctl->chn_ctl->state) { switch (tilt_ctl->chn_ctl->state) {
case RELAY_CHN_STATE_IDLE: case RELAY_CHN_STATE_IDLE:
// Relay channel is free, tilt can be issued immediately // Relay channel is free, tilt can be issued immediately
relay_chn_tilt_dispatch_cmd(tilt_ctl, cmd); relay_chn_tilt_dispatch_cmd(tilt_ctl, cmd);
break; break;
case RELAY_CHN_STATE_FORWARD_PENDING: case RELAY_CHN_STATE_FORWARD_PENDING:
case RELAY_CHN_STATE_REVERSE_PENDING: case RELAY_CHN_STATE_REVERSE_PENDING:
// Issue a stop command first so that the timer and pending cmd get cleared // Issue a stop command first so that the timer and pending cmd get cleared
@@ -126,7 +185,7 @@ static void relay_chn_tilt_issue_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_t
} else { } else {
// Channel needs timing before running tilting action, schedule it // Channel needs timing before running tilting action, schedule it
tilt_ctl->step = RELAY_CHN_TILT_STEP_PENDING; tilt_ctl->step = RELAY_CHN_TILT_STEP_PENDING;
relay_chn_start_esp_timer_once(tilt_ctl->tilt_timer, req_timing_ms); relay_chn_tilt_start_timer_or_stop(tilt_ctl, tilt_ctl->tilt_timer, req_timing_ms, "pending tilt");
} }
break; break;
} }
@@ -137,7 +196,7 @@ static void relay_chn_tilt_issue_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_t
relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP); relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP);
// Schedule for tilting // Schedule for tilting
tilt_ctl->step = RELAY_CHN_TILT_STEP_PENDING; tilt_ctl->step = RELAY_CHN_TILT_STEP_PENDING;
relay_chn_start_esp_timer_once(tilt_ctl->tilt_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS); 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) { } else if (cmd == RELAY_CHN_TILT_CMD_REVERSE) {
// Stop the running channel first // Stop the running channel first
relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP); relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP);
@@ -152,7 +211,7 @@ static void relay_chn_tilt_issue_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_t
relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP); relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP);
// Schedule for tilting // Schedule for tilting
tilt_ctl->step = RELAY_CHN_TILT_STEP_PENDING; tilt_ctl->step = RELAY_CHN_TILT_STEP_PENDING;
relay_chn_start_esp_timer_once(tilt_ctl->tilt_timer, CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS); 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) { } else if (cmd == RELAY_CHN_TILT_CMD_FORWARD) {
// Stop the running channel first // Stop the running channel first
relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP); relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP);
@@ -162,7 +221,7 @@ static void relay_chn_tilt_issue_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_t
break; break;
default: default:
ESP_LOGD(TAG, "relay_chn_tilt_issue_cmd: Unexpected relay channel state: %s!", relay_chn_state_str(tilt_ctl->chn_ctl->state)); ESP_LOGD(TAG, "relay_chn_tilt_issue_cmd: Unexpected relay channel state: %s!", relay_chn_state_to_str(tilt_ctl->chn_ctl->state));
} }
} }
@@ -177,13 +236,17 @@ static void relay_chn_tilt_issue_auto(relay_chn_tilt_ctl_t *tilt_ctl)
} }
} }
uint8_t relay_chn_tilt_get_default_sensitivity()
{
return RELAY_CHN_TILT_DEFAULT_SENSITIVITY;
}
#if CONFIG_RELAY_CHN_COUNT > 1 #if CONFIG_RELAY_CHN_COUNT > 1
static void relay_chn_tilt_issue_cmd_on_all_channels(relay_chn_tilt_cmd_t cmd) 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++) { for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
relay_chn_tilt_ctl_t* tilt_ctl = &tilt_ctls[i]; relay_chn_tilt_ctl_t* tilt_ctl = &s_tilt_ctls[i];
ESP_LOGI(TAG, "issue_cmd_on_all_channels: Command|chn|ctl.id: %d|%d|%d", cmd, i, tilt_ctl->chn_ctl->id); // TODO delete
relay_chn_tilt_issue_cmd(tilt_ctl, cmd); relay_chn_tilt_issue_cmd(tilt_ctl, cmd);
} }
} }
@@ -191,21 +254,21 @@ static void relay_chn_tilt_issue_cmd_on_all_channels(relay_chn_tilt_cmd_t cmd)
void relay_chn_tilt_auto(uint8_t chn_id) void relay_chn_tilt_auto(uint8_t chn_id)
{ {
if (relay_chn_is_channel_id_valid(chn_id)) { if (relay_chn_is_channel_id_valid(chn_id)) {
relay_chn_tilt_issue_auto(&tilt_ctls[chn_id]); relay_chn_tilt_issue_auto(&s_tilt_ctls[chn_id]);
} }
} }
void relay_chn_tilt_auto_all() void relay_chn_tilt_auto_all()
{ {
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
relay_chn_tilt_issue_auto(&tilt_ctls[i]); relay_chn_tilt_issue_auto(&s_tilt_ctls[i]);
} }
} }
void relay_chn_tilt_forward(uint8_t chn_id) void relay_chn_tilt_forward(uint8_t chn_id)
{ {
if (relay_chn_is_channel_id_valid(chn_id)) { if (relay_chn_is_channel_id_valid(chn_id)) {
relay_chn_tilt_issue_cmd(&tilt_ctls[chn_id], RELAY_CHN_TILT_CMD_FORWARD); relay_chn_tilt_issue_cmd(&s_tilt_ctls[chn_id], RELAY_CHN_TILT_CMD_FORWARD);
} }
} }
@@ -217,7 +280,7 @@ void relay_chn_tilt_forward_all()
void relay_chn_tilt_reverse(uint8_t chn_id) void relay_chn_tilt_reverse(uint8_t chn_id)
{ {
if (relay_chn_is_channel_id_valid(chn_id)) { if (relay_chn_is_channel_id_valid(chn_id)) {
relay_chn_tilt_issue_cmd(&tilt_ctls[chn_id], RELAY_CHN_TILT_CMD_REVERSE); relay_chn_tilt_issue_cmd(&s_tilt_ctls[chn_id], RELAY_CHN_TILT_CMD_REVERSE);
} }
} }
@@ -228,8 +291,8 @@ void relay_chn_tilt_reverse_all()
void relay_chn_tilt_stop(uint8_t chn_id) void relay_chn_tilt_stop(uint8_t chn_id)
{ {
if (!relay_chn_is_channel_id_valid(chn_id)) { if (relay_chn_is_channel_id_valid(chn_id)) {
relay_chn_tilt_dispatch_cmd(&tilt_ctls[chn_id], RELAY_CHN_TILT_CMD_STOP); relay_chn_tilt_dispatch_cmd(&s_tilt_ctls[chn_id], RELAY_CHN_TILT_CMD_STOP);
} }
} }
@@ -242,22 +305,22 @@ void relay_chn_tilt_stop_all()
void relay_chn_tilt_auto() void relay_chn_tilt_auto()
{ {
relay_chn_tilt_issue_auto(&tilt_ctl); relay_chn_tilt_issue_auto(&s_tilt_ctl);
} }
void relay_chn_tilt_forward() void relay_chn_tilt_forward()
{ {
relay_chn_tilt_issue_cmd(&tilt_ctl, RELAY_CHN_TILT_CMD_FORWARD); relay_chn_tilt_issue_cmd(&s_tilt_ctl, RELAY_CHN_TILT_CMD_FORWARD);
} }
void relay_chn_tilt_reverse() void relay_chn_tilt_reverse()
{ {
relay_chn_tilt_issue_cmd(&tilt_ctl, RELAY_CHN_TILT_CMD_REVERSE); relay_chn_tilt_issue_cmd(&s_tilt_ctl, RELAY_CHN_TILT_CMD_REVERSE);
} }
void relay_chn_tilt_stop() void relay_chn_tilt_stop()
{ {
relay_chn_tilt_dispatch_cmd(&tilt_ctl, RELAY_CHN_TILT_CMD_STOP); relay_chn_tilt_issue_cmd(&s_tilt_ctl, RELAY_CHN_TILT_CMD_STOP);
} }
#endif // CONFIG_RELAY_CHN_COUNT > 1 #endif // CONFIG_RELAY_CHN_COUNT > 1
@@ -296,7 +359,7 @@ static void relay_chn_tilt_compute_set_sensitivity(relay_chn_tilt_ctl_t *tilt_ct
uint32_t tilt_run_time_ms = 0, tilt_pause_time_ms = 0; 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_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); 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, relay_chn_tilt_set_timing_values(&tilt_ctl->tilt_timing,
sensitivity, sensitivity,
tilt_run_time_ms, tilt_run_time_ms,
@@ -308,8 +371,9 @@ static void relay_chn_tilt_compute_set_sensitivity(relay_chn_tilt_ctl_t *tilt_ct
void relay_chn_tilt_set_sensitivity(uint8_t chn_id, uint8_t sensitivity) void relay_chn_tilt_set_sensitivity(uint8_t chn_id, uint8_t sensitivity)
{ {
if (relay_chn_is_channel_id_valid(chn_id)) { if (relay_chn_is_channel_id_valid(chn_id)) {
relay_chn_tilt_compute_set_sensitivity(&tilt_ctls[chn_id], sensitivity); ADJUST_TILT_SENS_BOUNDARIES(sensitivity);
relay_chn_tilt_compute_set_sensitivity(&s_tilt_ctls[chn_id], sensitivity);
#if CONFIG_RELAY_CHN_ENABLE_NVS #if CONFIG_RELAY_CHN_ENABLE_NVS
relay_chn_nvs_set_tilt_sensitivity(chn_id, sensitivity); relay_chn_nvs_set_tilt_sensitivity(chn_id, sensitivity);
#endif // CONFIG_RELAY_CHN_ENABLE_NVS #endif // CONFIG_RELAY_CHN_ENABLE_NVS
@@ -319,14 +383,15 @@ void relay_chn_tilt_set_sensitivity(uint8_t chn_id, uint8_t sensitivity)
esp_err_t relay_chn_tilt_set_sensitivity_all(uint8_t *sensitivities) 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"); 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++) { for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
uint8_t *src_sensitivity = &sensitivities[i]; uint8_t *src_sensitivity = &sensitivities[i];
if (src_sensitivity == NULL) { 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); ESP_LOGW(TAG, "set_sensitivity_all: Run limits have been set until channel %d since sensitivities[%d] is NULL", i, i);
break; break;
} }
relay_chn_tilt_compute_set_sensitivity(&tilt_ctls[i], *src_sensitivity); ADJUST_TILT_SENS_BOUNDARIES(*src_sensitivity);
relay_chn_tilt_compute_set_sensitivity(&s_tilt_ctls[i], *src_sensitivity);
#if CONFIG_RELAY_CHN_ENABLE_NVS #if CONFIG_RELAY_CHN_ENABLE_NVS
relay_chn_nvs_set_tilt_sensitivity(i, *src_sensitivity); relay_chn_nvs_set_tilt_sensitivity(i, *src_sensitivity);
#endif // CONFIG_RELAY_CHN_ENABLE_NVS #endif // CONFIG_RELAY_CHN_ENABLE_NVS
@@ -337,7 +402,8 @@ esp_err_t relay_chn_tilt_set_sensitivity_all(uint8_t *sensitivities)
void relay_chn_tilt_set_sensitivity_all_with(uint8_t sensitivity) void relay_chn_tilt_set_sensitivity_all_with(uint8_t sensitivity)
{ {
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
relay_chn_tilt_compute_set_sensitivity(&tilt_ctls[i], sensitivity); ADJUST_TILT_SENS_BOUNDARIES(sensitivity);
relay_chn_tilt_compute_set_sensitivity(&s_tilt_ctls[i], sensitivity);
#if CONFIG_RELAY_CHN_ENABLE_NVS #if CONFIG_RELAY_CHN_ENABLE_NVS
relay_chn_nvs_set_tilt_sensitivity(i, sensitivity); relay_chn_nvs_set_tilt_sensitivity(i, sensitivity);
#endif // CONFIG_RELAY_CHN_ENABLE_NVS #endif // CONFIG_RELAY_CHN_ENABLE_NVS
@@ -347,20 +413,20 @@ void relay_chn_tilt_set_sensitivity_all_with(uint8_t sensitivity)
uint8_t relay_chn_tilt_get_sensitivity(uint8_t chn_id) uint8_t relay_chn_tilt_get_sensitivity(uint8_t chn_id)
{ {
return relay_chn_is_channel_id_valid(chn_id) ? return relay_chn_is_channel_id_valid(chn_id) ?
tilt_ctls[chn_id].tilt_timing.sensitivity : 0; s_tilt_ctls[chn_id].tilt_timing.sensitivity : 0;
} }
esp_err_t relay_chn_tilt_get_sensitivity_all(uint8_t *sensitivities) 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"); 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++) { for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
uint8_t *dest_sensitivity = &sensitivities[i]; uint8_t *dest_sensitivity = &sensitivities[i];
if (dest_sensitivity == NULL) { if (dest_sensitivity == NULL) {
ESP_LOGW(TAG, "get_sensitivity_all: Sensitivites have been copied until channel %d since sensitivities[%d] is NULL", i, i); ESP_LOGW(TAG, "get_sensitivity_all: Sensitivites have been copied until channel %d since sensitivities[%d] is NULL", i, i);
break; break;
} }
*dest_sensitivity = tilt_ctls[i].tilt_timing.sensitivity; *dest_sensitivity = s_tilt_ctls[i].tilt_timing.sensitivity;
} }
return ESP_OK; return ESP_OK;
} }
@@ -369,8 +435,9 @@ esp_err_t relay_chn_tilt_get_sensitivity_all(uint8_t *sensitivities)
void relay_chn_tilt_set_sensitivity(uint8_t sensitivity) void relay_chn_tilt_set_sensitivity(uint8_t sensitivity)
{ {
relay_chn_tilt_compute_set_sensitivity(&tilt_ctl, sensitivity); ADJUST_TILT_SENS_BOUNDARIES(sensitivity);
relay_chn_tilt_compute_set_sensitivity(&s_tilt_ctl, sensitivity);
#if CONFIG_RELAY_CHN_ENABLE_NVS #if CONFIG_RELAY_CHN_ENABLE_NVS
relay_chn_nvs_set_tilt_sensitivity(0, sensitivity); relay_chn_nvs_set_tilt_sensitivity(0, sensitivity);
#endif // CONFIG_RELAY_CHN_ENABLE_NVS #endif // CONFIG_RELAY_CHN_ENABLE_NVS
@@ -378,7 +445,7 @@ void relay_chn_tilt_set_sensitivity(uint8_t sensitivity)
uint8_t relay_chn_tilt_get_sensitivity() uint8_t relay_chn_tilt_get_sensitivity()
{ {
return tilt_ctl.tilt_timing.sensitivity; return s_tilt_ctl.tilt_timing.sensitivity;
} }
#endif // CONFIG_RELAY_CHN_COUNT > 1 #endif // CONFIG_RELAY_CHN_COUNT > 1
@@ -393,25 +460,25 @@ void relay_chn_tilt_reset_count(relay_chn_tilt_ctl_t *tilt_ctl)
/** /**
* @brief Update tilt count automatically and return the current value. * @brief Update tilt count automatically and return the current value.
* *
* This helper function updates the relevant tilt count depending on the * This helper function updates the relevant tilt count depending on the
* last run info and helps the tilt module in deciding whether the requested * last run info and helps the tilt module in deciding whether the requested
* tilt should execute or not. * tilt should execute or not.
* *
* This is useful to control reverse tilting for the same direction particularly. * This is useful to control reverse tilting for the same direction particularly.
* For example: * For example:
* - If the channel's last run was FORWARD and a TILT_FORWARD is requested, * - 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 * then the tilt count will count up on the relay_chn_tilt_ctl_t::tilt_count
* and the function will return the actual count. * and the function will return the actual count.
* - If the channel's last run was FORWARD and a TILT_REVERSE is requested, * - 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, * 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 * and then it will count down and return the actual count if it is greater
* than 0, else the function will return 0. * than 0, else the function will return 0.
* - If the tilt command is irrelevant then 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. * - If the last run is irrelevant then the function will return 0.
* *
* @param tilt_ctl The relay channel handle. * @param tilt_ctl The relay channel handle.
* *
* @return The actual value of the relevant count. * @return The actual value of the relevant count.
* @return 1 if the last tilt_count was 1 and decremented to 0. * @return 1 if the last tilt_count was 1 and decremented to 0.
* @return 0 if: * @return 0 if:
@@ -490,11 +557,14 @@ static void relay_chn_tilt_execute_stop(relay_chn_tilt_ctl_t *tilt_ctl)
if (relay_chn_output_stop(tilt_ctl->chn_ctl->output) != ESP_OK) { 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); ESP_LOGE(TAG, "relay_chn_tilt_execute_stop: Failed to output stop for relay channel #%d!", tilt_ctl->chn_ctl->id);
} }
relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP); relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_IDLE);
#if CONFIG_RELAY_CHN_ENABLE_NVS #if CONFIG_RELAY_CHN_ENABLE_NVS
// Start the flush debounce timer // Start the flush debounce timer
relay_chn_start_esp_timer_once(tilt_ctl->flush_timer, RELAY_CHN_TILT_FLUSH_DEBOUNCE_MS); 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 #endif
} }
@@ -503,11 +573,11 @@ 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) { 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); 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 // Stop tilting because of the error
relay_chn_tilt_dispatch_cmd(tilt_ctl, RELAY_CHN_TILT_CMD_STOP); relay_chn_tilt_execute_stop(tilt_ctl);
return; return;
} }
// Set the move time timer // Set the move time timer
relay_chn_start_esp_timer_once(tilt_ctl->tilt_timer, tilt_ctl->tilt_timing.move_time_ms); 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 // Set to pause step
tilt_ctl->step = RELAY_CHN_TILT_STEP_PAUSE; tilt_ctl->step = RELAY_CHN_TILT_STEP_PAUSE;
} }
@@ -517,11 +587,11 @@ 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) { 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); 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 // Stop tilting because of the error
relay_chn_tilt_dispatch_cmd(tilt_ctl, RELAY_CHN_TILT_CMD_STOP); relay_chn_tilt_execute_stop(tilt_ctl);
return; return;
} }
// Set the move time timer // Set the move time timer
relay_chn_start_esp_timer_once(tilt_ctl->tilt_timer, tilt_ctl->tilt_timing.move_time_ms); 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 // Set to pause step
tilt_ctl->step = RELAY_CHN_TILT_STEP_PAUSE; tilt_ctl->step = RELAY_CHN_TILT_STEP_PAUSE;
} }
@@ -532,7 +602,7 @@ static void relay_chn_tilt_execute_pause(relay_chn_tilt_ctl_t *tilt_ctl)
if (relay_chn_output_stop(tilt_ctl->chn_ctl->output) != ESP_OK) { 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); 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 // Stop tilting because of the error
relay_chn_tilt_dispatch_cmd(tilt_ctl, RELAY_CHN_TILT_CMD_STOP); relay_chn_tilt_execute_stop(tilt_ctl);
return; return;
} }
@@ -540,12 +610,12 @@ static void relay_chn_tilt_execute_pause(relay_chn_tilt_ctl_t *tilt_ctl)
if (relay_chn_tilt_count_update(tilt_ctl) == 0) { if (relay_chn_tilt_count_update(tilt_ctl) == 0) {
ESP_LOGD(TAG, "relay_chn_tilt_execute_pause: Relay channel cannot tilt anymore"); ESP_LOGD(TAG, "relay_chn_tilt_execute_pause: Relay channel cannot tilt anymore");
// Stop tilting since the tilting limit has been reached // Stop tilting since the tilting limit has been reached
relay_chn_tilt_dispatch_cmd(tilt_ctl, RELAY_CHN_TILT_CMD_STOP); relay_chn_tilt_execute_stop(tilt_ctl);
return; return;
} }
// Set the pause time timer // Set the pause time timer
relay_chn_start_esp_timer_once(tilt_ctl->tilt_timer, tilt_ctl->tilt_timing.pause_time_ms); 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 // Set to move step
tilt_ctl->step = RELAY_CHN_TILT_STEP_MOVE; tilt_ctl->step = RELAY_CHN_TILT_STEP_MOVE;
} }
@@ -553,7 +623,6 @@ static void relay_chn_tilt_execute_pause(relay_chn_tilt_ctl_t *tilt_ctl)
esp_err_t relay_chn_tilt_dispatch_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_tilt_cmd_t cmd) 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); ESP_LOGD(TAG, "relay_chn_tilt_dispatch_cmd: Command: %d", cmd);
ESP_LOGI(TAG, "tilt_dispatch_cmd: Command-chn: %d-%d", cmd, tilt_ctl->chn_ctl->id); // TODO delete
switch(cmd) { switch(cmd) {
case RELAY_CHN_TILT_CMD_STOP: case RELAY_CHN_TILT_CMD_STOP:
@@ -580,7 +649,7 @@ static void relay_chn_tilt_timer_cb(void *arg)
{ {
relay_chn_tilt_ctl_t* tilt_ctl = (relay_chn_tilt_ctl_t*) 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"); ESP_RETURN_VOID_ON_FALSE(tilt_ctl != NULL, TAG, "relay_chn_tilt_timer_cb: timer arg is NULL");
switch (tilt_ctl->step) switch (tilt_ctl->step)
{ {
case RELAY_CHN_TILT_STEP_MOVE: case RELAY_CHN_TILT_STEP_MOVE:
@@ -600,7 +669,7 @@ static void relay_chn_tilt_timer_cb(void *arg)
// Just dispatch the pending tilt command // Just dispatch the pending tilt command
relay_chn_tilt_dispatch_cmd(tilt_ctl, tilt_ctl->cmd); relay_chn_tilt_dispatch_cmd(tilt_ctl, tilt_ctl->cmd);
break; break;
default: default:
break; break;
} }
@@ -609,24 +678,16 @@ static void relay_chn_tilt_timer_cb(void *arg)
#if CONFIG_RELAY_CHN_ENABLE_NVS #if CONFIG_RELAY_CHN_ENABLE_NVS
static esp_err_t relay_chn_tilt_load_sensitivity(uint8_t ch, uint8_t *sensitivity) static esp_err_t relay_chn_tilt_load_sensitivity(uint8_t ch, uint8_t *sensitivity)
{ {
esp_err_t ret = relay_chn_nvs_get_tilt_sensitivity(ch, sensitivity); ESP_RETURN_ON_ERROR(relay_chn_nvs_get_tilt_sensitivity(ch, sensitivity, RELAY_CHN_TILT_DEFAULT_SENSITIVITY),
if (ret == ESP_ERR_NVS_NOT_FOUND) { TAG, "Failed to load tilt sensitivity for channel %d", ch);
*sensitivity = RELAY_CHN_TILT_DEFAULT_SENSITIVITY;
return ESP_OK;
}
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load tilt sensitivity for channel %d", ch);
return ESP_OK; return ESP_OK;
} }
static esp_err_t relay_chn_tilt_load_tilt_count(uint8_t ch, uint16_t *tilt_count) static esp_err_t relay_chn_tilt_load_tilt_count(uint8_t ch, uint16_t *tilt_count)
{ {
esp_err_t ret = relay_chn_nvs_get_tilt_count(ch, tilt_count); ESP_RETURN_ON_ERROR(relay_chn_nvs_get_tilt_count(ch, tilt_count, 0),
if (ret == ESP_ERR_NVS_NOT_FOUND) { TAG, "Failed to load tilt counters for channel %d", ch);
ESP_LOGD(TAG, "relay_chn_tilt_load_tilt_count: No tilt count found in NVS for channel %d, initializing to zero", ch); ESP_LOGD(TAG, "Loaded tilt count for channel %d: %d", ch, *tilt_count);
tilt_count = 0;
return ESP_OK;
}
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load tilt counters for channel %d", ch);
return ESP_OK; return ESP_OK;
} }
#endif // CONFIG_RELAY_CHN_ENABLE_NVS #endif // CONFIG_RELAY_CHN_ENABLE_NVS
@@ -643,7 +704,7 @@ static esp_err_t relay_chn_tilt_ctl_init(relay_chn_tilt_ctl_t *tilt_ctl,
tilt_ctl->chn_ctl = chn_ctl; tilt_ctl->chn_ctl = chn_ctl;
tilt_ctl->chn_ctl->tilt_ctl = tilt_ctl; tilt_ctl->chn_ctl->tilt_ctl = tilt_ctl;
// Create tilt timer for the channel // Create tilt timer for the channel
char timer_name[32]; char timer_name[32];
snprintf(timer_name, sizeof(timer_name), "relay_chn_%2d_tilt_timer", chn_ctl->id); snprintf(timer_name, sizeof(timer_name), "relay_chn_%2d_tilt_timer", chn_ctl->id);
@@ -670,7 +731,7 @@ esp_err_t relay_chn_tilt_init(relay_chn_ctl_t *chn_ctls)
{ {
uint8_t sensitivity; uint8_t sensitivity;
uint16_t tilt_count; uint16_t tilt_count;
#if CONFIG_RELAY_CHN_COUNT > 1 #if CONFIG_RELAY_CHN_COUNT > 1
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
esp_err_t ret; esp_err_t ret;
@@ -683,7 +744,7 @@ esp_err_t relay_chn_tilt_init(relay_chn_ctl_t *chn_ctls)
sensitivity = RELAY_CHN_TILT_DEFAULT_SENSITIVITY; sensitivity = RELAY_CHN_TILT_DEFAULT_SENSITIVITY;
tilt_count = 0; tilt_count = 0;
#endif // CONFIG_RELAY_CHN_ENABLE_NVS == 1 #endif // CONFIG_RELAY_CHN_ENABLE_NVS == 1
ret = relay_chn_tilt_ctl_init(&tilt_ctls[i], &chn_ctls[i], tilt_count, sensitivity); 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); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to init tilt control for channel %d", i);
} }
return ESP_OK; return ESP_OK;
@@ -696,7 +757,7 @@ esp_err_t relay_chn_tilt_init(relay_chn_ctl_t *chn_ctls)
ret = relay_chn_tilt_load_tilt_count(0, &tilt_count); 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); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to load tilt count for channel %d", 0);
#endif // CONFIG_RELAY_CHN_ENABLE_NVS == 1 #endif // CONFIG_RELAY_CHN_ENABLE_NVS == 1
return relay_chn_tilt_ctl_init(&tilt_ctl, chn_ctls, tilt_count, sensitivity); return relay_chn_tilt_ctl_init(&s_tilt_ctl, chn_ctls, tilt_count, sensitivity);
#endif // CONFIG_RELAY_CHN_COUNT > 1 #endif // CONFIG_RELAY_CHN_COUNT > 1
} }
@@ -718,9 +779,9 @@ void relay_chn_tilt_deinit()
{ {
#if CONFIG_RELAY_CHN_COUNT > 1 #if CONFIG_RELAY_CHN_COUNT > 1
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
relay_chn_tilt_ctl_deinit(&tilt_ctls[i]); relay_chn_tilt_ctl_deinit(&s_tilt_ctls[i]);
} }
#else #else
relay_chn_tilt_ctl_deinit(&tilt_ctl); relay_chn_tilt_ctl_deinit(&s_tilt_ctl);
#endif // CONFIG_RELAY_CHN_COUNT > 1 #endif // CONFIG_RELAY_CHN_COUNT > 1
} }

23
test_apps/.vscode/c_cpp_properties.json vendored Normal file
View 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
View 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"
}
]
}

View File

@@ -1,16 +1,18 @@
# === These files must be included in any case === # === These files must be included in any case ===
set(srcs "test_common.c" set(srcs "test_common.c"
"test_relay_chn_notify_common.c"
"test_app_main.c") "test_app_main.c")
set(incdirs ".") set(incdirs "."
"../../private_include")
# === Selective compilation based on channel count === # === Selective compilation based on channel count ===
if(CONFIG_RELAY_CHN_COUNT GREATER 1) if(CONFIG_RELAY_CHN_COUNT GREATER 1)
list(APPEND srcs "test_relay_chn_core_multi.c" list(APPEND srcs "test_relay_chn_core_multi.c"
"test_relay_chn_listener_multi.c") "test_relay_chn_notify_multi.c")
else() else()
list(APPEND srcs "test_relay_chn_core_single.c" list(APPEND srcs "test_relay_chn_core_single.c"
"test_relay_chn_listener_single.c") "test_relay_chn_notify_single.c")
endif() endif()
if(CONFIG_RELAY_CHN_ENABLE_TILTING) if(CONFIG_RELAY_CHN_ENABLE_TILTING)
@@ -22,7 +24,6 @@ if(CONFIG_RELAY_CHN_ENABLE_TILTING)
endif() endif()
if(CONFIG_RELAY_CHN_ENABLE_NVS) if(CONFIG_RELAY_CHN_ENABLE_NVS)
list(APPEND incdirs "../../private_include")
list(APPEND srcs "../../src/relay_chn_nvs.c") list(APPEND srcs "../../src/relay_chn_nvs.c")
if(CONFIG_RELAY_CHN_COUNT GREATER 1) if(CONFIG_RELAY_CHN_COUNT GREATER 1)
list(APPEND srcs "test_relay_chn_nvs_multi.c") list(APPEND srcs "test_relay_chn_nvs_multi.c")

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
*
* SPDX-License-Identifier: MIT
*/
#include <stdbool.h> #include <stdbool.h>
#include "esp_log.h" #include "esp_log.h"
@@ -18,14 +23,14 @@
#define RELAY_CHN_UNITY_TEST_GROUP_TAG "relay_chn" #define RELAY_CHN_UNITY_TEST_GROUP_TAG "relay_chn"
#endif #endif
void setUp() void setUp()
{ {
} }
void tearDown() void tearDown()
{ {
reset_channels_to_idle_state(); reset_channels_to_defaults();
} }
#if CONFIG_RELAY_CHN_ENABLE_NVS #if CONFIG_RELAY_CHN_ENABLE_NVS
@@ -70,7 +75,7 @@ static void test_nvs_flash_deinit(void)
} }
#endif #endif
void app_main(void) void app_main(void)
{ {
#if CONFIG_RELAY_CHN_ENABLE_NVS #if CONFIG_RELAY_CHN_ENABLE_NVS
// Init NVS once for all tests // Init NVS once for all tests
@@ -104,6 +109,6 @@ void app_main(void)
#endif #endif
ESP_LOGI(TEST_TAG, "All tests complete."); ESP_LOGI(TEST_TAG, "All tests complete.");
esp_restart(); // Restart to invoke qemu exit esp_restart(); // Restart to invoke qemu exit
} }

View File

@@ -1,11 +1,18 @@
/*
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
*
* SPDX-License-Identifier: MIT
*/
#include "test_common.h" #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"; const char *TEST_TAG = "RELAY_CHN_TEST";
const uint8_t relay_chn_count = CONFIG_RELAY_CHN_COUNT;
const uint32_t opposite_inertia_ms = CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS;
const uint32_t test_delay_margin_ms = 50; // ms tolerance
// Test-wide GPIO map // Test-wide GPIO map
#if CONFIG_RELAY_CHN_COUNT > 1 #if CONFIG_RELAY_CHN_COUNT > 1
const uint8_t gpio_map[] = { const uint8_t gpio_map[] = {
@@ -36,17 +43,49 @@ const uint8_t gpio_map[] = {4, 5};
const uint8_t gpio_count = sizeof(gpio_map) / sizeof(gpio_map[0]); const uint8_t gpio_count = sizeof(gpio_map) / sizeof(gpio_map[0]);
void reset_channels_to_idle_state() 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
}
void reset_channels_to_defaults()
{ {
#if CONFIG_RELAY_CHN_COUNT > 1 #if CONFIG_RELAY_CHN_COUNT > 1
relay_chn_stop_all(); relay_chn_ctl_t *ctls = relay_chn_ctl_get_all();
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); 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++) { 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_STATE_IDLE, relay_chn_get_state(i));
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(i));
} }
#else #else
relay_chn_stop(); relay_chn_ctl_t *ctl = relay_chn_ctl_get();
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); 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_STATE_IDLE, relay_chn_get_state());
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction());
#endif #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);
} }

View File

@@ -1,3 +1,9 @@
/*
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
*
* SPDX-License-Identifier: MIT
*/
#pragma once #pragma once
#include <string.h> // For memset #include <string.h> // For memset
@@ -13,14 +19,12 @@ extern const char *TEST_TAG;
// GPIO configurations // GPIO configurations
extern const uint8_t gpio_map[]; extern const uint8_t gpio_map[];
extern const uint8_t gpio_count; extern const uint8_t gpio_count;
extern const uint8_t relay_chn_count;
// Config variables for tests // Config variables for tests
extern const uint32_t opposite_inertia_ms; #define TEST_DELAY_MARGIN_MS 50
extern const uint32_t test_delay_margin_ms;
// Init state
extern bool g_is_component_initialized;
// Reset channels to Idle state // Reset channels to Idle state
void reset_channels_to_idle_state(void); 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);

View File

@@ -1,5 +1,28 @@
/*
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
*
* SPDX-License-Identifier: MIT
*/
#include "test_common.h" #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 --- // --- Initialization Tests ---
@@ -21,50 +44,55 @@ TEST_CASE("relay_chn_create handles invalid arguments", "[relay_chn][core]")
// --- Basic Functionality Tests --- // --- Basic Functionality Tests ---
// TEST_CASE: Test that relay channels initialize correctly to RELAY_CHN_STATE_IDLE // TEST_CASE: Test that relay channels initialize correctly to RELAY_CHN_STATE_IDLE
TEST_CASE("Relay channels initialize correctly to FREE state", "[relay_chn][core]") { TEST_CASE("Relay channels initialize correctly to FREE state", "[relay_chn][core]")
for (uint8_t i = 0; i < relay_chn_count; i++) { {
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(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: 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_CASE("Run forward does nothing if channel id is invalid", "[relay_chn][core]")
for (uint8_t i = 0; i < relay_chn_count; i++) { {
int invalid_id = relay_chn_count * 2 + i; for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
relay_chn_run_forward(invalid_id); // relay_chn_run_forward returns void int invalid_id = CONFIG_RELAY_CHN_COUNT * 2 + i;
relay_chn_run_forward(invalid_id);
// Short delay for state to update // Short delay for state to update
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i)); 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: 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_CASE("Relay channels run forward and update state", "[relay_chn][core]")
for (uint8_t i = 0; i < relay_chn_count; i++) { {
relay_chn_run_forward(i); // relay_chn_run_forward returns void for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
relay_chn_run_forward(i);
// Short delay for state to update // Short delay for state to update
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(i)); 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: 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_CASE("Run reverse does nothing if channel id is invalid", "[relay_chn][core]")
{
// Verify that no valid channels were affected // Verify that no valid channels were affected
for (uint8_t i = 0; i < relay_chn_count; i++) { for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
int invalid_id = relay_chn_count * 2 + i; int invalid_id = CONFIG_RELAY_CHN_COUNT * 2 + i;
// Call run_reverse with an invalid ID // Call run_reverse with an invalid ID
relay_chn_run_reverse(invalid_id); relay_chn_run_reverse(invalid_id);
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i)); 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: 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_CASE("Relay channels run reverse and update state", "[relay_chn][core]")
for (uint8_t i = 0; i < relay_chn_count; i++) { {
relay_chn_run_reverse(i); // relay_chn_run_reverse returns void for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); 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)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(i));
} }
} }
@@ -74,9 +102,9 @@ TEST_CASE("Relay channels run reverse and update state", "[relay_chn][core]") {
TEST_CASE("run_forward_all sets all channels to FORWARD", "[relay_chn][core][batch]") TEST_CASE("run_forward_all sets all channels to FORWARD", "[relay_chn][core][batch]")
{ {
relay_chn_run_forward_all(); relay_chn_run_forward_all();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
for (uint8_t i = 0; i < relay_chn_count; i++) { for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(i)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(i));
} }
} }
@@ -84,9 +112,9 @@ TEST_CASE("run_forward_all sets all channels to FORWARD", "[relay_chn][core][bat
TEST_CASE("run_reverse_all sets all channels to REVERSE", "[relay_chn][core][batch]") TEST_CASE("run_reverse_all sets all channels to REVERSE", "[relay_chn][core][batch]")
{ {
relay_chn_run_reverse_all(); relay_chn_run_reverse_all();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
for (uint8_t i = 0; i < relay_chn_count; i++) { for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(i)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(i));
} }
} }
@@ -95,14 +123,14 @@ 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 // 1. Start all channels forward to ensure they are in a known running state
relay_chn_run_forward_all(); relay_chn_run_forward_all();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
// 2. Stop all channels // 2. Stop all channels
relay_chn_stop_all(); relay_chn_stop_all();
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
// 3. Verify all channels have transitioned to the FREE state // 3. Verify all channels have transitioned to the FREE state
for (uint8_t i = 0; i < relay_chn_count; i++) { for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i));
} }
} }
@@ -111,84 +139,84 @@ TEST_CASE("stop_all stops all running channels", "[relay_chn][core][batch]")
// TEST_CASE: Test that relays stop and transition to RELAY_CHN_STATE_IDLE // 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. // 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_CASE("Relay channels stop and update to FREE state", "[relay_chn][core]")
for (uint8_t i = 0; i < relay_chn_count; i++) { {
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
// First, run forward to test stopping and transitioning to FREE state // First, run forward to test stopping and transitioning to FREE state
relay_chn_run_forward(i); // relay_chn_run_forward returns void relay_chn_run_forward(i);
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(i)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(i));
// Now, issue the stop command // Now, issue the stop command
relay_chn_stop(i); // relay_chn_stop returns void relay_chn_stop(i);
// Immediately after stop, state should be STOPPED // Immediately after stop, state should be STOPPED
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(i)); 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 // Then, wait for the inertia period for it to transition to RELAY_CHN_STATE_IDLE
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); 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_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 should return UNDEFINED when id is not valid
TEST_CASE("Get state returns UNDEFINED when id is invalid", "[relay_chn][core]") { TEST_CASE("Get state returns UNDEFINED when id is invalid", "[relay_chn][core]")
for (uint8_t i = 0; i < relay_chn_count; i++) { {
int invalid_id = relay_chn_count * 2 + i; 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_ASSERT_EQUAL(RELAY_CHN_STATE_UNDEFINED, relay_chn_get_state(invalid_id));
} }
// Test for running states also // Test for running states also
relay_chn_run_forward_all(); relay_chn_run_forward_all();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
for (uint8_t i = 0; i < relay_chn_count; i++) { for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
int invalid_id = relay_chn_count * 2 + i; int invalid_id = CONFIG_RELAY_CHN_COUNT * 2 + i;
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_UNDEFINED, relay_chn_get_state(invalid_id)); 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 should return "UNKNOWN" when id is not valid
TEST_CASE("Get state string returns UNKNOWN when id is invalid", "[relay_chn][core]") { TEST_CASE("Get state string returns UNKNOWN when id is invalid", "[relay_chn][core]")
for (uint8_t i = 0; i < relay_chn_count; i++) { {
int invalid_id = relay_chn_count * 2 + i; 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_ASSERT_EQUAL_STRING("UNKNOWN", relay_chn_get_state_str(invalid_id));
} }
// Test for running states also // Test for running states also
relay_chn_run_forward_all(); relay_chn_run_forward_all();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
for (uint8_t i = 0; i < relay_chn_count; i++) { for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
int invalid_id = relay_chn_count * 2 + i; int invalid_id = CONFIG_RELAY_CHN_COUNT * 2 + i;
TEST_ASSERT_EQUAL_STRING("UNKNOWN", relay_chn_get_state_str(invalid_id)); TEST_ASSERT_EQUAL_STRING("UNKNOWN", relay_chn_get_state_str(invalid_id));
} }
} }
// TEST_CASE: Test independent operation of multiple relay channels // TEST_CASE: Test independent operation of multiple relay channels
TEST_CASE("Multiple channels can operate independently", "[relay_chn][core]") { TEST_CASE("Multiple channels can operate independently", "[relay_chn][core]")
if (relay_chn_count >= 2) { {
// Start Channel 0 in forward direction // Start Channel 0 in forward direction
relay_chn_run_forward(0); // relay_chn_run_forward returns void relay_chn_run_forward(0);
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); 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_FORWARD, relay_chn_get_state(0));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(1)); // Other channel should not be affected TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(1)); // Other channel should not be affected
// Start Channel 1 in reverse direction // Start Channel 1 in reverse direction
relay_chn_run_reverse(1); // relay_chn_run_reverse returns void relay_chn_run_reverse(1);
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); 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_FORWARD, relay_chn_get_state(0));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(1)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(1));
// Stop Channel 0 and wait for it to become FREE // Stop Channel 0 and wait for it to become FREE
relay_chn_stop(0); // relay_chn_stop returns void relay_chn_stop(0);
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); 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(0));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(1)); // Other channel should continue running 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 // Stop Channel 1 and wait for it to become FREE
relay_chn_stop(1); // relay_chn_stop returns void relay_chn_stop(1);
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); 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(0));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(1)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(1));
} else {
ESP_LOGW("TEST", "Skipping 'Multiple channels can operate independently' test: Not enough channels available.");
}
} }
@@ -198,151 +226,220 @@ TEST_CASE("Multiple channels can operate independently", "[relay_chn][core]") {
// TEST_CASE: Test transition from forward to reverse with inertia and state checks // 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 // Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_REVERSE
TEST_CASE("Forward to Reverse transition with opposite inertia", "[relay_chn][core][inertia]") { TEST_CASE("Forward to Reverse transition with opposite inertia", "[relay_chn][core][inertia]")
uint8_t ch = 0; // Channel to test {
// 1. Start in forward direction // 1. Start in forward direction
relay_chn_run_forward(ch); // relay_chn_run_forward returns void relay_chn_run_forward_all();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Short delay for state stabilization 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)); 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 // 2. Issue reverse command
relay_chn_run_reverse(ch); // relay_chn_run_reverse returns void relay_chn_run_reverse_all();
// Immediately after the command, the motor should be stopped // Immediately after the command, the motor should be stopped
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE_PENDING, relay_chn_get_state(ch)); 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) // Wait for the inertia period (after which the reverse command will be dispatched)
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); 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(ch)); // Should now be in reverse state // 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 // 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 // Scenario: RELAY_CHN_STATE_REVERSE -> (relay_chn_run_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_FORWARD
TEST_CASE("Reverse to Forward transition with opposite inertia", "[relay_chn][core][inertia]") { TEST_CASE("Reverse to Forward transition with opposite inertia", "[relay_chn][core][inertia]")
uint8_t ch = 0; {
// 1. Start in reverse direction // 1. Start in reverse direction
relay_chn_run_reverse(ch); // relay_chn_run_reverse returns void relay_chn_run_reverse_all();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch)); 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 // 2. Issue forward command
relay_chn_run_forward(ch); // relay_chn_run_forward returns void relay_chn_run_forward_all();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD_PENDING, relay_chn_get_state(ch)); 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 // Wait for inertia
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); 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(ch)); 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) // 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 // Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD
TEST_CASE("Running in same direction does not incur inertia", "[relay_chn][core][inertia]") { TEST_CASE("Running in same direction does not incur inertia", "[relay_chn][core][inertia]")
uint8_t ch = 0; {
// 1. Start in forward direction // 1. Start in forward direction
relay_chn_run_forward(ch); // relay_chn_run_forward returns void relay_chn_run_forward_all();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch)); 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 // 2. Issue the same forward command again
relay_chn_run_forward(ch); // relay_chn_run_forward returns void relay_chn_run_forward_all();
// As per the code, is_direction_opposite_to_current_motion should return false, so no inertia. // 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. // Just a short delay to check state remains the same.
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch)); 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 transition from FREE state to running (no inertia expected)
// Scenario: RELAY_CHN_STATE_IDLE -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD
TEST_CASE("FREE to Running transition without inertia", "[relay_chn][core][inertia]") {
uint8_t ch = 0;
// setUp() should have already brought the channel to FREE state
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(ch));
// Start in forward direction
relay_chn_run_forward(ch); // relay_chn_run_forward returns void
// 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 // ### Direction Flipping Tests
TEST_CASE("Single channel direction can be flipped", "[relay_chn][core][direction]") TEST_CASE("Direction can be flipped for each channel independently", "[relay_chn][core][direction]")
{ {
const uint8_t ch = 0;
// 1. Initial direction should be default // 1. Initial direction should be default
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(ch)); 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 // 2. Flip the direction
relay_chn_flip_direction(ch); for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); // Wait for flip inertia 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 // 3. Verify direction is flipped
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction(ch)); 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 // 4. Flip back
relay_chn_flip_direction(ch); for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); // Wait for flip inertia 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 // 5. Verify direction is back to default
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(ch)); 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]") TEST_CASE("All channels direction can be flipped simultaneously", "[relay_chn][core][direction][batch]")
{ {
// 1. Flip all channels // 1. Flip all channels
relay_chn_flip_direction_all(); relay_chn_flip_direction_all();
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
// 2. Verify all channels are flipped // 2. Verify all channels are flipped
for (uint8_t i = 0; i < relay_chn_count; i++) { TEST_ESP_OK(relay_chn_get_direction_all(s_directions));
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction(i)); 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 // 3. Flip all back
relay_chn_flip_direction_all(); relay_chn_flip_direction_all();
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
// 4. Verify all channels are back to default // 4. Verify all channels are back to default
for (uint8_t i = 0; i < relay_chn_count; i++) { TEST_ESP_OK(relay_chn_get_direction_all(s_directions));
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(i)); 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]") TEST_CASE("Flipping a running channel stops it and flips direction", "[relay_chn][core][direction]")
{ {
const uint8_t ch = 0;
// 1. Start channel running and verify state // 1. Start channel running and verify state
relay_chn_run_forward(ch); relay_chn_run_forward_all();
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch)); 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 // 2. Flip the direction while running
relay_chn_flip_direction(ch); relay_chn_flip_direction_all();
// 3. The channel should stop as part of the flip process // 3. The channel should stop as part of the flip process
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(ch)); 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 FREE and FLIPPED // 4. Wait for the flip inertia to pass, after which it should be idle and FLIPPED
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); 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(ch));
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction(ch)); 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]") TEST_CASE("Direction flip handles invalid channel ID gracefully", "[relay_chn][core][direction]")
{ {
const uint8_t invalid_ch = relay_chn_count + 5; const uint8_t invalid_ch = CONFIG_RELAY_CHN_COUNT + 5;
relay_chn_flip_direction(invalid_ch); // Call with an invalid ID 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_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 #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
#define TEST_RUN_LIMIT_SEC 5 #define TEST_RUN_LIMIT_SEC 5
#define TEST_SHORT_RUN_LIMIT_SEC 2 #define TEST_SHORT_RUN_LIMIT_SEC 2
@@ -355,7 +452,7 @@ TEST_CASE("Test run limit initialization", "[relay_chn][run_limit]")
} }
} }
TEST_CASE("Test run limit setting boundaries", "[relay_chn][run_limit]") TEST_CASE("Test run limit setting boundaries", "[relay_chn][run_limit]")
{ {
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
// Test minimum boundary // Test minimum boundary
@@ -385,9 +482,9 @@ TEST_CASE("Test run limit stops channel after timeout", "[relay_chn][run_limit]"
// Check running forward // Check running forward
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(i)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(i));
} }
// Wait for run limit timeout // Wait for run limit timeout
vTaskDelay(pdMS_TO_TICKS(TEST_SHORT_RUN_LIMIT_SEC * 1000 + test_delay_margin_ms)); 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++) { for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(i)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(i));
} }
@@ -395,29 +492,31 @@ TEST_CASE("Test run limit stops channel after timeout", "[relay_chn][run_limit]"
TEST_CASE("Test run limit reset on direction change and time out finally", "[relay_chn][run_limit]") TEST_CASE("Test run limit reset on direction change and time out finally", "[relay_chn][run_limit]")
{ {
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { relay_chn_set_run_limit_all_with(TEST_SHORT_RUN_LIMIT_SEC);
// Set a short run limit
relay_chn_set_run_limit(i, TEST_SHORT_RUN_LIMIT_SEC); #if CONFIG_RELAY_CHN_ENABLE_NVS
// Wait for the NVS module task to process operations
// Start running forward vTaskDelay(300 / portTICK_PERIOD_MS); // Wait 1 second
relay_chn_run_forward(i); #endif
}
// Start running forward
relay_chn_run_forward_all();
vTaskDelay(1000 / portTICK_PERIOD_MS); // Wait 1 second vTaskDelay(1000 / portTICK_PERIOD_MS); // Wait 1 second
// Change direction before timeout // Change direction before timeout
relay_chn_run_reverse_all(); relay_chn_run_reverse_all();
// Wait for the inertia period (after which the reverse command will be dispatched) // Wait for the inertia period (after which the reverse command will be dispatched)
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); 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++) { for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(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 // 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)); 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++) { for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(i)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(i));
} }
@@ -428,13 +527,67 @@ TEST_CASE("Test run limit persistence across stop/start", "[relay_chn][run_limit
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
// Set initial run limit // Set initial run limit
relay_chn_set_run_limit(i, TEST_RUN_LIMIT_SEC); relay_chn_set_run_limit(i, TEST_RUN_LIMIT_SEC);
// Stop and start channel // Stop and start channel
relay_chn_stop(i); relay_chn_stop(i);
relay_chn_run_forward(i); relay_chn_run_forward(i);
// Run limit should persist // Run limit should persist
TEST_ASSERT_EQUAL(TEST_RUN_LIMIT_SEC, relay_chn_get_run_limit(i)); TEST_ASSERT_EQUAL(TEST_RUN_LIMIT_SEC, relay_chn_get_run_limit(i));
} }
} }
#endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1
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));
}
}

View File

@@ -1,3 +1,9 @@
/*
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
*
* SPDX-License-Identifier: MIT
*/
#include "test_common.h" #include "test_common.h"
@@ -21,42 +27,46 @@ TEST_CASE("relay_chn_create handles invalid arguments", "[relay_chn][core]")
// --- Basic Functionality Tests --- // --- Basic Functionality Tests ---
// TEST_CASE: Test that relay channels initialize correctly to RELAY_CHN_STATE_IDLE // 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_CASE("Relay channels initialize correctly to IDLE state", "[relay_chn][core]")
{
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); 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: 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_CASE("Relay channels run forward and update state", "[relay_chn][core]")
{
relay_chn_run_forward(); relay_chn_run_forward();
// Short delay for state to update // Short delay for state to update
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); 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: 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_CASE("Relay channels run reverse and update state", "[relay_chn][core]")
relay_chn_run_reverse(); // relay_chn_run_reverse returns void {
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); relay_chn_run_reverse();
vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state()); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state());
} }
// TEST_CASE: Test that relays stop and transition to RELAY_CHN_STATE_IDLE // 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. // This test also verifies the transition to IDLE state after a STOP command.
TEST_CASE("Relay channels stop and update to IDLE state", "[relay_chn][core]") { TEST_CASE("Relay channels stop and update to IDLE state", "[relay_chn][core]")
{
// First, run forward to test stopping and transitioning to IDLE state // First, run forward to test stopping and transitioning to IDLE state
relay_chn_run_forward(); relay_chn_run_forward();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state());
// Now, issue the stop command // Now, issue the stop command
relay_chn_stop(); // relay_chn_stop returns void relay_chn_stop();
// Immediately after stop, state should be STOPPED // Immediately after stop, state should be STOPPED
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state()); 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 // Then, wait for the inertia period for it to transition to RELAY_CHN_STATE_IDLE
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); 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_STATE_IDLE, relay_chn_get_state());
} }
@@ -67,67 +77,71 @@ TEST_CASE("Relay channels stop and update to IDLE state", "[relay_chn][core]") {
// TEST_CASE: Test transition from forward to reverse with inertia and state checks // 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 // Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_REVERSE
TEST_CASE("Forward to Reverse transition with opposite inertia", "[relay_chn][core][inertia]") { TEST_CASE("Forward to Reverse transition with opposite inertia", "[relay_chn][core][inertia]")
{
// 1. Start in forward direction // 1. Start in forward direction
relay_chn_run_forward(); relay_chn_run_forward();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Short delay for state stabilization vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS)); // Short delay for state stabilization
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state());
// 2. Issue reverse command // 2. Issue reverse command
relay_chn_run_reverse(); // relay_chn_run_reverse returns void relay_chn_run_reverse();
// Immediately after the command, the motor should be stopped // Immediately after the command, the motor should be stopped
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE_PENDING, relay_chn_get_state()); 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) // Wait for the inertia period (after which the reverse command will be dispatched)
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); 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_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 // 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 // Scenario: RELAY_CHN_STATE_REVERSE -> (relay_chn_run_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_FORWARD
TEST_CASE("Reverse to Forward transition with opposite inertia", "[relay_chn][core][inertia]") { TEST_CASE("Reverse to Forward transition with opposite inertia", "[relay_chn][core][inertia]")
{
// 1. Start in reverse direction // 1. Start in reverse direction
relay_chn_run_reverse(); // relay_chn_run_reverse returns void relay_chn_run_reverse();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state()); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state());
// 2. Issue forward command // 2. Issue forward command
relay_chn_run_forward(); relay_chn_run_forward();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD_PENDING, relay_chn_get_state()); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD_PENDING, relay_chn_get_state());
// Wait for inertia // Wait for inertia
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); 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_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state());
} }
// TEST_CASE: Test issuing the same run command while already running (no inertia expected) // 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 // Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD
TEST_CASE("Running in same direction does not incur inertia", "[relay_chn][core][inertia]") { TEST_CASE("Running in same direction does not incur inertia", "[relay_chn][core][inertia]")
{
// 1. Start in forward direction // 1. Start in forward direction
relay_chn_run_forward(); relay_chn_run_forward();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state());
// 2. Issue the same forward command again // 2. Issue the same forward command again
relay_chn_run_forward(); relay_chn_run_forward();
// As per the code, is_direction_opposite_to_current_motion should return false, so no inertia. // 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. // Just a short delay to check state remains the same.
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state());
} }
// TEST_CASE: Test transition from IDLE state to running (no inertia expected) // 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 // Scenario: RELAY_CHN_STATE_IDLE -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD
TEST_CASE("IDLE to Running transition without inertia", "[relay_chn][core][inertia]") { TEST_CASE("IDLE to Running transition without inertia", "[relay_chn][core][inertia]")
{
// setUp() should have already brought the channel to IDLE state // setUp() should have already brought the channel to IDLE state
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state());
// Start in forward direction // Start in forward direction
relay_chn_run_forward(); relay_chn_run_forward();
// No inertia is expected when starting from IDLE state. // No inertia is expected when starting from IDLE state.
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state());
} }
@@ -140,14 +154,14 @@ TEST_CASE("Single channel direction can be flipped", "[relay_chn][core][directio
// 2. Flip the direction // 2. Flip the direction
relay_chn_flip_direction(); relay_chn_flip_direction();
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); // Wait for flip inertia vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS)); // Wait for flip inertia
// 3. Verify direction is flipped // 3. Verify direction is flipped
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction()); TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction());
// 4. Flip back // 4. Flip back
relay_chn_flip_direction(); relay_chn_flip_direction();
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); // Wait for flip inertia 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 // 5. Verify direction is back to default
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction()); TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction());
@@ -157,18 +171,18 @@ TEST_CASE("Flipping a running channel stops it and flips direction", "[relay_chn
{ {
// 1. Start channel running and verify state // 1. Start channel running and verify state
relay_chn_run_forward(); relay_chn_run_forward();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state());
// 2. Flip the direction while running // 2. Flip the direction while running
relay_chn_flip_direction(); relay_chn_flip_direction();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Give time for events to process 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 // 3. The channel should stop as part of the flip process
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state()); 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 // 4. Wait for the flip inertia to pass, after which it should be IDLE and FLIPPED
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); 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_STATE_IDLE, relay_chn_get_state());
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction()); TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction());
} }
@@ -183,7 +197,7 @@ TEST_CASE("Test run limit initialization", "[relay_chn][run_limit]")
TEST_ASSERT_EQUAL(CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC, relay_chn_get_run_limit()); 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_CASE("Test run limit setting boundaries", "[relay_chn][run_limit]")
{ {
// Test minimum boundary // Test minimum boundary
relay_chn_set_run_limit(CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC - 1); relay_chn_set_run_limit(CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC - 1);
@@ -202,13 +216,13 @@ TEST_CASE("Test run limit stops channel after timeout", "[relay_chn][run_limit]"
{ {
// Set a short run limit for testing // Set a short run limit for testing
relay_chn_set_run_limit(TEST_SHORT_RUN_LIMIT_SEC); relay_chn_set_run_limit(TEST_SHORT_RUN_LIMIT_SEC);
// Start running forward // Start running forward
relay_chn_run_forward(); relay_chn_run_forward();
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state());
// Wait for run limit timeout // Wait for run limit timeout
vTaskDelay(pdMS_TO_TICKS(TEST_SHORT_RUN_LIMIT_SEC * 1000 + test_delay_margin_ms)); 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_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state());
} }
@@ -216,22 +230,22 @@ TEST_CASE("Test run limit reset on direction change and time out finally", "[rel
{ {
// Set a short run limit // Set a short run limit
relay_chn_set_run_limit(TEST_SHORT_RUN_LIMIT_SEC); relay_chn_set_run_limit(TEST_SHORT_RUN_LIMIT_SEC);
// Start running forward // Start running forward
relay_chn_run_forward(); relay_chn_run_forward();
vTaskDelay(1000 / portTICK_PERIOD_MS); // Wait 1 second vTaskDelay(1000 / portTICK_PERIOD_MS); // Wait 1 second
// Change direction before timeout // Change direction before timeout
relay_chn_run_reverse(); relay_chn_run_reverse();
// Wait for the inertia period (after which the reverse command will be dispatched) // Wait for the inertia period (after which the reverse command will be dispatched)
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); 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_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state());
// Timer should time out and stop the channel after the run limit time // 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)); 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_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state());
} }
@@ -239,12 +253,68 @@ TEST_CASE("Test run limit persistence across stop/start", "[relay_chn][run_limit
{ {
// Set initial run limit // Set initial run limit
relay_chn_set_run_limit(TEST_RUN_LIMIT_SEC); relay_chn_set_run_limit(TEST_RUN_LIMIT_SEC);
// Stop and start channel // Stop and start channel
relay_chn_stop(); relay_chn_stop();
relay_chn_run_forward(); relay_chn_run_forward();
// Run limit should persist // Run limit should persist
TEST_ASSERT_EQUAL(TEST_RUN_LIMIT_SEC, relay_chn_get_run_limit()); TEST_ASSERT_EQUAL(TEST_RUN_LIMIT_SEC, relay_chn_get_run_limit());
} }
#endif // CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT == 1 #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());
}

View File

@@ -1,123 +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;
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_IDLE, listener1_info.old_state);
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, listener1_info.new_state);
// 4. Unregister to clean up
relay_chn_unregister_listener(test_listener_1);
}
TEST_CASE("Unregistered listener is not called", "[relay_chn][listener]") {
uint8_t ch = 0;
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;
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_IDLE, listener1_info.old_state);
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, listener1_info.new_state);
// 4. Verify listener 2 was also called correctly
TEST_ASSERT_EQUAL(1, listener2_info.call_count);
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, listener2_info.old_state);
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, listener2_info.new_state);
// 5. Clean up
relay_chn_unregister_listener(test_listener_1);
relay_chn_unregister_listener(test_listener_2);
}
TEST_CASE("Listener registration handles invalid arguments and duplicates", "[relay_chn][listener]") {
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);
}

View File

@@ -1,118 +0,0 @@
#include "test_common.h"
// --- Listener Test Globals ---
typedef struct {
relay_chn_state_t old_state;
relay_chn_state_t new_state;
int call_count;
} listener_callback_info_t;
static listener_callback_info_t listener1_info;
static listener_callback_info_t listener2_info;
// --- Listener Test Helper Functions ---
// Clear the memory from possible garbage values
static void reset_listener_info(listener_callback_info_t* info) {
memset(info, 0, sizeof(listener_callback_info_t));
}
static void test_listener_1(uint8_t chn_id, relay_chn_state_t old_state, relay_chn_state_t new_state) {
/* Just ignore the channel id */
listener1_info.old_state = old_state;
listener1_info.new_state = new_state;
listener1_info.call_count++;
}
static void test_listener_2(uint8_t chn_id, relay_chn_state_t old_state, relay_chn_state_t new_state) {
/* Just ignore the channel id */
listener2_info.old_state = old_state;
listener2_info.new_state = new_state;
listener2_info.call_count++;
}
// ### Listener Functionality Tests
TEST_CASE("Listener is called on state change", "[relay_chn][listener]") {
reset_listener_info(&listener1_info);
// 1. Register the listener
TEST_ESP_OK(relay_chn_register_listener(test_listener_1));
// 2. Trigger a state change
relay_chn_run_forward();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Allow event to be processed
// 3. Verify the listener was called with correct parameters
TEST_ASSERT_EQUAL(1, listener1_info.call_count);
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, listener1_info.old_state);
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, listener1_info.new_state);
// 4. Unregister to clean up
relay_chn_unregister_listener(test_listener_1);
}
TEST_CASE("Unregistered listener is not called", "[relay_chn][listener]") {
reset_listener_info(&listener1_info);
// 1. Register and then immediately unregister the listener
TEST_ESP_OK(relay_chn_register_listener(test_listener_1));
relay_chn_unregister_listener(test_listener_1);
// 2. Trigger a state change
relay_chn_run_forward();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
// 3. Verify the listener was NOT called
TEST_ASSERT_EQUAL(0, listener1_info.call_count);
}
TEST_CASE("Multiple listeners are called on state change", "[relay_chn][listener]") {
reset_listener_info(&listener1_info);
reset_listener_info(&listener2_info);
// 1. Register two different listeners
TEST_ESP_OK(relay_chn_register_listener(test_listener_1));
TEST_ESP_OK(relay_chn_register_listener(test_listener_2));
// 2. Trigger a state change
relay_chn_run_forward();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
// 3. Verify listener 1 was called correctly
TEST_ASSERT_EQUAL(1, listener1_info.call_count);
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, listener1_info.old_state);
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, listener1_info.new_state);
// 4. Verify listener 2 was also called correctly
TEST_ASSERT_EQUAL(1, listener2_info.call_count);
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, listener2_info.old_state);
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, listener2_info.new_state);
// 5. Clean up
relay_chn_unregister_listener(test_listener_1);
relay_chn_unregister_listener(test_listener_2);
}
TEST_CASE("Listener registration handles invalid arguments and duplicates", "[relay_chn][listener]") {
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);
}

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

View 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

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

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

View File

@@ -9,144 +9,181 @@
#include "esp_system.h" #include "esp_system.h"
#include "nvs_flash.h" #include "nvs_flash.h"
#include "relay_chn_nvs.h" #include "relay_chn_nvs.h"
#include "test_common.h"
TEST_CASE("Test relay storage init/deinit", "[relay_chn][nvs]") #define TEST_NVS_TASK_TIME_OUT_MS 300
{
TEST_ESP_OK(relay_chn_nvs_init());
TEST_ESP_OK(relay_chn_nvs_deinit());
}
TEST_CASE("Test direction setting and getting", "[relay_chn][nvs]") TEST_CASE("Test direction setting and getting", "[relay_chn][nvs]")
{ {
TEST_ESP_OK(relay_chn_nvs_init());
// Test all channels // Test all channels
relay_chn_direction_t dir; relay_chn_direction_t dir, expect;
relay_chn_direction_t test_directions[] = {
RELAY_CHN_DIRECTION_DEFAULT, for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) {
RELAY_CHN_DIRECTION_FLIPPED dir = channel % 2 == 0 ? RELAY_CHN_DIRECTION_DEFAULT : RELAY_CHN_DIRECTION_FLIPPED;
}; TEST_ESP_OK(relay_chn_nvs_set_direction(channel, dir));
}
for (int channel = 0; channel < 2; channel++) {
TEST_ESP_OK(relay_chn_nvs_set_direction(channel, test_directions[channel])); // Wait for the batch commit timeout to ensure the value is written
TEST_ESP_OK(relay_chn_nvs_get_direction(channel, &dir)); vTaskDelay(pdMS_TO_TICKS(TEST_NVS_TASK_TIME_OUT_MS));
TEST_ASSERT_EQUAL(test_directions[channel], dir);
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_ESP_OK(relay_chn_nvs_deinit());
} }
TEST_CASE("Test invalid parameters", "[relay_chn][nvs]") TEST_CASE("Test invalid parameters", "[relay_chn][nvs]")
{ {
TEST_ESP_OK(relay_chn_nvs_init());
// Test NULL pointer for all channels // Test NULL pointer for all channels
for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) { for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) {
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_direction(channel, NULL)); TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_direction(channel, NULL, RELAY_CHN_DIRECTION_DEFAULT));
} }
TEST_ESP_OK(relay_chn_nvs_deinit());
} }
TEST_CASE("Test relay_chn_nvs_erase_all", "[relay_chn][nvs]") TEST_CASE("Test relay_chn_nvs_erase_all", "[relay_chn][nvs]")
{ {
TEST_ESP_OK(relay_chn_nvs_init());
// Store some test data first // Store some test data first
relay_chn_direction_t direction = RELAY_CHN_DIRECTION_FLIPPED;
for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) { for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) {
TEST_ESP_OK(relay_chn_nvs_set_direction(0, direction)); // 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 #if CONFIG_RELAY_CHN_ENABLE_TILTING
uint8_t sensitivity = 50; TEST_ESP_OK(relay_chn_nvs_set_tilt_sensitivity(channel, 50));
for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) { TEST_ESP_OK(relay_chn_nvs_set_tilt_count(channel, 100 + channel));
TEST_ESP_OK(relay_chn_nvs_set_tilt_sensitivity(0, sensitivity));
TEST_ESP_OK(relay_chn_nvs_set_tilt_count(0, 100));
} }
#endif #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 erase all
TEST_ESP_OK(relay_chn_nvs_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 // Verify data was erased by trying to read it back
relay_chn_direction_t read_direction; for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) {
TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_direction(0, &read_direction)); 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_TILTING #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
uint8_t read_sensitivity; for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) {
TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_tilt_sensitivity(0, &read_sensitivity)); 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));
uint16_t tilt_count; TEST_ASSERT_EQUAL(CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC, read_run_limit);
TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_tilt_count(0, &tilt_count)); }
#endif #endif
TEST_ESP_OK(relay_chn_nvs_deinit()); #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 #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
TEST_CASE("Test run limit setting and getting", "[relay_chn][nvs][run_limit]") TEST_CASE("Test run limit setting and getting", "[relay_chn][nvs][run_limit]")
{ {
TEST_ESP_OK(relay_chn_nvs_init()); // Use different values for each channel to detect overwrites
uint16_t test_limits[CONFIG_RELAY_CHN_COUNT];
const uint16_t run_limit_sec = 32;
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) { for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
TEST_ESP_OK(relay_chn_nvs_set_run_limit(i, run_limit_sec)); test_limits[i] = 30 + i; // e.g., 30, 31, 32...
uint16_t run_limit_read;
TEST_ESP_OK(relay_chn_nvs_get_run_limit(i, &run_limit_read));
TEST_ASSERT_EQUAL(run_limit_sec, run_limit_read);
} }
TEST_ESP_OK(relay_chn_nvs_deinit());
// 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 #endif
#if CONFIG_RELAY_CHN_ENABLE_TILTING #if CONFIG_RELAY_CHN_ENABLE_TILTING
TEST_CASE("Test sensitivity setting and getting", "[relay_chn][nvs][tilt]") TEST_CASE("Test sensitivity setting and getting", "[relay_chn][nvs][tilt]")
{ {
TEST_ESP_OK(relay_chn_nvs_init()); uint8_t test_sensitivities[CONFIG_RELAY_CHN_COUNT];
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
const uint8_t test_sensitivity = 75; test_sensitivities[i] = 70 + i; // e.g., 70, 71, 72...
uint8_t sensitivity; }
// Test all channels // 1. Set all values first
for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) { for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
TEST_ESP_OK(relay_chn_nvs_set_tilt_sensitivity(channel, test_sensitivity)); TEST_ESP_OK(relay_chn_nvs_set_tilt_sensitivity(i, test_sensitivities[i]));
TEST_ESP_OK(relay_chn_nvs_get_tilt_sensitivity(channel, &sensitivity)); }
TEST_ASSERT_EQUAL(test_sensitivity, sensitivity);
// 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_ESP_OK(relay_chn_nvs_deinit());
} }
TEST_CASE("Test tilt counter operations", "[relay_chn][nvs][tilt]") TEST_CASE("Test tilt counter operations", "[relay_chn][nvs][tilt]")
{ {
TEST_ESP_OK(relay_chn_nvs_init()); uint16_t test_counts[CONFIG_RELAY_CHN_COUNT];
for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
const uint16_t tilt_count = 100; test_counts[i] = 100 + i; // e.g., 100, 101, 102...
uint16_t tilt_count_read; }
// Test all channels // 1. Set all values first
for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) { for (uint8_t i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
// Test setting counters TEST_ESP_OK(relay_chn_nvs_set_tilt_count(i, test_counts[i]));
TEST_ESP_OK(relay_chn_nvs_set_tilt_count(channel, tilt_count)); }
TEST_ESP_OK(relay_chn_nvs_get_tilt_count(channel, &tilt_count_read));
TEST_ASSERT_EQUAL(tilt_count, tilt_count_read); // 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_ESP_OK(relay_chn_nvs_deinit());
} }
TEST_CASE("Test tilting invalid parameters", "[relay_chn][nvs][tilt]") TEST_CASE("Test tilting invalid parameters", "[relay_chn][nvs][tilt]")
{ {
TEST_ESP_OK(relay_chn_nvs_init());
// Test NULL pointers for all channels // Test NULL pointers for all channels
for (int channel = 0; channel < CONFIG_RELAY_CHN_COUNT; channel++) { 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)); 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)); TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_tilt_count(channel, NULL, 0));
} }
TEST_ESP_OK(relay_chn_nvs_deinit());
} }
#endif // CONFIG_RELAY_CHN_ENABLE_TILTING #endif // CONFIG_RELAY_CHN_ENABLE_TILTING

View File

@@ -9,50 +9,43 @@
#include "esp_system.h" #include "esp_system.h"
#include "nvs_flash.h" #include "nvs_flash.h"
#include "relay_chn_nvs.h" #include "relay_chn_nvs.h"
#include "test_common.h"
#define TEST_NVS_TASK_TIME_OUT_MS 300
TEST_CASE("Test relay storage init/deinit", "[relay_chn][nvs]")
{
TEST_ESP_OK(relay_chn_nvs_init());
TEST_ESP_OK(relay_chn_nvs_deinit());
}
TEST_CASE("Test direction setting and getting", "[relay_chn][nvs]") TEST_CASE("Test direction setting and getting", "[relay_chn][nvs]")
{ {
TEST_ESP_OK(relay_chn_nvs_init());
// Test channel 0 // Test channel 0
TEST_ESP_OK(relay_chn_nvs_set_direction(0, RELAY_CHN_DIRECTION_DEFAULT)); TEST_ESP_OK(relay_chn_nvs_set_direction(0, RELAY_CHN_DIRECTION_DEFAULT));
relay_chn_direction_t dir; relay_chn_direction_t dir;
TEST_ESP_OK(relay_chn_nvs_get_direction(0, &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_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, dir);
// Test channel 1 // Test channel 1
TEST_ESP_OK(relay_chn_nvs_set_direction(0, RELAY_CHN_DIRECTION_FLIPPED)); TEST_ESP_OK(relay_chn_nvs_set_direction(0, RELAY_CHN_DIRECTION_FLIPPED));
TEST_ESP_OK(relay_chn_nvs_get_direction(0, &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_FLIPPED, dir); TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, dir);
TEST_ESP_OK(relay_chn_nvs_deinit());
} }
TEST_CASE("Test invalid parameters", "[relay_chn][nvs]") TEST_CASE("Test invalid parameters", "[relay_chn][nvs]")
{ {
TEST_ESP_OK(relay_chn_nvs_init());
// Test NULL pointer // Test NULL pointer
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_direction(0, NULL)); TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_direction(0, NULL, RELAY_CHN_DIRECTION_DEFAULT));
TEST_ESP_OK(relay_chn_nvs_deinit());
} }
TEST_CASE("Test relay_chn_nvs_erase_all", "[relay_chn][nvs]") TEST_CASE("Test relay_chn_nvs_erase_all", "[relay_chn][nvs]")
{ {
TEST_ESP_OK(relay_chn_nvs_init());
// Store some test data first // Store some test data first
relay_chn_direction_t direction = RELAY_CHN_DIRECTION_FLIPPED; relay_chn_direction_t direction = RELAY_CHN_DIRECTION_FLIPPED;
TEST_ESP_OK(relay_chn_nvs_set_direction(0, direction)); 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 #if CONFIG_RELAY_CHN_ENABLE_TILTING
uint8_t sensitivity = 50; uint8_t sensitivity = 50;
TEST_ESP_OK(relay_chn_nvs_set_tilt_sensitivity(0, sensitivity)); TEST_ESP_OK(relay_chn_nvs_set_tilt_sensitivity(0, sensitivity));
@@ -62,76 +55,71 @@ TEST_CASE("Test relay_chn_nvs_erase_all", "[relay_chn][nvs]")
// Test erase all // Test erase all
TEST_ESP_OK(relay_chn_nvs_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 // Verify data was erased by trying to read it back
relay_chn_direction_t read_direction; relay_chn_direction_t read_direction;
TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_direction(0, &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_TILTING #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
uint8_t read_sensitivity; uint16_t read_run_limit;
TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_tilt_sensitivity(0, &read_sensitivity)); 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);
uint16_t tilt_count;
TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, relay_chn_nvs_get_tilt_count(0, &tilt_count));
#endif #endif
TEST_ESP_OK(relay_chn_nvs_deinit()); #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 #if CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT
TEST_CASE("Test run limit setting and getting", "[relay_chn][nvs][run_limit]") TEST_CASE("Test run limit setting and getting", "[relay_chn][nvs][run_limit]")
{ {
TEST_ESP_OK(relay_chn_nvs_init());
const uint16_t run_limit_sec = 32; const uint16_t run_limit_sec = 32;
TEST_ESP_OK(relay_chn_nvs_set_run_limit(0, run_limit_sec)); 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; uint16_t run_limit_read;
TEST_ESP_OK(relay_chn_nvs_get_run_limit(0, &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); TEST_ASSERT_EQUAL(run_limit_sec, run_limit_read);
TEST_ESP_OK(relay_chn_nvs_deinit());
} }
#endif #endif
#if CONFIG_RELAY_CHN_ENABLE_TILTING #if CONFIG_RELAY_CHN_ENABLE_TILTING
TEST_CASE("Test sensitivity setting and getting", "[relay_chn][nvs][tilt]") TEST_CASE("Test sensitivity setting and getting", "[relay_chn][nvs][tilt]")
{ {
TEST_ESP_OK(relay_chn_nvs_init());
const uint8_t test_sensitivity = 75; const uint8_t test_sensitivity = 75;
TEST_ESP_OK(relay_chn_nvs_set_tilt_sensitivity(0, test_sensitivity)); 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; uint8_t sensitivity;
TEST_ESP_OK(relay_chn_nvs_get_tilt_sensitivity(0, &sensitivity)); TEST_ESP_OK(relay_chn_nvs_get_tilt_sensitivity(0, &sensitivity, 0));
TEST_ASSERT_EQUAL(test_sensitivity, sensitivity); TEST_ASSERT_EQUAL(test_sensitivity, sensitivity);
TEST_ESP_OK(relay_chn_nvs_deinit());
} }
TEST_CASE("Test tilt counter operations", "[relay_chn][nvs][tilt]") TEST_CASE("Test tilt counter operations", "[relay_chn][nvs][tilt]")
{ {
TEST_ESP_OK(relay_chn_nvs_init());
const uint16_t tilt_count = 100; const uint16_t tilt_count = 100;
// Test setting counters // Test setting counters
TEST_ESP_OK(relay_chn_nvs_set_tilt_count(0, tilt_count)); 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; uint16_t tilt_count_read;
TEST_ESP_OK(relay_chn_nvs_get_tilt_count(0, &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_ASSERT_EQUAL(tilt_count, tilt_count_read);
TEST_ESP_OK(relay_chn_nvs_deinit());
} }
TEST_CASE("Test tilting invalid parameters", "[relay_chn][nvs][tilt]") TEST_CASE("Test tilting invalid parameters", "[relay_chn][nvs][tilt]")
{ {
TEST_ESP_OK(relay_chn_nvs_init());
// Test NULL pointers // Test NULL pointers
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_tilt_sensitivity(0, NULL)); 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)); TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_nvs_get_tilt_count(0, NULL, 0));
TEST_ESP_OK(relay_chn_nvs_deinit());
} }
#endif // CONFIG_RELAY_CHN_ENABLE_TILTING #endif // CONFIG_RELAY_CHN_ENABLE_TILTING

View File

@@ -1,3 +1,9 @@
/*
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
*
* SPDX-License-Identifier: MIT
*/
#include "test_common.h" #include "test_common.h"
@@ -12,171 +18,232 @@
#define RELAY_CHN_CMD_FORWARD 1 #define RELAY_CHN_CMD_FORWARD 1
#define RELAY_CHN_CMD_REVERSE 2 #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 // Helper function to prepare channel for tilt tests
void prepare_channel_for_tilt(uint8_t chn_id, int initial_cmd) { void prepare_channels_for_tilt_with_mixed_runs() {
// Ensure the channel reset tilt control vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
relay_chn_tilt_stop(chn_id);
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + 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' // Ensure the channel has had a 'last_run_cmd'
if (initial_cmd == RELAY_CHN_CMD_FORWARD) { if (initial_cmd == RELAY_CHN_CMD_FORWARD) {
relay_chn_run_forward(chn_id); relay_chn_run_forward_all();
} else { // Assuming initial_cmd is RELAY_CHN_CMD_REVERSE } else { // Assuming initial_cmd is RELAY_CHN_CMD_REVERSE
relay_chn_run_reverse(chn_id); relay_chn_run_reverse_all();
} }
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 relay_chn_state_t expect_state = initial_cmd == RELAY_CHN_CMD_FORWARD
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); ? RELAY_CHN_STATE_FORWARD : RELAY_CHN_STATE_REVERSE;
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(chn_id)); 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 // 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 // Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_tilt_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_FORWARD
TEST_CASE("Run Forward to Tilt Forward transition with inertia", "[relay_chn][tilt][inertia]") { TEST_CASE("Run Forward to Tilt Forward transition with inertia", "[relay_chn][tilt][inertia]")
uint8_t ch = 0; {
// 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 // 1. Start in forward direction
relay_chn_run_forward(ch); relay_chn_run_forward_all();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch)); check_all_channels_for_state(RELAY_CHN_STATE_FORWARD);
// 2. Issue tilt forward command // 2. Issue tilt forward command
relay_chn_tilt_forward(ch); relay_chn_tilt_forward_all();
// After tilt command, it should immediately stop and then trigger inertia. // After tilt command, it should immediately stop and then trigger inertia.
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(ch)); check_all_channels_for_state(RELAY_CHN_STATE_STOPPED);
// Wait for the inertia period (after which the tilt command will be dispatched) // Wait for the inertia period (after which the tilt command will be dispatched)
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch)); 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 // 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 // Scenario: RELAY_CHN_STATE_REVERSE -> (relay_chn_tilt_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_REVERSE
TEST_CASE("Run Reverse to Tilt Reverse transition with inertia", "[relay_chn][tilt][inertia]") { TEST_CASE("Run Reverse to Tilt Reverse transition with inertia", "[relay_chn][tilt][inertia]")
uint8_t ch = 0; {
// 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 // 1. Start in reverse direction
relay_chn_run_reverse(ch); relay_chn_run_reverse_all();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch)); check_all_channels_for_state(RELAY_CHN_STATE_REVERSE);
// 2. Issue tilt reverse command // 2. Issue tilt reverse command
relay_chn_tilt_reverse(ch); relay_chn_tilt_reverse_all();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(ch)); check_all_channels_for_state(RELAY_CHN_STATE_STOPPED);
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); 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(ch)); 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) // 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 // Scenario: RELAY_CHN_STATE_IDLE -> (prepare) -> RELAY_CHN_STATE_IDLE -> (relay_chn_tilt_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_FORWARD
TEST_CASE("FREE to Tilt Forward transition with inertia (prepared)", "[relay_chn][tilt][inertia]") { TEST_CASE("FREE to Tilt Forward transition with inertia (prepared)", "[relay_chn][tilt][inertia]")
uint8_t ch = 0; {
// Prepare channel by running forward first to set last_run_cmd // Prepare channel by running forward first to set last_run_cmd
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD); prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD);
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(ch)); // Ensure we are back to FREE 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 // Issue tilt forward command
relay_chn_tilt_forward(ch); relay_chn_tilt_forward_all();
// From FREE state, tilt command should still incur the inertia due to the internal timer logic // From FREE state, tilt command should still incur the inertia due to the internal timer logic
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch)); 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) // 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 // Scenario: RELAY_CHN_STATE_IDLE -> (prepare) -> RELAY_CHN_STATE_IDLE -> (relay_chn_tilt_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_REVERSE
TEST_CASE("FREE to Tilt Reverse transition with inertia (prepared)", "[relay_chn][tilt][inertia]") { TEST_CASE("FREE to Tilt Reverse transition with inertia (prepared)", "[relay_chn][tilt][inertia]")
uint8_t ch = 0; {
// Prepare channel by running reverse first to set last_run_cmd // Prepare channel by running reverse first to set last_run_cmd
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_REVERSE); prepare_all_channels_for_tilt(RELAY_CHN_CMD_REVERSE);
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(ch)); // Ensure we are back to FREE 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 // Issue tilt reverse command
relay_chn_tilt_reverse(ch); relay_chn_tilt_reverse_all();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(ch)); 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) // 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 // Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD
TEST_CASE("Tilt Forward to Run Forward transition with inertia", "[relay_chn][tilt][inertia]") { TEST_CASE("Tilt Forward to Run Forward transition with inertia", "[relay_chn][tilt][inertia]")
uint8_t ch = 0; {
// Prepare channel by running forward first to set last_run_cmd, then tilt // Prepare channel by running forward first to set last_run_cmd, then tilt
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD); prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD);
relay_chn_tilt_forward(ch); // Go to tilt state relay_chn_tilt_forward_all(); // Go to tilt state
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); 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(ch)); check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD);
// 2. Issue run forward command // 2. Issue run forward command
relay_chn_run_forward(ch); relay_chn_run_forward_all();
// From Tilt to Run in the same logical name but in the opposite direction, inertia is expected. // 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)); check_all_channels_for_state(RELAY_CHN_STATE_FORWARD_PENDING);
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); 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(ch)); 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) // 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 // Scenario: RELAY_CHN_STATE_TILT_REVERSE -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_REVERSE
TEST_CASE("Tilt Reverse to Run Reverse transition with inertia", "[relay_chn][tilt][inertia]") { TEST_CASE("Tilt Reverse to Run Reverse transition with inertia", "[relay_chn][tilt][inertia]")
uint8_t ch = 0; {
// Prepare channel by running reverse first to set last_run_cmd, then tilt // Prepare channel by running reverse first to set last_run_cmd, then tilt
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_REVERSE); prepare_all_channels_for_tilt(RELAY_CHN_CMD_REVERSE);
relay_chn_tilt_reverse(ch); // Go to tilt state relay_chn_tilt_reverse_all(); // Go to tilt state
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); 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(ch)); check_all_channels_for_state(RELAY_CHN_STATE_TILT_REVERSE);
// 2. Issue run reverse command // 2. Issue run reverse command
relay_chn_run_reverse(ch); relay_chn_run_reverse_all();
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE_PENDING, relay_chn_get_state(ch)); check_all_channels_for_state(RELAY_CHN_STATE_REVERSE_PENDING);
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); 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(ch)); 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) // 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 // Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_REVERSE
TEST_CASE("Tilt Forward to Run Reverse transition without inertia", "[relay_chn][tilt][inertia]") { TEST_CASE("Tilt Forward to Run Reverse transition without inertia", "[relay_chn][tilt][inertia]")
uint8_t ch = 0; {
// Prepare channel by running forward first to set last_run_cmd, then tilt // Prepare channel by running forward first to set last_run_cmd, then tilt
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD); prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD);
relay_chn_tilt_forward(ch); // Go to tilt state relay_chn_tilt_forward_all(); // Go to tilt state
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); 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(ch)); check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD);
// 2. Issue run reverse command (opposite direction) // 2. Issue run reverse command (opposite direction)
relay_chn_run_reverse(ch); relay_chn_run_reverse_all();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch)); 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) // 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 // 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]") { TEST_CASE("Tilt to Stop transition without immediate inertia for stop", "[relay_chn][tilt][inertia]")
uint8_t ch = 0; {
// Prepare all channels by running forward first to set last_run_cmd, then tilt
// Prepare channel by running forward first to set last_run_cmd, then tilt prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD);
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD); relay_chn_tilt_forward_all(); // Go to tilt state
relay_chn_tilt_forward(ch); // Go to tilt state vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); // Verify all channels are tilting forward
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch)); check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD);
// 2. Issue stop command // 2. Issue stop command
relay_chn_tilt_stop(ch); relay_chn_tilt_stop_all();
// Stop command should apply immediately, setting state to FREE since last state was tilt. // Stop command should apply immediately, setting state to FREE since last state was tilt.
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); 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(ch)); // Verify all channels are IDLE
check_all_channels_for_state(RELAY_CHN_STATE_IDLE);
} }
// ### Batch Tilt Control Tests // ### Batch Tilt Control Tests
@@ -184,96 +251,107 @@ TEST_CASE("Tilt to Stop transition without immediate inertia for stop", "[relay_
TEST_CASE("tilt_forward_all sets all channels to TILT_FORWARD", "[relay_chn][tilt][batch]") TEST_CASE("tilt_forward_all sets all channels to TILT_FORWARD", "[relay_chn][tilt][batch]")
{ {
// 1. Prepare all channels. // 1. Prepare all channels.
for (uint8_t i = 0; i < relay_chn_count; i++) { prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD);
prepare_channel_for_tilt(i, RELAY_CHN_CMD_FORWARD);
}
// 2. Issue tilt forward to all channels // 2. Issue tilt forward to all channels
relay_chn_tilt_forward_all(); relay_chn_tilt_forward_all();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Tilt from FREE doesn't have stop-inertia // 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 // 3. Verify all channels are tilting forward
for (uint8_t i = 0; i < relay_chn_count; i++) { check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD);
ESP_LOGI(TEST_TAG, "Checking channel %d", i);
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(i)); // 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]") TEST_CASE("tilt_reverse_all sets all channels to TILT_REVERSE", "[relay_chn][tilt][batch]")
{ {
// 1. Prepare all channels. // 1. Prepare all channels.
for (uint8_t i = 0; i < relay_chn_count; i++) { prepare_all_channels_for_tilt(RELAY_CHN_CMD_REVERSE);
prepare_channel_for_tilt(i, RELAY_CHN_CMD_REVERSE);
}
// 2. Issue tilt reverse to all channels // 2. Issue tilt reverse to all channels
relay_chn_tilt_reverse_all(); relay_chn_tilt_reverse_all();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // 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 // 3. Verify all channels are tilting reverse
for (uint8_t i = 0; i < relay_chn_count; i++) { check_all_channels_for_state(RELAY_CHN_STATE_TILT_REVERSE);
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(i));
} // Ensure the channel reset tilt control
relay_chn_tilt_stop_all();
} }
TEST_CASE("tilt_stop_all stops all tilting channels", "[relay_chn][tilt][batch]") TEST_CASE("tilt_stop_all stops all tilting channels", "[relay_chn][tilt][batch]")
{ {
// 1. Prepare and start all channels tilting forward // 1. Prepare and start all channels tilting forward
for (uint8_t i = 0; i < relay_chn_count; i++) { prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD);
prepare_channel_for_tilt(i, RELAY_CHN_CMD_REVERSE);
}
relay_chn_tilt_forward_all(); relay_chn_tilt_forward_all();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // 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 // 2. Stop tilting on all channels
relay_chn_tilt_stop_all(); relay_chn_tilt_stop_all();
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
// 3. Verify all channels are free // 3. Verify all channels are free
for (uint8_t i = 0; i < relay_chn_count; i++) { check_all_channels_for_state(RELAY_CHN_STATE_IDLE);
ESP_LOGI(TEST_TAG, "Checking channel %d", i);
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state(i));
}
} }
TEST_CASE("tilt_auto_all tilts channels based on last run direction", "[relay_chn][tilt][batch]") TEST_CASE("tilt_auto_all tilts channels based on last run direction", "[relay_chn][tilt][batch]")
{ {
// 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 // 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_channels_for_tilt_with_mixed_runs();
prepare_channel_for_tilt(1, RELAY_CHN_CMD_REVERSE);
// 2. Issue auto tilt command to all channels // 2. Issue auto tilt command to all channels
relay_chn_tilt_auto_all(); relay_chn_tilt_auto_all();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Tilt from FREE state is dispatched immediately // Should incur inertia timer
vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
// 3. Verify channel 0 tilts forward (last run was forward) and channel 1 tilts reverse (last run was reverse) // 3. Verify even channels tilt forward (last run was forward) and odd channels tilt reverse (last run was reverse)
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(0)); for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(1)); 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 relay_chn_tilt_auto() chooses correct tilt direction
TEST_CASE("relay_chn_tilt_auto chooses correct direction", "[relay_chn][tilt][auto]") { TEST_CASE("relay_chn_tilt_auto chooses correct direction", "[relay_chn][tilt][auto]")
uint8_t ch = 0; {
// Prepare FORWARD // Prepare FORWARD
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD); prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD);
relay_chn_tilt_auto(ch); relay_chn_tilt_auto_all();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Should incur inertia timer
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch)); vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
relay_chn_tilt_stop(ch); // Verify all tilt forward
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();
// Prepare REVERSE // Prepare REVERSE
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_REVERSE); prepare_all_channels_for_tilt(RELAY_CHN_CMD_REVERSE);
relay_chn_tilt_auto(ch); relay_chn_tilt_auto_all();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Should incur inertia timer
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(ch)); 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 sensitivity set/get
TEST_CASE("relay_chn_tilt_set_sensitivity and get", "[relay_chn][tilt][sensitivity]") { TEST_CASE("relay_chn_tilt_set_sensitivity and get", "[relay_chn][tilt][sensitivity]")
{
uint8_t ch = 0; uint8_t ch = 0;
relay_chn_tilt_set_sensitivity(ch, 0); relay_chn_tilt_set_sensitivity(ch, 0);
TEST_ASSERT_EQUAL_UINT8(0, relay_chn_tilt_get_sensitivity(ch)); TEST_ASSERT_EQUAL_UINT8(0, relay_chn_tilt_get_sensitivity(ch));
@@ -285,60 +363,142 @@ TEST_CASE("relay_chn_tilt_set_sensitivity and get", "[relay_chn][tilt][sensitivi
TEST_ASSERT_EQUAL_UINT8(100, relay_chn_tilt_get_sensitivity(ch)); TEST_ASSERT_EQUAL_UINT8(100, relay_chn_tilt_get_sensitivity(ch));
// Set all channels // Set all channels
relay_chn_tilt_set_sensitivity_all_with(42); relay_chn_tilt_set_sensitivity_all_with(42);
uint8_t vals[CONFIG_RELAY_CHN_COUNT] = {0}; uint8_t vals[CONFIG_RELAY_CHN_COUNT] = {0};
uint8_t expect[CONFIG_RELAY_CHN_COUNT]; uint8_t expect[CONFIG_RELAY_CHN_COUNT];
memset(expect, 42, CONFIG_RELAY_CHN_COUNT); memset(expect, 42, CONFIG_RELAY_CHN_COUNT);
TEST_ESP_OK(relay_chn_tilt_get_sensitivity_all(vals)); TEST_ESP_OK(relay_chn_tilt_get_sensitivity_all(vals));
TEST_ASSERT_EQUAL_UINT8_ARRAY(expect, vals, CONFIG_RELAY_CHN_COUNT); 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 tilt counter logic: forward x3, reverse x3, extra reverse fails
TEST_CASE("tilt counter logic: forward and reverse consumption", "[relay_chn][tilt][counter]") { TEST_CASE("tilt counter logic: forward and reverse consumption", "[relay_chn][tilt][counter]")
uint8_t ch = 0; {
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD); // 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 // Tilt forward 3 times
for (int i = 0; i < 3; ++i) { relay_chn_tilt_forward_all();
relay_chn_tilt_forward(ch); vTaskDelay(pdMS_TO_TICKS(TEST_TILT_EXECUTION_TIME_MS * 3 + TEST_DELAY_MARGIN_MS));
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD);
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch));
relay_chn_tilt_stop(ch); // Stop tilt on all channels
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); 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) // Now tilt reverse 3 times (should succeed)
for (int i = 0; i < 3; ++i) { relay_chn_tilt_reverse_all();
relay_chn_tilt_reverse(ch); vTaskDelay(pdMS_TO_TICKS(TEST_TILT_EXECUTION_TIME_MS));
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); check_all_channels_for_state(RELAY_CHN_STATE_TILT_REVERSE);
if (i < 3) { // Let it execute 2 at least, or more
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(ch)); vTaskDelay(pdMS_TO_TICKS(TEST_TILT_EXECUTION_TIME_MS * 3));
relay_chn_tilt_stop(ch);
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
}
}
// Extra reverse tilt should fail (counter exhausted) // More reverse tilt should fail (counter exhausted)
relay_chn_tilt_reverse(ch); check_all_channels_for_state(RELAY_CHN_STATE_IDLE);
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 run command during TILT state
TEST_CASE("run command during TILT state transitions correctly", "[relay_chn][tilt][run-during-tilt]") { TEST_CASE("run command during TILT state transitions correctly", "[relay_chn][tilt][run-during-tilt]")
uint8_t ch = 0; {
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD); prepare_all_channels_for_tilt(RELAY_CHN_CMD_FORWARD);
relay_chn_tilt_forward(ch); relay_chn_tilt_forward_all();
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); 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(ch)); check_all_channels_for_state(RELAY_CHN_STATE_TILT_FORWARD);
// Issue run reverse while in TILT_FORWARD // Issue run reverse while in TILT_FORWARD
relay_chn_run_reverse(ch); relay_chn_run_reverse_all();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
// Should transition to REVERSE or REVERSE_PENDING depending on inertia logic // Should transition to REVERSE
relay_chn_state_t state = relay_chn_get_state(ch); check_all_channels_for_state(RELAY_CHN_STATE_REVERSE);
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_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);
} }

View File

@@ -1,3 +1,9 @@
/*
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
*
* SPDX-License-Identifier: MIT
*/
#include "test_common.h" #include "test_common.h"
@@ -16,7 +22,7 @@
void prepare_channel_for_tilt(int initial_cmd) { void prepare_channel_for_tilt(int initial_cmd) {
// Ensure the channel reset tilt control // Ensure the channel reset tilt control
relay_chn_tilt_stop(); relay_chn_tilt_stop();
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
// Ensure the channel has had a 'last_run_cmd' // Ensure the channel has had a 'last_run_cmd'
if (initial_cmd == RELAY_CHN_CMD_FORWARD) { if (initial_cmd == RELAY_CHN_CMD_FORWARD) {
@@ -24,164 +30,175 @@ void prepare_channel_for_tilt(int initial_cmd) {
} else { // Assuming initial_cmd is RELAY_CHN_CMD_REVERSE } else { // Assuming initial_cmd is RELAY_CHN_CMD_REVERSE
relay_chn_run_reverse(); relay_chn_run_reverse();
} }
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Allow command to process vTaskDelay(pdMS_TO_TICKS(CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS + TEST_DELAY_MARGIN_MS));
relay_chn_stop(); // Stop it to set last_run_cmd but return to FREE for next test relay_chn_state_t expect_state = initial_cmd == RELAY_CHN_CMD_FORWARD ? RELAY_CHN_STATE_FORWARD : RELAY_CHN_STATE_REVERSE;
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); TEST_ASSERT_EQUAL(expect_state, relay_chn_get_state());
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state());
} }
// TEST_CASE: Test transition from running forward to tilt forward // 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 // Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_tilt_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_FORWARD
TEST_CASE("Run Forward to Tilt Forward transition with inertia", "[relay_chn][tilt][inertia]") { TEST_CASE("Run Forward to Tilt Forward transition with inertia", "[relay_chn][tilt][inertia]")
// Prepare channel by running forward first to set last_run_cmd {
prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD);
// 1. Start in forward direction // 1. Start in forward direction
relay_chn_run_forward(); relay_chn_run_forward();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state()); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state());
// 2. Issue tilt forward command // 2. Issue tilt forward command
relay_chn_tilt_forward(); relay_chn_tilt_forward();
// After tilt command, it should immediately stop and then trigger inertia. // After tilt command, it should immediately stop and then trigger inertia.
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state()); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state());
// Wait for the inertia period (after which the tilt command will be dispatched) // Wait for the inertia period (after which the tilt command will be dispatched)
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); 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_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state());
} }
// TEST_CASE: Test transition from running reverse to tilt reverse // 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 // Scenario: RELAY_CHN_STATE_REVERSE -> (relay_chn_tilt_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_REVERSE
TEST_CASE("Run Reverse to Tilt Reverse transition with inertia", "[relay_chn][tilt][inertia]") { TEST_CASE("Run Reverse to Tilt Reverse transition with inertia", "[relay_chn][tilt][inertia]")
// Prepare channel by running reverse first to set last_run_cmd {
prepare_channel_for_tilt(RELAY_CHN_CMD_REVERSE);
// 1. Start in reverse direction // 1. Start in reverse direction
relay_chn_run_reverse(); relay_chn_run_reverse();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state()); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state());
// 2. Issue tilt reverse command // 2. Issue tilt reverse command
relay_chn_tilt_reverse(); relay_chn_tilt_reverse();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state()); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state());
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); 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_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state());
} }
// TEST_CASE: Test transition from FREE state to tilt forward (now with preparation) // 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 // Scenario: RELAY_CHN_STATE_IDLE -> (prepare) -> RELAY_CHN_STATE_IDLE -> (relay_chn_tilt_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_FORWARD
TEST_CASE("FREE to Tilt Forward transition with inertia (prepared)", "[relay_chn][tilt][inertia]") { TEST_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 by running forward first to set last_run_cmd
prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); 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 TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); // Ensure we are back to FREE
// Issue tilt forward command // Issue tilt forward command
relay_chn_tilt_forward(); relay_chn_tilt_forward();
// From FREE state, tilt command should still incur the inertia due to the internal timer logic // From FREE state, tilt command should still incur the inertia due to the internal timer logic
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state()); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state());
} }
// TEST_CASE: Test transition from FREE state to tilt reverse (now with preparation) // 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 // Scenario: RELAY_CHN_STATE_IDLE -> (prepare) -> RELAY_CHN_STATE_IDLE -> (relay_chn_tilt_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_REVERSE
TEST_CASE("FREE to Tilt Reverse transition with inertia (prepared)", "[relay_chn][tilt][inertia]") { TEST_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 by running reverse first to set last_run_cmd
prepare_channel_for_tilt(RELAY_CHN_CMD_REVERSE); 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 TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); // Ensure we are back to FREE
// Issue tilt reverse command // Issue tilt reverse command
relay_chn_tilt_reverse(); relay_chn_tilt_reverse();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state()); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state());
} }
// TEST_CASE: Test transition from tilt forward to run forward (inertia expected for run) // 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 // Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD
TEST_CASE("Tilt Forward to Run Forward transition with inertia", "[relay_chn][tilt][inertia]") { TEST_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 by running forward first to set last_run_cmd, then tilt
prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD);
relay_chn_tilt_forward(); // Go to tilt state relay_chn_tilt_forward(); // Go to tilt state
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); 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_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state());
// 2. Issue run forward command // 2. Issue run forward command
relay_chn_run_forward(); relay_chn_run_forward();
// From Tilt to Run in the same logical name but in the opposite direction, inertia is expected. // 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()); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD_PENDING, relay_chn_get_state());
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); 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_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state());
} }
// TEST_CASE: Test transition from tilt reverse to run reverse (no inertia expected for run) // 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 // Scenario: RELAY_CHN_STATE_TILT_REVERSE -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_REVERSE
TEST_CASE("Tilt Reverse to Run Reverse transition with inertia", "[relay_chn][tilt][inertia]") { TEST_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 by running reverse first to set last_run_cmd, then tilt
prepare_channel_for_tilt(RELAY_CHN_CMD_REVERSE); prepare_channel_for_tilt(RELAY_CHN_CMD_REVERSE);
relay_chn_tilt_reverse(); // Go to tilt state relay_chn_tilt_reverse(); // Go to tilt state
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); 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_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state());
// 2. Issue run reverse command // 2. Issue run reverse command
relay_chn_run_reverse(); relay_chn_run_reverse();
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE_PENDING, relay_chn_get_state()); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE_PENDING, relay_chn_get_state());
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); 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_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state());
} }
// TEST_CASE: Test transition from tilt forward to run reverse (without inertia) // Test transition from tilt forward to run reverse (without inertia)
// Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_REVERSE // Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_REVERSE
TEST_CASE("Tilt Forward to Run Reverse transition without inertia", "[relay_chn][tilt][inertia]") { TEST_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 by running forward first to set last_run_cmd, then tilt
prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD);
relay_chn_tilt_forward(); // Go to tilt state relay_chn_tilt_forward(); // Go to tilt state
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); 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_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state());
// 2. Issue run reverse command (opposite direction) // 2. Issue run reverse command (opposite direction)
relay_chn_run_reverse(); relay_chn_run_reverse();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state()); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state());
} }
// TEST_CASE: Test stopping from a tilt state (no inertia for stop command itself) // Test stopping from a tilt state (no inertia for stop command itself)
// Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_stop) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_IDLE // 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]") { 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 by running forward first to set last_run_cmd, then tilt
prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD);
relay_chn_tilt_forward(); // Go to tilt state relay_chn_tilt_forward(); // Go to tilt state
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); 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_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state());
// 2. Issue stop command // 2. Issue stop command
relay_chn_stop(); relay_chn_tilt_stop();
// Stop command should apply immediately, setting state to FREE since last state was tilt. // Stop command should apply immediately, setting state to FREE since last state was tilt.
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state()); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_IDLE, relay_chn_get_state());
} }
// Test relay_chn_tilt_auto() chooses correct tilt direction // Test relay_chn_tilt_auto() chooses correct tilt direction
TEST_CASE("relay_chn_tilt_auto chooses correct direction", "[relay_chn][tilt][auto]") { TEST_CASE("relay_chn_tilt_auto chooses correct direction", "[relay_chn][tilt][auto]")
{
// Prepare FORWARD // Prepare FORWARD
prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD);
relay_chn_tilt_auto(); relay_chn_tilt_auto();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // 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()); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state());
relay_chn_tilt_stop(); relay_chn_tilt_stop();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
// Prepare REVERSE // Prepare REVERSE
prepare_channel_for_tilt(RELAY_CHN_CMD_REVERSE); prepare_channel_for_tilt(RELAY_CHN_CMD_REVERSE);
relay_chn_tilt_auto(); relay_chn_tilt_auto();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // 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_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state());
} }
// Test sensitivity set/get // Test sensitivity set/get
TEST_CASE("relay_chn_tilt_set_sensitivity and get", "[relay_chn][tilt][sensitivity]") { TEST_CASE("relay_chn_tilt_set_sensitivity and get", "[relay_chn][tilt][sensitivity]")
{
relay_chn_tilt_set_sensitivity(0); relay_chn_tilt_set_sensitivity(0);
TEST_ASSERT_EQUAL_UINT8(0, relay_chn_tilt_get_sensitivity()); TEST_ASSERT_EQUAL_UINT8(0, relay_chn_tilt_get_sensitivity());
@@ -195,49 +212,105 @@ TEST_CASE("relay_chn_tilt_set_sensitivity and get", "[relay_chn][tilt][sensitivi
TEST_ASSERT_EQUAL_UINT8(42, relay_chn_tilt_get_sensitivity()); 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 tilt counter logic: forward x3, reverse x3, extra reverse fails
TEST_CASE("tilt counter logic: forward and reverse consumption", "[relay_chn][tilt][counter]") { 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); prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD);
// Tilt forward 3 times // Tilt forward 3 times
for (int i = 0; i < 3; ++i) { relay_chn_tilt_forward();
relay_chn_tilt_forward(); vTaskDelay(pdMS_TO_TICKS(TEST_TILT_EXECUTION_TIME_MS * 3 + TEST_DELAY_MARGIN_MS));
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state());
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state()); relay_chn_tilt_stop();
relay_chn_tilt_stop();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
}
// Now tilt reverse 3 times (should succeed) // Now tilt reverse 3 times (should succeed)
for (int i = 0; i < 3; ++i) {
relay_chn_tilt_reverse();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
if (i < 3) {
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state());
relay_chn_tilt_stop();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
}
}
// Extra reverse tilt should fail (counter exhausted)
relay_chn_tilt_reverse(); relay_chn_tilt_reverse();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // 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 // Should not enter TILT_REVERSE, should remain FREE or STOPPED
relay_chn_state_t state = relay_chn_get_state(); relay_chn_state_t state = relay_chn_get_state();
TEST_ASSERT(state != RELAY_CHN_STATE_TILT_REVERSE); TEST_ASSERT(state != RELAY_CHN_STATE_TILT_REVERSE);
} }
// Test run command during TILT state // Test run command during TILT state
TEST_CASE("run command during TILT state transitions correctly", "[relay_chn][tilt][run-during-tilt]") { TEST_CASE("run command during TILT state transitions correctly", "[relay_chn][tilt][run-during-tilt]")
{
prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD); prepare_channel_for_tilt(RELAY_CHN_CMD_FORWARD);
relay_chn_tilt_forward(); relay_chn_tilt_forward();
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); 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_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state());
// Issue run reverse while in TILT_FORWARD // Issue run reverse while in TILT_FORWARD
relay_chn_run_reverse(); relay_chn_run_reverse();
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_MARGIN_MS));
// Should transition to REVERSE or REVERSE_PENDING depending on inertia logic // Should transition to REVERSE or REVERSE_PENDING depending on inertia logic
relay_chn_state_t state = relay_chn_get_state(); relay_chn_state_t state = relay_chn_get_state();
TEST_ASSERT(state == RELAY_CHN_STATE_REVERSE || state == RELAY_CHN_STATE_REVERSE_PENDING); 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());
} }

View 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"

View 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
View File

@@ -0,0 +1 @@
-DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.multi"

1
test_apps/profiles/nvs Normal file
View File

@@ -0,0 +1 @@
-DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.nvs"

View File

@@ -0,0 +1 @@
-DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.nvs;sdkconfig.defaults.nvs_custom"

View File

@@ -0,0 +1 @@
-DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.run_limit"

1
test_apps/profiles/tilt Normal file
View File

@@ -0,0 +1 @@
-DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.tilt"

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,9 @@
# Disable task WDT for tests # Disable task WDT for tests
CONFIG_ESP_TASK_WDT_INIT=n CONFIG_ESP_TASK_WDT_INIT=n
CONFIG_LOG_MAXIMUM_LEVEL_DEBUG=y
CONFIG_LOG_MAXIMUM_LEVEL=4
# Relay Channel Driver Default Configuration for Testing # Relay Channel Driver Default Configuration for Testing
# Keep this as short as possible for tests # Keep this as short as possible for tests
CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200 CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200

View File

@@ -1,7 +1 @@
# Disable task WDT for tests
CONFIG_ESP_TASK_WDT_INIT=n
# Relay Channel Driver Default Configuration for Testing
# Keep this as short as possible for tests
CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200
CONFIG_RELAY_CHN_COUNT=8 CONFIG_RELAY_CHN_COUNT=8

View File

@@ -1,14 +0,0 @@
# Disable task WDT for tests
CONFIG_ESP_TASK_WDT_INIT=n
# Partition configuration
CONFIG_PARTITION_TABLE_SINGLE_APP=y
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/part_nvs.csv"
# 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=8
CONFIG_RELAY_CHN_ENABLE_NVS=y
CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION=y

View File

@@ -1,17 +0,0 @@
# Disable task WDT for tests
CONFIG_ESP_TASK_WDT_INIT=n
# Partition configuration
CONFIG_PARTITION_TABLE_SINGLE_APP=y
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/part_nvs.csv"
# 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=8
CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT=y
CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC=1
CONFIG_RELAY_CHN_ENABLE_TILTING=y
CONFIG_RELAY_CHN_ENABLE_NVS=y
CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION=y

View File

@@ -1,8 +0,0 @@
# Disable task WDT for tests
CONFIG_ESP_TASK_WDT_INIT=n
# Relay Channel Driver Default Configuration for Testing
# Keep this as short as possible for tests
CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200
CONFIG_RELAY_CHN_COUNT=8
CONFIG_RELAY_CHN_ENABLE_NVS=y

View File

@@ -1,9 +0,0 @@
# Disable task WDT for tests
CONFIG_ESP_TASK_WDT_INIT=n
# Relay Channel Driver Default Configuration for Testing
# Keep this as short as possible for tests
CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200
CONFIG_RELAY_CHN_COUNT=8
CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT=y
CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC=1

View File

@@ -1,8 +0,0 @@
# Disable task WDT for tests
CONFIG_ESP_TASK_WDT_INIT=n
# Relay Channel Driver Default Configuration for Testing
# Keep this as short as possible for tests
CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200
CONFIG_RELAY_CHN_COUNT=8
CONFIG_RELAY_CHN_ENABLE_TILTING=y

View File

@@ -0,0 +1 @@
CONFIG_RELAY_CHN_ENABLE_NVS=y

View 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

View 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

View File

@@ -1,13 +0,0 @@
# Disable task WDT for tests
CONFIG_ESP_TASK_WDT_INIT=n
# Partition configuration
CONFIG_PARTITION_TABLE_SINGLE_APP=y
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/part_nvs.csv"
# 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_ENABLE_NVS=y
CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION=y

View File

@@ -1,16 +0,0 @@
# Disable task WDT for tests
CONFIG_ESP_TASK_WDT_INIT=n
# Partition configuration
CONFIG_PARTITION_TABLE_SINGLE_APP=y
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/part_nvs.csv"
# 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_ENABLE_RUN_LIMIT=y
CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC=1
CONFIG_RELAY_CHN_ENABLE_TILTING=y
CONFIG_RELAY_CHN_ENABLE_NVS=y
CONFIG_RELAY_CHN_NVS_CUSTOM_PARTITION=y

View File

@@ -1,7 +0,0 @@
# Disable task WDT for tests
CONFIG_ESP_TASK_WDT_INIT=n
# Relay Channel Driver Default Configuration for Testing
# Keep this as short as possible for tests
CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200
CONFIG_RELAY_CHN_ENABLE_NVS=y

View File

@@ -1,8 +0,0 @@
# Disable task WDT for tests
CONFIG_ESP_TASK_WDT_INIT=n
# Relay Channel Driver Default Configuration for Testing
# Keep this as short as possible for tests
CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200
CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT=y
CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC=1

View File

@@ -1,7 +0,0 @@
# Disable task WDT for tests
CONFIG_ESP_TASK_WDT_INIT=n
# Relay Channel Driver Default Configuration for Testing
# Keep this as short as possible for tests
CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS=200
CONFIG_RELAY_CHN_ENABLE_TILTING=y

View File

@@ -0,0 +1 @@
CONFIG_RELAY_CHN_ENABLE_TILTING=y

File diff suppressed because it is too large Load Diff