Add a multi channel example

Added a multi channel example with run limit, tilting and channel selection
features. Refs #1104 and closes #1111.
This commit is contained in:
2025-09-11 15:50:34 +03:00
parent e30b445b91
commit 4edebf206e
12 changed files with 732 additions and 0 deletions

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