Add source files.
This commit is contained in:
4
CMakeLists.txt
Normal file
4
CMakeLists.txt
Normal 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
20
Kconfig
Normal 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
7
idf_component.yml
Normal 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
162
include/relay_chn.h
Normal 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
580
relay_chn.c
Normal 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
8
test/CMakeLists.txt
Normal 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
3
test/main/CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS_DIRS "."
|
||||
PRIV_INCLUDE_DIRS "."
|
||||
PRIV_REQUIRES unity test_utils relay_chn)
|
||||
75
test/main/relay_chn_test.c
Normal file
75
test/main/relay_chn_test.c
Normal 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();
|
||||
}
|
||||
21
test/main/relay_chn_test_main.c
Normal file
21
test/main/relay_chn_test_main.c
Normal 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
5
test/sdkconfig.defaults
Normal file
@@ -0,0 +1,5 @@
|
||||
# For IDF 5.0
|
||||
CONFIG_ESP_TASK_WDT_EN=n
|
||||
|
||||
# For IDF4.4
|
||||
CONFIG_ESP_TASK_WDT=n
|
||||
Reference in New Issue
Block a user