21 Commits

Author SHA1 Message Date
0ebe1c791e Merge pull request 'fix/134-testing-issues' (!27) from fix/134-testing-issues into dev
Reviewed-on: KozmotronikTech/relay_chn_component#27
2025-07-07 14:31:27 +03:00
bacbe03e12 Update the manifest file with the latest information. 2025-07-07 09:50:36 +03:00
be09cb883a Correct the installation description.
Fixes  #1027
2025-07-07 09:39:29 +03:00
925fd5de74 Update descriptions about the inertia timing.
Fixes #1027
2025-07-07 09:27:28 +03:00
2e3e92bb63 Fix testing issues and add more tests.
Fixes #134.
Fix unit testing issues. Add more tests to cover more code.
2025-07-04 17:55:33 +03:00
c4482b8d49 Fix unhandled tilt to run mode transitions.
Fixes #1028.
This commit add unhandled logic to the relay_chn_issue_cmd function to handle transitions from tilt mode to run mode.
2025-07-04 17:31:31 +03:00
41c292cc89 Restructure the project tree for unit testing
Restructure the whole project tree so that the component can be unit tested. Also update some cmake files to update the modified paths, update test cases etc.
2025-07-04 00:38:57 +03:00
ed5b86e863 Fix CMakeLists.txt definitions and test cases. 2025-07-03 18:58:09 +03:00
a1c66d51c7 Merge pull request 'Fix error handling issues.' from fix/172-fix-error-handling into dev
Reviewed-on: https://dev.kozmotronik.com.tr/gitea/KozmotronikTech/relay_chn/pulls/25
2025-04-02 14:39:49 +03:00
421dea7d69 Merge pull request 'fix/172-fix-error-handling' (!24) from fix/172-fix-error-handling into fix/134-testing-issues
Reviewed-on: https://dev.kozmotronik.com.tr/gitea/KozmotronikTech/relay_chn/pulls/24
2025-04-02 14:38:32 +03:00
99d753238b Fix error handling issues.
Fix error handling so that the value in ret variable does not become corrupt.
2025-04-02 14:05:48 +03:00
7afe6144bd Set dev branch's upstream. 2025-03-24 09:31:33 +03:00
4f39308f13 Merge pull request 'fix/162-fix-error-handling' from fix/162-fix-error-handling into fix/134-testing-issues
Reviewed-on: https://dev.kozmotronik.com.tr/gitea/KozmotronikTech/relay_chn/pulls/23
2025-03-21 17:30:13 +03:00
fb425edc4b Merge pull request 'release-0.3.1' from release-0.3.1 into main
Reviewed-on: https://dev.kozmotronik.com.tr/gitea/KozmotronikTech/relay_chn/pulls/22
2025-03-21 17:06:05 +03:00
805df016fe Merge pull request 'fix/162-fix-error-handling' from fix/162-fix-error-handling into dev
Reviewed-on: https://dev.kozmotronik.com.tr/gitea/KozmotronikTech/relay_chn/pulls/21
2025-03-21 16:44:34 +03:00
f230477cad Fix error handling in the init function. 2025-03-21 16:42:06 +03:00
e19bd09389 Update gitignore as per esp-idf gitignore. 2025-03-21 16:41:22 +03:00
11786b7a06 Remove unnecessary unity functions. 2025-03-04 09:49:04 +03:00
7c18ddcc04 Fix declarations as per the documents. 2025-03-04 09:48:24 +03:00
e8303a9418 Fix test directory structure. 2025-03-03 16:20:29 +03:00
496755ed56 Merge pull request #3 from kozmotronik/release-0.3.0
Release 0.3.0
2025-03-03 13:58:25 +03:00
19 changed files with 4688 additions and 226 deletions

180
.gitignore vendored
View File

@@ -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/*

View File

@@ -1,5 +1,6 @@
{ {
"files.associations": { "files.associations": {
"relay_chn.h": "c" "relay_chn.h": "c"
} },
"idf.port": "/dev/ttyUSB0"
} }

View File

@@ -15,7 +15,7 @@ An ESP-IDF component for controlling relay channels, specifically designed for d
## 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. 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.
@@ -31,13 +31,14 @@ Configure the component through menuconfig under "Relay Channel Driver Configura
## 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

13
app_test/CMakeLists.txt Normal file
View 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)

View File

@@ -0,0 +1,3 @@
idf_component_register(SRCS "test_relay_chn.c"
INCLUDE_DIRS "."
REQUIRES unity relay_chn)

View 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

File diff suppressed because it is too large Load Diff

2033
app_test/sdkconfig.old Normal file

File diff suppressed because it is too large Load Diff

0
dev Normal file
View File

View File

@@ -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.

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

View File

@@ -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

View File

@@ -15,6 +15,7 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include "esp_err.h" #include "esp_err.h"
#include "esp_check.h"
#include "esp_log.h" #include "esp_log.h"
#include "esp_task.h" #include "esp_task.h"
#include "driver/gpio.h" #include "driver/gpio.h"
@@ -173,6 +174,7 @@ typedef struct relay_chn_type {
static esp_err_t relay_chn_init_tilt_control(relay_chn_t *relay_chn); static esp_err_t relay_chn_init_tilt_control(relay_chn_t *relay_chn);
static esp_err_t relay_chn_tilt_init(void); static esp_err_t relay_chn_tilt_init(void);
static void relay_chn_tilt_count_reset(relay_chn_t *relay_chn); static void relay_chn_tilt_count_reset(relay_chn_t *relay_chn);
static esp_err_t relay_chn_dispatch_tilt_cmd(relay_chn_t *relay_chn, relay_chn_tilt_cmd_t cmd);
#endif // RELAY_CHN_ENABLE_TILTING #endif // RELAY_CHN_ENABLE_TILTING
@@ -279,7 +281,8 @@ static esp_err_t relay_chn_create_event_loop()
.task_core_id = tskNO_AFFINITY .task_core_id = tskNO_AFFINITY
}; };
esp_err_t ret = esp_event_loop_create(&loop_args, &relay_chn_event_loop); esp_err_t ret = esp_event_loop_create(&loop_args, &relay_chn_event_loop);
ret |= esp_event_handler_register_with(relay_chn_event_loop, ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create event loop for relay channel");
ret = esp_event_handler_register_with(relay_chn_event_loop,
RELAY_CHN_CMD_EVENT, RELAY_CHN_CMD_EVENT,
ESP_EVENT_ANY_ID, ESP_EVENT_ANY_ID,
relay_chn_event_handler, NULL); relay_chn_event_handler, NULL);
@@ -320,14 +323,14 @@ esp_err_t relay_chn_create(const gpio_num_t* gpio_map, uint8_t gpio_count)
// Initialize the GPIOs // Initialize the GPIOs
ret = gpio_reset_pin(forward_pin); ret = gpio_reset_pin(forward_pin);
ret |= gpio_set_direction(forward_pin, GPIO_MODE_OUTPUT); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to reset GPIO forward pin for channel %d", i);
ret = gpio_set_direction(forward_pin, GPIO_MODE_OUTPUT);
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set GPIO direction for forward pin for channel %d", i);
ret |= gpio_reset_pin(reverse_pin); ret = gpio_reset_pin(reverse_pin);
ret |= gpio_set_direction(reverse_pin, GPIO_MODE_OUTPUT); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to reset GPIO reverse pin for channel %d", i);
if (ret != ESP_OK) { ret = gpio_set_direction(reverse_pin, GPIO_MODE_OUTPUT);
ESP_LOGE(TAG, "Failed to initialize GPIOs relay channel %d!", i); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set GPIO direction for reverse pin for channel %d", i);
return ret;
}
// Initialize the GPIOs // Initialize the GPIOs
// Initialize the relay channel // Initialize the relay channel
@@ -339,22 +342,22 @@ esp_err_t relay_chn_create(const gpio_num_t* gpio_map, uint8_t gpio_count)
relay_chn->state = RELAY_CHN_STATE_FREE; relay_chn->state = RELAY_CHN_STATE_FREE;
relay_chn->pending_cmd = RELAY_CHN_CMD_NONE; relay_chn->pending_cmd = RELAY_CHN_CMD_NONE;
relay_chn->run_info.last_run_cmd = RELAY_CHN_CMD_NONE; relay_chn->run_info.last_run_cmd = RELAY_CHN_CMD_NONE;
ret |= relay_chn_init_timer(relay_chn); // Create direction change inertia timer ret = relay_chn_init_timer(relay_chn); // Create direction change inertia timer
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create relay channel timer for channel %d", i);
#if RELAY_CHN_ENABLE_TILTING == 1 #if RELAY_CHN_ENABLE_TILTING == 1
ret |= relay_chn_init_tilt_control(relay_chn); ret = relay_chn_init_tilt_control(relay_chn);
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize tilt control for channel %d", i);
#endif #endif
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize relay channel %d!", i);
return ret;
}
} }
// Create relay channel command event loop // Create relay channel command event loop
ret |= relay_chn_create_event_loop(); ret = relay_chn_create_event_loop();
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create relay channel event loop");
#if RELAY_CHN_ENABLE_TILTING == 1 #if RELAY_CHN_ENABLE_TILTING == 1
// Must call after the event loop is initialized // Must call after the event loop is initialized
ret |= relay_chn_tilt_init(); // Initialize tilt feature ret = relay_chn_tilt_init(); // Initialize tilt feature
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize tilt feature");
#endif #endif
// Init the state listener manager // Init the state listener manager
@@ -607,6 +610,37 @@ static void relay_chn_issue_cmd(relay_chn_t* relay_chn, relay_chn_cmd_t cmd)
relay_chn_start_esp_timer_once(relay_chn->inertia_timer, RELAY_CHN_OPPOSITE_INERTIA_MS); relay_chn_start_esp_timer_once(relay_chn->inertia_timer, RELAY_CHN_OPPOSITE_INERTIA_MS);
break; break;
#if RELAY_CHN_ENABLE_TILTING == 1
case RELAY_CHN_STATE_TILT_FORWARD:
// Terminate tilting first
relay_chn_dispatch_tilt_cmd(relay_chn, RELAY_CHN_TILT_CMD_STOP);
if (cmd == RELAY_CHN_CMD_FORWARD) {
// Schedule for running forward
relay_chn->pending_cmd = cmd;
relay_chn_update_state(relay_chn, RELAY_CHN_STATE_FORWARD_PENDING);
relay_chn_start_esp_timer_once(relay_chn->inertia_timer, RELAY_CHN_OPPOSITE_INERTIA_MS);
} else if (cmd == RELAY_CHN_CMD_REVERSE) {
// Run directly since it is the same direction
relay_chn_dispatch_cmd(relay_chn, cmd);
relay_chn_update_state(relay_chn, RELAY_CHN_STATE_REVERSE);
}
break;
case RELAY_CHN_STATE_TILT_REVERSE:
// Terminate tilting first
relay_chn_dispatch_tilt_cmd(relay_chn, RELAY_CHN_TILT_CMD_STOP);
if (cmd == RELAY_CHN_CMD_FORWARD) {
// Run directly since it is the same direction
relay_chn_dispatch_cmd(relay_chn, cmd);
relay_chn_update_state(relay_chn, RELAY_CHN_STATE_FORWARD);
} else if (cmd == RELAY_CHN_CMD_REVERSE) {
// Schedule for running reverse
relay_chn->pending_cmd = cmd;
relay_chn_update_state(relay_chn, RELAY_CHN_STATE_REVERSE_PENDING);
relay_chn_start_esp_timer_once(relay_chn->inertia_timer, RELAY_CHN_OPPOSITE_INERTIA_MS);
}
break;
#endif
default: ESP_LOGD(TAG, "relay_chn_evaluate: Unknown relay channel state!"); default: ESP_LOGD(TAG, "relay_chn_evaluate: Unknown relay channel state!");
} }
} }
@@ -700,7 +734,8 @@ static esp_err_t relay_chn_output_stop(relay_chn_t *relay_chn)
{ {
esp_err_t ret; esp_err_t ret;
ret = gpio_set_level(relay_chn->output.forward_pin, 0); ret = gpio_set_level(relay_chn->output.forward_pin, 0);
ret |= gpio_set_level(relay_chn->output.reverse_pin, 0); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set forward pin to LOW for relay channel #%d", relay_chn->id);
ret = gpio_set_level(relay_chn->output.reverse_pin, 0);
return ret; return ret;
} }
@@ -708,7 +743,8 @@ static esp_err_t relay_chn_output_forward(relay_chn_t *relay_chn)
{ {
esp_err_t ret; esp_err_t ret;
ret = gpio_set_level(relay_chn->output.forward_pin, 1); ret = gpio_set_level(relay_chn->output.forward_pin, 1);
ret |= gpio_set_level(relay_chn->output.reverse_pin, 0); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set forward pin to HIGH for relay channel #%d", relay_chn->id);
ret = gpio_set_level(relay_chn->output.reverse_pin, 0);
return ret; return ret;
} }
@@ -716,7 +752,8 @@ static esp_err_t relay_chn_output_reverse(relay_chn_t *relay_chn)
{ {
esp_err_t ret; esp_err_t ret;
ret = gpio_set_level(relay_chn->output.forward_pin, 0); ret = gpio_set_level(relay_chn->output.forward_pin, 0);
ret |= gpio_set_level(relay_chn->output.reverse_pin, 1); ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set forward pin to LOW for relay channel #%d", relay_chn->id);
ret = gpio_set_level(relay_chn->output.reverse_pin, 1);
return ret; return ret;
} }

View File

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

View File

@@ -1,3 +0,0 @@
idf_component_register(SRCS_DIRS "."
PRIV_INCLUDE_DIRS "."
PRIV_REQUIRES unity test_utils relay_chn)

View File

@@ -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();
}