Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0ebe1c791e | |||
|
bacbe03e12
|
|||
|
be09cb883a
|
|||
|
925fd5de74
|
|||
|
2e3e92bb63
|
|||
|
c4482b8d49
|
|||
| 41c292cc89 | |||
|
ed5b86e863
|
|||
| a1c66d51c7 | |||
| 421dea7d69 | |||
| 99d753238b | |||
| 7afe6144bd | |||
| 4f39308f13 | |||
| fb425edc4b | |||
| 805df016fe | |||
| f230477cad | |||
| e19bd09389 | |||
| 11786b7a06 | |||
| 7c18ddcc04 | |||
| e8303a9418 | |||
| 496755ed56 | |||
| 5fe76bb738 | |||
| 46dd0db939 | |||
| 46f7c28829 | |||
| 0e68c1f627 | |||
|
|
9ff243c673 | ||
| 330c996b7b | |||
|
|
4b8b6fd636 | ||
|
|
8d96914a38 | ||
| b7a23f3633 | |||
| df935a593b | |||
|
|
f72fe6b1f0 | ||
|
|
e54e28020c | ||
|
|
f5481f79e7 | ||
|
|
7d3f08b56b | ||
| a694938224 |
180
.gitignore
vendored
180
.gitignore
vendored
@@ -1,107 +1,111 @@
|
|||||||
# ---> C
|
.config
|
||||||
# Prerequisites
|
|
||||||
*.d
|
|
||||||
|
|
||||||
# Object files
|
|
||||||
*.o
|
*.o
|
||||||
*.ko
|
*.pyc
|
||||||
*.obj
|
|
||||||
*.elf
|
|
||||||
|
|
||||||
# Linker output
|
# gtags
|
||||||
*.ilk
|
GTAGS
|
||||||
*.map
|
GRTAGS
|
||||||
*.exp
|
GPATH
|
||||||
|
|
||||||
# Precompiled Headers
|
# emacs
|
||||||
*.gch
|
.dir-locals.el
|
||||||
*.pch
|
|
||||||
|
|
||||||
# Libraries
|
# emacs temp file suffixes
|
||||||
*.lib
|
*~
|
||||||
*.a
|
.#*
|
||||||
*.la
|
\#*#
|
||||||
*.lo
|
|
||||||
|
|
||||||
# Shared objects (inc. Windows DLLs)
|
# eclipse setting
|
||||||
*.dll
|
.settings
|
||||||
*.so
|
|
||||||
*.so.*
|
|
||||||
*.dylib
|
|
||||||
|
|
||||||
# Executables
|
# MacOS directory files
|
||||||
*.exe
|
.DS_Store
|
||||||
*.out
|
|
||||||
*.app
|
|
||||||
*.i*86
|
|
||||||
*.x86_64
|
|
||||||
*.hex
|
|
||||||
|
|
||||||
# Debug files
|
# cache dir
|
||||||
*.dSYM/
|
.cache/
|
||||||
*.su
|
|
||||||
*.idb
|
|
||||||
*.pdb
|
|
||||||
|
|
||||||
# Kernel Module Compile Results
|
# Doc build artifacts
|
||||||
*.mod*
|
docs/_build/
|
||||||
*.cmd
|
docs/doxygen_sqlite3.db
|
||||||
.tmp_versions/
|
|
||||||
modules.order
|
|
||||||
Module.symvers
|
|
||||||
Mkfile.old
|
|
||||||
dkms.conf
|
|
||||||
|
|
||||||
# ---> C++
|
# Downloaded font files
|
||||||
# Prerequisites
|
docs/_static/DejaVuSans.ttf
|
||||||
*.d
|
docs/_static/NotoSansSC-Regular.otf
|
||||||
|
|
||||||
# Compiled Object files
|
# Components Unit Test Apps files
|
||||||
*.slo
|
components/**/build/
|
||||||
*.lo
|
components/**/build_*_*/
|
||||||
*.o
|
components/**/sdkconfig
|
||||||
*.obj
|
components/**/sdkconfig.old
|
||||||
|
|
||||||
# Precompiled Headers
|
# Example project files
|
||||||
*.gch
|
examples/**/build/
|
||||||
*.pch
|
examples/**/build_*_*/
|
||||||
|
examples/**/sdkconfig
|
||||||
|
examples/**/sdkconfig.old
|
||||||
|
|
||||||
# Compiled Dynamic libraries
|
# Unit test app files
|
||||||
*.so
|
tools/unit-test-app/build
|
||||||
*.dylib
|
tools/unit-test-app/build_*_*/
|
||||||
*.dll
|
tools/unit-test-app/sdkconfig
|
||||||
|
tools/unit-test-app/sdkconfig.old
|
||||||
|
|
||||||
# Fortran module files
|
# test application build files
|
||||||
*.mod
|
tools/test_apps/**/build/
|
||||||
*.smod
|
tools/test_apps/**/build_*_*/
|
||||||
|
tools/test_apps/**/sdkconfig
|
||||||
|
tools/test_apps/**/sdkconfig.old
|
||||||
|
|
||||||
# Compiled Static libraries
|
TEST_LOGS/
|
||||||
*.lai
|
build_summary_*.xml
|
||||||
*.la
|
|
||||||
*.a
|
|
||||||
*.lib
|
|
||||||
|
|
||||||
# Executables
|
# gcov coverage reports
|
||||||
*.exe
|
*.gcda
|
||||||
*.out
|
*.gcno
|
||||||
*.app
|
coverage.info
|
||||||
|
coverage_report/
|
||||||
|
|
||||||
# ---> CMake
|
test_multi_heap_host
|
||||||
CMakeLists.txt.user
|
|
||||||
CMakeCache.txt
|
|
||||||
CMakeFiles
|
|
||||||
CMakeScripts
|
|
||||||
Testing
|
|
||||||
Makefile
|
|
||||||
cmake_install.cmake
|
|
||||||
install_manifest.txt
|
|
||||||
compile_commands.json
|
|
||||||
CTestTestfile.cmake
|
|
||||||
_deps
|
|
||||||
CMakeUserPresets.json
|
|
||||||
|
|
||||||
# Build directory
|
# VS Code Settings
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# VIM files
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# Sublime Text files
|
||||||
|
*.sublime-project
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# Clion IDE CMake build & config
|
||||||
|
.idea/
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# Results for the checking of the Python coding style and static analysis
|
||||||
|
.mypy_cache
|
||||||
|
flake8_output.txt
|
||||||
|
|
||||||
|
# ESP-IDF default build directory name
|
||||||
build
|
build
|
||||||
|
|
||||||
# unity-app directory
|
# lock files for examples and components
|
||||||
unity-app
|
dependencies.lock
|
||||||
|
|
||||||
|
# managed_components for examples
|
||||||
|
managed_components
|
||||||
|
|
||||||
|
# pytest log
|
||||||
|
pytest-embedded/
|
||||||
|
# legacy one
|
||||||
|
pytest_embedded_log/
|
||||||
|
list_job*.txt
|
||||||
|
size_info*.txt
|
||||||
|
XUNIT_RESULT*.xml
|
||||||
|
.manifest_sha
|
||||||
|
|
||||||
|
# clang config (for LSP)
|
||||||
|
.clangd
|
||||||
|
|
||||||
|
# Vale
|
||||||
|
.vale/styles/*
|
||||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"files.associations": {
|
"files.associations": {
|
||||||
"relay_chn.h": "c"
|
"relay_chn.h": "c"
|
||||||
}
|
},
|
||||||
|
"idf.port": "/dev/ttyUSB0"
|
||||||
}
|
}
|
||||||
39
README.md
39
README.md
@@ -11,10 +11,15 @@ An ESP-IDF component for controlling relay channels, specifically designed for d
|
|||||||
- Forward/Reverse direction control
|
- Forward/Reverse direction control
|
||||||
- Direction flipping capability
|
- Direction flipping capability
|
||||||
- State monitoring and reporting
|
- State monitoring and reporting
|
||||||
|
- Optional sensitivty adjustable tilting feature
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
Each relay channel consists of 2 output relays controlled by 2 GPIO pins. The component provides APIs to control these relay pairs while ensuring safe operation, particularly for driving bipolar motors. It prevents short-circuits by automatically managing direction changes with configurable inertia timing.
|
Each relay channel consists of 2 output relays controlled by 2 GPIO pins. The component provides APIs to control these relay pairs while ensuring safe operation, particularly for driving bipolar motors. To prevent mechanical strain on the motor, the component automatically manages direction changes with a configurable inertia delay, protecting it from abrupt reversals.
|
||||||
|
|
||||||
|
It also provides an optional tilting interface per channel base. Tilting makes a channel move with a specific pattern moving with small steps at a time. Tilting is specifically designed for controlling some types of curtains that need to be adjusted to let enter specific amount of day light.
|
||||||
|
Since it operates on relays, the switching frequency is limited to 10Hz which complies with the most of the general purpose relays' requirements. The minimum frequency is 2Hz and the duty cycle is about 10% in all ranges.
|
||||||
|
The module also handles all the required timing between the movement transitions automatically to ensure reliable operation.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
@@ -22,16 +27,18 @@ Configure the component through menuconfig under "Relay Channel Driver Configura
|
|||||||
|
|
||||||
- `CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS`: Time to wait before changing direction (200-1500ms, default: 800ms)
|
- `CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS`: Time to wait before changing direction (200-1500ms, default: 800ms)
|
||||||
- `CONFIG_RELAY_CHN_COUNT`: Number of relay channels (1-8, default: 1)
|
- `CONFIG_RELAY_CHN_COUNT`: Number of relay channels (1-8, default: 1)
|
||||||
|
- `CONFIG_RELAY_CHN_ENABLE_TILTING`: Enable tilting interface on all channels. (default: n)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
1. Copy the component to your project's components directory
|
Just add it as a custom dependency to your project's `idf_component.yml`:
|
||||||
2. Add dependency to your project's `idf_component.yml`:
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
dependencies:
|
dependencies:
|
||||||
|
# Add as a custom component from git repository
|
||||||
relay_chn:
|
relay_chn:
|
||||||
version: "^0.1.0"
|
git: https://git.kozmotronik.com.tr/KozmotronikTech/relay_chn.git
|
||||||
|
version: '>=0.4.0'
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
@@ -77,6 +84,30 @@ char *state_str = relay_chn_get_state_str(0);
|
|||||||
relay_chn_direction_t direction = relay_chn_get_direction(0);
|
relay_chn_direction_t direction = relay_chn_get_direction(0);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 4. Tilting Interface (if enabled)
|
||||||
|
|
||||||
|
```c
|
||||||
|
// Assuming CONFIG_RELAY_CHN_ENABLE_TILTING is enabled
|
||||||
|
|
||||||
|
// Start tilting automatically (channel 0)
|
||||||
|
relay_chn_tilt_auto(0);
|
||||||
|
|
||||||
|
// Tilt forward (channel 0)
|
||||||
|
relay_chn_tilt_forward(0);
|
||||||
|
|
||||||
|
// Tilt reverse (channel 0)
|
||||||
|
relay_chn_tilt_reverse(0);
|
||||||
|
|
||||||
|
// Stop tilting (channel 0)
|
||||||
|
relay_chn_tilt_stop(0);
|
||||||
|
|
||||||
|
// Set tilting sensitivity (channel 0, sensitivity as percentage)
|
||||||
|
relay_chn_tilt_sensitivity_set(0, 90);
|
||||||
|
|
||||||
|
// Get tilting sensitivity (channel 0, sensitivty as percentage)
|
||||||
|
uint8_t sensitivity = relay_chn_tilt_sensitivity_get(0);
|
||||||
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
[MIT License](LICENSE) - Copyright (c) 2025 kozmotronik.
|
[MIT License](LICENSE) - Copyright (c) 2025 kozmotronik.
|
||||||
|
|||||||
13
app_test/CMakeLists.txt
Normal file
13
app_test/CMakeLists.txt
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.5)
|
||||||
|
|
||||||
|
# Define component search paths
|
||||||
|
# IMPORTANT: We should tell to the ESP-IDF
|
||||||
|
# where it can find relay_chn component.
|
||||||
|
# We add the 'relay_chn' directory to the COMPONENT_DIRS by specifying: ../relay_chn
|
||||||
|
set(EXTRA_COMPONENT_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/../relay_chn")
|
||||||
|
|
||||||
|
# Include ESP-IDF project build system
|
||||||
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||||
|
|
||||||
|
# Define the name of this project
|
||||||
|
project(relay_chn_app_test)
|
||||||
3
app_test/main/CMakeLists.txt
Normal file
3
app_test/main/CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
idf_component_register(SRCS "test_relay_chn.c"
|
||||||
|
INCLUDE_DIRS "."
|
||||||
|
REQUIRES unity relay_chn)
|
||||||
443
app_test/main/test_relay_chn.c
Normal file
443
app_test/main/test_relay_chn.c
Normal file
@@ -0,0 +1,443 @@
|
|||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "unity.h"
|
||||||
|
#include "unity_test_utils.h"
|
||||||
|
#include "relay_chn.h" // Main header file for the relay_chn component
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
#include "sdkconfig.h" // For accessing CONFIG_* values
|
||||||
|
|
||||||
|
// Test GPIOs and channel IDs
|
||||||
|
// Please ensure these GPIOs are correct and suitable for your board.
|
||||||
|
// Two channels (4 GPIOs) are used as an example.
|
||||||
|
const gpio_num_t gpio_map[] = {GPIO_NUM_4, GPIO_NUM_5, GPIO_NUM_18, GPIO_NUM_19};
|
||||||
|
const uint8_t gpio_count = sizeof(gpio_map) / sizeof(gpio_map[0]);
|
||||||
|
// Assuming 2 GPIOs are used per channel
|
||||||
|
const uint8_t relay_chn_count = gpio_count / 2;
|
||||||
|
|
||||||
|
// Retrieve inertia value from SDKconfig
|
||||||
|
#ifndef CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS
|
||||||
|
#define CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS 500 // Default if not defined in SDKconfig
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const uint32_t opposite_inertia_ms = CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS;
|
||||||
|
|
||||||
|
// Tolerant delay margin to ensure operations complete, especially after inertia.
|
||||||
|
const uint32_t test_delay_margin_ms = 50;
|
||||||
|
|
||||||
|
// --- Test Setup/Teardown Functions ---
|
||||||
|
void setUp(void) {
|
||||||
|
ESP_LOGI("TEST_SETUP", "Running setUp for relay_chn tests.");
|
||||||
|
// Re-create the component before each test. relay_chn_create returns esp_err_t.
|
||||||
|
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
||||||
|
// Ensure all relays are stopped at the beginning, and transition to FREE state
|
||||||
|
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||||
|
relay_chn_stop(i); // relay_chn_stop returns void
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); // Wait for FREE state
|
||||||
|
}
|
||||||
|
ESP_LOGI("TEST_SETUP", "All channels initialized to RELAY_CHN_STATE_FREE.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void tearDown(void) {
|
||||||
|
ESP_LOGI("TEST_TEARDOWN", "Running tearDown for relay_chn tests.");
|
||||||
|
// Stop all relays after each test, and transition to FREE state
|
||||||
|
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||||
|
relay_chn_stop(i); // relay_chn_stop returns void
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms)); // Wait for FREE state
|
||||||
|
}
|
||||||
|
ESP_LOGI("TEST_TEARDOWN", "All channels returned to RELAY_CHN_STATE_FREE.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Basic Functionality Tests ---
|
||||||
|
|
||||||
|
// TEST_CASE 1: Test that relay channels initialize correctly to RELAY_CHN_STATE_FREE
|
||||||
|
TEST_CASE("Relay channels initialize correctly to FREE state", "[relay_chn]") {
|
||||||
|
ESP_LOGI("TEST", "Running test: Relay channels initialize correctly to FREE state");
|
||||||
|
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST_CASE 2: Test that relays run in the forward direction and update their state
|
||||||
|
TEST_CASE("Relay channels run forward and update state", "[relay_chn]") {
|
||||||
|
ESP_LOGI("TEST", "Running test: Relay channels run forward and update state");
|
||||||
|
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||||
|
relay_chn_run_forward(i); // relay_chn_run_forward returns void
|
||||||
|
// Short delay for state to update
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST_CASE 3: Test that relays run in the reverse direction and update their state
|
||||||
|
TEST_CASE("Relay channels run reverse and update state", "[relay_chn]") {
|
||||||
|
ESP_LOGI("TEST", "Running test: Relay channels run reverse and update state");
|
||||||
|
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||||
|
relay_chn_run_reverse(i); // relay_chn_run_reverse returns void
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST_CASE 4: Test that relays stop and transition to RELAY_CHN_STATE_FREE
|
||||||
|
// This test also verifies the transition to FREE state after a STOP command.
|
||||||
|
TEST_CASE("Relay channels stop and update to FREE state", "[relay_chn]") {
|
||||||
|
ESP_LOGI("TEST", "Running test: Relay channels stop and update to FREE state");
|
||||||
|
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
||||||
|
// First, run forward to test stopping and transitioning to FREE state
|
||||||
|
relay_chn_run_forward(i); // relay_chn_run_forward returns void
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(i));
|
||||||
|
|
||||||
|
// Now, issue the stop command
|
||||||
|
relay_chn_stop(i); // relay_chn_stop returns void
|
||||||
|
// Immediately after stop, state should be STOPPED
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(i));
|
||||||
|
|
||||||
|
// Then, wait for the inertia period for it to transition to RELAY_CHN_STATE_FREE
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST_CASE 5: Test function calls with invalid channel IDs
|
||||||
|
// TEST_CASE("Invalid channel ID handling", "[relay_chn]") {
|
||||||
|
// ESP_LOGI("TEST", "Running test: Invalid channel ID handling");
|
||||||
|
// uint8_t invalid_channel_id = relay_chn_count + 1; // An ID that is out of bounds
|
||||||
|
|
||||||
|
// // These calls are expected to return ESP_ERR_INVALID_ARG, so TEST_ASSERT_EQUAL is appropriate.
|
||||||
|
// TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_run_forward(invalid_channel_id));
|
||||||
|
// TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_run_reverse(invalid_channel_id));
|
||||||
|
// TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_stop(invalid_channel_id));
|
||||||
|
|
||||||
|
// // Test tilt commands only if tilt functionality is enabled
|
||||||
|
// #if CONFIG_RELAY_CHN_ENABLE_TILTING == 1
|
||||||
|
// TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_tilt_forward(invalid_channel_id));
|
||||||
|
// TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_tilt_reverse(invalid_channel_id));
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, relay_chn_get_state(invalid_channel_id));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// TEST_CASE 6: Test independent operation of multiple relay channels
|
||||||
|
TEST_CASE("Multiple channels can operate independently", "[relay_chn]") {
|
||||||
|
ESP_LOGI("TEST", "Running test: Multiple channels can operate independently");
|
||||||
|
if (relay_chn_count >= 2) {
|
||||||
|
// Start Channel 0 in forward direction
|
||||||
|
relay_chn_run_forward(0); // relay_chn_run_forward returns void
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(0));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(1)); // Other channel should not be affected
|
||||||
|
|
||||||
|
// Start Channel 1 in reverse direction
|
||||||
|
relay_chn_run_reverse(1); // relay_chn_run_reverse returns void
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(0));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(1));
|
||||||
|
|
||||||
|
// Stop Channel 0 and wait for it to become FREE
|
||||||
|
relay_chn_stop(0); // relay_chn_stop returns void
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(0));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(1)); // Other channel should continue running
|
||||||
|
|
||||||
|
// Stop Channel 1 and wait for it to become FREE
|
||||||
|
relay_chn_stop(1); // relay_chn_stop returns void
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(0));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(1));
|
||||||
|
} else {
|
||||||
|
ESP_LOGW("TEST", "Skipping 'Multiple channels can operate independently' test: Not enough channels available.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ### Inertia and State Transition Tests
|
||||||
|
|
||||||
|
// This section specifically targets the inertia periods and complex state transitions as per the component's logic.
|
||||||
|
|
||||||
|
// TEST_CASE 7: Test transition from forward to reverse with inertia and state checks
|
||||||
|
// Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_REVERSE
|
||||||
|
TEST_CASE("Forward to Reverse transition with opposite inertia", "[relay_chn][inertia]") {
|
||||||
|
ESP_LOGI("TEST", "Running test: Forward to Reverse transition with opposite inertia");
|
||||||
|
uint8_t ch = 0; // Channel to test
|
||||||
|
|
||||||
|
// 1. Start in forward direction
|
||||||
|
relay_chn_run_forward(ch); // relay_chn_run_forward returns void
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Short delay for state stabilization
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch));
|
||||||
|
|
||||||
|
// 2. Issue reverse command
|
||||||
|
relay_chn_run_reverse(ch); // relay_chn_run_reverse returns void
|
||||||
|
// Immediately after the command, the motor should be stopped
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE_PENDING, relay_chn_get_state(ch));
|
||||||
|
|
||||||
|
// Wait for the inertia period (after which the reverse command will be dispatched)
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch)); // Should now be in reverse state
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST_CASE 8: Test transition from reverse to forward with inertia and state checks
|
||||||
|
// Scenario: RELAY_CHN_STATE_REVERSE -> (relay_chn_run_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_FORWARD
|
||||||
|
TEST_CASE("Reverse to Forward transition with opposite inertia", "[relay_chn][inertia]") {
|
||||||
|
ESP_LOGI("TEST", "Running test: Reverse to Forward transition with opposite inertia");
|
||||||
|
uint8_t ch = 0;
|
||||||
|
|
||||||
|
// 1. Start in reverse direction
|
||||||
|
relay_chn_run_reverse(ch); // relay_chn_run_reverse returns void
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch));
|
||||||
|
|
||||||
|
// 2. Issue forward command
|
||||||
|
relay_chn_run_forward(ch); // relay_chn_run_forward returns void
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD_PENDING, relay_chn_get_state(ch));
|
||||||
|
|
||||||
|
// Wait for inertia
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST_CASE 9: Test issuing the same run command while already running (no inertia expected)
|
||||||
|
// Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD
|
||||||
|
TEST_CASE("Running in same direction does not incur inertia", "[relay_chn][inertia]") {
|
||||||
|
ESP_LOGI("TEST", "Running test: Running in same direction does not incur inertia");
|
||||||
|
uint8_t ch = 0;
|
||||||
|
|
||||||
|
// 1. Start in forward direction
|
||||||
|
relay_chn_run_forward(ch); // relay_chn_run_forward returns void
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch));
|
||||||
|
|
||||||
|
// 2. Issue the same forward command again
|
||||||
|
relay_chn_run_forward(ch); // relay_chn_run_forward returns void
|
||||||
|
// As per the code, is_direction_opposite_to_current_motion should return false, so no inertia.
|
||||||
|
// Just a short delay to check state remains the same.
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST_CASE 10: Test transition from FREE state to running (no inertia expected)
|
||||||
|
// Scenario: RELAY_CHN_STATE_FREE -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD
|
||||||
|
TEST_CASE("FREE to Running transition without inertia", "[relay_chn][inertia]") {
|
||||||
|
ESP_LOGI("TEST", "Running test: FREE to Running transition without inertia");
|
||||||
|
uint8_t ch = 0;
|
||||||
|
|
||||||
|
// setUp() should have already brought the channel to FREE state
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(ch));
|
||||||
|
|
||||||
|
// Start in forward direction
|
||||||
|
relay_chn_run_forward(ch); // relay_chn_run_forward returns void
|
||||||
|
// No inertia is expected when starting from FREE state.
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ### Tilt Functionality Tests (Conditional)
|
||||||
|
|
||||||
|
// This section will only be compiled if **`CONFIG_RELAY_CHN_ENABLE_TILTING`** is defined as **`1`** in `sdkconfig`.
|
||||||
|
|
||||||
|
#if CONFIG_RELAY_CHN_ENABLE_TILTING == 1
|
||||||
|
|
||||||
|
#define RELAY_CHN_CMD_FORWARD 1
|
||||||
|
#define RELAY_CHN_CMD_REVERSE 2
|
||||||
|
|
||||||
|
// Helper function to prepare channel for tilt tests
|
||||||
|
void prepare_channel_for_tilt(uint8_t chn_id, int initial_cmd) {
|
||||||
|
// Ensure the channel has had a 'last_run_cmd'
|
||||||
|
if (initial_cmd == RELAY_CHN_CMD_FORWARD) {
|
||||||
|
relay_chn_run_forward(chn_id);
|
||||||
|
} else { // Assuming initial_cmd is RELAY_CHN_CMD_REVERSE
|
||||||
|
relay_chn_run_reverse(chn_id);
|
||||||
|
}
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms)); // Allow command to process
|
||||||
|
relay_chn_stop(chn_id); // Stop it to set last_run_cmd but return to FREE for next test
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(chn_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST_CASE 11: Test transition from running forward to tilt forward
|
||||||
|
// Scenario: RELAY_CHN_STATE_FORWARD -> (relay_chn_tilt_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_FORWARD
|
||||||
|
TEST_CASE("Run Forward to Tilt Forward transition with inertia", "[relay_chn][tilt][inertia]") {
|
||||||
|
ESP_LOGI("TEST", "Running test: Run Forward to Tilt Forward transition with 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
|
||||||
|
relay_chn_run_forward(ch);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch));
|
||||||
|
|
||||||
|
// 2. Issue tilt forward command
|
||||||
|
relay_chn_tilt_forward(ch);
|
||||||
|
// After tilt command, it should immediately stop and then trigger inertia.
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(ch));
|
||||||
|
|
||||||
|
// Wait for the inertia period (after which the tilt command will be dispatched)
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST_CASE 12: Test transition from running reverse to tilt reverse
|
||||||
|
// Scenario: RELAY_CHN_STATE_REVERSE -> (relay_chn_tilt_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_REVERSE
|
||||||
|
TEST_CASE("Run Reverse to Tilt Reverse transition with inertia", "[relay_chn][tilt][inertia]") {
|
||||||
|
ESP_LOGI("TEST", "Running test: Run Reverse to Tilt Reverse transition with 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
|
||||||
|
relay_chn_run_reverse(ch);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch));
|
||||||
|
|
||||||
|
// 2. Issue tilt reverse command
|
||||||
|
relay_chn_tilt_reverse(ch);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(ch));
|
||||||
|
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(ch));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST_CASE 13: Test transition from FREE state to tilt forward (now with preparation)
|
||||||
|
// Scenario: RELAY_CHN_STATE_FREE -> (prepare) -> RELAY_CHN_STATE_FREE -> (relay_chn_tilt_forward) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_FORWARD
|
||||||
|
TEST_CASE("FREE to Tilt Forward transition with inertia (prepared)", "[relay_chn][tilt][inertia]") {
|
||||||
|
ESP_LOGI("TEST", "Running test: FREE to Tilt Forward transition with inertia (prepared)");
|
||||||
|
uint8_t ch = 0;
|
||||||
|
|
||||||
|
// Prepare channel by running forward first to set last_run_cmd
|
||||||
|
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD);
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(ch)); // Ensure we are back to FREE
|
||||||
|
|
||||||
|
// Issue tilt forward command
|
||||||
|
relay_chn_tilt_forward(ch);
|
||||||
|
// From FREE state, tilt command should still incur the inertia due to the internal timer logic
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST_CASE 14: Test transition from FREE state to tilt reverse (now with preparation)
|
||||||
|
// Scenario: RELAY_CHN_STATE_FREE -> (prepare) -> RELAY_CHN_STATE_FREE -> (relay_chn_tilt_reverse) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_TILT_REVERSE
|
||||||
|
TEST_CASE("FREE to Tilt Reverse transition with inertia (prepared)", "[relay_chn][tilt][inertia]") {
|
||||||
|
ESP_LOGI("TEST", "Running test: FREE to Tilt Reverse transition with inertia (prepared)");
|
||||||
|
uint8_t ch = 0;
|
||||||
|
|
||||||
|
// Prepare channel by running reverse first to set last_run_cmd
|
||||||
|
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_REVERSE);
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(ch)); // Ensure we are back to FREE
|
||||||
|
|
||||||
|
// Issue tilt reverse command
|
||||||
|
relay_chn_tilt_reverse(ch);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(ch));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST_CASE 15: Test transition from tilt forward to run forward (inertia expected for run)
|
||||||
|
// Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_run_forward) -> RELAY_CHN_STATE_FORWARD
|
||||||
|
TEST_CASE("Tilt Forward to Run Forward transition with inertia", "[relay_chn][tilt][inertia]") {
|
||||||
|
ESP_LOGI("TEST", "Running test: Tilt Forward to Run Forward transition with inertia");
|
||||||
|
uint8_t ch = 0;
|
||||||
|
|
||||||
|
// Prepare channel by running forward first to set last_run_cmd, then tilt
|
||||||
|
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD);
|
||||||
|
relay_chn_tilt_forward(ch); // Go to tilt state
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch));
|
||||||
|
|
||||||
|
// 2. Issue run forward command
|
||||||
|
relay_chn_run_forward(ch);
|
||||||
|
// From Tilt to Run in the same logical name but in the opposite direction, inertia is expected.
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD_PENDING, relay_chn_get_state(ch));
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(ch));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST_CASE 16: Test transition from tilt reverse to run reverse (no inertia expected for run)
|
||||||
|
// Scenario: RELAY_CHN_STATE_TILT_REVERSE -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_REVERSE
|
||||||
|
TEST_CASE("Tilt Reverse to Run Reverse transition with inertia", "[relay_chn][tilt][inertia]") {
|
||||||
|
ESP_LOGI("TEST", "Running test: Tilt Reverse to Run Reverse transition with inertia");
|
||||||
|
uint8_t ch = 0;
|
||||||
|
|
||||||
|
// Prepare channel by running reverse first to set last_run_cmd, then tilt
|
||||||
|
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_REVERSE);
|
||||||
|
relay_chn_tilt_reverse(ch); // Go to tilt state
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_REVERSE, relay_chn_get_state(ch));
|
||||||
|
|
||||||
|
// 2. Issue run reverse command
|
||||||
|
relay_chn_run_reverse(ch);
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE_PENDING, relay_chn_get_state(ch));
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST_CASE 17: Test transition from tilt forward to run reverse (without inertia)
|
||||||
|
// Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_run_reverse) -> RELAY_CHN_STATE_REVERSE
|
||||||
|
TEST_CASE("Tilt Forward to Run Reverse transition without inertia", "[relay_chn][tilt][inertia]") {
|
||||||
|
ESP_LOGI("TEST", "Running test: Tilt Forward to Run Reverse transition without inertia");
|
||||||
|
uint8_t ch = 0;
|
||||||
|
|
||||||
|
// Prepare channel by running forward first to set last_run_cmd, then tilt
|
||||||
|
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD);
|
||||||
|
relay_chn_tilt_forward(ch); // Go to tilt state
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch));
|
||||||
|
|
||||||
|
// 2. Issue run reverse command (opposite direction)
|
||||||
|
relay_chn_run_reverse(ch);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(ch));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST_CASE 18: Test stopping from a tilt state (no inertia for stop command itself)
|
||||||
|
// Scenario: RELAY_CHN_STATE_TILT_FORWARD -> (relay_chn_stop) -> RELAY_CHN_STATE_STOPPED -> (inertia) -> RELAY_CHN_STATE_FREE
|
||||||
|
TEST_CASE("Tilt to Stop transition without immediate inertia for stop", "[relay_chn][tilt][inertia]") {
|
||||||
|
ESP_LOGI("TEST", "Running test: Tilt to Stop transition without immediate inertia for stop");
|
||||||
|
uint8_t ch = 0;
|
||||||
|
|
||||||
|
// Prepare channel by running forward first to set last_run_cmd, then tilt
|
||||||
|
prepare_channel_for_tilt(ch, RELAY_CHN_CMD_FORWARD);
|
||||||
|
relay_chn_tilt_forward(ch); // Go to tilt state
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(opposite_inertia_ms + test_delay_margin_ms));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_TILT_FORWARD, relay_chn_get_state(ch));
|
||||||
|
|
||||||
|
// 2. Issue stop command
|
||||||
|
relay_chn_stop(ch);
|
||||||
|
// Stop command should apply immediately, setting state to FREE since last state was tilt.
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(test_delay_margin_ms));
|
||||||
|
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FREE, relay_chn_get_state(ch));
|
||||||
|
}
|
||||||
|
|
||||||
|
#else // CONFIG_RELAY_CHN_ENABLE_TILTING == 0
|
||||||
|
// If tilt functionality is disabled, these tests are skipped.
|
||||||
|
// A dummy test case is added to indicate this in the test output.
|
||||||
|
TEST_CASE("Tilt functionality is disabled, skipping tilt tests", "[relay_chn][tilt_disabled]") {
|
||||||
|
ESP_LOGI("TEST", "Tilt functionality is disabled (CONFIG_RELAY_CHN_ENABLE_TILTING is 0). Skipping tilt tests.");
|
||||||
|
TEST_ASSERT_TRUE(true); // Just to ensure at least one test passes for visibility
|
||||||
|
}
|
||||||
|
#endif // CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||||
|
|
||||||
|
|
||||||
|
// ### `app_main` Function
|
||||||
|
|
||||||
|
// --- app_main function ---
|
||||||
|
void app_main(void) {
|
||||||
|
ESP_LOGI("APP_MAIN", "Starting relay_chn unit tests...");
|
||||||
|
|
||||||
|
// Run the Unity test runner
|
||||||
|
unity_run_all_tests();
|
||||||
|
|
||||||
|
// After tests complete, instead of restarting, the device will halt.
|
||||||
|
ESP_LOGI("APP_MAIN", "All relay_chn tests completed. Device halted.");
|
||||||
|
while (1) {
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(1000)); // Wait with low power consumption
|
||||||
|
}
|
||||||
|
}
|
||||||
2033
app_test/sdkconfig
Normal file
2033
app_test/sdkconfig
Normal file
File diff suppressed because it is too large
Load Diff
2033
app_test/sdkconfig.old
Normal file
2033
app_test/sdkconfig.old
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,7 +0,0 @@
|
|||||||
name: relay_chn
|
|
||||||
version: 0.1.0
|
|
||||||
description: Custom component for relay channel control
|
|
||||||
dependencies:
|
|
||||||
idf:
|
|
||||||
version: ">=4.0"
|
|
||||||
# TODO: Repo ve belgelendirme bağlantılarını ekle.
|
|
||||||
@@ -17,4 +17,13 @@ menu "Relay Channel Driver Configuration"
|
|||||||
help
|
help
|
||||||
Number of relay channels between 1 and 8.
|
Number of relay channels between 1 and 8.
|
||||||
|
|
||||||
|
config RELAY_CHN_ENABLE_TILTING
|
||||||
|
bool "Enable tilting on relay channels"
|
||||||
|
default n
|
||||||
|
help
|
||||||
|
This option controls enabling tilting on channels. Tilting makes
|
||||||
|
a channel move with a specific pattern moving with small steps
|
||||||
|
at a time. Tilting is specifically designed for controlling some
|
||||||
|
types of curtains that need to be adjusted to let enter specific
|
||||||
|
amount of day light.
|
||||||
endmenu
|
endmenu
|
||||||
6
relay_chn/idf_component.yml
Normal file
6
relay_chn/idf_component.yml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
name: relay_chn
|
||||||
|
version: "0.4.0"
|
||||||
|
description: "Custom component for relay channel control"
|
||||||
|
license: "MIT"
|
||||||
|
url: "https://git.kozmotronik.com.tr/KozmotronikTech/relay_chn_component"
|
||||||
|
repository: "https://git.kozmotronik.com.tr/KozmotronikTech/relay_chn_component.git"
|
||||||
@@ -14,7 +14,8 @@
|
|||||||
* 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.
|
||||||
* The module also automatically manages the direction change inertia to prevent short-circuiting the motor.
|
* To prevent mechanical strain on the motor, the component automatically manages direction changes
|
||||||
|
* with a configurable inertia delay, protecting it from abrupt reversals.
|
||||||
* The STOP command overrides any other command and clears the pending command if any.
|
* The STOP command overrides any other command and clears the pending command if any.
|
||||||
*
|
*
|
||||||
* The module internally uses a custom esp event loop to handle relay commands serially to ensure
|
* The module internally uses a custom esp event loop to handle relay commands serially to ensure
|
||||||
@@ -54,6 +55,10 @@ enum relay_chn_state_enum {
|
|||||||
RELAY_CHN_STATE_REVERSE, ///< The relay channel is running in the reverse direction.
|
RELAY_CHN_STATE_REVERSE, ///< The relay channel is running in the reverse direction.
|
||||||
RELAY_CHN_STATE_FORWARD_PENDING, ///< The relay channel is pending to run in the forward direction.
|
RELAY_CHN_STATE_FORWARD_PENDING, ///< The relay channel is pending to run in the forward direction.
|
||||||
RELAY_CHN_STATE_REVERSE_PENDING, ///< The relay channel is pending to run in the reverse direction.
|
RELAY_CHN_STATE_REVERSE_PENDING, ///< The relay channel is pending to run in the reverse direction.
|
||||||
|
#if CONFIG_RELAY_CHN_ENABLE_TILTING == 1
|
||||||
|
RELAY_CHN_STATE_TILT_FORWARD, ///< The relay channel is tilting for forward.
|
||||||
|
RELAY_CHN_STATE_TILT_REVERSE, ///< The relay channel is tilting for reverse.
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -198,6 +203,77 @@ void relay_chn_flip_direction(uint8_t chn_id);
|
|||||||
*/
|
*/
|
||||||
relay_chn_direction_t relay_chn_get_direction(uint8_t chn_id);
|
relay_chn_direction_t relay_chn_get_direction(uint8_t chn_id);
|
||||||
|
|
||||||
|
|
||||||
|
#if CONFIG_RELAY_CHN_ENABLE_TILTING == 1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Enables automatic tilting for the specified relay channel.
|
||||||
|
*
|
||||||
|
* This function enables automatic tilting mode for the given relay channel. The channel will automatically
|
||||||
|
* switch between forward and reverse tilting based on some internal sensing mechanism (not detailed here).
|
||||||
|
* Requires appropriate hardware support and configuration.
|
||||||
|
*
|
||||||
|
* @param chn_id The ID of the relay channel to enable automatic tilting.
|
||||||
|
*/
|
||||||
|
void relay_chn_tilt_auto(uint8_t chn_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Tilts the specified relay channel forward.
|
||||||
|
*
|
||||||
|
* This function initiates a forward tilting action for the specified relay channel. This is a manual tilting
|
||||||
|
* operation, unlike `relay_chn_tilt_auto()`.
|
||||||
|
*
|
||||||
|
* @param chn_id The ID of the relay channel to tilt forward.
|
||||||
|
*/
|
||||||
|
void relay_chn_tilt_forward(uint8_t chn_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Tilts the specified relay channel reverse.
|
||||||
|
*
|
||||||
|
* This function initiates a reverse tilting action for the specified relay channel. This is a manual tilting
|
||||||
|
* operation, unlike `relay_chn_tilt_auto()`.
|
||||||
|
*
|
||||||
|
* @param chn_id The ID of the relay channel to tilt reverse.
|
||||||
|
*/
|
||||||
|
void relay_chn_tilt_reverse(uint8_t chn_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Stops the tilting action on the specified relay channel.
|
||||||
|
*
|
||||||
|
* This function stops any ongoing tilting action (automatic or manual) on the specified relay channel.
|
||||||
|
*
|
||||||
|
* @param chn_id The ID of the relay channel to stop tilting.
|
||||||
|
*/
|
||||||
|
void relay_chn_tilt_stop(uint8_t chn_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the tilting sensitivity for the specified relay channel.
|
||||||
|
*
|
||||||
|
* This function sets the sensitivity for the automatic tilting mechanism. A higher sensitivity value
|
||||||
|
* typically means the channel will react more readily to tilting events.
|
||||||
|
*
|
||||||
|
* @param chn_id The ID of the relay channel to set the sensitivity for.
|
||||||
|
* @param sensitivity The sensitivity in percentage: 0 - 100%.
|
||||||
|
*/
|
||||||
|
void relay_chn_tilt_sensitivity_set(uint8_t chn_id, uint8_t sensitivity);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the tilting sensitivity for the specified relay channel.
|
||||||
|
*
|
||||||
|
* This function retrieves the currently set sensitivity for the specified relay channel's automatic
|
||||||
|
* tilting mechanism.
|
||||||
|
*
|
||||||
|
* @param chn_id The ID of the relay channel to get the sensitivity for.
|
||||||
|
* @param sensitivity The pointer to the memory in to which the sensitivity values will be copied.
|
||||||
|
* @param length The length of the sensitvity memory.
|
||||||
|
* @return
|
||||||
|
* - ESP_OK: Success
|
||||||
|
* - ESP_ERR_INVALID_ARG: Invalid argument
|
||||||
|
*/
|
||||||
|
esp_err_t relay_chn_tilt_sensitivity_get(uint8_t chn_id, uint8_t *sensitivity, size_t length);
|
||||||
|
|
||||||
|
#endif // CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
1415
relay_chn/src/relay_chn.c
Normal file
1415
relay_chn/src/relay_chn.c
Normal file
File diff suppressed because it is too large
Load Diff
714
src/relay_chn.c
714
src/relay_chn.c
@@ -1,714 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file relay_chn.c
|
|
||||||
*
|
|
||||||
* @author
|
|
||||||
* Ismail Sahillioglu <ismailsahillioglu@gmail.com>
|
|
||||||
*
|
|
||||||
* @date 2025.02.08
|
|
||||||
*
|
|
||||||
* @ingroup relay_chn
|
|
||||||
*
|
|
||||||
* @brief This file contains the implementation of the relay channel component.
|
|
||||||
* @{
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include "esp_err.h"
|
|
||||||
#include "esp_log.h"
|
|
||||||
#include "esp_task.h"
|
|
||||||
#include "driver/gpio.h"
|
|
||||||
#include "esp_timer.h"
|
|
||||||
#include "esp_event_base.h"
|
|
||||||
#include "esp_event.h"
|
|
||||||
#include "relay_chn.h"
|
|
||||||
#include "sdkconfig.h"
|
|
||||||
|
|
||||||
|
|
||||||
#define RELAY_CHN_OPPOSITE_INERTIA_MS CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS
|
|
||||||
#define RELAY_CHN_COUNT CONFIG_RELAY_CHN_COUNT
|
|
||||||
|
|
||||||
static const char *TAG = "relay_chn";
|
|
||||||
|
|
||||||
ESP_EVENT_DEFINE_BASE(RELAY_CHN_CMD_EVENT);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Enumeration for relay channel commands.
|
|
||||||
*/
|
|
||||||
enum relay_chn_cmd_enum {
|
|
||||||
RELAY_CHN_CMD_NONE, ///< No command.
|
|
||||||
RELAY_CHN_CMD_STOP, ///< Stop the relay channel.
|
|
||||||
RELAY_CHN_CMD_FORWARD, ///< Run the relay channel in the forward direction.
|
|
||||||
RELAY_CHN_CMD_REVERSE, ///< Run the relay channel in the reverse direction.
|
|
||||||
RELAY_CHN_CMD_FLIP, ///< Flip the direction of the relay channel.
|
|
||||||
RELAY_CHN_CMD_FREE ///< Free the relay channel.
|
|
||||||
};
|
|
||||||
|
|
||||||
/// @brief Alias for the enum type relay_chn_cmd_enum.
|
|
||||||
typedef enum relay_chn_cmd_enum relay_chn_cmd_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Structure to hold runtime information for a relay channel.
|
|
||||||
*/
|
|
||||||
typedef struct relay_chn_run_info_type {
|
|
||||||
relay_chn_cmd_t last_run_cmd; ///< The last run command issued on the relay channel; forward or reverse.
|
|
||||||
uint32_t last_run_cmd_time_ms; ///< The time in milliseconds when the last run command was issued.
|
|
||||||
} relay_chn_run_info_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Structure to hold the output configuration of a relay channel.
|
|
||||||
*/
|
|
||||||
typedef struct relay_chn_output_type {
|
|
||||||
gpio_num_t forward_pin; ///< GPIO pin number for the forward direction.
|
|
||||||
gpio_num_t reverse_pin; ///< GPIO pin number for the reverse direction.
|
|
||||||
relay_chn_direction_t direction; ///< The current direction of the relay channel.
|
|
||||||
} relay_chn_output_t;
|
|
||||||
|
|
||||||
typedef struct relay_chn_type relay_chn_t; // Forward declaration
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Function pointer type for relay channel command execution functions.
|
|
||||||
* @param relay_chn Pointer to the relay channel to execute the command on.
|
|
||||||
*/
|
|
||||||
typedef void(*relay_chn_cmd_fn_t)(relay_chn_t*);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Structure to hold the state and configuration of a relay channel.
|
|
||||||
*/
|
|
||||||
typedef struct relay_chn_type {
|
|
||||||
uint8_t id; ///< The ID of the relay channel.
|
|
||||||
relay_chn_state_t state; ///< The current state of the relay channel.
|
|
||||||
relay_chn_run_info_t run_info; ///< Runtime information of the relay channel.
|
|
||||||
relay_chn_output_t output; ///< Output configuration of the relay channel.
|
|
||||||
relay_chn_cmd_t pending_cmd; ///< The command that is pending to be issued
|
|
||||||
esp_timer_handle_t inertia_timer; ///< Timer to handle the opposite direction inertia time.
|
|
||||||
} relay_chn_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Structure to manage the state change listeners.
|
|
||||||
*/
|
|
||||||
struct relay_chn_state_listener_manager_type {
|
|
||||||
uint8_t listener_count; ///< The number of registered listeners.
|
|
||||||
relay_chn_state_listener_t *listeners; ///< The list that holds references to the registered listeners.
|
|
||||||
} relay_chn_state_listener_manager;
|
|
||||||
|
|
||||||
|
|
||||||
static relay_chn_t relay_channels[RELAY_CHN_COUNT];
|
|
||||||
static esp_event_loop_handle_t relay_chn_event_loop;
|
|
||||||
|
|
||||||
|
|
||||||
// Private function declarations
|
|
||||||
// Event handler for the relay channel command event
|
|
||||||
static void relay_chn_event_handler(void* handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Check if the provided channel ID is valid.
|
|
||||||
*
|
|
||||||
* @param chn_id Channel ID to check.
|
|
||||||
* @return true Channel ID is valid.
|
|
||||||
* @return false Channel ID is invalid.
|
|
||||||
*/
|
|
||||||
static bool relay_chn_is_channel_id_valid(uint8_t chn_id);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Dispatches a relay channel command to the event loop.
|
|
||||||
*
|
|
||||||
* @param relay_chn The relay channel.
|
|
||||||
* @param cmd The command to dispatch.
|
|
||||||
*/
|
|
||||||
static void relay_chn_dispatch_cmd(relay_chn_t *relay_chn, relay_chn_cmd_t cmd);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns the string representation of a relay channel command.
|
|
||||||
*
|
|
||||||
* @param cmd The relay channel command.
|
|
||||||
* @return char* The string representation of the command.
|
|
||||||
*/
|
|
||||||
static char *relay_chn_cmd_str(relay_chn_cmd_t cmd);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Timer callback function for relay channel direction change inertia.
|
|
||||||
*
|
|
||||||
* This function is called when the opposite direction inertia timer expires. It checks if the channel
|
|
||||||
* has a pending command and dispatches it if there is one.
|
|
||||||
*
|
|
||||||
* @param arg The channel ID of the relay channel.
|
|
||||||
*/
|
|
||||||
static void relay_chn_timer_cb(void* arg)
|
|
||||||
{
|
|
||||||
uint8_t chn_id = *(uint8_t*) arg;
|
|
||||||
if (!relay_chn_is_channel_id_valid(chn_id)) {
|
|
||||||
ESP_LOGE(TAG, "relay_chn_timer_cb: Invalid relay channel ID!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
relay_chn_t* relay_chn = &relay_channels[chn_id];
|
|
||||||
// Does channel have a pending command?
|
|
||||||
if (relay_chn->pending_cmd != RELAY_CHN_CMD_NONE) {
|
|
||||||
relay_chn_dispatch_cmd(relay_chn, relay_chn->pending_cmd);
|
|
||||||
relay_chn->pending_cmd = RELAY_CHN_CMD_NONE;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ESP_LOGE(TAG, "relay_chn_timer_cb: No pending cmd for relay channel %d!", chn_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static esp_err_t relay_chn_init_timer(relay_chn_t *relay_chn)
|
|
||||||
{
|
|
||||||
char timer_name[32];
|
|
||||||
snprintf(timer_name, sizeof(timer_name), "relay_chn_%d_timer", relay_chn->id);
|
|
||||||
esp_timer_create_args_t timer_args = {
|
|
||||||
.callback = relay_chn_timer_cb,
|
|
||||||
.arg = &relay_chn->id,
|
|
||||||
.name = timer_name
|
|
||||||
};
|
|
||||||
return esp_timer_create(&timer_args, &relay_chn->inertia_timer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Check if the provided GPIO pin number is valid for the current device.
|
|
||||||
*
|
|
||||||
* @param gpio The GPIO pin number to check.
|
|
||||||
* @return true GPIO pin number is valid.
|
|
||||||
* @return false GPIO pin number is invalid.
|
|
||||||
*/
|
|
||||||
static bool relay_chn_is_gpio_valid(gpio_num_t gpio)
|
|
||||||
{
|
|
||||||
return gpio >= 0 && gpio < GPIO_PIN_COUNT;
|
|
||||||
}
|
|
||||||
|
|
||||||
static esp_err_t relay_chn_create_event_loop()
|
|
||||||
{
|
|
||||||
esp_event_loop_args_t loop_args = {
|
|
||||||
.queue_size = 10,
|
|
||||||
.task_name = "relay_chn_event_loop",
|
|
||||||
.task_priority = ESP_TASKD_EVENT_PRIO - 1,
|
|
||||||
.task_stack_size = 2048,
|
|
||||||
.task_core_id = tskNO_AFFINITY
|
|
||||||
};
|
|
||||||
esp_err_t ret = esp_event_loop_create(&loop_args, &relay_chn_event_loop);
|
|
||||||
ret |= esp_event_handler_register_with(relay_chn_event_loop,
|
|
||||||
RELAY_CHN_CMD_EVENT,
|
|
||||||
ESP_EVENT_ANY_ID,
|
|
||||||
relay_chn_event_handler, NULL);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_err_t relay_chn_create(const gpio_num_t* gpio_map, uint8_t gpio_count)
|
|
||||||
{
|
|
||||||
// Check if the device's GPIOs are enough for the number of channels
|
|
||||||
if (RELAY_CHN_COUNT > (GPIO_PIN_COUNT / 2)) {
|
|
||||||
ESP_LOGE(TAG, "Not enough GPIOs for the number of channels!");
|
|
||||||
ESP_LOGE(TAG, "Max available num of channels: %d, requested channels: %d", GPIO_PIN_COUNT / 2, RELAY_CHN_COUNT);
|
|
||||||
return ESP_ERR_INVALID_ARG;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the provided GPIOs correspond to the number of channels
|
|
||||||
if (gpio_count != RELAY_CHN_COUNT * 2) {
|
|
||||||
ESP_LOGE(TAG, "Invalid number of GPIOs provided: %d", gpio_count);
|
|
||||||
ESP_LOGE(TAG, "Expected number of GPIOs: %d", RELAY_CHN_COUNT * 2);
|
|
||||||
return ESP_ERR_INVALID_ARG;
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_err_t ret;
|
|
||||||
for (int i = 0; i < RELAY_CHN_COUNT; i++) {
|
|
||||||
gpio_num_t forward_pin = gpio_map[i];
|
|
||||||
gpio_num_t reverse_pin = gpio_map[i+1];
|
|
||||||
// Check if the GPIOs are valid
|
|
||||||
if (!relay_chn_is_gpio_valid(forward_pin)) {
|
|
||||||
ESP_LOGE(TAG, "Invalid GPIO pin number: %d", forward_pin);
|
|
||||||
return ESP_ERR_INVALID_ARG;
|
|
||||||
}
|
|
||||||
if (!relay_chn_is_gpio_valid(reverse_pin)) {
|
|
||||||
ESP_LOGE(TAG, "Invalid GPIO pin number: %d", reverse_pin);
|
|
||||||
return ESP_ERR_INVALID_ARG;
|
|
||||||
}
|
|
||||||
// Check if the GPIOs are valid
|
|
||||||
|
|
||||||
// Initialize the GPIOs
|
|
||||||
ret = gpio_reset_pin(forward_pin);
|
|
||||||
ret |= gpio_set_direction(forward_pin, GPIO_MODE_OUTPUT);
|
|
||||||
|
|
||||||
ret |= gpio_reset_pin(reverse_pin);
|
|
||||||
ret |= gpio_set_direction(reverse_pin, GPIO_MODE_OUTPUT);
|
|
||||||
if (ret != ESP_OK) {
|
|
||||||
ESP_LOGE(TAG, "Failed to initialize GPIOs relay channel %d!", i);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
// Initialize the GPIOs
|
|
||||||
|
|
||||||
// Initialize the relay channel
|
|
||||||
relay_chn_t* relay_chn = &relay_channels[i];
|
|
||||||
relay_chn->id = i;
|
|
||||||
relay_chn->output.forward_pin = forward_pin;
|
|
||||||
relay_chn->output.reverse_pin = reverse_pin;
|
|
||||||
relay_chn->output.direction = RELAY_CHN_DIRECTION_DEFAULT;
|
|
||||||
relay_chn->state = RELAY_CHN_STATE_STOPPED;
|
|
||||||
relay_chn->pending_cmd = RELAY_CHN_CMD_NONE;
|
|
||||||
relay_chn->run_info.last_run_cmd = RELAY_CHN_CMD_NONE;
|
|
||||||
ret |= relay_chn_init_timer(relay_chn);// Create direction change inertia timer
|
|
||||||
if (ret != ESP_OK) {
|
|
||||||
ESP_LOGE(TAG, "Failed to initialize relay channel %d!", i);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create relay channel command event loop
|
|
||||||
ret |= relay_chn_create_event_loop();
|
|
||||||
|
|
||||||
// Init the state listener manager
|
|
||||||
relay_chn_state_listener_manager.listeners = malloc(sizeof(relay_chn_state_listener_t*));
|
|
||||||
if (relay_chn_state_listener_manager.listeners == NULL) {
|
|
||||||
ESP_LOGE(TAG, "Failed to initialize memory for the listeners!");
|
|
||||||
ret = ESP_ERR_NO_MEM;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int relay_chn_listener_index(relay_chn_state_listener_t listener)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < relay_chn_state_listener_manager.listener_count; i++) {
|
|
||||||
if (relay_chn_state_listener_manager.listeners[i] == listener) {
|
|
||||||
// This is the listener to unregister. Check if it is in the middle
|
|
||||||
ESP_LOGD(TAG, "relay_chn_listener_index: Listener %p; found at index %d.", listener, i);
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_err_t relay_chn_register_listener(relay_chn_state_listener_t listener)
|
|
||||||
{
|
|
||||||
if (listener == NULL) {
|
|
||||||
ESP_LOGE(TAG, "relay_chn_register_listener: A NULL listener given.");
|
|
||||||
return ESP_ERR_INVALID_ARG;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (relay_chn_listener_index(listener) > -1) {
|
|
||||||
ESP_LOGD(TAG, "relay_chn_register_listener: The listener %p is already registered.", listener);
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGD(TAG, "relay_chn_register_listener: Register listener: %p", listener);
|
|
||||||
relay_chn_state_listener_manager.listeners[relay_chn_state_listener_manager.listener_count] = listener;
|
|
||||||
// Update listener count
|
|
||||||
relay_chn_state_listener_manager.listener_count++;
|
|
||||||
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
void relay_chn_unregister_listener(relay_chn_state_listener_t listener)
|
|
||||||
{
|
|
||||||
if (listener == NULL) {
|
|
||||||
ESP_LOGD(TAG, "relay_chn_unregister_listener: A NULL listener given, nothing to do.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Search the listener in the listeners list and get its index if exists
|
|
||||||
int i = relay_chn_listener_index(listener);
|
|
||||||
if (i == -1) {
|
|
||||||
ESP_LOGD(TAG, "relay_chn_unregister_listener: %p is not registered already.", listener);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t max_index = relay_chn_state_listener_manager.listener_count - 1;
|
|
||||||
// Check whether the listener's index is in the middle
|
|
||||||
if (i == max_index) {
|
|
||||||
// free(&relay_chn_state_listener_manager.listeners[i]);
|
|
||||||
relay_chn_state_listener_manager.listeners[i] = NULL;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// It is in the middle, so align the next elements in the list and then free the last empty pointer
|
|
||||||
// Align the next elements
|
|
||||||
uint8_t num_of_elements = max_index - i;
|
|
||||||
relay_chn_state_listener_t *pnext = NULL;
|
|
||||||
// (i + j): current index; (i + j + 1): next index
|
|
||||||
for (uint8_t j = 0; j < num_of_elements; j++) {
|
|
||||||
uint8_t current_index = i + j;
|
|
||||||
uint8_t next_index = current_index + 1;
|
|
||||||
pnext = &relay_chn_state_listener_manager.listeners[next_index];
|
|
||||||
relay_chn_state_listener_manager.listeners[current_index] = *pnext;
|
|
||||||
}
|
|
||||||
// free(&relay_chn_state_listener_manager.listeners[max_index]); // Free the last element
|
|
||||||
relay_chn_state_listener_manager.listeners[max_index] = NULL; // Free the last element
|
|
||||||
}
|
|
||||||
// Decrease listener count
|
|
||||||
relay_chn_state_listener_manager.listener_count--;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Check channel ID validity
|
|
||||||
*
|
|
||||||
* @param chn_id Channel ID to check
|
|
||||||
* @return true If channel is valid
|
|
||||||
* @return false If channel is invalid
|
|
||||||
*/
|
|
||||||
static bool relay_chn_is_channel_id_valid(uint8_t chn_id)
|
|
||||||
{
|
|
||||||
bool valid = (chn_id >= 0 && chn_id < RELAY_CHN_COUNT) || chn_id == RELAY_CHN_ID_ALL;
|
|
||||||
if (!valid) {
|
|
||||||
ESP_LOGE(TAG, "Invalid channel ID: %d", chn_id);
|
|
||||||
}
|
|
||||||
return valid;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Dispatch relay channel command to its event loop
|
|
||||||
static void relay_chn_dispatch_cmd(relay_chn_t *relay_chn, relay_chn_cmd_t cmd) {
|
|
||||||
if (cmd == RELAY_CHN_CMD_NONE) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
esp_event_post_to(relay_chn_event_loop,
|
|
||||||
RELAY_CHN_CMD_EVENT,
|
|
||||||
cmd,
|
|
||||||
&relay_chn->id,
|
|
||||||
sizeof(relay_chn->id), portMAX_DELAY);
|
|
||||||
}
|
|
||||||
|
|
||||||
static esp_err_t relay_chn_invalidate_inertia_timer(relay_chn_t *relay_chn)
|
|
||||||
{
|
|
||||||
if (esp_timer_is_active(relay_chn->inertia_timer)) {
|
|
||||||
return esp_timer_stop(relay_chn->inertia_timer);
|
|
||||||
}
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static esp_err_t relay_chn_start_inertia_timer(relay_chn_t *relay_chn, uint32_t time_ms)
|
|
||||||
{
|
|
||||||
// Invalidate the channel's timer if it is active
|
|
||||||
relay_chn_invalidate_inertia_timer(relay_chn);
|
|
||||||
return esp_timer_start_once(relay_chn->inertia_timer, time_ms * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void relay_chn_update_state(relay_chn_t *relay_chn, relay_chn_state_t new_state)
|
|
||||||
{
|
|
||||||
relay_chn_state_t old = relay_chn->state;
|
|
||||||
relay_chn->state = new_state;
|
|
||||||
for (uint8_t i = 0; i < relay_chn_state_listener_manager.listener_count; i++) {
|
|
||||||
relay_chn_state_listener_t listener = relay_chn_state_listener_manager.listeners[i];
|
|
||||||
if (listener == NULL) {
|
|
||||||
relay_chn_state_listener_manager.listener_count -= 1;
|
|
||||||
ESP_LOGD(TAG, "relay_chn_update_state: A listener is NULL at index: %u", i);
|
|
||||||
}
|
|
||||||
// Emit the state change to the listeners
|
|
||||||
listener(relay_chn->id, old, new_state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The command issuer function.
|
|
||||||
*
|
|
||||||
* This function is the deciding logic for issuing a command to a relay channel. It evaluates
|
|
||||||
* the current state of the channel before issuing the command. Then it decides whether to run
|
|
||||||
* the command immediately or wait for the opposite inertia time.
|
|
||||||
*
|
|
||||||
* The STOP command is an exception, it is always run immediately since it is safe in any case.
|
|
||||||
*
|
|
||||||
* Another special consideration is the FLIP command. If the channel is running, the FLIP command
|
|
||||||
* is issued after the channel is stopped. If the channel is stopped, the FLIP command is issued
|
|
||||||
* immediately.
|
|
||||||
*
|
|
||||||
* @param relay_chn The relay channel to issue the command to.
|
|
||||||
* @param cmd The command to issue.
|
|
||||||
*/
|
|
||||||
static void relay_chn_issue_cmd(relay_chn_t* relay_chn, relay_chn_cmd_t cmd)
|
|
||||||
{
|
|
||||||
if (cmd == RELAY_CHN_CMD_NONE) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cmd == RELAY_CHN_CMD_STOP) {
|
|
||||||
if (relay_chn->state == RELAY_CHN_STATE_STOPPED) {
|
|
||||||
return; // Do nothing if already stopped
|
|
||||||
}
|
|
||||||
// If the command is STOP, issue it immediately
|
|
||||||
relay_chn_dispatch_cmd(relay_chn, cmd);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Evaluate the channel's next move depending on its status
|
|
||||||
switch (relay_chn->state)
|
|
||||||
{
|
|
||||||
case RELAY_CHN_STATE_FREE:
|
|
||||||
// If the channel is free, run the command immediately
|
|
||||||
relay_chn_dispatch_cmd(relay_chn, cmd);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case RELAY_CHN_STATE_FORWARD_PENDING:
|
|
||||||
case RELAY_CHN_STATE_REVERSE_PENDING:
|
|
||||||
// The channel is already waiting for the opposite inertia time,
|
|
||||||
// so do nothing unless the command is STOP
|
|
||||||
if (cmd == RELAY_CHN_CMD_STOP) {
|
|
||||||
relay_chn_dispatch_cmd(relay_chn, cmd);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case RELAY_CHN_STATE_STOPPED:
|
|
||||||
if (relay_chn->run_info.last_run_cmd == cmd || relay_chn->run_info.last_run_cmd == RELAY_CHN_CMD_NONE) {
|
|
||||||
// If this is the first run or the last run command is the same as the current command, run the command immediately
|
|
||||||
relay_chn_dispatch_cmd(relay_chn, cmd);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// If the last run command is different from the current command, calculate the time passed
|
|
||||||
// since the last run command stopped and decide whether to run the command immediately or wait
|
|
||||||
uint32_t inertia_time_passed_ms = (uint32_t) (esp_timer_get_time() / 1000) - relay_chn->run_info.last_run_cmd_time_ms;
|
|
||||||
uint32_t inertia_time_ms = RELAY_CHN_OPPOSITE_INERTIA_MS - inertia_time_passed_ms;
|
|
||||||
if (inertia_time_ms > 0) {
|
|
||||||
relay_chn->pending_cmd = cmd;
|
|
||||||
relay_chn_state_t new_state = cmd == RELAY_CHN_CMD_FORWARD
|
|
||||||
? RELAY_CHN_STATE_FORWARD_PENDING : RELAY_CHN_STATE_REVERSE_PENDING;
|
|
||||||
relay_chn_update_state(relay_chn, new_state);
|
|
||||||
// If the time passed is less than the opposite inertia time, wait for the remaining time
|
|
||||||
relay_chn_start_inertia_timer(relay_chn, inertia_time_ms);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// If the time passed is more than the opposite inertia time, run the command immediately
|
|
||||||
relay_chn_dispatch_cmd(relay_chn, cmd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case RELAY_CHN_STATE_FORWARD:
|
|
||||||
case RELAY_CHN_STATE_REVERSE:
|
|
||||||
if (cmd == RELAY_CHN_CMD_FLIP) {
|
|
||||||
// If the command is FLIP, stop the running channel first, then issue the FLIP command
|
|
||||||
relay_chn_dispatch_cmd(relay_chn, RELAY_CHN_CMD_STOP);
|
|
||||||
relay_chn_dispatch_cmd(relay_chn, cmd);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (relay_chn->run_info.last_run_cmd == cmd) {
|
|
||||||
// If the last run command is the same as the current command, do nothing
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the last run command is different from the current command, wait for the opposite inertia time
|
|
||||||
relay_chn->pending_cmd = cmd;
|
|
||||||
relay_chn_state_t new_state = cmd == RELAY_CHN_CMD_FORWARD
|
|
||||||
? RELAY_CHN_STATE_FORWARD_PENDING : RELAY_CHN_STATE_REVERSE_PENDING;
|
|
||||||
relay_chn_update_state(relay_chn, new_state);
|
|
||||||
relay_chn_start_inertia_timer(relay_chn, RELAY_CHN_OPPOSITE_INERTIA_MS);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default: ESP_LOGD(TAG, "relay_chn_evaluate: Unknown relay channel state!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* relay_chn APIs */
|
|
||||||
relay_chn_state_t relay_chn_get_state(uint8_t chn_id)
|
|
||||||
{
|
|
||||||
if (!relay_chn_is_channel_id_valid(chn_id)) {
|
|
||||||
return RELAY_CHN_STATE_STOPPED;
|
|
||||||
}
|
|
||||||
return relay_channels[chn_id].state;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *relay_chn_get_state_str(uint8_t chn_id)
|
|
||||||
{
|
|
||||||
if (!relay_chn_is_channel_id_valid(chn_id)) {
|
|
||||||
return "INVALID";
|
|
||||||
}
|
|
||||||
return relay_chn_state_str(relay_channels[chn_id].state);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void relay_chn_issue_cmd_on_all_channels(relay_chn_cmd_t cmd)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < RELAY_CHN_COUNT; i++) {
|
|
||||||
relay_chn_issue_cmd(&relay_channels[i], cmd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void relay_chn_run_forward(uint8_t chn_id)
|
|
||||||
{
|
|
||||||
if (!relay_chn_is_channel_id_valid(chn_id)) return;
|
|
||||||
|
|
||||||
if (chn_id == RELAY_CHN_ID_ALL) {
|
|
||||||
relay_chn_issue_cmd_on_all_channels(RELAY_CHN_CMD_FORWARD);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
relay_chn_t* relay_chn = &relay_channels[chn_id];
|
|
||||||
relay_chn_issue_cmd(relay_chn, RELAY_CHN_CMD_FORWARD);
|
|
||||||
}
|
|
||||||
|
|
||||||
void relay_chn_run_reverse(uint8_t chn_id)
|
|
||||||
{
|
|
||||||
if (!relay_chn_is_channel_id_valid(chn_id)) return;
|
|
||||||
|
|
||||||
if (chn_id == RELAY_CHN_ID_ALL) {
|
|
||||||
relay_chn_issue_cmd_on_all_channels(RELAY_CHN_CMD_REVERSE);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
relay_chn_t* relay_chn = &relay_channels[chn_id];
|
|
||||||
relay_chn_issue_cmd(relay_chn, RELAY_CHN_CMD_REVERSE);
|
|
||||||
}
|
|
||||||
|
|
||||||
void relay_chn_stop(uint8_t chn_id)
|
|
||||||
{
|
|
||||||
if (!relay_chn_is_channel_id_valid(chn_id)) return;
|
|
||||||
|
|
||||||
if (chn_id == RELAY_CHN_ID_ALL) {
|
|
||||||
relay_chn_issue_cmd_on_all_channels(RELAY_CHN_CMD_STOP);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
relay_chn_t* relay_chn = &relay_channels[chn_id];
|
|
||||||
relay_chn_issue_cmd(relay_chn, RELAY_CHN_CMD_STOP);
|
|
||||||
}
|
|
||||||
|
|
||||||
void relay_chn_flip_direction(uint8_t chn_id)
|
|
||||||
{
|
|
||||||
if (!relay_chn_is_channel_id_valid(chn_id)) return;
|
|
||||||
|
|
||||||
if (chn_id == RELAY_CHN_ID_ALL) {
|
|
||||||
relay_chn_issue_cmd_on_all_channels(RELAY_CHN_CMD_FLIP);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
relay_chn_t* relay_chn = &relay_channels[chn_id];
|
|
||||||
relay_chn_issue_cmd(relay_chn, RELAY_CHN_CMD_FLIP);
|
|
||||||
}
|
|
||||||
|
|
||||||
relay_chn_direction_t relay_chn_get_direction(uint8_t chn_id)
|
|
||||||
{
|
|
||||||
if (!relay_chn_is_channel_id_valid(chn_id)) {
|
|
||||||
return RELAY_CHN_DIRECTION_DEFAULT;
|
|
||||||
}
|
|
||||||
return relay_channels[chn_id].output.direction;
|
|
||||||
}
|
|
||||||
/* relay_chn APIs */
|
|
||||||
|
|
||||||
|
|
||||||
static void relay_chn_execute_stop(relay_chn_t *relay_chn)
|
|
||||||
{
|
|
||||||
gpio_set_level(relay_chn->output.forward_pin, 0);
|
|
||||||
gpio_set_level(relay_chn->output.reverse_pin, 0);
|
|
||||||
relay_chn_update_state(relay_chn, RELAY_CHN_STATE_STOPPED);
|
|
||||||
|
|
||||||
// If there is any pending command, cancel it since the STOP command is issued right after it
|
|
||||||
relay_chn->pending_cmd = RELAY_CHN_CMD_NONE;
|
|
||||||
// Invalidate the channel's timer if it is active
|
|
||||||
relay_chn_invalidate_inertia_timer(relay_chn);
|
|
||||||
|
|
||||||
// If the channel was running, schedule a free command for the channel
|
|
||||||
relay_chn_cmd_t last_run_cmd = relay_chn->run_info.last_run_cmd;
|
|
||||||
if (last_run_cmd == RELAY_CHN_CMD_FORWARD || last_run_cmd == RELAY_CHN_CMD_REVERSE) {
|
|
||||||
// Record the command's last run time
|
|
||||||
relay_chn->run_info.last_run_cmd_time_ms = esp_timer_get_time() / 1000;
|
|
||||||
// Schedule a free command for the channel
|
|
||||||
relay_chn->pending_cmd = RELAY_CHN_CMD_FREE;
|
|
||||||
relay_chn_start_inertia_timer(relay_chn, RELAY_CHN_OPPOSITE_INERTIA_MS);
|
|
||||||
} else {
|
|
||||||
// If the channel was not running, issue a free command immediately
|
|
||||||
relay_chn_dispatch_cmd(relay_chn, RELAY_CHN_CMD_FREE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void relay_chn_execute_forward(relay_chn_t *relay_chn)
|
|
||||||
{
|
|
||||||
gpio_set_level(relay_chn->output.reverse_pin, 0);
|
|
||||||
gpio_set_level(relay_chn->output.forward_pin, 1);
|
|
||||||
relay_chn->run_info.last_run_cmd = RELAY_CHN_CMD_FORWARD;
|
|
||||||
relay_chn_update_state(relay_chn, RELAY_CHN_STATE_FORWARD);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void relay_chn_execute_reverse(relay_chn_t *relay_chn)
|
|
||||||
{
|
|
||||||
gpio_set_level(relay_chn->output.forward_pin, 0);
|
|
||||||
gpio_set_level(relay_chn->output.reverse_pin, 1);
|
|
||||||
relay_chn->run_info.last_run_cmd = RELAY_CHN_CMD_REVERSE;
|
|
||||||
relay_chn_update_state(relay_chn, RELAY_CHN_STATE_REVERSE);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void relay_chn_execute_flip(relay_chn_t *relay_chn)
|
|
||||||
{
|
|
||||||
// Flip the output GPIO pins
|
|
||||||
gpio_num_t temp = relay_chn->output.forward_pin;
|
|
||||||
relay_chn->output.forward_pin = relay_chn->output.reverse_pin;
|
|
||||||
relay_chn->output.reverse_pin = temp;
|
|
||||||
// Flip the direction
|
|
||||||
relay_chn->output.direction = (relay_chn->output.direction == RELAY_CHN_DIRECTION_DEFAULT)
|
|
||||||
? RELAY_CHN_DIRECTION_FLIPPED
|
|
||||||
: RELAY_CHN_DIRECTION_DEFAULT;
|
|
||||||
// Set an inertia on the channel to prevent any immediate movement
|
|
||||||
relay_chn->pending_cmd = RELAY_CHN_CMD_FREE;
|
|
||||||
relay_chn_start_inertia_timer(relay_chn, RELAY_CHN_OPPOSITE_INERTIA_MS);
|
|
||||||
}
|
|
||||||
|
|
||||||
void relay_chn_execute_free(relay_chn_t *relay_chn)
|
|
||||||
{
|
|
||||||
relay_chn->pending_cmd = RELAY_CHN_CMD_NONE;
|
|
||||||
// Invalidate the channel's timer if it is active
|
|
||||||
relay_chn_invalidate_inertia_timer(relay_chn);
|
|
||||||
relay_chn_update_state(relay_chn, RELAY_CHN_STATE_FREE);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void relay_chn_event_handler(void* handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
|
|
||||||
{
|
|
||||||
uint8_t chn_id = *(uint8_t*) event_data;
|
|
||||||
if (!relay_chn_is_channel_id_valid(chn_id)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
relay_chn_t* relay_chn = &relay_channels[chn_id];
|
|
||||||
ESP_LOGD(TAG, "relay_chn_event_handler: Channel %d, Command: %s", relay_chn->id, relay_chn_cmd_str(event_id));
|
|
||||||
switch (event_id) {
|
|
||||||
case RELAY_CHN_CMD_STOP:
|
|
||||||
relay_chn_execute_stop(relay_chn);
|
|
||||||
break;
|
|
||||||
case RELAY_CHN_CMD_FORWARD:
|
|
||||||
relay_chn_execute_forward(relay_chn);
|
|
||||||
break;
|
|
||||||
case RELAY_CHN_CMD_REVERSE:
|
|
||||||
relay_chn_execute_reverse(relay_chn);
|
|
||||||
break;
|
|
||||||
case RELAY_CHN_CMD_FLIP:
|
|
||||||
relay_chn_execute_flip(relay_chn);
|
|
||||||
break;
|
|
||||||
case RELAY_CHN_CMD_FREE:
|
|
||||||
relay_chn_execute_free(relay_chn);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
ESP_LOGD(TAG, "Unknown relay channel command!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static char *relay_chn_cmd_str(relay_chn_cmd_t cmd)
|
|
||||||
{
|
|
||||||
switch (cmd) {
|
|
||||||
case RELAY_CHN_CMD_STOP:
|
|
||||||
return "STOP";
|
|
||||||
case RELAY_CHN_CMD_FORWARD:
|
|
||||||
return "FORWARD";
|
|
||||||
case RELAY_CHN_CMD_REVERSE:
|
|
||||||
return "REVERSE";
|
|
||||||
case RELAY_CHN_CMD_FLIP:
|
|
||||||
return "FLIP";
|
|
||||||
case RELAY_CHN_CMD_FREE:
|
|
||||||
return "FREE";
|
|
||||||
default:
|
|
||||||
return "UNKNOWN";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char *relay_chn_state_str(relay_chn_state_t state)
|
|
||||||
{
|
|
||||||
switch (state) {
|
|
||||||
case RELAY_CHN_STATE_FREE:
|
|
||||||
return "FREE";
|
|
||||||
case RELAY_CHN_STATE_STOPPED:
|
|
||||||
return "STOPPED";
|
|
||||||
case RELAY_CHN_STATE_FORWARD:
|
|
||||||
return "FORWARD";
|
|
||||||
case RELAY_CHN_STATE_REVERSE:
|
|
||||||
return "REVERSE";
|
|
||||||
case RELAY_CHN_STATE_FORWARD_PENDING:
|
|
||||||
return "FORWARD_PENDING";
|
|
||||||
case RELAY_CHN_STATE_REVERSE_PENDING:
|
|
||||||
return "REVERSE_PENDING";
|
|
||||||
default:
|
|
||||||
return "UNKNOWN";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
# The following 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(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/unit-test-app/components"
|
|
||||||
"../../relay_chn")
|
|
||||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
|
||||||
project(relay_chn_test)
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
idf_component_register(SRCS_DIRS "."
|
|
||||||
PRIV_INCLUDE_DIRS "."
|
|
||||||
PRIV_REQUIRES unity test_utils relay_chn)
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
#include "driver/gpio.h"
|
|
||||||
#include "unity.h"
|
|
||||||
#include "unity_test_utils.h"
|
|
||||||
#include "relay_chn.h"
|
|
||||||
|
|
||||||
|
|
||||||
const gpio_num_t gpio_map[] = {GPIO_NUM_4, GPIO_NUM_5, GPIO_NUM_18, GPIO_NUM_19};
|
|
||||||
const uint8_t gpio_count = sizeof(gpio_map) / sizeof(gpio_map[0]);
|
|
||||||
const uint8_t relay_chn_count = gpio_count / 2;
|
|
||||||
|
|
||||||
TEST_CASE("relay chn inits correctly", "[relay_chn]")
|
|
||||||
{
|
|
||||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Relay channels run forward and update state", "[relay_chn][forward]")
|
|
||||||
{
|
|
||||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
|
||||||
// Test forward run on all channels
|
|
||||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
|
||||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(i));
|
|
||||||
relay_chn_run_forward(i); // Run the channel forward
|
|
||||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(i));
|
|
||||||
relay_chn_stop(i); // Stop the channel
|
|
||||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(i));
|
|
||||||
|
|
||||||
relay_chn_flip_direction(i); // Flip the direction
|
|
||||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction(i));
|
|
||||||
relay_chn_run_forward(i); // Run the channel forward
|
|
||||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_FORWARD, relay_chn_get_state(i));
|
|
||||||
relay_chn_stop(i); // Stop the channel
|
|
||||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Relay channels run reverse and update state", "[relay_chn][reverse]")
|
|
||||||
{
|
|
||||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
|
||||||
// Test reverse run on all channels
|
|
||||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
|
||||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(i));
|
|
||||||
relay_chn_run_reverse(i); // Run the channel reverse
|
|
||||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(i));
|
|
||||||
relay_chn_stop(i); // Stop the channel
|
|
||||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(i));
|
|
||||||
|
|
||||||
relay_chn_flip_direction(i); // Flip the direction
|
|
||||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_FLIPPED, relay_chn_get_direction(i));
|
|
||||||
relay_chn_run_reverse(i); // Run the channel forward
|
|
||||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_REVERSE, relay_chn_get_state(i));
|
|
||||||
relay_chn_stop(i); // Stop the channel
|
|
||||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void check_channels_state_unchanged(void)
|
|
||||||
{
|
|
||||||
for (uint8_t i = 0; i < relay_chn_count; i++) {
|
|
||||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(i));
|
|
||||||
TEST_ASSERT_EQUAL(RELAY_CHN_DIRECTION_DEFAULT, relay_chn_get_direction(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Relay channels do not change state for invalid channel", "[relay_chn][invalid]")
|
|
||||||
{
|
|
||||||
TEST_ESP_OK(relay_chn_create(gpio_map, gpio_count));
|
|
||||||
// Test invalid channel run
|
|
||||||
relay_chn_run_forward(relay_chn_count + 1); // Run the channel forward
|
|
||||||
check_channels_state_unchanged();
|
|
||||||
relay_chn_run_reverse(relay_chn_count + 1); // Run the channel reverse
|
|
||||||
check_channels_state_unchanged();
|
|
||||||
relay_chn_stop(relay_chn_count + 1); // Stop the channel
|
|
||||||
TEST_ASSERT_EQUAL(RELAY_CHN_STATE_STOPPED, relay_chn_get_state(relay_chn_count + 1));
|
|
||||||
check_channels_state_unchanged();
|
|
||||||
relay_chn_flip_direction(relay_chn_count + 1); // Flip the direction
|
|
||||||
check_channels_state_unchanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void setUp(void)
|
|
||||||
{
|
|
||||||
// Run before each test
|
|
||||||
}
|
|
||||||
|
|
||||||
void tearDown(void)
|
|
||||||
{
|
|
||||||
// Run after each test
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test app entry point
|
|
||||||
void app_main(void)
|
|
||||||
{
|
|
||||||
// Run the Unity tests menu
|
|
||||||
unity_run_menu();
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user