Add source files.

This commit is contained in:
2025-02-11 10:54:43 +03:00
parent 51b48f761a
commit d3fb1e6620
10 changed files with 885 additions and 0 deletions

4
CMakeLists.txt Normal file
View File

@@ -0,0 +1,4 @@
idf_component_register(SRCS "relay_chn.c"
INCLUDE_DIRS include
REQUIRES driver
PRIV_REQUIRES esp_timer esp_event)

20
Kconfig Normal file
View File

@@ -0,0 +1,20 @@
menu "Relay Channel Driver Configuration"
config RELAY_CHN_OPPOSITE_INERTIA_MS
int "Inertia time before it runs opposite direction (ms)"
range 200 1500
default 800
help
Time to wait after changing the direction of the output before
starting the output. This is useful for the motors or some other
mechanical actuators to allow them to stop and settle before
changing the direction.
config RELAY_CHN_COUNT
int "Number of relay channels"
range 1 8
default 1
help
Number of relay channels between 1 and 8.
endmenu

7
idf_component.yml Normal file
View File

@@ -0,0 +1,7 @@
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.

162
include/relay_chn.h Normal file
View File

@@ -0,0 +1,162 @@
#ifndef RELAY_CHN_H
#define RELAY_CHN_H
/**
* @file relay_chn.h
*
* @author
* Ismail Sahillioglu <ismailsahillioglu@gmail.com>
*
* @date 2025.02.08
*
* @defgroup relay_chn Relay Channel Controller
* @ingroup components
* @{
* 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.
* 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.
* 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
* reliability and prevent conflict operations. Also, the esp timer is used to manage the direction change inertia.
*/
#include "esp_err.h"
#include "driver/gpio.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Enumeration for relay channel direction.
*/
enum relay_chn_direction_enum {
RELAY_CHN_DIRECTION_DEFAULT, ///< Default direction of the relay channel.
RELAY_CHN_DIRECTION_FLIPPED ///< Flipped direction of the relay channel.
};
/**
* @brief Alias for the enum type relay_chn_direction_enum.
*/
typedef enum relay_chn_direction_enum relay_chn_direction_t;
/**
* @brief Enums that represent the state of a relay channel.
*/
enum relay_chn_state_enum {
RELAY_CHN_STATE_FREE, ///< The relay channel is free to run or execute commands.
RELAY_CHN_STATE_STOPPED, ///< The relay channel is stopped and not running.
RELAY_CHN_STATE_FORWARD, ///< The relay channel is running in the forward direction.
RELAY_CHN_STATE_REVERSE, ///< The relay channel is running in the reverse direction.
RELAY_CHN_STATE_FORWARD_PENDING, ///< The relay channel is pending to run in the forward direction.
RELAY_CHN_STATE_REVERSE_PENDING, ///< The relay channel is pending to run in the reverse direction.
};
/**
* @brief Alias for the enum type relay_chn_state_enum.
*/
typedef enum relay_chn_state_enum relay_chn_state_t;
/**
* @brief Create and initialize relay channels.
*
* This function initializes the relay channels based on the provided GPIO map.
*
* @param gpio_map Pointer to an array of GPIO numbers that correspond to the relay channels.
* @param gpio_count The number of GPIOs in the gpio_map array.
*
* @return
* - ESP_OK: Success
* - ESP_ERR_INVALID_ARG: Invalid argument
* - ESP_FAIL: General failure
*/
esp_err_t relay_chn_create(const gpio_num_t* gpio_map, uint8_t gpio_count);
/**
* @brief Get the state of the specified relay channel.
*
* This function retrieves the current state of the relay channel identified by the given channel ID.
*
* @param chn_id The ID of the relay channel whose state is to be retrieved.
* @return The current state of the specified relay channel.
*/
relay_chn_state_t relay_chn_get_state(uint8_t chn_id);
/**
* @brief Get the state string of the specified relay channel.
*
* This function returns a string representation of the state of the relay
* channel identified by the given channel ID.
*
* @param chn_id The ID of the relay channel whose state is to be retrieved.
* The valid range of channel IDs depends on the specific hardware
* and implementation.
*
* @return A pointer to a string representing the state of the specified relay
* channel. The returned string is managed internally and should not be
* modified or freed by the caller.
*/
char *relay_chn_get_state_str(uint8_t chn_id);
/**
* @brief Runs the relay channel in the forward direction.
*
* This function activates the specified relay channel to run in the forward direction.
*
* @param chn_id The ID of the relay channel to be activated.
*/
void relay_chn_run_forward(uint8_t chn_id);
/**
* @brief Runs the relay channel in reverse.
*
* This function activates the specified relay channel to run in reverse.
*
* @param chn_id The ID of the relay channel to be reversed.
*/
void relay_chn_run_reverse(uint8_t chn_id);
/**
* @brief Stops the relay channel specified by the channel ID.
*
* This function stops the operation of the relay channel identified by the
* provided channel ID. It is typically used to turn off or disable the relay
* channel.
*
* @param chn_id The ID of the relay channel to stop.
*/
void relay_chn_stop(uint8_t chn_id);
/**
* @brief Flips the direction of the specified relay channel.
*
* This function toggles the direction of the relay channel identified by the
* given channel ID. It is typically used to change the state of the relay
* from its current direction to the opposite direction.
*
* @param chn_id The ID of the relay channel to flip. This should be a valid
* channel ID within the range of available relay channels.
*/
void relay_chn_flip_direction(uint8_t chn_id);
/**
* @brief Get the direction of the specified relay channel.
*
* This function retrieves the direction configuration of a relay channel
* identified by the given channel ID.
*
* @param chn_id The ID of the relay channel to query.
* @return The direction of the specified relay channel as a value of type
* relay_chn_direction_t.
*/
relay_chn_direction_t relay_chn_get_direction(uint8_t chn_id);
#ifdef __cplusplus
}
#endif
/// @}
#endif // RELAY_CHN_H

580
relay_chn.c Normal file
View File

@@ -0,0 +1,580 @@
/**
* @file relay_chn.c
*
* @author
* Ismail Sahillioglu <ismailsahillioglu@gmail.com>
*
* @date 2025.02.08
*
* @ingroup relay_chn
*
* @brief This file contains the implementation of the relay channel component.
* @{
*/
#include <stdio.h>
#include "esp_err.h"
#include "esp_log.h"
#include "esp_task.h"
#include "driver/gpio.h"
#include "esp_timer.h"
#include "esp_event_base.h"
#include "esp_event.h"
#include "relay_chn.h"
#include "sdkconfig.h"
// TODO: on_state change API si ekle
#define RELAY_CHN_OPPOSITE_INERTIA_MS CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS
#define RELAY_CHN_COUNT CONFIG_RELAY_CHN_COUNT
const char* TAG = "relay_chn";
ESP_EVENT_DEFINE_BASE(RELAY_CHN_CMD_EVENT);
/**
* @brief Enumeration for relay channel commands.
*/
enum relay_chn_cmd_enum {
RELAY_CHN_CMD_NONE, ///< No command.
RELAY_CHN_CMD_STOP, ///< Stop the relay channel.
RELAY_CHN_CMD_FORWARD, ///< Run the relay channel in the forward direction.
RELAY_CHN_CMD_REVERSE, ///< Run the relay channel in the reverse direction.
RELAY_CHN_CMD_FLIP, ///< Flip the direction of the relay channel.
RELAY_CHN_CMD_FREE ///< Free the relay channel.
};
/// @brief Alias for the enum type relay_chn_cmd_enum.
typedef enum relay_chn_cmd_enum relay_chn_cmd_t;
/**
* @brief Structure to hold runtime information for a relay channel.
*/
typedef struct relay_chn_run_info_type {
relay_chn_cmd_t last_run_cmd; ///< The last run command issued on the relay channel; forward or reverse.
uint32_t last_run_cmd_time_ms; ///< The time in milliseconds when the last run command was issued.
} relay_chn_run_info_t;
/**
* @brief Structure to hold the output configuration of a relay channel.
*/
typedef struct relay_chn_output_type {
gpio_num_t forward_pin; ///< GPIO pin number for the forward direction.
gpio_num_t reverse_pin; ///< GPIO pin number for the reverse direction.
relay_chn_direction_t direction; ///< The current direction of the relay channel.
} relay_chn_output_t;
typedef struct relay_chn_type relay_chn_t; // Forward declaration
/**
* @brief Function pointer type for relay channel command execution functions.
* @param relay_chn Pointer to the relay channel to execute the command on.
*/
typedef void(*relay_chn_cmd_fn_t)(relay_chn_t*);
/**
* @brief Structure to hold the state and configuration of a relay channel.
*/
typedef struct relay_chn_type {
uint8_t id; ///< The ID of the relay channel.
relay_chn_state_t state; ///< The current state of the relay channel.
relay_chn_run_info_t run_info; ///< Runtime information of the relay channel.
relay_chn_output_t output; ///< Output configuration of the relay channel.
relay_chn_cmd_t pending_cmd; ///< The command that is pending to be issued
esp_timer_handle_t timer; ///< Timer to handle the opposite direction inertia time.
} relay_chn_t;
static relay_chn_t relay_channels[RELAY_CHN_COUNT];
static esp_event_loop_handle_t relay_chn_event_loop;
// Private function declarations
// Event handler for the relay channel command event
static void relay_chn_event_handler(void* handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data);
/**
* @brief Check if the provided channel ID is valid.
*
* @param chn_id Channel ID to check.
* @return true Channel ID is valid.
* @return false Channel ID is invalid.
*/
static bool relay_chn_is_channel_id_valid(uint8_t chn_id);
/**
* @brief Dispatches a relay channel command to the event loop.
*
* @param relay_chn The relay channel.
* @param cmd The command to dispatch.
*/
static void relay_chn_dispatch_cmd(relay_chn_t *relay_chn, relay_chn_cmd_t cmd);
/**
* @brief Returns the string representation of a relay channel command.
*
* @param cmd The relay channel command.
* @return char* The string representation of the command.
*/
static char *relay_chn_cmd_str(relay_chn_cmd_t cmd);
/**
* @brief Timer callback function for relay channel direction change inertia.
*
* This function is called when the opposite direction inertia timer expires. It checks if the channel
* has a pending command and dispatches it if there is one.
*
* @param arg The channel ID of the relay channel.
*/
static void relay_chn_timer_cb(void* arg)
{
uint8_t chn_id = *(uint8_t*) arg;
if (!relay_chn_is_channel_id_valid(chn_id)) {
ESP_LOGE(TAG, "relay_chn_timer_cb: Invalid relay channel ID!");
return;
}
relay_chn_t* relay_chn = &relay_channels[chn_id];
// Does channel have a pending command?
if (relay_chn->pending_cmd != RELAY_CHN_CMD_NONE) {
relay_chn_dispatch_cmd(relay_chn, relay_chn->pending_cmd);
relay_chn->pending_cmd = RELAY_CHN_CMD_NONE;
}
else {
ESP_LOGE(TAG, "relay_chn_timer_cb: No pending cmd for relay channel %d!", chn_id);
}
}
static esp_err_t relay_chn_init_timer(relay_chn_t *relay_chn)
{
char timer_name[32];
snprintf(timer_name, sizeof(timer_name), "relay_chn_%d_timer", relay_chn->id);
esp_timer_create_args_t timer_args = {
.callback = relay_chn_timer_cb,
.arg = &relay_chn->id,
.name = timer_name
};
return esp_timer_create(&timer_args, &relay_chn->timer);
}
/**
* @brief Check if the provided GPIO pin number is valid for the current device.
*
* @param gpio The GPIO pin number to check.
* @return true GPIO pin number is valid.
* @return false GPIO pin number is invalid.
*/
static bool relay_chn_is_gpio_valid(gpio_num_t gpio)
{
return gpio >= 0 && gpio < GPIO_PIN_COUNT;
}
static esp_err_t relay_chn_create_event_loop()
{
esp_event_loop_args_t loop_args = {
.queue_size = 10,
.task_name = "relay_chn_event_loop",
.task_priority = ESP_TASKD_EVENT_PRIO - 1,
.task_stack_size = 2048,
.task_core_id = tskNO_AFFINITY
};
esp_err_t ret = esp_event_loop_create(&loop_args, &relay_chn_event_loop);
ret |= esp_event_handler_register_with(relay_chn_event_loop,
RELAY_CHN_CMD_EVENT,
ESP_EVENT_ANY_ID,
relay_chn_event_handler, NULL);
return ret;
}
esp_err_t relay_chn_create(const gpio_num_t* gpio_map, uint8_t gpio_count)
{
// Check if the device's GPIOs are enough for the number of channels
if (RELAY_CHN_COUNT > (GPIO_PIN_COUNT / 2)) {
ESP_LOGE(TAG, "Not enough GPIOs for the number of channels!");
ESP_LOGE(TAG, "Max available num of channels: %d, requested channels: %d", GPIO_PIN_COUNT / 2, RELAY_CHN_COUNT);
return ESP_ERR_INVALID_ARG;
}
// Check if the provided GPIOs correspond to the number of channels
if (gpio_count != RELAY_CHN_COUNT * 2) {
ESP_LOGE(TAG, "Invalid number of GPIOs provided: %d", gpio_count);
ESP_LOGE(TAG, "Expected number of GPIOs: %d", RELAY_CHN_COUNT * 2);
return ESP_ERR_INVALID_ARG;
}
esp_err_t ret;
for (int i = 0; i < RELAY_CHN_COUNT; i++) {
gpio_num_t forward_pin = gpio_map[i];
gpio_num_t reverse_pin = gpio_map[i+1];
// Check if the GPIOs are valid
if (!relay_chn_is_gpio_valid(forward_pin)) {
ESP_LOGE(TAG, "Invalid GPIO pin number: %d", forward_pin);
return ESP_ERR_INVALID_ARG;
}
if (!relay_chn_is_gpio_valid(reverse_pin)) {
ESP_LOGE(TAG, "Invalid GPIO pin number: %d", reverse_pin);
return ESP_ERR_INVALID_ARG;
}
// Check if the GPIOs are valid
// Initialize the GPIOs
ret = gpio_reset_pin(forward_pin);
ret |= gpio_set_direction(forward_pin, GPIO_MODE_OUTPUT);
ret |= gpio_reset_pin(reverse_pin);
ret |= gpio_set_direction(reverse_pin, GPIO_MODE_OUTPUT);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize GPIOs relay channel %d!", i);
return ret;
}
// Initialize the GPIOs
// Initialize the relay channel
relay_chn_t* relay_chn = &relay_channels[i];
relay_chn->id = i;
relay_chn->output.forward_pin = forward_pin;
relay_chn->output.reverse_pin = reverse_pin;
relay_chn->output.direction = RELAY_CHN_DIRECTION_DEFAULT;
relay_chn->state = RELAY_CHN_STATE_STOPPED;
relay_chn->pending_cmd = RELAY_CHN_CMD_NONE;
relay_chn->run_info.last_run_cmd = RELAY_CHN_CMD_NONE;
ret |= relay_chn_init_timer(relay_chn);// Create direction change inertia timer
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize relay channel %d!", i);
return ret;
}
}
// Create relay channel command event loop
ret |= relay_chn_create_event_loop();
return ret;
}
/**
* @brief Check channel ID validity
*
* @param chn_id Channel ID to check
* @return true If channel is valid
* @return false If channel is invalid
*/
static bool relay_chn_is_channel_id_valid(uint8_t chn_id)
{
bool valid = chn_id >= 0 && chn_id < RELAY_CHN_COUNT;
if (!valid) {
ESP_LOGE(TAG, "Invalid channel ID: %d", chn_id);
}
return valid;
}
// Dispatch relay channel command to its event loop
static void relay_chn_dispatch_cmd(relay_chn_t *relay_chn, relay_chn_cmd_t cmd) {
if (cmd == RELAY_CHN_CMD_NONE) {
return;
}
esp_event_post_to(relay_chn_event_loop,
RELAY_CHN_CMD_EVENT,
cmd,
&relay_chn->id,
sizeof(relay_chn->id), portMAX_DELAY);
}
static esp_err_t relay_chn_invalidate_timer(relay_chn_t *relay_chn)
{
if (esp_timer_is_active(relay_chn->timer)) {
return esp_timer_stop(relay_chn->timer);
}
return ESP_OK;
}
static esp_err_t relay_chn_start_timer(relay_chn_t *relay_chn, uint32_t time_ms)
{
// Invalidate the channel's timer if it is active
relay_chn_invalidate_timer(relay_chn);
return esp_timer_start_once(relay_chn->timer, time_ms * 1000);
}
/**
* @brief The command issuer function.
*
* This function is the deciding logic for issuing a command to a relay channel. It evaluates
* the current state of the channel before issuing the command. Then it decides whether to run
* the command immediately or wait for the opposite inertia time.
*
* The STOP command is an exception, it is always run immediately since it is safe in any case.
*
* Another special consideration is the FLIP command. If the channel is running, the FLIP command
* is issued after the channel is stopped. If the channel is stopped, the FLIP command is issued
* immediately.
*
* @param relay_chn The relay channel to issue the command to.
* @param cmd The command to issue.
*/
static void relay_chn_issue_cmd(relay_chn_t* relay_chn, relay_chn_cmd_t cmd)
{
if (cmd == RELAY_CHN_CMD_NONE) {
return;
}
if (cmd == RELAY_CHN_CMD_STOP) {
if (relay_chn->state == RELAY_CHN_STATE_STOPPED) {
return; // Do nothing if already stopped
}
// If the command is STOP, issue it immediately
relay_chn_dispatch_cmd(relay_chn, cmd);
return;
}
// Evaluate the channel's next move depending on its status
switch (relay_chn->state)
{
case RELAY_CHN_STATE_FREE:
// If the channel is free, run the command immediately
relay_chn_dispatch_cmd(relay_chn, cmd);
break;
case RELAY_CHN_STATE_FORWARD_PENDING:
case RELAY_CHN_STATE_REVERSE_PENDING:
// The channel is already waiting for the opposite inertia time,
// so do nothing unless the command is STOP
if (cmd == RELAY_CHN_CMD_STOP) {
relay_chn_dispatch_cmd(relay_chn, cmd);
}
break;
case RELAY_CHN_STATE_STOPPED:
if (relay_chn->run_info.last_run_cmd == cmd) {
// If the last run command is the same as the current command, run the command immediately
relay_chn_dispatch_cmd(relay_chn, cmd);
}
else {
// If the last run command is different from the current command, calculate the time passed
// since the last run command stopped and decide whether to run the command immediately or wait
uint32_t inertia_time_passed_ms = (uint32_t) (esp_timer_get_time() / 1000) - relay_chn->run_info.last_run_cmd_time_ms;
uint32_t inertia_time_ms = RELAY_CHN_OPPOSITE_INERTIA_MS - inertia_time_passed_ms;
if (inertia_time_ms > 0) {
relay_chn->pending_cmd = cmd;
relay_chn->state = cmd == RELAY_CHN_CMD_FORWARD
? RELAY_CHN_STATE_FORWARD_PENDING
: RELAY_CHN_STATE_REVERSE_PENDING;
// If the time passed is less than the opposite inertia time, wait for the remaining time
relay_chn_start_timer(relay_chn, inertia_time_ms);
}
else {
// If the time passed is more than the opposite inertia time, run the command immediately
relay_chn_dispatch_cmd(relay_chn, cmd);
}
}
break;
case RELAY_CHN_STATE_FORWARD:
case RELAY_CHN_STATE_REVERSE:
if (cmd == RELAY_CHN_CMD_FLIP) {
// If the command is FLIP, stop the running channel first, then issue the FLIP command
relay_chn_dispatch_cmd(relay_chn, RELAY_CHN_CMD_STOP);
relay_chn_dispatch_cmd(relay_chn, cmd);
return;
}
if (relay_chn->run_info.last_run_cmd == cmd) {
// If the last run command is the same as the current command, do nothing
return;
}
// If the last run command is different from the current command, wait for the opposite inertia time
relay_chn->pending_cmd = cmd;
relay_chn->state = cmd == RELAY_CHN_CMD_FORWARD ? RELAY_CHN_STATE_FORWARD_PENDING : RELAY_CHN_STATE_REVERSE_PENDING;
relay_chn_start_timer(relay_chn, RELAY_CHN_OPPOSITE_INERTIA_MS);
break;
default: ESP_LOGD(TAG, "relay_chn_evaluate: Unknown relay channel state!");
}
}
/* relay_chn APIs */
relay_chn_state_t relay_chn_get_state(uint8_t chn_id)
{
if (!relay_chn_is_channel_id_valid(chn_id)) {
return RELAY_CHN_STATE_STOPPED;
}
return relay_channels[chn_id].state;
}
char *relay_chn_get_state_str(uint8_t chn_id)
{
if (!relay_chn_is_channel_id_valid(chn_id)) {
return "INVALID";
}
switch (relay_channels[chn_id].state) {
case RELAY_CHN_STATE_FREE:
return "FREE";
case RELAY_CHN_STATE_STOPPED:
return "STOPPED";
case RELAY_CHN_STATE_FORWARD:
return "FORWARD";
case RELAY_CHN_STATE_REVERSE:
return "REVERSE";
case RELAY_CHN_STATE_FORWARD_PENDING:
return "FORWARD_PENDING";
case RELAY_CHN_STATE_REVERSE_PENDING:
return "REVERSE_PENDING";
default:
return "UNKNOWN";
}
}
void relay_chn_run_forward(uint8_t chn_id)
{
if (!relay_chn_is_channel_id_valid(chn_id)) return;
relay_chn_t* relay_chn = &relay_channels[chn_id];
relay_chn_issue_cmd(relay_chn, RELAY_CHN_CMD_FORWARD);
}
void relay_chn_run_reverse(uint8_t chn_id)
{
if (!relay_chn_is_channel_id_valid(chn_id)) return;
relay_chn_t* relay_chn = &relay_channels[chn_id];
relay_chn_issue_cmd(relay_chn, RELAY_CHN_CMD_REVERSE);
}
void relay_chn_stop(uint8_t chn_id)
{
if (!relay_chn_is_channel_id_valid(chn_id)) return;
relay_chn_t* relay_chn = &relay_channels[chn_id];
relay_chn_issue_cmd(relay_chn, RELAY_CHN_CMD_STOP);
}
void relay_chn_flip_direction(uint8_t chn_id)
{
if (!relay_chn_is_channel_id_valid(chn_id)) return;
relay_chn_t* relay_chn = &relay_channels[chn_id];
relay_chn_issue_cmd(relay_chn, RELAY_CHN_CMD_FLIP);
}
relay_chn_direction_t relay_chn_get_direction(uint8_t chn_id)
{
if (!relay_chn_is_channel_id_valid(chn_id)) {
return RELAY_CHN_DIRECTION_DEFAULT;
}
return relay_channels[chn_id].output.direction;
}
/* relay_chn APIs */
static void relay_chn_execute_stop(relay_chn_t *relay_chn)
{
gpio_set_level(relay_chn->output.forward_pin, 0);
gpio_set_level(relay_chn->output.reverse_pin, 0);
relay_chn->state = RELAY_CHN_STATE_STOPPED;
// If there is any pending command, cancel it since the STOP command is issued right after it
relay_chn->pending_cmd = RELAY_CHN_CMD_NONE;
// Invalidate the channel's timer if it is active
relay_chn_invalidate_timer(relay_chn);
// If the channel was running, schedule a free command for the channel
relay_chn_cmd_t last_run_cmd = relay_chn->run_info.last_run_cmd;
if (last_run_cmd == RELAY_CHN_CMD_FORWARD || last_run_cmd == RELAY_CHN_CMD_REVERSE) {
// Record the command's last run time
relay_chn->run_info.last_run_cmd_time_ms = esp_timer_get_time() / 1000;
// Schedule a free command for the channel
relay_chn->pending_cmd = RELAY_CHN_CMD_FREE;
relay_chn_start_timer(relay_chn, RELAY_CHN_OPPOSITE_INERTIA_MS);
} else {
// If the channel was not running, issue a free command immediately
relay_chn_dispatch_cmd(relay_chn, RELAY_CHN_CMD_FREE);
}
}
static void relay_chn_execute_forward(relay_chn_t *relay_chn)
{
gpio_set_level(relay_chn->output.reverse_pin, 0);
gpio_set_level(relay_chn->output.forward_pin, 1);
relay_chn->state = RELAY_CHN_STATE_FORWARD;
relay_chn->run_info.last_run_cmd = RELAY_CHN_CMD_FORWARD;
}
static void relay_chn_execute_reverse(relay_chn_t *relay_chn)
{
gpio_set_level(relay_chn->output.forward_pin, 0);
gpio_set_level(relay_chn->output.reverse_pin, 1);
relay_chn->state = RELAY_CHN_STATE_REVERSE;
relay_chn->run_info.last_run_cmd = RELAY_CHN_CMD_REVERSE;
}
static void relay_chn_execute_flip(relay_chn_t *relay_chn)
{
// Flip the output GPIO pins
gpio_num_t temp = relay_chn->output.forward_pin;
relay_chn->output.forward_pin = relay_chn->output.reverse_pin;
relay_chn->output.reverse_pin = temp;
// Flip the direction
relay_chn->output.direction = (relay_chn->output.direction == RELAY_CHN_DIRECTION_DEFAULT)
? RELAY_CHN_DIRECTION_FLIPPED
: RELAY_CHN_DIRECTION_DEFAULT;
// Set an inertia on the channel to prevent any immediate movement
relay_chn->pending_cmd = RELAY_CHN_CMD_FREE;
relay_chn_start_timer(relay_chn, RELAY_CHN_OPPOSITE_INERTIA_MS);
}
void relay_chn_execute_free(relay_chn_t *relay_chn)
{
relay_chn->state = RELAY_CHN_STATE_FREE;
relay_chn->pending_cmd = RELAY_CHN_CMD_NONE;
// Invalidate the channel's timer if it is active
relay_chn_invalidate_timer(relay_chn);
}
static void relay_chn_event_handler(void* handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
{
uint8_t chn_id = *(uint8_t*) event_data;
if (!relay_chn_is_channel_id_valid(chn_id)) {
return;
}
relay_chn_t* relay_chn = &relay_channels[chn_id];
ESP_LOGD(TAG, "relay_chn_event_handler: Channel %d, Command: %s", relay_chn->id, relay_chn_cmd_str(event_id));
switch (event_id) {
case RELAY_CHN_CMD_STOP:
relay_chn_execute_stop(relay_chn);
break;
case RELAY_CHN_CMD_FORWARD:
relay_chn_execute_forward(relay_chn);
break;
case RELAY_CHN_CMD_REVERSE:
relay_chn_execute_reverse(relay_chn);
break;
case RELAY_CHN_CMD_FLIP:
relay_chn_execute_flip(relay_chn);
break;
case RELAY_CHN_CMD_FREE:
relay_chn_execute_free(relay_chn);
break;
default:
ESP_LOGD(TAG, "Unknown relay channel command!");
}
}
static char *relay_chn_cmd_str(relay_chn_cmd_t cmd)
{
switch (cmd) {
case RELAY_CHN_CMD_STOP:
return "STOP";
case RELAY_CHN_CMD_FORWARD:
return "FORWARD";
case RELAY_CHN_CMD_REVERSE:
return "REVERSE";
case RELAY_CHN_CMD_FLIP:
return "FLIP";
case RELAY_CHN_CMD_FREE:
return "FREE";
default:
return "UNKNOWN";
}
}
/// @}

8
test/CMakeLists.txt Normal file
View File

@@ -0,0 +1,8 @@
# 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)

3
test/main/CMakeLists.txt Normal file
View File

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

View File

@@ -0,0 +1,75 @@
#include "unity.h"
#include "relay_chn.h"
const gpip_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();
}

View File

@@ -0,0 +1,21 @@
#include <stdio.h>
#include <string.h>
#include "unity.h"
#include "unity_test_runner.h"
static void print_banner(const char*);
void app_main(void) {
print_banner("Starting interactive test menu");
/* This function will not return, and will be busy waiting for UART input.
* Make sure that task watchdog is disabled if you use this function.
*/
unity_run_menu();
}
static void print_banner(const char* text)
{
printf("\n##### %s #####\n\n", text);
}

5
test/sdkconfig.defaults Normal file
View File

@@ -0,0 +1,5 @@
# For IDF 5.0
CONFIG_ESP_TASK_WDT_EN=n
# For IDF4.4
CONFIG_ESP_TASK_WDT=n