feat/1104-add-examples #38

Merged
ismail merged 9 commits from feat/1104-add-examples into dev 2025-09-11 15:44:20 +02:00
31 changed files with 1320 additions and 25 deletions

1
.gitignore vendored
View File

@@ -73,6 +73,7 @@ test_multi_heap_host
# VS Code Settings # VS Code Settings
# .vscode/ # .vscode/
settings.json
# VIM files # VIM files
*.swp *.swp

View File

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

View File

@@ -16,7 +16,7 @@ else()
list(APPEND srcs "src/relay_chn_ctl_single.c") list(APPEND srcs "src/relay_chn_ctl_single.c")
endif() endif()
if(CONFIG_RELAY_CHN_NVS) if(CONFIG_RELAY_CHN_ENABLE_NVS)
list(APPEND srcs "src/relay_chn_nvs.c") list(APPEND srcs "src/relay_chn_nvs.c")
endif() endif()

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

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

View File

@@ -0,0 +1,23 @@
{
"configurations": [
{
"name": "ESP-IDF",
"compilerPath": "${config:idf.toolsPath}/tools/riscv32-esp-elf/esp-14.2.0_20241119/riscv32-esp-elf/bin/riscv32-esp-elf-gcc",
"compileCommands": "/disk/Projeler/ESP-Components/relay_chn/examples/relay_chn_single/build/compile_commands.json",
"includePath": [
"${config:idf.espIdfPath}/components/**",
"${config:idf.espIdfPathWin}/components/**",
"${workspaceFolder}/**"
],
"browse": {
"path": [
"${config:idf.espIdfPath}/components",
"${config:idf.espIdfPathWin}/components",
"${workspaceFolder}"
],
"limitSymbolsToIncludedHeaders": true
}
}
],
"version": 4
}

View File

@@ -0,0 +1,15 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "gdbtarget",
"request": "attach",
"name": "Eclipse CDT GDB Adapter"
},
{
"type": "espidf",
"name": "Launch",
"request": "launch"
}
]
}

View File

@@ -0,0 +1,10 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
set(COMPONENTS main)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(relay_chn_single)

View File

@@ -0,0 +1,176 @@
# Relay Channel Multi Example
## Introduction
This example demonstrates how to use the relay channel component to control a 3-channel setup with button inputs and LED status indication. It showcases:
- Basic relay channel operations (forward/reverse running, stopping)
- Secondary operations (tilting, direction flipping)
- State change event handling with a listener
- Relay channel run limit
- Button event handling using esp-iot-solution's button component
- Visual feedback using esp-iot-solution's LED indicator component
## How to Use Example
This example has been tested on an `ESP32-C3-DevKitM-1U` board. However, it can be adapted to any ESP32-based board with at least six available GPIO pins by adjusting the configuration options.
### Hardware Required
* An ESP32-based development board
* 2 relays connected to GPIO pins (default: GPIO4, GPIO5)
* 3 buttons connected to GPIO pins:
- UP button (default: GPIO0)
- DOWN button (default: GPIO1)
- STOP button (default: GPIO2)
- SELECT button (default: GPIO3)
* LED indicators for status indication of each channel (default: GPIO4, GPIO10 and GPIO9 respectively)
#### Hardware Schematic
Relay blocks are ommitted for simplicity. You can refer to schematic of the [Single-channel example](/examples/relay_chn_single/README.md) for a fully implemented relay block.
> [!NOTE]
> A single relay channel consists of two relay block and two GPIO pins.
![Hardware Schematic](example_schematic.png)
### Configuration
The example can be configured through `menuconfig` under "Relay Channel Multi Example Configuration":
1. Button active level (`EXAMPLE_RLCHN_BTN_ACTIVE_LEVEL`)
- Select between active LOW or HIGH logic level for buttons
2. GPIO assignments:
- UP button pin (`EXAMPLE_RLCHN_BTN_UP_IO_NUM`, default: 0)
- DOWN button pin (`EXAMPLE_RLCHN_BTN_DOWN_IO_NUM`, default: 1)
- STOP button pin (`EXAMPLE_RLCHN_BTN_STOP_IO_NUM`, default: 2)
- SELECT button pin (`EXAMPLE_RLCHN_BTN_SELECT_IO_NUM`, default: 3)
- LED indicator pin (`EXAMPLE_RLCHN_LED_INDICATOR1_IO_NUM`, default: 4)
- LED indicator pin (`EXAMPLE_RLCHN_LED_INDICATOR2_IO_NUM`, default: 10)
- LED indicator pin (`EXAMPLE_RLCHN_LED_INDICATOR3_IO_NUM`, default: 9)
3. Long press timing:
- `EXAMPLE_RLCHN_BTN_LONG_PRESS_TIME_MS`: Duration for long press actions (1500-3000ms, default: 2000ms)
### Button Operations
The example uses esp-iot-solution's `button` component to handle the following operations:
- **UP button**:
* Short press: Start forward movement
* Long press: Start forward tilt (stops on release)
- **DOWN button**:
* Short press: Start reverse movement
* Long press: Start reverse tilt (stops on release)
- **STOP button**:
* Short press: Stop movement
* Long press: Flip movement direction
- **SELECT button**:
* Short press: Selects a channel to operate
* Long press: Selects all channels to operate in batch
### LED Indicator States
The example uses esp-iot-solution's `led_indicator` component to show different states:
- **Running**: LED blinks at 300ms on, 100ms off
- **Tilting**: Fast blink at 100ms on, 50ms off
- **Operation Success**: Two quick blinks
- **Operation Fail**: One long blink
### Dependencies
This example requires:
- ESP-IDF v4.1 or later
- esp-iot-solution components:
* button v4.1.1 or later
* led_indicator v1.1.1 or later
- relay_chn component
## Example Output
When the application boots, it will wait for a button event. Then ta state listener will print state changes.
```log
I (269) main_task: Calling app_main()
I (269) RELAY_CHN_MULTI_EXAMPLE: Initializing relay channel
I (279) gpio: GPIO[18]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (279) gpio: GPIO[19]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (289) gpio: GPIO[5]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (299) gpio: GPIO[6]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (309) gpio: GPIO[7]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (319) gpio: GPIO[8]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (329) RELAY_CHN_MULTI_EXAMPLE: Initializing buttons
I (329) RELAY_CHN_MULTI_EXAMPLE: Initializing buttons with active level: 0
I (339) gpio: GPIO[0]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (339) button: IoT Button Version: 4.1.3
I (349) gpio: GPIO[1]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (359) gpio: GPIO[2]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (359) gpio: GPIO[3]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (369) RELAY_CHN_MULTI_EXAMPLE: Setting up button callbacks. Configured long press time: 2000 ms
I (379) RELAY_CHN_MULTI_EXAMPLE: Initializing LED indicator
I (389) gpio: GPIO[4]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (389) led_indicator: LED Indicator Version: 1.1.1
I (399) gpio: GPIO[4]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (409) led_indicator: Indicator create successfully. type:GPIO mode, hardware_data:0x3fc96498, blink_lists:custom
I (419) gpio: GPIO[10]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (429) led_indicator: LED Indicator Version: 1.1.1
I (429) gpio: GPIO[10]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (439) led_indicator: Indicator create successfully. type:GPIO mode, hardware_data:0x3fc965a4, blink_lists:custom
I (449) gpio: GPIO[9]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (459) led_indicator: LED Indicator Version: 1.1.1
I (459) gpio: GPIO[9]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (469) led_indicator: Indicator create successfully. type:GPIO mode, hardware_data:0x3fc966b0, blink_lists:custom
I (479) RELAY_CHN_MULTI_EXAMPLE: Relay Channel Multi Example is ready to operate
I (489) main_task: Returned from app_main()
I (3759) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from IDLE to FORWARD
I (6639) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from FORWARD to STOPPED
I (7439) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from STOPPED to IDLE
I (7439) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from IDLE to TILT_FORWARD
I (8119) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from TILT_FORWARD to IDLE
I (13579) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from IDLE to TILT_REVERSE
I (14419) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from TILT_REVERSE to IDLE
I (20319) RELAY_CHN_MULTI_EXAMPLE: Selected channel: 1
I (21479) RELAY_CHN_MULTI_EXAMPLE: Selected channel: 2
I (22229) RELAY_CHN_MULTI_EXAMPLE: Selected channel: 0
I (23169) RELAY_CHN_MULTI_EXAMPLE: Selected channel: 1
I (29039) RELAY_CHN_MULTI_EXAMPLE: Selected all channels
I (35419) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from IDLE to FORWARD
I (35419) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #1, from IDLE to FORWARD
I (35419) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #2, from IDLE to FORWARD
I (39349) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from FORWARD to STOPPED
I (39349) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #1, from FORWARD to STOPPED
I (39359) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #2, from FORWARD to STOPPED
I (40149) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from STOPPED to IDLE
I (40149) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from IDLE to TILT_FORWARD
I (40159) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #1, from STOPPED to IDLE
I (40169) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #1, from IDLE to TILT_FORWARD
I (40179) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #2, from STOPPED to IDLE
I (40189) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #2, from IDLE to TILT_FORWARD
I (41699) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from TILT_FORWARD to IDLE
I (41699) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #1, from TILT_FORWARD to IDLE
I (41709) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #2, from TILT_FORWARD to IDLE
I (64129) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from IDLE to TILT_REVERSE
I (64129) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #1, from IDLE to TILT_REVERSE
I (64139) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #2, from IDLE to TILT_REVERSE
I (68929) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from TILT_REVERSE to IDLE
I (68929) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #1, from TILT_REVERSE to IDLE
I (68939) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #2, from TILT_REVERSE to IDLE
I (280109) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from IDLE to FORWARD
I (280109) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #1, from IDLE to FORWARD
I (280109) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #2, from IDLE to FORWARD
I (283219) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from FORWARD to STOPPED
I (283219) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from STOPPED to REVERSE_PENDING
I (283229) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #1, from FORWARD to STOPPED
I (283239) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #1, from STOPPED to REVERSE_PENDING
I (283249) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #2, from FORWARD to STOPPED
I (283259) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #2, from STOPPED to REVERSE_PENDING
I (284019) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #0, from REVERSE_PENDING to REVERSE
I (284019) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #1, from REVERSE_PENDING to REVERSE
I (284029) RELAY_CHN_MULTI_EXAMPLE: example_event_listener: State change for #2, from REVERSE_PENDING to REVERSE
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

View File

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

View File

@@ -0,0 +1,52 @@
menu "Relay Channel Multi Example Configuration"
choice EXAMPLE_RLCHN_BTN_ACTIVE_LEVEL
prompt "Choose an active level for buttons"
default EXAMPLE_RLCHN_BTN_ACTIVE_LEVEL_LOW
help
Specify the active level for buttons.
config EXAMPLE_RLCHN_BTN_ACTIVE_LEVEL_LOW
bool "Active level LOW"
config EXAMPLE_RLCHN_BTN_ACTIVE_LEVEL_HIGH
bool "Active level HIGH"
endchoice
config EXAMPLE_RLCHN_BTN_UP_IO_NUM
int "GPIO number for UP button"
default 0
config EXAMPLE_RLCHN_BTN_DOWN_IO_NUM
int "GPIO number for DOWN button"
default 1
config EXAMPLE_RLCHN_BTN_STOP_IO_NUM
int "GPIO number for STOP button"
default 2
config EXAMPLE_RLCHN_BTN_SELECT_IO_NUM
int "GPIO number for STOP button"
default 3
config EXAMPLE_RLCHN_LED_INDICATOR1_IO_NUM
int "GPIO number for LED indicator output for channel 1"
default 4
config EXAMPLE_RLCHN_LED_INDICATOR2_IO_NUM
int "GPIO number for LED indicator output for channel 2"
default 10
config EXAMPLE_RLCHN_LED_INDICATOR3_IO_NUM
int "GPIO number for LED indicator output for channel 3"
default 9
config EXAMPLE_RLCHN_BTN_LONG_PRESS_TIME_MS
int "Long press time in ms to start secondary actions"
range 1500 3000
default 2000
help
Long press time in milliseconds is required to start secondary actions
like tilting and flipping.
endmenu

View File

@@ -0,0 +1,8 @@
dependencies:
idf:
version: '>=4.1.0'
espressif/button: ^4.1.1
espressif/led_indicator: ^1.1.1
relay_chn:
version: '*'
override_path: ../../../

View File

@@ -0,0 +1,427 @@
/*
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_check.h"
#include "driver/gpio.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "esp_timer.h"
#include "button_gpio.h"
#include "iot_button.h"
#include "led_indicator.h"
#include "relay_chn.h"
static const char *TAG = "RELAY_CHN_MULTI_EXAMPLE";
#define EXAMPLE_CHN_INDICATOR_ON_TIME_MS 3000
#define EXAMPLE_ALL_CHANNELS CONFIG_RELAY_CHN_COUNT
/**
* @brief LED indicator modes for different states.
*/
typedef enum {
INDICATOR_MODE_SELECT, /*!< Channel select indication */
INDICATOR_MODE_OK, /*!< OK/Success indication */
INDICATOR_MODE_FAIL, /*!< Fail/Error indication */
INDICATOR_MODE_TILTING, /*!< Tilting operation in progress */
INDICATOR_MODE_RUNNING, /*!< Full run operation in progress */
INDICATOR_MODE_MAX /*!< Maximum number of indicator modes */
} indicator_mode_t;
/** @brief Blink pattern for channel select indication. */
static const blink_step_t indc_mode_select[] = {
{LED_BLINK_HOLD, LED_STATE_ON, EXAMPLE_CHN_INDICATOR_ON_TIME_MS}, // step1: turn on LED 3000 ms
{LED_BLINK_HOLD, LED_STATE_OFF, 10}, // step2: turn off LED 500 ms
{LED_BLINK_STOP, 0, 0}, // step4: stop blink (off)
};
/** @brief Blink pattern for OK/Success indication. */
static const blink_step_t indc_mode_ok[] = {
{LED_BLINK_HOLD, LED_STATE_ON, 100}, // step1: turn on LED 100 ms
{LED_BLINK_HOLD, LED_STATE_OFF, 50}, // step2: turn off LED 50 ms
{LED_BLINK_HOLD, LED_STATE_ON, 100}, // step3: turn on LED 100 ms
{LED_BLINK_HOLD, LED_STATE_OFF, 50}, // step4: turn off LED 50 ms
{LED_BLINK_STOP, 0, 0}, // step5: stop blink (off)
};
/** @brief Blink pattern for Fail/Error indication. */
static const blink_step_t indc_mode_fail[] = {
{LED_BLINK_HOLD, LED_STATE_ON, 1000}, // step1: turn on LED 1000 ms
{LED_BLINK_HOLD, LED_STATE_OFF, 500}, // step2: turn off LED 500 ms
{LED_BLINK_STOP, 0, 0}, // step4: stop blink (off)
};
/** @brief Blink pattern for full run operation. */
static const blink_step_t indc_mode_running[] = {
{LED_BLINK_HOLD, LED_STATE_ON, 300}, // step1: turn on LED 300 ms
{LED_BLINK_HOLD, LED_STATE_OFF, 100}, // step2: turn off LED 100 ms
{LED_BLINK_LOOP, 0, 0}, // step3: loop from step1
};
/** @brief Blink pattern for tilting operation. */
static const blink_step_t indc_mode_tilting[] = {
{LED_BLINK_HOLD, LED_STATE_ON, 100}, // step1: turn on LED 100 ms
{LED_BLINK_HOLD, LED_STATE_OFF, 50}, // step2: turn off LED 50 ms
{LED_BLINK_LOOP, 0, 0}, // step3: loop from step1
};
/** @brief Array of LED indicator blink patterns. */
blink_step_t const *led_indicator_modes[] = {
[INDICATOR_MODE_SELECT] = indc_mode_select,
[INDICATOR_MODE_OK] = indc_mode_ok,
[INDICATOR_MODE_FAIL] = indc_mode_fail,
[INDICATOR_MODE_RUNNING] = indc_mode_running,
[INDICATOR_MODE_TILTING] = indc_mode_tilting,
[INDICATOR_MODE_MAX] = NULL,
};
/** @brief Handle for the LED indicator. */
static led_indicator_handle_t indicators[CONFIG_RELAY_CHN_COUNT];
static uint8_t selected_channel = 0; /*!< Currently selected channel */
static uint8_t selected_channel_backup = 0; /*!< Backup for last selected channel */
static int64_t last_indc_update_us = 0; /*!< Timestamp of the last channel selection */
/**
* @brief Initializes the buttons for user interaction.
*
* This function configures and creates GPIO buttons for UP, DOWN, and STOP
* operations. It also registers callbacks for single-click and long-press
* events to control the relay channel.
*
* @return esp_err_t
* - ESP_OK: Success
* - Others: Fail
*/
static esp_err_t init_buttons(void);
/**
* @brief Initializes the LED indicator.
*
* This function configures and creates the LED indicator used to provide
* visual feedback on the relay channel's status.
*
* @return esp_err_t
* - ESP_OK: Success
* - ESP_FAIL: Fail
*/
static esp_err_t init_led_indicators(void);
/**
* @brief Starts all indicators with the specified mode.
*
* @param mode One of the indicator modes defined with indicator_mode_t.
*/
static void start_all_indicators_for_mode(indicator_mode_t mode);
/**
* @brief Stops all indicators with the specified mode.
*
* @param mode One of the indicator modes defined with indicator_mode_t.
*/
static void stop_all_indicators_for_mode(indicator_mode_t mode);
/**
* @brief Event listener for relay channel state changes to log the state transition.
*/
static void example_event_listener(uint8_t ch, relay_chn_state_t old_state, relay_chn_state_t new_state);
void app_main(void)
{
const uint8_t gpio_map[] = { 18, 19, 5, 6, 7, 8 };
const uint8_t gpio_count = sizeof(gpio_map) / sizeof(gpio_map[0]);
ESP_LOGI(TAG, "Initializing relay channel");
ESP_ERROR_CHECK(relay_chn_create(gpio_map, gpio_count));
ESP_ERROR_CHECK(relay_chn_register_listener(example_event_listener));
ESP_LOGI(TAG, "Initializing buttons");
ESP_ERROR_CHECK(init_buttons());
ESP_LOGI(TAG, "Initializing LED indicator");
ESP_ERROR_CHECK(init_led_indicators());
ESP_LOGI(TAG, "Relay Channel Multi Example is ready to operate");
// Indicate init was successful
start_all_indicators_for_mode(INDICATOR_MODE_OK);
}
static void on_click_up(void *arg, void *data)
{
if (selected_channel == EXAMPLE_ALL_CHANNELS) {
relay_chn_run_forward_all();
} else {
relay_chn_run_forward(selected_channel);
}
}
static void on_click_down(void *arg, void *data)
{
if (selected_channel == EXAMPLE_ALL_CHANNELS) {
relay_chn_run_reverse_all();
} else {
relay_chn_run_reverse(selected_channel);
}
}
static void on_click_stop(void *arg, void *data)
{
if (selected_channel == EXAMPLE_ALL_CHANNELS) {
relay_chn_stop_all();
} else {
relay_chn_stop(selected_channel);
}
}
static void on_click_flip(void *arg, void *data)
{
if (selected_channel == EXAMPLE_ALL_CHANNELS) {
relay_chn_flip_direction_all();
start_all_indicators_for_mode(INDICATOR_MODE_OK);
} else {
relay_chn_flip_direction(selected_channel);
led_indicator_start(indicators[selected_channel], INDICATOR_MODE_OK);
}
}
static void on_click_tilt_up(void *arg, void *data)
{
if (selected_channel == EXAMPLE_ALL_CHANNELS) {
relay_chn_tilt_forward_all();
} else {
relay_chn_tilt_forward(selected_channel);
}
}
static void on_click_tilt_down(void *arg, void *data)
{
if (selected_channel == EXAMPLE_ALL_CHANNELS) {
relay_chn_tilt_reverse_all();
} else {
relay_chn_tilt_reverse(selected_channel);
}
}
static void on_release_tilt(void *arg, void *data)
{
if (selected_channel == EXAMPLE_ALL_CHANNELS) {
relay_chn_tilt_stop_all();
} else {
relay_chn_tilt_stop(selected_channel);
}
}
static void on_click_select(void *arg, void *data)
{
int64_t now_us = esp_timer_get_time();
uint32_t delta_ms = (now_us - last_indc_update_us) / 1000;
if (delta_ms >= EXAMPLE_CHN_INDICATOR_ON_TIME_MS) {
// Channel indicator was off, turn it on first
if (selected_channel == EXAMPLE_ALL_CHANNELS) {
start_all_indicators_for_mode(INDICATOR_MODE_SELECT);
} else {
led_indicator_start(indicators[selected_channel], INDICATOR_MODE_SELECT);
}
last_indc_update_us = esp_timer_get_time(); // Save last selection time
return;
}
if (selected_channel == EXAMPLE_ALL_CHANNELS) {
// All channels selected previously, restore the selected channel
selected_channel = selected_channel_backup;
ESP_LOGI(TAG, "Restored selected channel: %d", selected_channel);
stop_all_indicators_for_mode(INDICATOR_MODE_SELECT);
led_indicator_start(indicators[selected_channel], INDICATOR_MODE_SELECT);
return;
}
// Channel indicator is turned on, select the next channel
selected_channel = ((selected_channel + 1) % CONFIG_RELAY_CHN_COUNT);
ESP_LOGI(TAG, "Selected channel: %d", selected_channel);
// Update the indicator
stop_all_indicators_for_mode(INDICATOR_MODE_SELECT);
led_indicator_start(indicators[selected_channel], INDICATOR_MODE_SELECT);
last_indc_update_us = esp_timer_get_time(); // Save last selection time
}
static void on_click_select_all(void *arg, void *data)
{
if (selected_channel == EXAMPLE_ALL_CHANNELS) {
ESP_LOGI(TAG, "All channels are selected already");
int64_t now_us = esp_timer_get_time();
uint32_t delta_ms = (now_us - last_indc_update_us) / 1000;
// If the indicators in sleep, wake up
if (delta_ms >= EXAMPLE_CHN_INDICATOR_ON_TIME_MS) {
start_all_indicators_for_mode(INDICATOR_MODE_SELECT);
}
return;
}
selected_channel_backup = selected_channel;
selected_channel = EXAMPLE_ALL_CHANNELS;
ESP_LOGI(TAG, "Selected all channels");
start_all_indicators_for_mode(INDICATOR_MODE_SELECT);
last_indc_update_us = esp_timer_get_time(); // Save last selection time
}
static void start_all_indicators_for_mode(indicator_mode_t mode)
{
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
led_indicator_start(indicators[i], mode);
}
}
static void stop_all_indicators_for_mode(indicator_mode_t mode)
{
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
led_indicator_stop(indicators[i], mode);
led_indicator_set_on_off(indicators[i], false);
}
}
static void example_event_listener(uint8_t ch, relay_chn_state_t old_state, relay_chn_state_t new_state)
{
ESP_LOGI(TAG, "example_event_listener: State change for #%d, from %s to %s",
ch, relay_chn_state_to_str(old_state), relay_chn_state_to_str(new_state));
switch (new_state) {
case RELAY_CHN_STATE_FORWARD:
case RELAY_CHN_STATE_REVERSE:
led_indicator_start(indicators[ch], INDICATOR_MODE_RUNNING);
break;
case RELAY_CHN_STATE_STOPPED:
case RELAY_CHN_STATE_IDLE:
if (old_state == RELAY_CHN_STATE_FORWARD || old_state == RELAY_CHN_STATE_REVERSE) {
led_indicator_stop(indicators[ch], INDICATOR_MODE_RUNNING);
// Make sure the indicator turned off
led_indicator_set_on_off(indicators[ch], false);
}
else if (old_state == RELAY_CHN_STATE_TILT_FORWARD || old_state == RELAY_CHN_STATE_TILT_REVERSE) {
led_indicator_stop(indicators[ch], INDICATOR_MODE_TILTING);
// Make sure the indicator turned off
led_indicator_set_on_off(indicators[ch], false);
}
break;
case RELAY_CHN_STATE_TILT_FORWARD:
case RELAY_CHN_STATE_TILT_REVERSE:
led_indicator_start(indicators[ch], INDICATOR_MODE_TILTING);
break;
default: // No-op
}
}
static esp_err_t init_buttons()
{
esp_err_t ret;
button_config_t btn_cfg = {0};
uint8_t active_level = CONFIG_EXAMPLE_RLCHN_BTN_ACTIVE_LEVEL_LOW ? 0 : 1;
button_gpio_config_t btn_gpio_ccfg = {
.gpio_num = (gpio_num_t) CONFIG_EXAMPLE_RLCHN_BTN_UP_IO_NUM,
.active_level = active_level
};
ESP_LOGI(TAG, "Initializing buttons with active level: %u", active_level);
button_handle_t btn_up = NULL, btn_down = NULL, btn_stop = NULL, btn_select = NULL;
// --- Create buttons ---
ret = iot_button_new_gpio_device(&btn_cfg, &btn_gpio_ccfg, &btn_up);
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create UP button");
ESP_RETURN_ON_FALSE(btn_up != NULL, ret, TAG, "Failed to create UP button");
btn_gpio_ccfg.gpio_num = (gpio_num_t) CONFIG_EXAMPLE_RLCHN_BTN_DOWN_IO_NUM;
ret = iot_button_new_gpio_device(&btn_cfg, &btn_gpio_ccfg, &btn_down);
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create DOWN button");
ESP_RETURN_ON_FALSE(btn_down != NULL, ret, TAG, "Failed to create DOWN button");
btn_gpio_ccfg.gpio_num = (gpio_num_t) CONFIG_EXAMPLE_RLCHN_BTN_STOP_IO_NUM;
ret = iot_button_new_gpio_device(&btn_cfg, &btn_gpio_ccfg, &btn_stop);
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create STOP button");
ESP_RETURN_ON_FALSE(btn_stop != NULL, ret, TAG, "Failed to create STOP button");
btn_gpio_ccfg.gpio_num = (gpio_num_t) CONFIG_EXAMPLE_RLCHN_BTN_SELECT_IO_NUM;
ret = iot_button_new_gpio_device(&btn_cfg, &btn_gpio_ccfg, &btn_select);
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create SELECT button");
ESP_RETURN_ON_FALSE(btn_select != NULL, ret, TAG, "Failed to create SELECT button");
// --- Create buttons ---
// --- Register button callbacks ---
ESP_LOGI(TAG, "Setting up button callbacks. Configured long press time: %d ms", CONFIG_EXAMPLE_RLCHN_BTN_LONG_PRESS_TIME_MS);
button_event_args_t btn_event_args = {
.long_press.press_time = CONFIG_EXAMPLE_RLCHN_BTN_LONG_PRESS_TIME_MS
};
// --- Register UP and TILT_UP operations on UP button ---
ret = iot_button_register_cb(btn_up, BUTTON_SINGLE_CLICK, NULL, on_click_up, NULL);
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register UP button click callback");
ret = iot_button_register_cb(btn_up, BUTTON_LONG_PRESS_START, &btn_event_args, on_click_tilt_up, NULL);
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register TILT_UP button press callback");
ret = iot_button_register_cb(btn_up, BUTTON_LONG_PRESS_UP, NULL, on_release_tilt, NULL);
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register TILT_UP button release callback");
// --- Register DOWN and TILT_DOWN operations on DOWN button ---
ret = iot_button_register_cb(btn_down, BUTTON_SINGLE_CLICK, NULL, on_click_down, NULL);
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register DOWN button click callback");
ret = iot_button_register_cb(btn_down, BUTTON_LONG_PRESS_START, &btn_event_args, on_click_tilt_down, NULL);
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register TILT_DOWN button press callback");
ret = iot_button_register_cb(btn_down, BUTTON_LONG_PRESS_UP, NULL, on_release_tilt, NULL);
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register TILT_DOWN button release callback");
// --- Register STOP and FLIP operations on STOP ---
ret = iot_button_register_cb(btn_stop, BUTTON_SINGLE_CLICK, NULL, on_click_stop, NULL);
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register STOP button click callback");
ret = iot_button_register_cb(btn_stop, BUTTON_LONG_PRESS_START, &btn_event_args, on_click_flip, NULL);
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register FLIP button press callback");
// --- Register channel SELECT and SELECT_ALL ---
ret = iot_button_register_cb(btn_select, BUTTON_SINGLE_CLICK, NULL, on_click_select, NULL);
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register SELECT button click callback");
ret = iot_button_register_cb(btn_select, BUTTON_LONG_PRESS_START, &btn_event_args, on_click_select_all, NULL);
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register SELECT_ALL press callback");
return ESP_OK;
}
static esp_err_t init_led_indicators()
{
const int indicator_io_nums[CONFIG_RELAY_CHN_COUNT] = {
CONFIG_EXAMPLE_RLCHN_LED_INDICATOR1_IO_NUM,
CONFIG_EXAMPLE_RLCHN_LED_INDICATOR2_IO_NUM,
CONFIG_EXAMPLE_RLCHN_LED_INDICATOR3_IO_NUM
};
for (int i = 0; i < CONFIG_RELAY_CHN_COUNT; i++) {
gpio_num_t indicator_io_num = (gpio_num_t) indicator_io_nums[i];
gpio_reset_pin(indicator_io_num); // Clear the output buffers
led_indicator_gpio_config_t led_indicator_gpio_cfg = {
.gpio_num = indicator_io_num,
.is_active_level_high = true
};
led_indicator_config_t led_indicator_cfg = {
.mode = LED_GPIO_MODE,
.led_indicator_gpio_config = &led_indicator_gpio_cfg,
.blink_lists = led_indicator_modes,
.blink_list_num = INDICATOR_MODE_MAX
};
indicators[i] = led_indicator_create(&led_indicator_cfg);
if (!indicators[i]) {
ESP_LOGE(TAG, "Failed to create LED indicator");
return ESP_FAIL;
}
}
return ESP_OK;
}

View File

@@ -0,0 +1,10 @@
# Halt on panic
CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT=y
# Relay Channel Configs
CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT=y
CONFIG_RELAY_CHN_COUNT=3
# Keep this as short as possible for example purposes
CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC=5
CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC=20
CONFIG_RELAY_CHN_ENABLE_TILTING=y

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

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

View File

@@ -0,0 +1,23 @@
{
"configurations": [
{
"name": "ESP-IDF",
"compilerPath": "${config:idf.toolsPath}/tools/riscv32-esp-elf/esp-14.2.0_20241119/riscv32-esp-elf/bin/riscv32-esp-elf-gcc",
"compileCommands": "/disk/Projeler/ESP-Components/relay_chn/examples/relay_chn_single/build/compile_commands.json",
"includePath": [
"${config:idf.espIdfPath}/components/**",
"${config:idf.espIdfPathWin}/components/**",
"${workspaceFolder}/**"
],
"browse": {
"path": [
"${config:idf.espIdfPath}/components",
"${config:idf.espIdfPathWin}/components",
"${workspaceFolder}"
],
"limitSymbolsToIncludedHeaders": true
}
}
],
"version": 4
}

View File

@@ -0,0 +1,15 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "gdbtarget",
"request": "attach",
"name": "Eclipse CDT GDB Adapter"
},
{
"type": "espidf",
"name": "Launch",
"request": "launch"
}
]
}

View File

@@ -0,0 +1,10 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
set(COMPONENTS main)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(relay_chn_single)

View File

@@ -0,0 +1,126 @@
# Relay Channel Single Example
## Introduction
This example demonstrates how to use the relay channel component to control a 2-relay setup with button inputs and LED status indication. It showcases:
- Basic relay channel operations (forward/reverse running, stopping)
- Secondary operations (tilting, direction flipping)
- State change event handling with multiple listeners
- Relay channel run limit
- Button event handling using esp-iot-solution's button component
- Visual feedback using esp-iot-solution's LED indicator component
## How to Use Example
This example has been tested on an `ESP32-C3-DevKitM-1U` board. However, it can be adapted to any ESP32-based board with at least six available GPIO pins by adjusting the configuration options.
### Hardware Required
* An ESP32-based development board
* 2 relays connected to GPIO pins (default: GPIO4, GPIO5)
* 3 buttons connected to GPIO pins:
- UP button (default: GPIO0)
- DOWN button (default: GPIO1)
- STOP button (default: GPIO2)
* 1 LED for status indication (default: GPIO3)
#### Hardware Schematic
![Hardware Schematic](example_schematic.png)
### Configuration
The example can be configured through `menuconfig` under "Relay Channel Single Example Configuration":
1. Button active level (`EXAMPLE_RLCHN_BTN_ACTIVE_LEVEL`)
- Select between active LOW or HIGH logic level for buttons
2. GPIO assignments:
- UP button pin (`EXAMPLE_RLCHN_BTN_UP_IO_NUM`, default: 0)
- DOWN button pin (`EXAMPLE_RLCHN_BTN_DOWN_IO_NUM`, default: 1)
- STOP button pin (`EXAMPLE_RLCHN_BTN_STOP_IO_NUM`, default: 2)
- LED indicator pin (`EXAMPLE_RLCHN_LED_INDICATOR_IO_NUM`, default: 3)
3. Long press timing:
- `EXAMPLE_RLCHN_BTN_LONG_PRESS_TIME_MS`: Duration for long press actions (1500-3000ms, default: 2000ms)
### Button Operations
The example uses esp-iot-solution's `button` component to handle the following operations:
- **UP button**:
* Short press: Start forward movement
* Long press: Start forward tilt (stops on release)
- **DOWN button**:
* Short press: Start reverse movement
* Long press: Start reverse tilt (stops on release)
- **STOP button**:
* Short press: Stop movement
* Long press: Flip movement direction
### LED Indicator States
The example uses esp-iot-solution's `led_indicator` component to show different states:
- **Running**: LED blinks at 300ms on, 100ms off
- **Tilting**: Fast blink at 100ms on, 50ms off
- **Operation Success**: Two quick blinks
- **Operation Fail**: One long blink
### Dependencies
This example requires:
- ESP-IDF v4.1 or later
- esp-iot-solution components:
* button v4.1.1 or later
* led_indicator v1.1.1 or later
- relay_chn component
## Example Output
When the application boots, it will wait for a button event. Then the two state listeners will print state changes.
```log
I (273) main_task: Calling app_main()
I (273) RELAY_CHN_SINGLE_EXAMPLE: Initializing default NVS storage
I (283) RELAY_CHN_SINGLE_EXAMPLE: nvs_flash_init: NVS flash init return: ESP_OK
I (283) RELAY_CHN_SINGLE_EXAMPLE: Initializing relay channel
I (293) gpio: GPIO[4]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (303) gpio: GPIO[5]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (313) RELAY_CHN_SINGLE_EXAMPLE: Initializing buttons
I (313) RELAY_CHN_SINGLE_EXAMPLE: Initializing buttons with active level: 0
I (323) gpio: GPIO[0]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (323) button: IoT Button Version: 4.1.3
I (333) gpio: GPIO[1]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (343) gpio: GPIO[2]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (343) RELAY_CHN_SINGLE_EXAMPLE: Setting up button callbacks. Configured long press time: 2000 ms
I (353) RELAY_CHN_SINGLE_EXAMPLE: Initializing LED indicator
I (363) gpio: GPIO[3]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (373) led_indicator: LED Indicator Version: 1.1.1
I (373) gpio: GPIO[3]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (383) led_indicator: Indicator create successfully. type:GPIO mode, hardware_data:0x3fc97be4, blink_lists:custom
I (393) RELAY_CHN_SINGLE_EXAMPLE: Relay Channel Single Example is ready to operate
I (403) main_task: Returned from app_main()
I (3683) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_1: Defining new indicator mode for #0 and state change from 1 to 3
I (3683) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_2: State change for #0, from IDLE to FORWARD
I (9513) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_1: Defining new indicator mode for #0 and state change from 3 to 2
I (9513) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_2: State change for #0, from FORWARD to STOPPED
I (9523) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_1: Defining new indicator mode for #0 and state change from 2 to 6
I (9533) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_2: State change for #0, from STOPPED to REVERSE_PENDING
I (10313) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_1: Defining new indicator mode for #0 and state change from 6 to 4
I (10313) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_2: State change for #0, from REVERSE_PENDING to REVERSE
I (32173) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_1: Defining new indicator mode for #0 and state change from 4 to 2
I (32173) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_2: State change for #0, from REVERSE to STOPPED
I (32973) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_1: Defining new indicator mode for #0 and state change from 2 to 1
I (32973) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_2: State change for #0, from STOPPED to IDLE
I (36423) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_1: Defining new indicator mode for #0 and state change from 1 to 8
I (36423) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_2: State change for #0, from IDLE to TILT_REVERSE
I (41153) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_1: Defining new indicator mode for #0 and state change from 8 to 1
I (41153) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_2: State change for #0, from TILT_REVERSE to IDLE
I (47113) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_1: Defining new indicator mode for #0 and state change from 1 to 7
I (47113) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_2: State change for #0, from IDLE to TILT_FORWARD
I (51913) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_1: Defining new indicator mode for #0 and state change from 7 to 1
I (51913) RELAY_CHN_SINGLE_EXAMPLE: example_event_listener_2: State change for #0, from TILT_FORWARD to IDLE
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

View File

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

View File

@@ -0,0 +1,40 @@
menu "Relay Channel Single Example Configuration"
choice EXAMPLE_RLCHN_BTN_ACTIVE_LEVEL
prompt "Choose an active level for buttons"
default EXAMPLE_RLCHN_BTN_ACTIVE_LEVEL_LOW
help
Specify the active level for buttons.
config EXAMPLE_RLCHN_BTN_ACTIVE_LEVEL_LOW
bool "Active level LOW"
config EXAMPLE_RLCHN_BTN_ACTIVE_LEVEL_HIGH
bool "Active level HIGH"
endchoice
config EXAMPLE_RLCHN_BTN_UP_IO_NUM
int "GPIO number for UP button"
default 0
config EXAMPLE_RLCHN_BTN_DOWN_IO_NUM
int "GPIO number for DOWN button"
default 1
config EXAMPLE_RLCHN_BTN_STOP_IO_NUM
int "GPIO number for STOP button"
default 2
config EXAMPLE_RLCHN_LED_INDICATOR_IO_NUM
int "GPIO number for LED indicator output"
default 3
config EXAMPLE_RLCHN_BTN_LONG_PRESS_TIME_MS
int "Long press time in ms to start secondary actions"
range 1500 3000
default 2000
help
Long press time in milliseconds is required to start secondary actions
like tilting and flipping.
endmenu

View File

@@ -0,0 +1,8 @@
dependencies:
idf:
version: '>=4.1.0'
espressif/button: ^4.1.1
espressif/led_indicator: ^1.1.1
relay_chn:
version: '*'
override_path: ../../../

View File

@@ -0,0 +1,290 @@
/*
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_check.h"
#include "driver/gpio.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "button_gpio.h"
#include "iot_button.h"
#include "led_indicator.h"
#include "relay_chn.h"
static const char *TAG = "RELAY_CHN_SINGLE_EXAMPLE";
/**
* @brief LED indicator modes for different states.
*/
typedef enum {
INDICATOR_MODE_OK, /*!< OK/Success indication */
INDICATOR_MODE_FAIL, /*!< Fail/Error indication */
INDICATOR_MODE_TILTING, /*!< Tilting operation in progress */
INDICATOR_MODE_RUNNING, /*!< Full run operation in progress */
INDICATOR_MODE_MAX /*!< Maximum number of indicator modes */
} indicator_mode_t;
/** @brief Blink pattern for OK/Success indication. */
static const blink_step_t indc_mode_ok[] = {
{LED_BLINK_HOLD, LED_STATE_ON, 100}, // step1: turn on LED 100 ms
{LED_BLINK_HOLD, LED_STATE_OFF, 50}, // step2: turn off LED 50 ms
{LED_BLINK_HOLD, LED_STATE_ON, 100}, // step3: turn on LED 100 ms
{LED_BLINK_HOLD, LED_STATE_OFF, 50}, // step4: turn off LED 50 ms
{LED_BLINK_STOP, 0, 0}, // step5: stop blink (off)
};
/** @brief Blink pattern for Fail/Error indication. */
static const blink_step_t indc_mode_fail[] = {
{LED_BLINK_HOLD, LED_STATE_ON, 1000}, // step1: turn on LED 1000 ms
{LED_BLINK_HOLD, LED_STATE_OFF, 500}, // step2: turn off LED 500 ms
{LED_BLINK_STOP, 0, 0}, // step4: stop blink (off)
};
/** @brief Blink pattern for full run operation. */
static const blink_step_t indc_mode_running[] = {
{LED_BLINK_HOLD, LED_STATE_ON, 300}, // step1: turn on LED 300 ms
{LED_BLINK_HOLD, LED_STATE_OFF, 100}, // step2: turn off LED 100 ms
{LED_BLINK_LOOP, 0, 0}, // step3: loop from step1
};
/** @brief Blink pattern for tilting operation. */
static const blink_step_t indc_mode_tilting[] = {
{LED_BLINK_HOLD, LED_STATE_ON, 100}, // step1: turn on LED 100 ms
{LED_BLINK_HOLD, LED_STATE_OFF, 50}, // step2: turn off LED 50 ms
{LED_BLINK_LOOP, 0, 0}, // step3: loop from step1
};
/** @brief Array of LED indicator blink patterns. */
blink_step_t const *led_indicator_modes[] = {
[INDICATOR_MODE_OK] = indc_mode_ok,
[INDICATOR_MODE_FAIL] = indc_mode_fail,
[INDICATOR_MODE_RUNNING] = indc_mode_running,
[INDICATOR_MODE_TILTING] = indc_mode_tilting,
[INDICATOR_MODE_MAX] = NULL,
};
/** @brief Handle for the LED indicator. */
static led_indicator_handle_t indicator = NULL;
/**
* @brief Initializes the buttons for user interaction.
*
* This function configures and creates GPIO buttons for UP, DOWN, and STOP
* operations. It also registers callbacks for single-click and long-press
* events to control the relay channel.
*
* @return esp_err_t
* - ESP_OK: Success
* - Others: Fail
*/
static esp_err_t init_buttons(void);
/**
* @brief Initializes the LED indicator.
*
* This function configures and creates the LED indicator used to provide
* visual feedback on the relay channel's status.
*
* @return esp_err_t
* - ESP_OK: Success
* - ESP_FAIL: Fail
*/
static esp_err_t init_led_indicator(void);
/**
* @brief Event listener for relay channel state changes to control the LED indicator.
*/
static void example_event_listener_1(uint8_t ch, relay_chn_state_t old_state, relay_chn_state_t new_state);
/**
* @brief Event listener for relay channel state changes to log the state transition.
*/
static void example_event_listener_2(uint8_t ch, relay_chn_state_t old_state, relay_chn_state_t new_state);
void app_main(void)
{
const uint8_t gpio_map[] = { 4, 5 };
const uint8_t gpio_count = sizeof(gpio_map) / sizeof(gpio_map[0]);
ESP_LOGI(TAG, "Initializing relay channel");
ESP_ERROR_CHECK(relay_chn_create(gpio_map, gpio_count));
ESP_ERROR_CHECK(relay_chn_register_listener(example_event_listener_1));
ESP_ERROR_CHECK(relay_chn_register_listener(example_event_listener_2));
ESP_LOGI(TAG, "Initializing buttons");
ESP_ERROR_CHECK(init_buttons());
ESP_LOGI(TAG, "Initializing LED indicator");
ESP_ERROR_CHECK(init_led_indicator());
ESP_LOGI(TAG, "Relay Channel Single Example is ready to operate");
// Indicate init was successful
led_indicator_start(indicator, INDICATOR_MODE_OK);
}
static void on_click_up(void *arg, void *data)
{
relay_chn_run_forward();
}
static void on_click_down(void *arg, void *data)
{
relay_chn_run_reverse();
}
static void on_click_stop(void *arg, void *data)
{
relay_chn_stop();
}
static void on_click_flip(void *arg, void *data)
{
relay_chn_flip_direction();
led_indicator_start(indicator, INDICATOR_MODE_OK);
}
static void on_click_tilt_up(void *arg, void *data)
{
relay_chn_tilt_forward();
}
static void on_click_tilt_down(void *arg, void *data)
{
relay_chn_tilt_reverse();
}
static void on_release_tilt(void *arg, void *data)
{
relay_chn_tilt_stop();
}
static void example_event_listener_1(uint8_t ch, relay_chn_state_t old_state, relay_chn_state_t new_state)
{
ESP_LOGI(TAG, "example_event_listener_1: Defining new indicator mode for #%d and state change from %d to %d", ch, old_state, new_state);
switch (new_state) {
case RELAY_CHN_STATE_FORWARD:
case RELAY_CHN_STATE_REVERSE:
led_indicator_start(indicator, INDICATOR_MODE_RUNNING);
break;
case RELAY_CHN_STATE_STOPPED:
case RELAY_CHN_STATE_IDLE:
if (old_state == RELAY_CHN_STATE_FORWARD || old_state == RELAY_CHN_STATE_REVERSE) {
led_indicator_stop(indicator, INDICATOR_MODE_RUNNING);
// Make sure the indicator turned off
led_indicator_set_on_off(indicator, false);
}
else if (old_state == RELAY_CHN_STATE_TILT_FORWARD || old_state == RELAY_CHN_STATE_TILT_REVERSE) {
led_indicator_stop(indicator, INDICATOR_MODE_TILTING);
// Make sure the indicator turned off
led_indicator_set_on_off(indicator, false);
}
break;
case RELAY_CHN_STATE_TILT_FORWARD:
case RELAY_CHN_STATE_TILT_REVERSE:
led_indicator_start(indicator, INDICATOR_MODE_TILTING);
break;
default: // No-op
}
}
static void example_event_listener_2(uint8_t ch, relay_chn_state_t old_state, relay_chn_state_t new_state)
{
ESP_LOGI(TAG, "example_event_listener_2: State change for #%d, from %s to %s",
ch, relay_chn_state_to_str(old_state), relay_chn_state_to_str(new_state));
}
static esp_err_t init_buttons()
{
esp_err_t ret;
button_config_t btn_cfg = {0};
uint8_t active_level = CONFIG_EXAMPLE_RLCHN_BTN_ACTIVE_LEVEL_LOW ? 0 : 1;
button_gpio_config_t btn_gpio_ccfg = {
.gpio_num = (gpio_num_t) CONFIG_EXAMPLE_RLCHN_BTN_UP_IO_NUM,
.active_level = active_level
};
ESP_LOGI(TAG, "Initializing buttons with active level: %u", active_level);
button_handle_t btn_up = NULL, btn_down = NULL, btn_stop = NULL;
// --- Create buttons ---
ret = iot_button_new_gpio_device(&btn_cfg, &btn_gpio_ccfg, &btn_up);
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create UP button");
ESP_RETURN_ON_FALSE(btn_up != NULL, ret, TAG, "Failed to create UP button");
btn_gpio_ccfg.gpio_num = (gpio_num_t) CONFIG_EXAMPLE_RLCHN_BTN_DOWN_IO_NUM;
ret = iot_button_new_gpio_device(&btn_cfg, &btn_gpio_ccfg, &btn_down);
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create DOWN button");
ESP_RETURN_ON_FALSE(btn_down != NULL, ret, TAG, "Failed to create DOWN button");
btn_gpio_ccfg.gpio_num = (gpio_num_t) CONFIG_EXAMPLE_RLCHN_BTN_STOP_IO_NUM;
ret = iot_button_new_gpio_device(&btn_cfg, &btn_gpio_ccfg, &btn_stop);
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create STOP button");
ESP_RETURN_ON_FALSE(btn_stop != NULL, ret, TAG, "Failed to create STOP button");
// --- Create buttons ---
// --- Register button callbacks ---
ESP_LOGI(TAG, "Setting up button callbacks. Configured long press time: %d ms", CONFIG_EXAMPLE_RLCHN_BTN_LONG_PRESS_TIME_MS);
button_event_args_t btn_event_args = {
.long_press.press_time = CONFIG_EXAMPLE_RLCHN_BTN_LONG_PRESS_TIME_MS
};
// --- Register UP and TILT_UP operations on UP button ---
ret = iot_button_register_cb(btn_up, BUTTON_SINGLE_CLICK, NULL, on_click_up, NULL);
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register UP button click callback");
ret = iot_button_register_cb(btn_up, BUTTON_LONG_PRESS_START, &btn_event_args, on_click_tilt_up, NULL);
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register TILT_UP button press callback");
ret = iot_button_register_cb(btn_up, BUTTON_LONG_PRESS_UP, NULL, on_release_tilt, NULL);
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register TILT_UP button release callback");
// --- Register DOWN and TILT_DOWN operations on DOWN button ---
ret = iot_button_register_cb(btn_down, BUTTON_SINGLE_CLICK, NULL, on_click_down, NULL);
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register DOWN button click callback");
ret = iot_button_register_cb(btn_down, BUTTON_LONG_PRESS_START, &btn_event_args, on_click_tilt_down, NULL);
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register TILT_DOWN button press callback");
ret = iot_button_register_cb(btn_down, BUTTON_LONG_PRESS_UP, NULL, on_release_tilt, NULL);
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register TILT_DOWN button release callback");
// --- Register STOP and FLIP operations on STOP ---
ret = iot_button_register_cb(btn_stop, BUTTON_SINGLE_CLICK, NULL, on_click_stop, NULL);
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register STOP button click callback");
ret = iot_button_register_cb(btn_stop, BUTTON_LONG_PRESS_START, &btn_event_args, on_click_flip, NULL);
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to register FLIP button press callback");
return ESP_OK;
}
static esp_err_t init_led_indicator()
{
const gpio_num_t indicator_io_num = (gpio_num_t) CONFIG_EXAMPLE_RLCHN_LED_INDICATOR_IO_NUM;
gpio_reset_pin(indicator_io_num); // Clear the output buffers
led_indicator_gpio_config_t led_indicator_gpio_cfg = {
.gpio_num = indicator_io_num,
.is_active_level_high = true
};
led_indicator_config_t led_indicator_cfg = {
.mode = LED_GPIO_MODE,
.led_indicator_gpio_config = &led_indicator_gpio_cfg,
.blink_lists = led_indicator_modes,
.blink_list_num = INDICATOR_MODE_MAX
};
indicator = led_indicator_create(&led_indicator_cfg);
if (!indicator) {
ESP_LOGE(TAG, "Failed to create LED indicator");
return ESP_FAIL;
}
return ESP_OK;
}

View File

@@ -0,0 +1,9 @@
# Halt on panic
CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT=y
# Relay Channel Configs
CONFIG_RELAY_CHN_ENABLE_RUN_LIMIT=y
# Keep this as short as possible for example purposes
CONFIG_RELAY_CHN_RUN_LIMIT_MIN_SEC=5
CONFIG_RELAY_CHN_RUN_LIMIT_DEFAULT_SEC=20
CONFIG_RELAY_CHN_ENABLE_TILTING=y

View File

@@ -63,6 +63,14 @@ esp_err_t relay_chn_register_listener(relay_chn_state_listener_t listener);
*/ */
void relay_chn_unregister_listener(relay_chn_state_listener_t listener); void relay_chn_unregister_listener(relay_chn_state_listener_t listener);
/**
* @brief Return the text presentation of an state.
*
* @param state A state with type of relay_chn_state_t.
* @return char* The text presentation of the state. "UNKNOWN" if the state is not known.
*/
char *relay_chn_state_to_str(relay_chn_state_t state);
#if CONFIG_RELAY_CHN_ENABLE_TILTING #if CONFIG_RELAY_CHN_ENABLE_TILTING
/** /**
* @brief Get the default tilting sensitivity for the relay channel. * @brief Get the default tilting sensitivity for the relay channel.

View File

@@ -93,14 +93,6 @@ esp_err_t relay_chn_start_esp_timer_once(esp_timer_handle_t esp_timer, uint32_t
*/ */
void relay_chn_update_state(relay_chn_ctl_t *chn_ctl, relay_chn_state_t new_state); void relay_chn_update_state(relay_chn_ctl_t *chn_ctl, relay_chn_state_t new_state);
/**
* @brief Return the text presentation of an state.
*
* @param state A state with type of relay_chn_state_t.
* @return char* The text presentation of the state. "UNKNOWN" if the state is not known.
*/
char *relay_chn_state_str(relay_chn_state_t state);
#if CONFIG_RELAY_CHN_COUNT > 1 #if CONFIG_RELAY_CHN_COUNT > 1
/** /**
* @brief Check if the provided channel ID is valid. * @brief Check if the provided channel ID is valid.

View File

@@ -216,8 +216,8 @@ void relay_chn_issue_cmd(relay_chn_ctl_t* chn_ctl, relay_chn_cmd_t cmd)
} }
if (cmd == RELAY_CHN_CMD_STOP) { if (cmd == RELAY_CHN_CMD_STOP) {
if (chn_ctl->state == RELAY_CHN_STATE_STOPPED) { if (chn_ctl->state == RELAY_CHN_STATE_STOPPED || chn_ctl->state == RELAY_CHN_STATE_IDLE) {
return; // Do nothing if already stopped return; // Do nothing if already stopped or idle
} }
// If the command is STOP, issue it immediately // If the command is STOP, issue it immediately
relay_chn_dispatch_cmd(chn_ctl, cmd); relay_chn_dispatch_cmd(chn_ctl, cmd);
@@ -471,7 +471,7 @@ char *relay_chn_cmd_str(relay_chn_cmd_t cmd)
} }
} }
char *relay_chn_state_str(relay_chn_state_t state) char *relay_chn_state_to_str(relay_chn_state_t state)
{ {
switch (state) { switch (state) {
case RELAY_CHN_STATE_IDLE: case RELAY_CHN_STATE_IDLE:

View File

@@ -5,6 +5,7 @@
*/ */
#include "esp_check.h" #include "esp_check.h"
#include "relay_chn.h"
#include "relay_chn_priv_types.h" #include "relay_chn_priv_types.h"
#include "relay_chn_core.h" #include "relay_chn_core.h"
#include "relay_chn_ctl.h" #include "relay_chn_ctl.h"
@@ -92,8 +93,8 @@ esp_err_t relay_chn_ctl_get_state_all(relay_chn_state_t *states)
char *relay_chn_ctl_get_state_str(uint8_t chn_id) char *relay_chn_ctl_get_state_str(uint8_t chn_id)
{ {
return relay_chn_is_channel_id_valid(chn_id) return relay_chn_is_channel_id_valid(chn_id)
? relay_chn_state_str(s_chn_ctls[chn_id].state) ? relay_chn_state_to_str(s_chn_ctls[chn_id].state)
: relay_chn_state_str(RELAY_CHN_STATE_UNDEFINED); : relay_chn_state_to_str(RELAY_CHN_STATE_UNDEFINED);
} }

View File

@@ -5,6 +5,7 @@
*/ */
#include "esp_check.h" #include "esp_check.h"
#include "relay_chn.h"
#include "relay_chn_priv_types.h" #include "relay_chn_priv_types.h"
#include "relay_chn_core.h" #include "relay_chn_core.h"
#include "relay_chn_ctl.h" #include "relay_chn_ctl.h"
@@ -65,7 +66,7 @@ relay_chn_state_t relay_chn_ctl_get_state()
char *relay_chn_ctl_get_state_str() char *relay_chn_ctl_get_state_str()
{ {
return relay_chn_state_str(s_chn_ctl.state); return relay_chn_state_to_str(s_chn_ctl.state);
} }
void relay_chn_ctl_run_forward() void relay_chn_ctl_run_forward()

View File

@@ -5,6 +5,7 @@
*/ */
#include "esp_check.h" #include "esp_check.h"
#include "relay_chn.h"
#include "relay_chn_core.h" #include "relay_chn_core.h"
#include "relay_chn_output.h" #include "relay_chn_output.h"
#include "relay_chn_run_info.h" #include "relay_chn_run_info.h"
@@ -103,16 +104,47 @@ static void relay_chn_tilt_start_timer_or_stop(relay_chn_tilt_ctl_t *tilt_ctl, e
} }
} }
/**
* @brief Checks if the relay channel can perform the current tilt command.
*
* This function evaluates whether a tilt command can be executed based on the
* channel's history. The rules are as follows:
* - Tilting in the same direction as the last full run command (e.g., TILT_FORWARD
* after a FORWARD run) is always allowed.
* - Tilting in the opposite direction of the last full run (e.g., TILT_REVERSE
* after a FORWARD run) is only allowed if the tilt counter is greater than zero,
* which indicates that the channel has previously tilted in the primary direction.
* - If the channel has not been run before, tilting is not allowed.
*
* @param tilt_ctl Pointer to the tilt control structure for the channel.
* @param tilt_cmd The tilt command to check against.
*
* @return true if the tilt command is allowed, false otherwise.
*/
static bool relay_chn_can_perform_tilt_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_tilt_cmd_t tilt_cmd)
{
relay_chn_cmd_t last_run_cmd = relay_chn_run_info_get_last_run_cmd(tilt_ctl->chn_ctl->run_info);
if (last_run_cmd == RELAY_CHN_CMD_FORWARD) {
return (tilt_cmd == RELAY_CHN_TILT_CMD_FORWARD) ||
(tilt_cmd == RELAY_CHN_TILT_CMD_REVERSE && tilt_ctl->tilt_count > 0);
} else if (last_run_cmd == RELAY_CHN_CMD_REVERSE) {
return (tilt_cmd == RELAY_CHN_TILT_CMD_REVERSE) ||
(tilt_cmd == RELAY_CHN_TILT_CMD_FORWARD && tilt_ctl->tilt_count > 0);
}
return false;
}
// Issue a tilt command to a specific relay channel. // Issue a tilt command to a specific relay channel.
static void relay_chn_tilt_issue_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_tilt_cmd_t cmd) static void relay_chn_tilt_issue_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_tilt_cmd_t cmd)
{ {
// TILT_STOP is safe and high priority // TILT_STOP is safe and high priority
if (cmd == RELAY_CHN_TILT_CMD_STOP) { if (cmd == RELAY_CHN_TILT_CMD_STOP) {
if (tilt_ctl->chn_ctl->state == RELAY_CHN_STATE_STOPPED) { relay_chn_state_t state = tilt_ctl->chn_ctl->state;
return; // Do nothing if already stopped if (state == RELAY_CHN_STATE_TILT_FORWARD || state == RELAY_CHN_STATE_TILT_REVERSE) {
}
// If the command is TILT_STOP, issue it immediately // If the command is TILT_STOP, issue it immediately
relay_chn_tilt_dispatch_cmd(tilt_ctl, cmd); relay_chn_tilt_dispatch_cmd(tilt_ctl, cmd);
}
return; return;
} }
@@ -127,6 +159,11 @@ static void relay_chn_tilt_issue_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_t
return; return;
} }
if (!relay_chn_can_perform_tilt_cmd(tilt_ctl, cmd)) {
ESP_LOGD(TAG, "Cannot perform tilt command: %d for #%d", cmd, tilt_ctl->chn_ctl->id);
return;
}
// Set the command that will be processed // Set the command that will be processed
tilt_ctl->cmd = cmd; tilt_ctl->cmd = cmd;
switch (tilt_ctl->chn_ctl->state) { switch (tilt_ctl->chn_ctl->state) {
@@ -184,7 +221,7 @@ static void relay_chn_tilt_issue_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_t
break; break;
default: default:
ESP_LOGD(TAG, "relay_chn_tilt_issue_cmd: Unexpected relay channel state: %s!", relay_chn_state_str(tilt_ctl->chn_ctl->state)); ESP_LOGD(TAG, "relay_chn_tilt_issue_cmd: Unexpected relay channel state: %s!", relay_chn_state_to_str(tilt_ctl->chn_ctl->state));
} }
} }
@@ -254,7 +291,7 @@ void relay_chn_tilt_reverse_all()
void relay_chn_tilt_stop(uint8_t chn_id) void relay_chn_tilt_stop(uint8_t chn_id)
{ {
if (!relay_chn_is_channel_id_valid(chn_id)) { if (relay_chn_is_channel_id_valid(chn_id)) {
relay_chn_tilt_dispatch_cmd(&s_tilt_ctls[chn_id], RELAY_CHN_TILT_CMD_STOP); relay_chn_tilt_dispatch_cmd(&s_tilt_ctls[chn_id], RELAY_CHN_TILT_CMD_STOP);
} }
} }
@@ -283,7 +320,7 @@ void relay_chn_tilt_reverse()
void relay_chn_tilt_stop() void relay_chn_tilt_stop()
{ {
relay_chn_tilt_dispatch_cmd(&s_tilt_ctl, RELAY_CHN_TILT_CMD_STOP); relay_chn_tilt_issue_cmd(&s_tilt_ctl, RELAY_CHN_TILT_CMD_STOP);
} }
#endif // CONFIG_RELAY_CHN_COUNT > 1 #endif // CONFIG_RELAY_CHN_COUNT > 1