release-1.0.0 #39
21
.vscode/settings.json
vendored
21
.vscode/settings.json
vendored
@@ -1,6 +1,23 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"relay_chn.h": "c"
|
||||
"relay_chn.h": "c",
|
||||
"stdlib.h": "c",
|
||||
"cstdint": "c",
|
||||
"relay_chn_run_info.h": "c",
|
||||
"esp_err.h": "c",
|
||||
"relay_chn_output.h": "c",
|
||||
"relay_chn_core.h": "c",
|
||||
"relay_chn_ctl.h": "c",
|
||||
"relay_chn_tilt.h": "c",
|
||||
"relay_chn_defs.h": "c",
|
||||
"esp_check.h": "c",
|
||||
"esp_event_base.h": "c",
|
||||
"esp_event.h": "c",
|
||||
"queue.h": "c",
|
||||
"relay_chn_priv_types.h": "c",
|
||||
"relay_chn_adapter.h": "c",
|
||||
"relay_chn_types.h": "c"
|
||||
},
|
||||
"idf.port": "/dev/ttyUSB0"
|
||||
"idf.port": "/dev/ttyUSB0",
|
||||
"idf.pythonInstallPath": "/usr/bin/python"
|
||||
}
|
||||
186
include/relay_chn_adapter.h
Normal file
186
include/relay_chn_adapter.h
Normal file
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* An adapter header to expose the appropriate API functions to the public API
|
||||
* depending on the RELAY_CHN_COUNT value which determines single or multi mode.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if RELAY_CHN_COUNT > 1
|
||||
/**
|
||||
* @brief Get the current state of a relay channel.
|
||||
*
|
||||
* @param[in] chn_id Channel ID to get state for.
|
||||
* @return Current state of the specified channel, or RELAY_CHN_STATE_UNDEFINED if invalid.
|
||||
*/
|
||||
extern relay_chn_state_t relay_chn_ctl_get_state(uint8_t chn_id);
|
||||
|
||||
/**
|
||||
* @brief Get string representation of a relay channel's state.
|
||||
*
|
||||
* @param[in] chn_id Channel ID to get state string for.
|
||||
* @return String representation of channel state, or "UNDEFINED" if invalid.
|
||||
*/
|
||||
extern char *relay_chn_ctl_get_state_str(uint8_t chn_id);
|
||||
|
||||
/**
|
||||
* @brief Run a relay channel in forward direction.
|
||||
*
|
||||
* @param[in] chn_id Channel ID to run forward, or RELAY_CHN_ID_ALL for all channels.
|
||||
*/
|
||||
extern void relay_chn_ctl_run_forward(uint8_t chn_id);
|
||||
|
||||
/**
|
||||
* @brief Run a relay channel in reverse direction.
|
||||
*
|
||||
* @param[in] chn_id Channel ID to run reverse, or RELAY_CHN_ID_ALL for all channels.
|
||||
*/
|
||||
extern void relay_chn_ctl_run_reverse(uint8_t chn_id);
|
||||
|
||||
/**
|
||||
* @brief Stop a relay channel.
|
||||
*
|
||||
* @param[in] chn_id Channel ID to stop, or RELAY_CHN_ID_ALL for all channels.
|
||||
*/
|
||||
extern void relay_chn_ctl_stop(uint8_t chn_id);
|
||||
|
||||
/**
|
||||
* @brief Flip the running direction of a relay channel.
|
||||
*
|
||||
* @param[in] chn_id Channel ID to flip direction for, or RELAY_CHN_ID_ALL for all channels.
|
||||
*/
|
||||
extern void relay_chn_ctl_flip_direction(uint8_t chn_id);
|
||||
|
||||
/**
|
||||
* @brief Get the current direction of a relay channel.
|
||||
*
|
||||
* @param[in] chn_id Channel ID to get direction for.
|
||||
* @return Current direction of the specified channel, or RELAY_CHN_DIRECTION_DEFAULT if invalid.
|
||||
*/
|
||||
extern relay_chn_direction_t relay_chn_ctl_get_direction(uint8_t chn_id);
|
||||
|
||||
static inline relay_chn_state_t relay_chn_get_state(uint8_t chn_id)
|
||||
{
|
||||
return relay_chn_ctl_get_state(chn_id);
|
||||
}
|
||||
|
||||
static inline char *relay_chn_get_state_str(uint8_t chn_id)
|
||||
{
|
||||
return relay_chn_ctl_get_state_str(chn_id);
|
||||
}
|
||||
|
||||
static inline void relay_chn_run_forward(uint8_t chn_id)
|
||||
{
|
||||
relay_chn_ctl_run_forward(chn_id);
|
||||
}
|
||||
|
||||
static inline void relay_chn_run_reverse(uint8_t chn_id)
|
||||
{
|
||||
relay_chn_ctl_run_reverse(chn_id);
|
||||
}
|
||||
|
||||
static inline void relay_chn_stop(uint8_t chn_id)
|
||||
{
|
||||
relay_chn_ctl_stop(chn_id);
|
||||
}
|
||||
|
||||
static inline void relay_chn_flip_direction(uint8_t chn_id)
|
||||
{
|
||||
relay_chn_ctl_flip_direction(chn_id);
|
||||
}
|
||||
|
||||
static inline relay_chn_direction_t relay_chn_get_direction(uint8_t chn_id)
|
||||
{
|
||||
return relay_chn_ctl_get_direction(chn_id);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
/**
|
||||
* @brief Get the current state of the relay channel.
|
||||
*
|
||||
* @return Current state of the channel.
|
||||
*/
|
||||
extern relay_chn_state_t relay_chn_ctl_get_state(void);
|
||||
|
||||
/**
|
||||
* @brief Get string representation of the relay channel's state.
|
||||
*
|
||||
* @return String representation of channel state.
|
||||
*/
|
||||
extern char *relay_chn_ctl_get_state_str(void);
|
||||
|
||||
/**
|
||||
* @brief Run the relay channel in forward direction.
|
||||
*/
|
||||
extern void relay_chn_ctl_run_forward(void);
|
||||
|
||||
/**
|
||||
* @brief Run the relay channel in reverse direction.
|
||||
*/
|
||||
extern void relay_chn_ctl_run_reverse(void);
|
||||
|
||||
/**
|
||||
* @brief Stop the relay channel.
|
||||
*/
|
||||
extern void relay_chn_ctl_stop(void);
|
||||
|
||||
/**
|
||||
* @brief Flip the running direction of the relay channel.
|
||||
*/
|
||||
extern void relay_chn_ctl_flip_direction(void);
|
||||
|
||||
/**
|
||||
* @brief Get the current direction of the relay channel.
|
||||
*
|
||||
* @return Current direction of the channel.
|
||||
*/
|
||||
extern relay_chn_direction_t relay_chn_ctl_get_direction(void);
|
||||
|
||||
static inline relay_chn_state_t relay_chn_get_state(void)
|
||||
{
|
||||
return relay_chn_ctl_get_state();
|
||||
}
|
||||
|
||||
static inline char *relay_chn_get_state_str(void)
|
||||
{
|
||||
return relay_chn_ctl_get_state_str();
|
||||
}
|
||||
|
||||
static inline void relay_chn_run_forward(void)
|
||||
{
|
||||
relay_chn_ctl_run_forward();
|
||||
}
|
||||
|
||||
static inline void relay_chn_run_reverse(void)
|
||||
{
|
||||
relay_chn_ctl_run_reverse();
|
||||
}
|
||||
|
||||
static inline void relay_chn_stop(void)
|
||||
{
|
||||
relay_chn_ctl_stop();
|
||||
}
|
||||
|
||||
static inline void relay_chn_flip_direction(void)
|
||||
{
|
||||
relay_chn_ctl_flip_direction();
|
||||
}
|
||||
|
||||
static inline relay_chn_direction_t relay_chn_get_direction(void)
|
||||
{
|
||||
return relay_chn_ctl_get_direction();
|
||||
}
|
||||
|
||||
#endif // RELAY_CHN_COUNT > 1
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
24
include/relay_chn_defs.h
Normal file
24
include/relay_chn_defs.h
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Config defines for covenient writing */
|
||||
#define RELAY_CHN_OPPOSITE_INERTIA_MS CONFIG_RELAY_CHN_OPPOSITE_INERTIA_MS
|
||||
#define RELAY_CHN_COUNT CONFIG_RELAY_CHN_COUNT
|
||||
#define RELAY_CHN_ENABLE_TILTING CONFIG_RELAY_CHN_ENABLE_TILTING
|
||||
|
||||
#if RELAY_CHN_COUNT > 1
|
||||
#define RELAY_CHN_ID_ALL RELAY_CHN_COUNT /*!< Special ID to address all channels */
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
58
include/relay_chn_types.h
Normal file
58
include/relay_chn_types.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "relay_chn_defs.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Enumeration for relay channel direction.
|
||||
*/
|
||||
typedef 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 */
|
||||
} relay_chn_direction_t;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Enums that represent the state of a relay channel.
|
||||
*/
|
||||
typedef enum relay_chn_state_enum {
|
||||
RELAY_CHN_STATE_UNDEFINED, /*!< The relay channel state is undefined */
|
||||
RELAY_CHN_STATE_IDLE, /*!< 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 */
|
||||
#if CONFIG_RELAY_CHN_ENABLE_TILTING == 1
|
||||
RELAY_CHN_STATE_TILT_FORWARD, /*!< The relay channel is tilting for forward */
|
||||
RELAY_CHN_STATE_TILT_REVERSE, /*!< The relay channel is tilting for reverse */
|
||||
#endif
|
||||
} relay_chn_state_t;
|
||||
|
||||
/**
|
||||
* @brief Relay channel state change listener.
|
||||
*
|
||||
* An optional interface to listen to the channel state change events.
|
||||
* The listeners SHOULD be implemented as light functions and SHOULD NOT contain
|
||||
* any blocking calls. Otherwise the relay_chn module would not function properly
|
||||
* since it is designed as event driven.
|
||||
*
|
||||
* @param chn_id The ID of the channel whose state has changed.
|
||||
* @param old_state The old state of the channel.
|
||||
* @param new_state The new state of the channel.
|
||||
*/
|
||||
typedef void (*relay_chn_state_listener_t)(uint8_t chn_id, relay_chn_state_t old_state, relay_chn_state_t new_state);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
114
private_include/relay_chn_core.h
Normal file
114
private_include/relay_chn_core.h
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_event_base.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_timer.h"
|
||||
#include "relay_chn_defs.h"
|
||||
#include "relay_chn_types.h"
|
||||
#include "relay_chn_priv_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/// Event base used by *_core, *_ctl, and *_tilt modules.
|
||||
ESP_EVENT_DECLARE_BASE(RELAY_CHN_CMD_EVENT);
|
||||
|
||||
/**
|
||||
* @brief Initializes the relay channel timer.
|
||||
*
|
||||
* This function creates a timer for the relay channel to handle direction change inertia.
|
||||
* Required by *_ctl_* module.
|
||||
*
|
||||
* @param chn_ctl Pointer to the relay channel control structure.
|
||||
* @return esp_err_t ESP_OK on success, or an error code on failure.
|
||||
*/
|
||||
esp_err_t relay_chn_init_timer(relay_chn_ctl_t *chn_ctl);
|
||||
|
||||
/**
|
||||
* @brief Issues a command to the relay channel.
|
||||
*
|
||||
* Evaluates the current state of the relay channel and issues the command accordingly.
|
||||
* Required by *_core, *_ctl_* and *_tilt modules.
|
||||
*
|
||||
* @param chn_ctl Pointer to the relay channel control structure.
|
||||
* @param cmd The command to issue.
|
||||
*/
|
||||
void relay_chn_issue_cmd(relay_chn_ctl_t* chn_ctl, relay_chn_cmd_t cmd);
|
||||
|
||||
/**
|
||||
* @brief Dispatches a relay channel command to the event loop.
|
||||
*
|
||||
* @param chn_ctl Pointer to the relay channel control structure.
|
||||
* @param cmd The command to dispatch.
|
||||
*/
|
||||
void relay_chn_dispatch_cmd(relay_chn_ctl_t *chn_ctl, 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.
|
||||
*/
|
||||
char *relay_chn_cmd_str(relay_chn_cmd_t cmd);
|
||||
|
||||
/**
|
||||
* @brief Starts the ESP timer once with the specified time in milliseconds.
|
||||
*
|
||||
* Starts the ESP timer to run once after the specified time.
|
||||
* If the timer is already running, it stops it first and then starts it again.
|
||||
* Required by *_ctl_* and *_tilt modules.
|
||||
*
|
||||
* @param esp_timer The ESP timer handle.
|
||||
* @param time_ms The time in milliseconds to wait before the timer expires.
|
||||
* @return esp_err_t ESP_OK on success, or an error code on failure.
|
||||
*/
|
||||
esp_err_t relay_chn_start_esp_timer_once(esp_timer_handle_t esp_timer, uint32_t time_ms);
|
||||
|
||||
/**
|
||||
* @brief Updates the state of the relay channel and notifies listeners.
|
||||
*
|
||||
* This function updates the state of the relay channel and notifies all registered listeners
|
||||
* about the state change.
|
||||
* Required by *_ctl_* and *_tilt modules.
|
||||
*
|
||||
* @param chn_ctl Pointer to the relay channel control structure.
|
||||
* @param new_state The new state to set for the relay channel.
|
||||
*/
|
||||
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 RELAY_CHN_COUNT > 1
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
bool relay_chn_is_channel_id_valid(uint8_t chn_id);
|
||||
#endif // RELAY_CHN_COUNT > 1
|
||||
|
||||
#if RELAY_CHN_ENABLE_TILTING == 1
|
||||
/// Relay channel event loop handle declaration for *_tilt module.
|
||||
extern esp_event_loop_handle_t relay_chn_event_loop;
|
||||
#endif // RELAY_CHN_ENABLE_TILTING
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
56
private_include/relay_chn_ctl.h
Normal file
56
private_include/relay_chn_ctl.h
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech developer@kozmotronik.com.tr
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Expose the *_ctl functions required by *_core.c file.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "relay_chn_priv_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Initialize the relay channel control.
|
||||
*
|
||||
* @param output Pointer to the output object(s).
|
||||
* @param run_info Pointer to the runtime information object(s).
|
||||
*
|
||||
* @return esp_err_t Returns ESP_OK on success, or an error code on failure.
|
||||
*/
|
||||
esp_err_t relay_chn_ctl_init(relay_chn_output_t *output, relay_chn_run_info_t *run_info);
|
||||
|
||||
/**
|
||||
* @brief Deinitialize the relay channel control.
|
||||
*
|
||||
* This function cleans up resources used by the relay channel control.
|
||||
*/
|
||||
void relay_chn_ctl_deinit(void);
|
||||
|
||||
#if RELAY_CHN_COUNT > 1
|
||||
/**
|
||||
* @brief Get the control structure for a specific relay channel.
|
||||
*
|
||||
* @param chn_id The ID of the relay channel to retrieve.
|
||||
*
|
||||
* @return relay_chn_ctl_t* Pointer to the control structure for the specified channel, or NULL if not found.
|
||||
*/
|
||||
relay_chn_ctl_t *relay_chn_ctl_get(uint8_t chn_id);
|
||||
|
||||
/**
|
||||
* @brief Get the control structures for all relay channels.
|
||||
*
|
||||
* @return relay_chn_ctl_t* Pointer to the array of control structures for all channels.
|
||||
*/
|
||||
relay_chn_ctl_t *relay_chn_ctl_get_all(void);
|
||||
#else
|
||||
relay_chn_ctl_t *relay_chn_ctl_get(void);
|
||||
#endif // RELAY_CHN_COUNT > 1
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
113
private_include/relay_chn_output.h
Normal file
113
private_include/relay_chn_output.h
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Abstraction layer for controlling relay channel outputs. This is the layer
|
||||
* that interacts with the GPIO pins to control the relay channels.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
#include "relay_chn_priv_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Initialize relay channel outputs.
|
||||
*
|
||||
* Maps relay channels to GPIO pins and prepares them for operation.
|
||||
*
|
||||
* @param[in] gpio_map Array of GPIO pin numbers for each relay channel.
|
||||
* @param[in] gpio_count Number of GPIO pins (relay channels).
|
||||
*
|
||||
* @return ESP_OK on success, error code otherwise.
|
||||
*/
|
||||
esp_err_t relay_chn_output_init(const uint8_t* gpio_map, uint8_t gpio_count);
|
||||
|
||||
/**
|
||||
* @brief Deinitialize relay channel outputs.
|
||||
*
|
||||
* Releases resources and resets GPIO pins used for relay channels.
|
||||
*/
|
||||
void relay_chn_output_deinit(void);
|
||||
|
||||
#if RELAY_CHN_COUNT > 1
|
||||
/**
|
||||
* @brief Get the relay channel output object for a specific channel.
|
||||
*
|
||||
* @param[in] chn_id Channel ID.
|
||||
*
|
||||
* @return Pointer to relay channel output object, or NULL if invalid.
|
||||
*/
|
||||
relay_chn_output_t *relay_chn_output_get(uint8_t chn_id);
|
||||
|
||||
/**
|
||||
* @brief Get all relay channel output objects.
|
||||
*
|
||||
* @return Pointer to array of relay channel output objects.
|
||||
*/
|
||||
relay_chn_output_t *relay_chn_output_get_all(void);
|
||||
#else
|
||||
/**
|
||||
* @brief Get the relay channel output object.
|
||||
*
|
||||
* @return Pointer to relay channel output object.
|
||||
*/
|
||||
relay_chn_output_t *relay_chn_output_get(void);
|
||||
#endif // RELAY_CHN_COUNT > 1
|
||||
|
||||
/**
|
||||
* @brief Stop the relay channel output.
|
||||
*
|
||||
* Sets the relay channel to the stop state.
|
||||
*
|
||||
* @param[in] output Pointer to relay channel output object.
|
||||
*
|
||||
* @return ESP_OK on success, error code otherwise.
|
||||
*/
|
||||
esp_err_t relay_chn_output_stop(relay_chn_output_t *output);
|
||||
|
||||
/**
|
||||
* @brief Set relay channel output to forward direction.
|
||||
*
|
||||
* @param[in] output Pointer to relay channel output object.
|
||||
*
|
||||
* @return ESP_OK on success, error code otherwise.
|
||||
*/
|
||||
esp_err_t relay_chn_output_forward(relay_chn_output_t *output);
|
||||
|
||||
/**
|
||||
* @brief Set relay channel output to reverse direction.
|
||||
*
|
||||
* @param[in] output Pointer to relay channel output object.
|
||||
*
|
||||
* @return ESP_OK on success, error code otherwise.
|
||||
*/
|
||||
esp_err_t relay_chn_output_reverse(relay_chn_output_t *output);
|
||||
|
||||
/**
|
||||
* @brief Flip the direction of the relay channel output.
|
||||
*
|
||||
* Changes the direction from forward to reverse or vice versa.
|
||||
*
|
||||
* @param[in] output Pointer to relay channel output object.
|
||||
*/
|
||||
void relay_chn_output_flip(relay_chn_output_t *output);
|
||||
|
||||
/**
|
||||
* @brief Get the current direction of the relay channel output.
|
||||
*
|
||||
* @param[in] output Pointer to relay channel output object.
|
||||
*
|
||||
* @return Current direction of the relay channel.
|
||||
*/
|
||||
relay_chn_direction_t relay_chn_output_get_direction(relay_chn_output_t *output);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
78
private_include/relay_chn_priv_types.h
Normal file
78
private_include/relay_chn_priv_types.h
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_timer.h"
|
||||
#include "relay_chn_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Enumeration for relay channel commands.
|
||||
*/
|
||||
typedef 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_IDLE /*!< Free the relay channel */
|
||||
} relay_chn_cmd_t;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Structure to hold the output configuration of a relay channel.
|
||||
*/
|
||||
typedef struct {
|
||||
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;
|
||||
|
||||
/**
|
||||
* @brief Structure to hold runtime information for a relay channel.
|
||||
*/
|
||||
typedef struct {
|
||||
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;
|
||||
|
||||
#if RELAY_CHN_ENABLE_TILTING == 1
|
||||
/// @brief Tilt commands.
|
||||
typedef enum {
|
||||
RELAY_CHN_TILT_CMD_NONE, /*!< No command */
|
||||
RELAY_CHN_TILT_CMD_STOP, /*!< Tilt command stop */
|
||||
RELAY_CHN_TILT_CMD_FORWARD, /*!< Tilt command for forward */
|
||||
RELAY_CHN_TILT_CMD_REVERSE /*!< Tilt command for reverse */
|
||||
} relay_chn_tilt_cmd_t;
|
||||
|
||||
/// Forward declaration for relay_chn_tilt_ctl
|
||||
typedef struct relay_chn_tilt_ctl relay_chn_tilt_ctl_t;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Structure to hold the state and configuration of a relay channel.
|
||||
*/
|
||||
typedef struct {
|
||||
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 inertia_timer; /*!< Timer to handle the opposite direction inertia time */
|
||||
#if RELAY_CHN_ENABLE_TILTING == 1
|
||||
relay_chn_tilt_ctl_t *tilt_ctl; /*!< Pointer to the tilt control structure if tilting is enabled */
|
||||
#endif
|
||||
} relay_chn_ctl_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
84
private_include/relay_chn_run_info.h
Normal file
84
private_include/relay_chn_run_info.h
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* This is for managing the run information of relay channels.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "relay_chn_priv_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Initialize relay channel run information.
|
||||
*
|
||||
* Initializes the run information for all relay channels with default values.
|
||||
*/
|
||||
void relay_chn_run_info_init(void);
|
||||
|
||||
#if RELAY_CHN_COUNT > 1
|
||||
/**
|
||||
* @brief Get run information object for a specific relay channel.
|
||||
*
|
||||
* @param[in] chn_id Channel ID to get run information for.
|
||||
* @return Pointer to run information structure, or NULL if channel ID is invalid.
|
||||
*/
|
||||
relay_chn_run_info_t *relay_chn_run_info_get(uint8_t chn_id);
|
||||
|
||||
/**
|
||||
* @brief Get run information objects for all relay channels.
|
||||
*
|
||||
* @return Pointer to array of run information structures.
|
||||
*/
|
||||
relay_chn_run_info_t *relay_chn_run_info_get_all(void);
|
||||
#else
|
||||
/**
|
||||
* @brief Get run information object for the single relay channel.
|
||||
*
|
||||
* @return Pointer to run information structure.
|
||||
*/
|
||||
relay_chn_run_info_t *relay_chn_run_info_get(void);
|
||||
#endif // RELAY_CHN_COUNT > 1
|
||||
|
||||
/**
|
||||
* @brief Get the last run command for a relay channel.
|
||||
*
|
||||
* @param[in] run_info Pointer to run information structure.
|
||||
*
|
||||
* @return Last command that was executed, or RELAY_CHN_CMD_NONE if invalid.
|
||||
*/
|
||||
relay_chn_cmd_t relay_chn_run_info_get_last_run_cmd(relay_chn_run_info_t *run_info);
|
||||
|
||||
/**
|
||||
* @brief Set the last run command for a relay channel.
|
||||
*
|
||||
* @param[in] run_info Pointer to run information structure.
|
||||
* @param[in] cmd Command to set as last run command.
|
||||
*/
|
||||
void relay_chn_run_info_set_last_run_cmd(relay_chn_run_info_t *run_info, relay_chn_cmd_t cmd);
|
||||
|
||||
/**
|
||||
* @brief Get the timestamp of the last run command.
|
||||
*
|
||||
* @param[in] run_info Pointer to run information structure.
|
||||
*
|
||||
* @return Timestamp in milliseconds of last command, or 0 if invalid.
|
||||
*/
|
||||
uint32_t relay_chn_run_info_get_last_run_cmd_time_ms(relay_chn_run_info_t *run_info);
|
||||
|
||||
/**
|
||||
* @brief Set the timestamp for the last run command.
|
||||
*
|
||||
* @param[in] run_info Pointer to run information structure.
|
||||
* @param[in] time_ms Timestamp in milliseconds to set.
|
||||
*/
|
||||
void relay_chn_run_info_set_last_run_cmd_time_ms(relay_chn_run_info_t *run_info, uint32_t time_ms);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
58
private_include/relay_chn_tilt.h
Normal file
58
private_include/relay_chn_tilt.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "relay_chn_priv_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Initialize relay channel tilt controls.
|
||||
*
|
||||
* Sets up tilt functionality for relay channels including timers and event handlers.
|
||||
* Must be called before using any other tilt functions.
|
||||
*
|
||||
* @param[in] chn_ctls Array of relay channel control structures.
|
||||
*
|
||||
* @return ESP_OK on success, error code otherwise.
|
||||
*/
|
||||
esp_err_t relay_chn_tilt_init(relay_chn_ctl_t *chn_ctls);
|
||||
|
||||
/**
|
||||
* @brief Deinitialize relay channel tilt controls.
|
||||
*
|
||||
* Cleans up tilt resources including timers and event handlers.
|
||||
* Should be called when tilt functionality is no longer needed.
|
||||
*/
|
||||
void relay_chn_tilt_deinit(void);
|
||||
|
||||
/**
|
||||
* @brief Dispatch a tilt command to a relay channel.
|
||||
*
|
||||
* Queues a tilt command for execution on the specified channel.
|
||||
*
|
||||
* @param[in] tilt_ctl Pointer to tilt control structure.
|
||||
* @param[in] cmd Tilt command to execute.
|
||||
*
|
||||
* @return ESP_OK on success, error code otherwise.
|
||||
*/
|
||||
esp_err_t relay_chn_tilt_dispatch_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_tilt_cmd_t cmd);
|
||||
|
||||
/**
|
||||
* @brief Reset tilt counters for a relay channel.
|
||||
*
|
||||
* Resets both forward and reverse tilt counters to zero.
|
||||
*
|
||||
* @param[in] tilt_ctl Pointer to tilt control structure.
|
||||
*/
|
||||
void relay_chn_tilt_reset_count(relay_chn_tilt_ctl_t *tilt_ctl);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
1470
src/relay_chn.c
1470
src/relay_chn.c
File diff suppressed because it is too large
Load Diff
574
src/relay_chn_core.c
Normal file
574
src/relay_chn_core.c
Normal file
@@ -0,0 +1,574 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "esp_check.h"
|
||||
#include "esp_task.h"
|
||||
#include "relay_chn_output.h"
|
||||
#include "relay_chn_run_info.h"
|
||||
#include "relay_chn_ctl.h"
|
||||
#if RELAY_CHN_ENABLE_TILTING == 1
|
||||
#include "relay_chn_tilt.h"
|
||||
#endif
|
||||
#include "relay_chn_core.h"
|
||||
|
||||
|
||||
static const char *TAG = "RELAY_CHN_CORE";
|
||||
|
||||
ESP_EVENT_DEFINE_BASE(RELAY_CHN_CMD_EVENT);
|
||||
|
||||
|
||||
// Structure to hold a listener entry in the linked list.
|
||||
typedef struct relay_chn_listener_entry_type {
|
||||
relay_chn_state_listener_t listener; /*!< The listener function pointer */
|
||||
ListItem_t list_item; /*!< FreeRTOS list item */
|
||||
} relay_chn_listener_entry_t;
|
||||
|
||||
// The list that holds references to the registered listeners.
|
||||
static List_t relay_chn_listener_list;
|
||||
|
||||
// Define the event loop for global access both for this module and tilt module.
|
||||
esp_event_loop_handle_t relay_chn_event_loop = NULL;
|
||||
|
||||
|
||||
// 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);
|
||||
|
||||
|
||||
// Timer callback function for relay channel direction change inertia.
|
||||
static void relay_chn_timer_cb(void* arg)
|
||||
{
|
||||
relay_chn_ctl_t* chn_ctl = (relay_chn_ctl_t*) arg;
|
||||
// Does channel have a pending command?
|
||||
if (chn_ctl->pending_cmd != RELAY_CHN_CMD_NONE) {
|
||||
relay_chn_dispatch_cmd(chn_ctl, chn_ctl->pending_cmd);
|
||||
chn_ctl->pending_cmd = RELAY_CHN_CMD_NONE;
|
||||
}
|
||||
else {
|
||||
ESP_LOGE(TAG, "relay_chn_timer_cb: No pending cmd for relay channel %d!", chn_ctl->id);
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_init_timer(relay_chn_ctl_t *chn_ctl)
|
||||
{
|
||||
char timer_name[32];
|
||||
snprintf(timer_name, sizeof(timer_name), "relay_chn_%d_timer", chn_ctl->id);
|
||||
esp_timer_create_args_t timer_args = {
|
||||
.callback = relay_chn_timer_cb,
|
||||
.arg = chn_ctl,
|
||||
.name = timer_name
|
||||
};
|
||||
return esp_timer_create(&timer_args, &chn_ctl->inertia_timer);
|
||||
}
|
||||
|
||||
static esp_err_t relay_chn_create_event_loop()
|
||||
{
|
||||
esp_event_loop_args_t loop_args = {
|
||||
.queue_size = RELAY_CHN_COUNT * 8,
|
||||
.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);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create event loop for relay channel");
|
||||
ret = esp_event_handler_register_with(relay_chn_event_loop,
|
||||
RELAY_CHN_CMD_EVENT,
|
||||
ESP_EVENT_ANY_ID,
|
||||
relay_chn_event_handler, NULL);
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_create(const uint8_t* gpio_map, uint8_t gpio_count)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(gpio_map != NULL, ESP_ERR_INVALID_ARG, TAG, "gpio_map cannot be NULL");
|
||||
|
||||
esp_err_t ret;
|
||||
// Initialize the output
|
||||
ret = relay_chn_output_init(gpio_map, gpio_count);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize relay channel outputs");
|
||||
|
||||
// Initialize the run info
|
||||
relay_chn_run_info_init();
|
||||
|
||||
#if RELAY_CHN_COUNT > 1
|
||||
relay_chn_output_t *outputs = relay_chn_output_get_all();
|
||||
relay_chn_run_info_t *run_infos = relay_chn_run_info_get_all();
|
||||
#else
|
||||
relay_chn_output_t *outputs = relay_chn_output_get();
|
||||
relay_chn_run_info_t *run_infos = relay_chn_run_info_get();
|
||||
#endif
|
||||
|
||||
// Initialize the relay channel controls
|
||||
ret = relay_chn_ctl_init(outputs, run_infos);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize relay channel control");
|
||||
|
||||
// Create relay channel command event loop
|
||||
ret = relay_chn_create_event_loop();
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create relay channel event loop");
|
||||
|
||||
#if RELAY_CHN_ENABLE_TILTING == 1
|
||||
// Initialize the tilt feature
|
||||
#if RELAY_CHN_COUNT > 1
|
||||
relay_chn_ctl_t *chn_ctls = relay_chn_ctl_get_all();
|
||||
#else
|
||||
relay_chn_ctl_t *chn_ctls = relay_chn_ctl_get();
|
||||
#endif // RELAY_CHN_COUNT > 1
|
||||
ret = relay_chn_tilt_init(chn_ctls); // Initialize tilt feature
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize tilt feature");
|
||||
#endif
|
||||
|
||||
// Init the state listener list
|
||||
vListInitialise(&relay_chn_listener_list);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void relay_chn_destroy(void)
|
||||
{
|
||||
#if RELAY_CHN_ENABLE_TILTING == 1
|
||||
relay_chn_tilt_deinit();
|
||||
#endif
|
||||
relay_chn_ctl_deinit();
|
||||
relay_chn_output_deinit();
|
||||
|
||||
// Destroy the event loop
|
||||
esp_event_loop_delete(relay_chn_event_loop);
|
||||
relay_chn_event_loop = NULL;
|
||||
|
||||
// Free the listeners
|
||||
while (listCURRENT_LIST_LENGTH(&relay_chn_listener_list) > 0) {
|
||||
ListItem_t *pxItem = listGET_HEAD_ENTRY(&relay_chn_listener_list);
|
||||
relay_chn_listener_entry_t *entry = listGET_LIST_ITEM_OWNER(pxItem);
|
||||
uxListRemove(pxItem);
|
||||
free(entry);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Find a listener entry in the list by its function pointer.
|
||||
*
|
||||
* This function replaces the old index-based search and is used to check
|
||||
* for the existence of a listener before registration or for finding it
|
||||
* during unregistration.
|
||||
*
|
||||
* @param listener The listener function pointer to find.
|
||||
* @return Pointer to the listener entry if found, otherwise NULL.
|
||||
*/
|
||||
static relay_chn_listener_entry_t* find_listener_entry(relay_chn_state_listener_t listener)
|
||||
{
|
||||
// Iterate through the linked list of listeners
|
||||
for (ListItem_t *pxListItem = listGET_HEAD_ENTRY(&relay_chn_listener_list);
|
||||
pxListItem != listGET_END_MARKER(&relay_chn_listener_list);
|
||||
pxListItem = listGET_NEXT(pxListItem)) {
|
||||
|
||||
relay_chn_listener_entry_t *entry = (relay_chn_listener_entry_t *) listGET_LIST_ITEM_OWNER(pxListItem);
|
||||
if (entry->listener == listener) {
|
||||
// Found the listener, return the entry
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
// Listener was not found in the list
|
||||
return NULL;
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_register_listener(relay_chn_state_listener_t listener)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(listener, ESP_ERR_INVALID_ARG, TAG, "Listener cannot be NULL");
|
||||
|
||||
// Check for duplicates
|
||||
if (find_listener_entry(listener) != NULL) {
|
||||
ESP_LOGD(TAG, "Listener %p already registered", listener);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Allocate memory for the new listener entry
|
||||
relay_chn_listener_entry_t *entry = malloc(sizeof(relay_chn_listener_entry_t));
|
||||
ESP_RETURN_ON_FALSE(entry, ESP_ERR_NO_MEM, TAG, "Failed to allocate memory for listener");
|
||||
|
||||
// Initialize and insert the new listener
|
||||
entry->listener = listener;
|
||||
vListInitialiseItem(&(entry->list_item));
|
||||
listSET_LIST_ITEM_OWNER(&(entry->list_item), (void *)entry);
|
||||
vListInsertEnd(&relay_chn_listener_list, &(entry->list_item));
|
||||
|
||||
ESP_LOGD(TAG, "Registered listener %p", listener);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void relay_chn_unregister_listener(relay_chn_state_listener_t listener)
|
||||
{
|
||||
if (listener == NULL)
|
||||
{
|
||||
ESP_LOGD(TAG, "Cannot unregister a NULL listener.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the listener entry in the list
|
||||
relay_chn_listener_entry_t *entry = find_listener_entry(listener);
|
||||
|
||||
if (entry != NULL) {
|
||||
// Remove the item from the list and free the allocated memory
|
||||
uxListRemove(&(entry->list_item));
|
||||
free(entry);
|
||||
ESP_LOGD(TAG, "Unregistered listener %p", listener);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Listener %p not found for unregistration.", listener);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Dispatch relay channel command to its event loop
|
||||
void relay_chn_dispatch_cmd(relay_chn_ctl_t *chn_ctl, relay_chn_cmd_t cmd) {
|
||||
if (cmd == RELAY_CHN_CMD_NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Since the event_loop library creates a deep copy of the event data,
|
||||
// and we need to pass the pointer of the relevant channel, here we need
|
||||
// to pass the pointer to the pointer of the channel (&chn_ctl) so that
|
||||
// the pointer value is preserved in the event data.
|
||||
esp_event_post_to(relay_chn_event_loop,
|
||||
RELAY_CHN_CMD_EVENT,
|
||||
cmd,
|
||||
&chn_ctl,
|
||||
sizeof(chn_ctl),
|
||||
portMAX_DELAY);
|
||||
|
||||
#if RELAY_CHN_ENABLE_TILTING == 1
|
||||
// Reset the tilt counter when the command is either FORWARD or REVERSE
|
||||
if (cmd == RELAY_CHN_CMD_FORWARD || cmd == RELAY_CHN_CMD_REVERSE) {
|
||||
relay_chn_tilt_reset_count(chn_ctl->tilt_ctl);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_start_esp_timer_once(esp_timer_handle_t esp_timer, uint32_t time_ms)
|
||||
{
|
||||
esp_err_t ret = esp_timer_start_once(esp_timer, time_ms * 1000);
|
||||
if (ret == ESP_ERR_INVALID_STATE) {
|
||||
// This timer is already running, stop the timer first
|
||||
ret = esp_timer_stop(esp_timer);
|
||||
if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) {
|
||||
return ret;
|
||||
}
|
||||
ret = esp_timer_start_once(esp_timer, time_ms * 1000);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void relay_chn_update_state(relay_chn_ctl_t *chn_ctl, relay_chn_state_t new_state)
|
||||
{
|
||||
relay_chn_state_t old_state = chn_ctl->state;
|
||||
|
||||
// Only update and notify if the state has actually changed.
|
||||
if (old_state == new_state) {
|
||||
return;
|
||||
}
|
||||
|
||||
chn_ctl->state = new_state;
|
||||
|
||||
// Iterate through the linked list of listeners and notify them.
|
||||
for (ListItem_t *pxListItem = listGET_HEAD_ENTRY(&relay_chn_listener_list);
|
||||
pxListItem != listGET_END_MARKER(&relay_chn_listener_list);
|
||||
pxListItem = listGET_NEXT(pxListItem)) {
|
||||
relay_chn_listener_entry_t *entry = (relay_chn_listener_entry_t *) listGET_LIST_ITEM_OWNER(pxListItem);
|
||||
if (entry && entry->listener) {
|
||||
// Emit the state change to the listeners
|
||||
entry->listener(chn_ctl->id, old_state, new_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 chn_ctl The relay channel to issue the command to.
|
||||
* @param cmd The command to issue.
|
||||
*/
|
||||
void relay_chn_issue_cmd(relay_chn_ctl_t* chn_ctl, relay_chn_cmd_t cmd)
|
||||
{
|
||||
if (cmd == RELAY_CHN_CMD_NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == RELAY_CHN_CMD_STOP) {
|
||||
if (chn_ctl->state == RELAY_CHN_STATE_STOPPED) {
|
||||
return; // Do nothing if already stopped
|
||||
}
|
||||
// If the command is STOP, issue it immediately
|
||||
relay_chn_dispatch_cmd(chn_ctl, cmd);
|
||||
return;
|
||||
}
|
||||
|
||||
relay_chn_cmd_t last_run_cmd = relay_chn_run_info_get_last_run_cmd(chn_ctl->run_info);
|
||||
// Evaluate the channel's next move depending on its status
|
||||
switch (chn_ctl->state)
|
||||
{
|
||||
case RELAY_CHN_STATE_IDLE:
|
||||
// If the channel is idle, run the command immediately
|
||||
relay_chn_dispatch_cmd(chn_ctl, 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(chn_ctl, cmd);
|
||||
}
|
||||
break;
|
||||
|
||||
case RELAY_CHN_STATE_STOPPED:
|
||||
if (last_run_cmd == cmd || last_run_cmd == RELAY_CHN_CMD_NONE) {
|
||||
// Since the state is STOPPED, the inertia timer should be running and must be invalidated
|
||||
// with the pending FREE command
|
||||
esp_timer_stop(chn_ctl->inertia_timer);
|
||||
chn_ctl->pending_cmd = RELAY_CHN_CMD_NONE;
|
||||
|
||||
// If this is the first run or the last run command is the same as the current command,
|
||||
// run the command immediately
|
||||
relay_chn_dispatch_cmd(chn_ctl, 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 last_run_cmd_time_ms = relay_chn_run_info_get_last_run_cmd_time_ms(chn_ctl->run_info);
|
||||
uint32_t inertia_time_passed_ms = (uint32_t) (esp_timer_get_time() / 1000) - last_run_cmd_time_ms;
|
||||
uint32_t inertia_time_ms = RELAY_CHN_OPPOSITE_INERTIA_MS - inertia_time_passed_ms;
|
||||
if (inertia_time_ms > 0) {
|
||||
chn_ctl->pending_cmd = cmd;
|
||||
relay_chn_state_t new_state = cmd == RELAY_CHN_CMD_FORWARD
|
||||
? RELAY_CHN_STATE_FORWARD_PENDING : RELAY_CHN_STATE_REVERSE_PENDING;
|
||||
relay_chn_update_state(chn_ctl, new_state);
|
||||
// If the time passed is less than the opposite inertia time, wait for the remaining time
|
||||
relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, inertia_time_ms);
|
||||
}
|
||||
else {
|
||||
// If the time passed is more than the opposite inertia time, run the command immediately
|
||||
relay_chn_dispatch_cmd(chn_ctl, 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(chn_ctl, RELAY_CHN_CMD_STOP);
|
||||
relay_chn_dispatch_cmd(chn_ctl, cmd);
|
||||
return;
|
||||
}
|
||||
|
||||
if (last_run_cmd == cmd) {
|
||||
// If the last run command is the same as the current command, do nothing
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop the channel first before the schedule
|
||||
relay_chn_dispatch_cmd(chn_ctl, RELAY_CHN_CMD_STOP);
|
||||
|
||||
// If the last run command is different from the current command, wait for the opposite inertia time
|
||||
chn_ctl->pending_cmd = cmd;
|
||||
relay_chn_state_t new_state = cmd == RELAY_CHN_CMD_FORWARD
|
||||
? RELAY_CHN_STATE_FORWARD_PENDING : RELAY_CHN_STATE_REVERSE_PENDING;
|
||||
relay_chn_update_state(chn_ctl, new_state);
|
||||
relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, RELAY_CHN_OPPOSITE_INERTIA_MS);
|
||||
break;
|
||||
|
||||
#if RELAY_CHN_ENABLE_TILTING == 1
|
||||
case RELAY_CHN_STATE_TILT_FORWARD:
|
||||
// Terminate tilting first
|
||||
relay_chn_tilt_dispatch_cmd(chn_ctl->tilt_ctl, RELAY_CHN_TILT_CMD_STOP);
|
||||
if (cmd == RELAY_CHN_CMD_FORWARD) {
|
||||
// Schedule for running forward
|
||||
chn_ctl->pending_cmd = cmd;
|
||||
relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_FORWARD_PENDING);
|
||||
relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, RELAY_CHN_OPPOSITE_INERTIA_MS);
|
||||
} else if (cmd == RELAY_CHN_CMD_REVERSE) {
|
||||
// Run directly since it is the same direction
|
||||
relay_chn_dispatch_cmd(chn_ctl, cmd);
|
||||
relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_REVERSE);
|
||||
}
|
||||
break;
|
||||
case RELAY_CHN_STATE_TILT_REVERSE:
|
||||
// Terminate tilting first
|
||||
relay_chn_tilt_dispatch_cmd(chn_ctl->tilt_ctl, RELAY_CHN_TILT_CMD_STOP);
|
||||
if (cmd == RELAY_CHN_CMD_FORWARD) {
|
||||
// Run directly since it is the same direction
|
||||
relay_chn_dispatch_cmd(chn_ctl, cmd);
|
||||
relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_FORWARD);
|
||||
} else if (cmd == RELAY_CHN_CMD_REVERSE) {
|
||||
// Schedule for running reverse
|
||||
chn_ctl->pending_cmd = cmd;
|
||||
relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_REVERSE_PENDING);
|
||||
relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, RELAY_CHN_OPPOSITE_INERTIA_MS);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
default: ESP_LOGD(TAG, "relay_chn_evaluate: Unknown relay channel state!");
|
||||
}
|
||||
}
|
||||
|
||||
#if RELAY_CHN_COUNT > 1
|
||||
bool relay_chn_is_channel_id_valid(uint8_t chn_id)
|
||||
{
|
||||
bool valid = (chn_id < RELAY_CHN_COUNT) || chn_id == RELAY_CHN_ID_ALL;
|
||||
if (!valid) {
|
||||
ESP_LOGE(TAG, "Invalid channel ID: %d", chn_id);
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
#endif // RELAY_CHN_COUNT > 1
|
||||
|
||||
|
||||
static void relay_chn_execute_stop(relay_chn_ctl_t *chn_ctl)
|
||||
{
|
||||
if (relay_chn_output_stop(chn_ctl->output) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "relay_chn_execute_stop: Failed to output stop for relay channel #%d!", chn_ctl->id);
|
||||
}
|
||||
relay_chn_state_t previous_state = chn_ctl->state;
|
||||
relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_STOPPED);
|
||||
|
||||
// If there is any pending command, cancel it since the STOP command is issued right after it
|
||||
chn_ctl->pending_cmd = RELAY_CHN_CMD_NONE;
|
||||
// Invalidate the channel's timer if it is active
|
||||
esp_timer_stop(chn_ctl->inertia_timer);
|
||||
|
||||
// Save the last run time only if the previous state was either STATE FORWARD
|
||||
// or STATE_REVERSE. Then schedule a free command.
|
||||
if (previous_state == RELAY_CHN_STATE_FORWARD || previous_state == RELAY_CHN_STATE_REVERSE) {
|
||||
// Record the command's last run time
|
||||
relay_chn_run_info_set_last_run_cmd_time_ms(chn_ctl->run_info, (uint32_t)(esp_timer_get_time() / 1000));
|
||||
// Schedule a free command for the channel
|
||||
chn_ctl->pending_cmd = RELAY_CHN_CMD_IDLE;
|
||||
relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, RELAY_CHN_OPPOSITE_INERTIA_MS);
|
||||
} else {
|
||||
// If the channel was not running one of the run or fwd, issue a free command immediately
|
||||
relay_chn_dispatch_cmd(chn_ctl, RELAY_CHN_CMD_IDLE);
|
||||
}
|
||||
}
|
||||
|
||||
static void relay_chn_execute_forward(relay_chn_ctl_t *chn_ctl)
|
||||
{
|
||||
if (relay_chn_output_forward(chn_ctl->output) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "relay_chn_execute_forward: Failed to output forward for relay channel #%d!", chn_ctl->id);
|
||||
return;
|
||||
}
|
||||
relay_chn_run_info_set_last_run_cmd(chn_ctl->run_info, RELAY_CHN_CMD_FORWARD);
|
||||
relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_FORWARD);
|
||||
}
|
||||
|
||||
static void relay_chn_execute_reverse(relay_chn_ctl_t *chn_ctl)
|
||||
{
|
||||
if (relay_chn_output_reverse(chn_ctl->output) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "relay_chn_execute_reverse: Failed to output reverse for relay channel #%d!", chn_ctl->id);
|
||||
return;
|
||||
}
|
||||
relay_chn_run_info_set_last_run_cmd(chn_ctl->run_info, RELAY_CHN_CMD_REVERSE);
|
||||
relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_REVERSE);
|
||||
}
|
||||
|
||||
static void relay_chn_execute_flip(relay_chn_ctl_t *chn_ctl)
|
||||
{
|
||||
relay_chn_output_flip(chn_ctl->output);
|
||||
// Set an inertia on the channel to prevent any immediate movement
|
||||
chn_ctl->pending_cmd = RELAY_CHN_CMD_IDLE;
|
||||
relay_chn_start_esp_timer_once(chn_ctl->inertia_timer, RELAY_CHN_OPPOSITE_INERTIA_MS);
|
||||
}
|
||||
|
||||
void relay_chn_execute_idle(relay_chn_ctl_t *chn_ctl)
|
||||
{
|
||||
chn_ctl->pending_cmd = RELAY_CHN_CMD_NONE;
|
||||
// Invalidate the channel's timer if it is active
|
||||
esp_timer_stop(chn_ctl->inertia_timer);
|
||||
relay_chn_update_state(chn_ctl, RELAY_CHN_STATE_IDLE);
|
||||
}
|
||||
|
||||
static void relay_chn_event_handler(void* handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
|
||||
{
|
||||
relay_chn_ctl_t* chn_ctl = *(relay_chn_ctl_t**) event_data;
|
||||
ESP_RETURN_VOID_ON_FALSE(chn_ctl != NULL, TAG, "event_data is NULL");
|
||||
ESP_LOGD(TAG, "relay_chn_event_handler: Command: %s", relay_chn_cmd_str(event_id));
|
||||
|
||||
switch (event_id) {
|
||||
case RELAY_CHN_CMD_STOP:
|
||||
relay_chn_execute_stop(chn_ctl);
|
||||
break;
|
||||
case RELAY_CHN_CMD_FORWARD:
|
||||
relay_chn_execute_forward(chn_ctl);
|
||||
break;
|
||||
case RELAY_CHN_CMD_REVERSE:
|
||||
relay_chn_execute_reverse(chn_ctl);
|
||||
break;
|
||||
case RELAY_CHN_CMD_FLIP:
|
||||
relay_chn_execute_flip(chn_ctl);
|
||||
break;
|
||||
case RELAY_CHN_CMD_IDLE:
|
||||
relay_chn_execute_idle(chn_ctl);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGD(TAG, "Unknown relay channel command!");
|
||||
}
|
||||
}
|
||||
|
||||
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_IDLE:
|
||||
return "IDLE";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
char *relay_chn_state_str(relay_chn_state_t state)
|
||||
{
|
||||
switch (state) {
|
||||
case RELAY_CHN_STATE_IDLE:
|
||||
return "IDLE";
|
||||
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";
|
||||
#if RELAY_CHN_ENABLE_TILTING == 1
|
||||
case RELAY_CHN_STATE_TILT_FORWARD:
|
||||
return "TILT_FORWARD";
|
||||
case RELAY_CHN_STATE_TILT_REVERSE:
|
||||
return "TILT_REVERSE";
|
||||
#endif
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
137
src/relay_chn_ctl_multi.c
Normal file
137
src/relay_chn_ctl_multi.c
Normal file
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "esp_check.h"
|
||||
#include "relay_chn_priv_types.h"
|
||||
#include "relay_chn_core.h"
|
||||
#include "relay_chn_ctl.h"
|
||||
#include "relay_chn_output.h"
|
||||
|
||||
static const char *TAG = "RELAY_CHN_CTL";
|
||||
|
||||
static relay_chn_ctl_t chn_ctls[RELAY_CHN_COUNT];
|
||||
|
||||
|
||||
esp_err_t relay_chn_ctl_init(relay_chn_output_t *outputs, relay_chn_run_info_t *run_infos)
|
||||
{
|
||||
// Initialize all relay channels
|
||||
esp_err_t ret;
|
||||
for (int i = 0; i < RELAY_CHN_COUNT; i++) {
|
||||
relay_chn_ctl_t* chn_ctl = &chn_ctls[i];
|
||||
relay_chn_output_t* output = &outputs[i];
|
||||
relay_chn_run_info_t* run_info = &run_infos[i];
|
||||
|
||||
chn_ctl->id = i;
|
||||
chn_ctl->state = RELAY_CHN_STATE_IDLE;
|
||||
chn_ctl->pending_cmd = RELAY_CHN_CMD_NONE;
|
||||
|
||||
chn_ctl->output = output;
|
||||
chn_ctl->run_info = run_info;
|
||||
ret = relay_chn_init_timer(chn_ctl); // Create direction change inertia timer
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to create relay channel timer for channel %d", i);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void relay_chn_ctl_deinit()
|
||||
{
|
||||
for (int i = 0; i < RELAY_CHN_COUNT; i++) {
|
||||
relay_chn_ctl_t* chn_ctl = &chn_ctls[i];
|
||||
if (chn_ctl->inertia_timer != NULL) {
|
||||
esp_timer_delete(chn_ctl->inertia_timer);
|
||||
chn_ctl->inertia_timer = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
relay_chn_state_t relay_chn_ctl_get_state(uint8_t chn_id)
|
||||
{
|
||||
if (!relay_chn_is_channel_id_valid(chn_id) || chn_id == RELAY_CHN_ID_ALL) {
|
||||
return RELAY_CHN_STATE_UNDEFINED;
|
||||
}
|
||||
return chn_ctls[chn_id].state;
|
||||
}
|
||||
|
||||
char *relay_chn_ctl_get_state_str(uint8_t chn_id)
|
||||
{
|
||||
if (!relay_chn_is_channel_id_valid(chn_id) || chn_id == RELAY_CHN_ID_ALL) {
|
||||
return relay_chn_state_str(RELAY_CHN_STATE_UNDEFINED);
|
||||
}
|
||||
return relay_chn_state_str(chn_ctls[chn_id].state);
|
||||
}
|
||||
|
||||
static void relay_chn_ctl_issue_cmd_on_all_channels(relay_chn_cmd_t cmd)
|
||||
{
|
||||
for (int i = 0; i < RELAY_CHN_COUNT; i++) {
|
||||
relay_chn_issue_cmd(&chn_ctls[i], cmd);
|
||||
}
|
||||
}
|
||||
|
||||
void relay_chn_ctl_run_forward(uint8_t chn_id)
|
||||
{
|
||||
if (!relay_chn_is_channel_id_valid(chn_id)) return;
|
||||
|
||||
if (chn_id == RELAY_CHN_ID_ALL) {
|
||||
relay_chn_ctl_issue_cmd_on_all_channels(RELAY_CHN_CMD_FORWARD);
|
||||
return;
|
||||
}
|
||||
relay_chn_issue_cmd(&chn_ctls[chn_id], RELAY_CHN_CMD_FORWARD);
|
||||
}
|
||||
|
||||
void relay_chn_ctl_run_reverse(uint8_t chn_id)
|
||||
{
|
||||
if (!relay_chn_is_channel_id_valid(chn_id)) return;
|
||||
|
||||
if (chn_id == RELAY_CHN_ID_ALL) {
|
||||
relay_chn_ctl_issue_cmd_on_all_channels(RELAY_CHN_CMD_REVERSE);
|
||||
return;
|
||||
}
|
||||
relay_chn_issue_cmd(&chn_ctls[chn_id], RELAY_CHN_CMD_REVERSE);
|
||||
}
|
||||
|
||||
void relay_chn_ctl_stop(uint8_t chn_id)
|
||||
{
|
||||
if (!relay_chn_is_channel_id_valid(chn_id)) return;
|
||||
|
||||
if (chn_id == RELAY_CHN_ID_ALL) {
|
||||
relay_chn_ctl_issue_cmd_on_all_channels(RELAY_CHN_CMD_STOP);
|
||||
return;
|
||||
}
|
||||
relay_chn_issue_cmd(&chn_ctls[chn_id], RELAY_CHN_CMD_STOP);
|
||||
}
|
||||
|
||||
void relay_chn_ctl_flip_direction(uint8_t chn_id)
|
||||
{
|
||||
if (!relay_chn_is_channel_id_valid(chn_id)) return;
|
||||
|
||||
if (chn_id == RELAY_CHN_ID_ALL) {
|
||||
relay_chn_ctl_issue_cmd_on_all_channels(RELAY_CHN_CMD_FLIP);
|
||||
return;
|
||||
}
|
||||
relay_chn_issue_cmd(&chn_ctls[chn_id], RELAY_CHN_CMD_FLIP);
|
||||
}
|
||||
|
||||
relay_chn_direction_t relay_chn_ctl_get_direction(uint8_t chn_id)
|
||||
{
|
||||
if (!relay_chn_is_channel_id_valid(chn_id)) {
|
||||
return RELAY_CHN_DIRECTION_DEFAULT;
|
||||
}
|
||||
relay_chn_ctl_t *chn_ctl = &chn_ctls[chn_id];
|
||||
return relay_chn_output_get_direction(chn_ctl->output);
|
||||
}
|
||||
|
||||
relay_chn_ctl_t *relay_chn_ctl_get(uint8_t chn_id)
|
||||
{
|
||||
if (!relay_chn_is_channel_id_valid(chn_id)) {
|
||||
return NULL;
|
||||
}
|
||||
return &chn_ctls[chn_id];
|
||||
}
|
||||
|
||||
relay_chn_ctl_t *relay_chn_ctl_get_all(void)
|
||||
{
|
||||
return chn_ctls;
|
||||
}
|
||||
76
src/relay_chn_ctl_single.c
Normal file
76
src/relay_chn_ctl_single.c
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "relay_chn_priv_types.h"
|
||||
#include "relay_chn_core.h"
|
||||
#include "relay_chn_ctl.h"
|
||||
#include "relay_chn_output.h"
|
||||
|
||||
|
||||
static relay_chn_ctl_t chn_ctl;
|
||||
|
||||
|
||||
esp_err_t relay_chn_ctl_init(relay_chn_output_t *output, relay_chn_run_info_t *run_info)
|
||||
{
|
||||
// Initialize the relay channel
|
||||
chn_ctl.id = 0; // Single channel, so ID is 0
|
||||
chn_ctl.state = RELAY_CHN_STATE_IDLE;
|
||||
chn_ctl.pending_cmd = RELAY_CHN_CMD_NONE;
|
||||
chn_ctl.output = output;
|
||||
chn_ctl.run_info = run_info;
|
||||
return relay_chn_init_timer(&chn_ctl); // Create direction change inertia timer
|
||||
}
|
||||
|
||||
|
||||
void relay_chn_ctl_deinit()
|
||||
{
|
||||
if (chn_ctl.inertia_timer != NULL) {
|
||||
esp_timer_delete(chn_ctl.inertia_timer);
|
||||
chn_ctl.inertia_timer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* relay_chn APIs */
|
||||
relay_chn_state_t relay_chn_ctl_get_state()
|
||||
{
|
||||
return chn_ctl.state;
|
||||
}
|
||||
|
||||
char *relay_chn_ctl_get_state_str()
|
||||
{
|
||||
return relay_chn_state_str(chn_ctl.state);
|
||||
}
|
||||
|
||||
void relay_chn_ctl_run_forward()
|
||||
{
|
||||
relay_chn_issue_cmd(&chn_ctl, RELAY_CHN_CMD_FORWARD);
|
||||
}
|
||||
|
||||
void relay_chn_ctl_run_reverse()
|
||||
{
|
||||
relay_chn_issue_cmd(&chn_ctl, RELAY_CHN_CMD_REVERSE);
|
||||
}
|
||||
|
||||
void relay_chn_ctl_stop()
|
||||
{
|
||||
relay_chn_issue_cmd(&chn_ctl, RELAY_CHN_CMD_STOP);
|
||||
}
|
||||
|
||||
void relay_chn_ctl_flip_direction()
|
||||
{
|
||||
relay_chn_issue_cmd(&chn_ctl, RELAY_CHN_CMD_FLIP);
|
||||
}
|
||||
|
||||
relay_chn_direction_t relay_chn_ctl_get_direction()
|
||||
{
|
||||
return relay_chn_output_get_direction(chn_ctl.output);
|
||||
}
|
||||
/* relay_chn APIs */
|
||||
|
||||
relay_chn_ctl_t *relay_chn_ctl_get()
|
||||
{
|
||||
return &chn_ctl;
|
||||
}
|
||||
168
src/relay_chn_output.c
Normal file
168
src/relay_chn_output.c
Normal file
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "esp_check.h"
|
||||
#include "esp_log.h"
|
||||
#include "relay_chn_defs.h"
|
||||
#include "relay_chn_output.h"
|
||||
#include "relay_chn_core.h"
|
||||
|
||||
|
||||
static const char *TAG = "RELAY_CHN_OUTPUT";
|
||||
|
||||
#if RELAY_CHN_COUNT > 1
|
||||
static relay_chn_output_t outputs[RELAY_CHN_COUNT];
|
||||
#else
|
||||
static relay_chn_output_t output;
|
||||
#endif
|
||||
|
||||
|
||||
static esp_err_t relay_chn_output_check_gpio_capabilities(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;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t relay_chn_output_ctl_init(relay_chn_output_t *output, gpio_num_t forward_pin, gpio_num_t reverse_pin)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(GPIO_IS_VALID_OUTPUT_GPIO(forward_pin), ESP_ERR_INVALID_ARG, TAG,
|
||||
"Invalid GPIO pin number for forward_pin: %d", forward_pin);
|
||||
ESP_RETURN_ON_FALSE(GPIO_IS_VALID_OUTPUT_GPIO(reverse_pin), ESP_ERR_INVALID_ARG, TAG,
|
||||
"Invalid GPIO pin number for reverse_pin: %d", reverse_pin);
|
||||
|
||||
// Check if the GPIOs are valid
|
||||
esp_err_t ret;
|
||||
// Initialize the GPIOs
|
||||
ret = gpio_reset_pin(forward_pin);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to reset GPIO forward pin: %d", forward_pin);
|
||||
ret = gpio_set_direction(forward_pin, GPIO_MODE_OUTPUT);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set GPIO direction for forward pin: %d", forward_pin);
|
||||
|
||||
ret = gpio_reset_pin(reverse_pin);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to reset GPIO reverse pin: %d", reverse_pin);
|
||||
ret = gpio_set_direction(reverse_pin, GPIO_MODE_OUTPUT);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set GPIO direction for reverse pin: %d", reverse_pin);
|
||||
// Initialize the GPIOs
|
||||
|
||||
// Initialize the relay channel output
|
||||
output->forward_pin = forward_pin;
|
||||
output->reverse_pin = reverse_pin;
|
||||
output->direction = RELAY_CHN_DIRECTION_DEFAULT;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_output_init(const uint8_t* gpio_map, uint8_t gpio_count)
|
||||
{
|
||||
esp_err_t ret;
|
||||
ret = relay_chn_output_check_gpio_capabilities(gpio_count);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Device does not support the provided GPIOs");
|
||||
|
||||
#if RELAY_CHN_COUNT > 1
|
||||
for (int i = 0; i < RELAY_CHN_COUNT; i++) {
|
||||
relay_chn_output_t* output = &outputs[i];
|
||||
int gpio_index = i << 1; // gpio_index = i * 2
|
||||
gpio_num_t forward_pin = (gpio_num_t) gpio_map[gpio_index];
|
||||
gpio_num_t reverse_pin = (gpio_num_t) gpio_map[gpio_index + 1];
|
||||
|
||||
ret = relay_chn_output_ctl_init(output, forward_pin, reverse_pin);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize relay channel %d", i);
|
||||
}
|
||||
#else
|
||||
ret = relay_chn_output_ctl_init(&output, gpio_map[0], gpio_map[1]);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to initialize relay channel");
|
||||
#endif
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void relay_chn_output_ctl_deinit(relay_chn_output_t *output)
|
||||
{
|
||||
gpio_reset_pin(output->forward_pin);
|
||||
gpio_reset_pin(output->reverse_pin);
|
||||
}
|
||||
|
||||
void relay_chn_output_deinit()
|
||||
{
|
||||
#if RELAY_CHN_COUNT > 1
|
||||
for (int i = 0; i < RELAY_CHN_COUNT; i++) {
|
||||
relay_chn_output_ctl_deinit(&outputs[i]);
|
||||
}
|
||||
#else
|
||||
relay_chn_output_ctl_deinit(&output);
|
||||
#endif // RELAY_CHN_COUNT > 1
|
||||
}
|
||||
|
||||
#if RELAY_CHN_COUNT > 1
|
||||
relay_chn_output_t *relay_chn_output_get(uint8_t chn_id)
|
||||
{
|
||||
if (!relay_chn_is_channel_id_valid(chn_id)) {
|
||||
return NULL;
|
||||
}
|
||||
return &outputs[chn_id];
|
||||
}
|
||||
|
||||
relay_chn_output_t *relay_chn_output_get_all(void)
|
||||
{
|
||||
return outputs;
|
||||
}
|
||||
#else
|
||||
relay_chn_output_t *relay_chn_output_get(void)
|
||||
{
|
||||
return &output;
|
||||
}
|
||||
#endif // RELAY_CHN_COUNT > 1
|
||||
|
||||
esp_err_t relay_chn_output_stop(relay_chn_output_t *output)
|
||||
{
|
||||
esp_err_t ret;
|
||||
ret = gpio_set_level(output->forward_pin, 0);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set forward pin to LOW");
|
||||
return gpio_set_level(output->reverse_pin, 0);
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_output_forward(relay_chn_output_t *output)
|
||||
{
|
||||
esp_err_t ret;
|
||||
ret = gpio_set_level(output->forward_pin, 1);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set forward pin to HIGH");
|
||||
return gpio_set_level(output->reverse_pin, 0);
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_output_reverse(relay_chn_output_t *output)
|
||||
{
|
||||
esp_err_t ret;
|
||||
ret = gpio_set_level(output->forward_pin, 0);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "Failed to set forward pin to LOW");
|
||||
return gpio_set_level(output->reverse_pin, 1);
|
||||
}
|
||||
|
||||
void relay_chn_output_flip(relay_chn_output_t *output)
|
||||
{
|
||||
// Flip the output GPIO pins
|
||||
gpio_num_t temp = output->forward_pin;
|
||||
output->forward_pin = output->reverse_pin;
|
||||
output->reverse_pin = temp;
|
||||
// Flip the direction
|
||||
output->direction = (output->direction == RELAY_CHN_DIRECTION_DEFAULT)
|
||||
? RELAY_CHN_DIRECTION_FLIPPED
|
||||
: RELAY_CHN_DIRECTION_DEFAULT;
|
||||
}
|
||||
|
||||
relay_chn_direction_t relay_chn_output_get_direction(relay_chn_output_t *output)
|
||||
{
|
||||
return output->direction;
|
||||
}
|
||||
74
src/relay_chn_run_info.c
Normal file
74
src/relay_chn_run_info.c
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "relay_chn_core.h"
|
||||
#include "relay_chn_run_info.h"
|
||||
|
||||
|
||||
#if RELAY_CHN_COUNT > 1
|
||||
static relay_chn_run_info_t run_infos[RELAY_CHN_COUNT];
|
||||
#else
|
||||
static relay_chn_run_info_t run_info;
|
||||
#endif
|
||||
|
||||
void relay_chn_run_info_init()
|
||||
{
|
||||
#if RELAY_CHN_COUNT > 1
|
||||
for (int i = 0; i < RELAY_CHN_COUNT; i++) {
|
||||
run_infos[i].last_run_cmd = RELAY_CHN_CMD_NONE;
|
||||
run_infos[i].last_run_cmd_time_ms = 0;
|
||||
}
|
||||
#else
|
||||
run_info.last_run_cmd = RELAY_CHN_CMD_NONE;
|
||||
run_info.last_run_cmd_time_ms = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if RELAY_CHN_COUNT > 1
|
||||
relay_chn_run_info_t *relay_chn_run_info_get(uint8_t chn_id)
|
||||
{
|
||||
if (!relay_chn_is_channel_id_valid(chn_id)) {
|
||||
return NULL;
|
||||
}
|
||||
return &run_infos[chn_id];
|
||||
}
|
||||
|
||||
relay_chn_run_info_t *relay_chn_run_info_get_all()
|
||||
{
|
||||
return run_infos;
|
||||
}
|
||||
#else
|
||||
relay_chn_run_info_t *relay_chn_run_info_get()
|
||||
{
|
||||
return &run_info;
|
||||
}
|
||||
#endif // RELAY_CHN_COUNT > 1
|
||||
|
||||
relay_chn_cmd_t relay_chn_run_info_get_last_run_cmd(relay_chn_run_info_t *run_info)
|
||||
{
|
||||
return run_info == NULL ? RELAY_CHN_CMD_NONE : run_info->last_run_cmd;
|
||||
}
|
||||
|
||||
void relay_chn_run_info_set_last_run_cmd(relay_chn_run_info_t *run_info, relay_chn_cmd_t cmd)
|
||||
{
|
||||
if (!run_info) {
|
||||
return;
|
||||
}
|
||||
run_info->last_run_cmd = cmd;
|
||||
}
|
||||
|
||||
uint32_t relay_chn_run_info_get_last_run_cmd_time_ms(relay_chn_run_info_t *run_info)
|
||||
{
|
||||
return run_info == NULL ? 0 : run_info->last_run_cmd_time_ms;
|
||||
}
|
||||
|
||||
void relay_chn_run_info_set_last_run_cmd_time_ms(relay_chn_run_info_t *run_info, uint32_t time_ms)
|
||||
{
|
||||
if (!run_info) {
|
||||
return;
|
||||
}
|
||||
run_info->last_run_cmd_time_ms = time_ms;
|
||||
}
|
||||
634
src/relay_chn_tilt.c
Normal file
634
src/relay_chn_tilt.c
Normal file
@@ -0,0 +1,634 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Kozmotronik Tech
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "esp_check.h"
|
||||
#include "relay_chn_core.h"
|
||||
#include "relay_chn_output.h"
|
||||
#include "relay_chn_run_info.h"
|
||||
#include "relay_chn_tilt.h"
|
||||
|
||||
|
||||
static const char *TAG = "RELAY_CHN_TILT";
|
||||
|
||||
/**@{*/
|
||||
/*
|
||||
* Tilt Pattern Timing Definitions
|
||||
*
|
||||
* The min and max timing definitions as well as the default timing definitions.
|
||||
* These definitions are used to define and adjust the tilt sensitivity.
|
||||
*/
|
||||
#define RELAY_CHN_TILT_RUN_MIN_MS 50
|
||||
#define RELAY_CHN_TILT_RUN_MAX_MS 10
|
||||
#define RELAY_CHN_TILT_PAUSE_MIN_MS 450
|
||||
#define RELAY_CHN_TILT_PAUSE_MAX_MS 90
|
||||
|
||||
#define RELAY_CHN_TILT_DEFAULT_RUN_MS 15
|
||||
#define RELAY_CHN_TILT_DEFAULT_PAUSE_MS 150
|
||||
|
||||
#define RELAY_CHN_TILT_DEFAULT_SENSITIVITY \
|
||||
( (RELAY_CHN_TILT_DEFAULT_RUN_MS - RELAY_CHN_TILT_RUN_MIN_MS) \
|
||||
* 100 / (RELAY_CHN_TILT_RUN_MAX_MS - RELAY_CHN_TILT_RUN_MIN_MS) )
|
||||
/**@}*/
|
||||
|
||||
ESP_EVENT_DEFINE_BASE(RELAY_CHN_TILT_CMD_EVENT_BASE);
|
||||
|
||||
|
||||
/// @brief Tilt steps.
|
||||
typedef enum {
|
||||
RELAY_CHN_TILT_STEP_NONE, /*!< No step */
|
||||
RELAY_CHN_TILT_STEP_PENDING, /*!< Pending step */
|
||||
RELAY_CHN_TILT_STEP_MOVE, /*!< Move step. Tilt is driving either for forward or reverse */
|
||||
RELAY_CHN_TILT_STEP_PAUSE /*!< Pause step. Tilt is paused */
|
||||
} relay_chn_tilt_step_t;
|
||||
|
||||
/// @brief Tilt timing structure to manage tilt pattern timing.
|
||||
typedef struct {
|
||||
uint8_t sensitivity; /*!< Tilt sensitivity in percentage (%) */
|
||||
uint32_t move_time_ms; /*!< Move time in milliseconds */
|
||||
uint32_t pause_time_ms; /*!< Pause time in milliseconds */
|
||||
} relay_chn_tilt_timing_t;
|
||||
|
||||
/// @brief Tilt counter structure to manage tilt count.
|
||||
typedef struct {
|
||||
uint32_t tilt_forward_count; /*!< Tilt forward count */
|
||||
uint32_t tilt_reverse_count; /*!< Tilt reverse count */
|
||||
} relay_chn_tilt_counter_t;
|
||||
|
||||
/// @brief Tilt control structure to manage tilt operations.
|
||||
typedef struct relay_chn_tilt_ctl {
|
||||
relay_chn_ctl_t *chn_ctl; /*!< The relay channel control structure */
|
||||
relay_chn_tilt_cmd_t cmd; /*!< The tilt command in process */
|
||||
relay_chn_tilt_step_t step; /*!< Current tilt step */
|
||||
relay_chn_tilt_timing_t tilt_timing; /*!< Tilt timing structure */
|
||||
relay_chn_tilt_counter_t tilt_counter; /*!< Tilt counter structure */
|
||||
esp_timer_handle_t tilt_timer; /*!< Tilt timer handle */
|
||||
} relay_chn_tilt_ctl_t;
|
||||
|
||||
|
||||
#if RELAY_CHN_COUNT > 1
|
||||
static relay_chn_tilt_ctl_t tilt_ctls[RELAY_CHN_COUNT];
|
||||
#else
|
||||
static relay_chn_tilt_ctl_t tilt_ctl;
|
||||
#endif
|
||||
|
||||
|
||||
esp_err_t relay_chn_tilt_dispatch_cmd(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_tilt_cmd_t cmd)
|
||||
{
|
||||
if (cmd == RELAY_CHN_TILT_CMD_NONE) return ESP_ERR_INVALID_ARG;
|
||||
|
||||
// Since the event_loop library creates a deep copy of the event data,
|
||||
// and we need to pass the pointer of the relevant tilt control, here we need
|
||||
// to pass the pointer to the pointer of the tilt_control (&tilt_ctl) so that
|
||||
// the pointer value is preserved in the event data.
|
||||
return esp_event_post_to(relay_chn_event_loop,
|
||||
RELAY_CHN_TILT_CMD_EVENT_BASE,
|
||||
cmd,
|
||||
&tilt_ctl,
|
||||
sizeof(tilt_ctl), portMAX_DELAY);
|
||||
}
|
||||
|
||||
// Returns the required timing before tilting depending on the last run.
|
||||
static uint32_t relay_chn_tilt_get_required_timing_before_tilting(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_tilt_cmd_t cmd)
|
||||
{
|
||||
relay_chn_cmd_t last_run_cmd = relay_chn_run_info_get_last_run_cmd(tilt_ctl->chn_ctl->run_info);
|
||||
if (cmd == RELAY_CHN_TILT_CMD_FORWARD && last_run_cmd == RELAY_CHN_CMD_REVERSE)
|
||||
return 0;
|
||||
else if (cmd == RELAY_CHN_TILT_CMD_REVERSE && last_run_cmd == RELAY_CHN_CMD_FORWARD)
|
||||
return 0;
|
||||
|
||||
uint32_t last_run_cmd_time_ms = relay_chn_run_info_get_last_run_cmd_time_ms(tilt_ctl->chn_ctl->run_info);
|
||||
uint32_t inertia_time_passed_ms = (uint32_t) (esp_timer_get_time() / 1000) - last_run_cmd_time_ms;
|
||||
return RELAY_CHN_OPPOSITE_INERTIA_MS - inertia_time_passed_ms;
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
if (relay_chn_run_info_get_last_run_cmd(tilt_ctl->chn_ctl->run_info) == RELAY_CHN_CMD_NONE) {
|
||||
// Do not tilt if the channel hasn't been run before
|
||||
ESP_LOGD(TAG, "relay_chn_tilt_issue_cmd: Tilt will not be executed since the channel hasn't been run yet");
|
||||
return;
|
||||
}
|
||||
|
||||
if (tilt_ctl->cmd == cmd) {
|
||||
ESP_LOGD(TAG, "relay_chn_tilt_issue_cmd: There is already a tilt command in progress!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the command that will be processed
|
||||
tilt_ctl->cmd = cmd;
|
||||
switch (tilt_ctl->chn_ctl->state) {
|
||||
case RELAY_CHN_STATE_IDLE:
|
||||
// Relay channel is free, tilt can be issued immediately
|
||||
relay_chn_tilt_dispatch_cmd(tilt_ctl, cmd);
|
||||
break;
|
||||
|
||||
case RELAY_CHN_STATE_FORWARD_PENDING:
|
||||
case RELAY_CHN_STATE_REVERSE_PENDING:
|
||||
// Issue a stop command first so that the timer and pending cmd get cleared
|
||||
relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP);
|
||||
// break not put intentionally
|
||||
case RELAY_CHN_STATE_STOPPED: {
|
||||
// Check if channel needs timing before tilting
|
||||
uint32_t req_timing_ms = relay_chn_tilt_get_required_timing_before_tilting(tilt_ctl, cmd);
|
||||
if (req_timing_ms == 0) {
|
||||
relay_chn_tilt_dispatch_cmd(tilt_ctl, cmd);
|
||||
} else {
|
||||
// Channel needs timing before running tilting action, schedule it
|
||||
tilt_ctl->step = RELAY_CHN_TILT_STEP_PENDING;
|
||||
relay_chn_start_esp_timer_once(tilt_ctl->tilt_timer, req_timing_ms);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case RELAY_CHN_STATE_FORWARD:
|
||||
if (cmd == RELAY_CHN_TILT_CMD_FORWARD) {
|
||||
// Stop the running channel first
|
||||
relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP);
|
||||
// Schedule for tilting
|
||||
tilt_ctl->step = RELAY_CHN_TILT_STEP_PENDING;
|
||||
relay_chn_start_esp_timer_once(tilt_ctl->tilt_timer, RELAY_CHN_OPPOSITE_INERTIA_MS);
|
||||
} else if (cmd == RELAY_CHN_TILT_CMD_REVERSE) {
|
||||
// Stop the running channel first
|
||||
relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP);
|
||||
// If the tilt cmd is TILT_REVERSE then dispatch it immediately
|
||||
relay_chn_tilt_dispatch_cmd(tilt_ctl, cmd);
|
||||
}
|
||||
break;
|
||||
|
||||
case RELAY_CHN_STATE_REVERSE:
|
||||
if (cmd == RELAY_CHN_TILT_CMD_REVERSE) {
|
||||
// Stop the running channel first
|
||||
relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP);
|
||||
// Schedule for tilting
|
||||
tilt_ctl->step = RELAY_CHN_TILT_STEP_PENDING;
|
||||
relay_chn_start_esp_timer_once(tilt_ctl->tilt_timer, RELAY_CHN_OPPOSITE_INERTIA_MS);
|
||||
} else if (cmd == RELAY_CHN_TILT_CMD_FORWARD) {
|
||||
// Stop the running channel first
|
||||
relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP);
|
||||
// If the tilt cmd is TILT_FORWARD then dispatch it immediately
|
||||
relay_chn_tilt_dispatch_cmd(tilt_ctl, cmd);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
ESP_LOGD(TAG, "relay_chn_tilt_issue_cmd: Unexpected relay channel state: %s!", relay_chn_state_str(tilt_ctl->chn_ctl->state));
|
||||
}
|
||||
}
|
||||
|
||||
static void relay_chn_tilt_issue_auto(relay_chn_tilt_ctl_t *tilt_ctl)
|
||||
{
|
||||
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 || tilt_ctl->chn_ctl->state == RELAY_CHN_STATE_FORWARD) {
|
||||
relay_chn_tilt_issue_cmd(tilt_ctl, RELAY_CHN_TILT_CMD_FORWARD);
|
||||
}
|
||||
else if (last_run_cmd == RELAY_CHN_CMD_REVERSE || tilt_ctl->chn_ctl->state == RELAY_CHN_STATE_REVERSE) {
|
||||
relay_chn_tilt_issue_cmd(tilt_ctl, RELAY_CHN_TILT_CMD_REVERSE);
|
||||
}
|
||||
}
|
||||
|
||||
#if RELAY_CHN_COUNT > 1
|
||||
void relay_chn_tilt_auto(uint8_t chn_id)
|
||||
{
|
||||
if (!relay_chn_is_channel_id_valid(chn_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Execute for all channels
|
||||
if (chn_id == RELAY_CHN_ID_ALL) {
|
||||
for (int i = 0; i < RELAY_CHN_COUNT; i++) {
|
||||
relay_chn_tilt_issue_auto(&tilt_ctls[i]);
|
||||
}
|
||||
}
|
||||
// Execute for a single channel
|
||||
else {
|
||||
relay_chn_tilt_ctl_t* tilt_ctl = &tilt_ctls[chn_id];
|
||||
relay_chn_tilt_issue_auto(tilt_ctl);
|
||||
}
|
||||
}
|
||||
|
||||
static void relay_chn_tilt_issue_cmd_on_all_channels(relay_chn_tilt_cmd_t cmd)
|
||||
{
|
||||
for (int i = 0; i < RELAY_CHN_COUNT; i++) {
|
||||
relay_chn_tilt_ctl_t* tilt_ctl = &tilt_ctls[i];
|
||||
relay_chn_tilt_issue_cmd(tilt_ctl, cmd);
|
||||
}
|
||||
}
|
||||
|
||||
void relay_chn_tilt_forward(uint8_t chn_id)
|
||||
{
|
||||
if (!relay_chn_is_channel_id_valid(chn_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (chn_id == RELAY_CHN_ID_ALL)
|
||||
relay_chn_tilt_issue_cmd_on_all_channels(RELAY_CHN_TILT_CMD_FORWARD);
|
||||
else {
|
||||
relay_chn_tilt_ctl_t* tilt_ctl = &tilt_ctls[chn_id];
|
||||
relay_chn_tilt_issue_cmd(tilt_ctl, RELAY_CHN_TILT_CMD_FORWARD);
|
||||
}
|
||||
}
|
||||
|
||||
void relay_chn_tilt_reverse(uint8_t chn_id)
|
||||
{
|
||||
if (!relay_chn_is_channel_id_valid(chn_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (chn_id == RELAY_CHN_ID_ALL)
|
||||
relay_chn_tilt_issue_cmd_on_all_channels(RELAY_CHN_TILT_CMD_REVERSE);
|
||||
else {
|
||||
relay_chn_tilt_ctl_t* tilt_ctl = &tilt_ctls[chn_id];
|
||||
relay_chn_tilt_issue_cmd(tilt_ctl, RELAY_CHN_TILT_CMD_REVERSE);
|
||||
}
|
||||
}
|
||||
|
||||
void relay_chn_tilt_stop(uint8_t chn_id)
|
||||
{
|
||||
if (!relay_chn_is_channel_id_valid(chn_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (chn_id == RELAY_CHN_ID_ALL) {
|
||||
for (int i = 0; i < RELAY_CHN_COUNT; i++) {
|
||||
relay_chn_tilt_dispatch_cmd(&tilt_ctls[i], RELAY_CHN_TILT_CMD_STOP);
|
||||
}
|
||||
}
|
||||
else {
|
||||
relay_chn_tilt_dispatch_cmd(&tilt_ctls[chn_id], RELAY_CHN_TILT_CMD_STOP);
|
||||
}
|
||||
}
|
||||
|
||||
#else // RELAY_CHN_COUNT > 1
|
||||
|
||||
void relay_chn_tilt_auto()
|
||||
{
|
||||
relay_chn_tilt_issue_auto(&tilt_ctl);
|
||||
}
|
||||
|
||||
void relay_chn_tilt_forward()
|
||||
{
|
||||
relay_chn_tilt_issue_cmd(&tilt_ctl, RELAY_CHN_TILT_CMD_FORWARD);
|
||||
}
|
||||
|
||||
void relay_chn_tilt_reverse()
|
||||
{
|
||||
relay_chn_tilt_issue_cmd(&tilt_ctl, RELAY_CHN_TILT_CMD_REVERSE);
|
||||
}
|
||||
|
||||
void relay_chn_tilt_stop()
|
||||
{
|
||||
relay_chn_tilt_dispatch_cmd(&tilt_ctl, RELAY_CHN_TILT_CMD_STOP);
|
||||
}
|
||||
#endif // RELAY_CHN_COUNT > 1
|
||||
|
||||
static void relay_chn_tilt_set_timing_values(relay_chn_tilt_timing_t *tilt_timing,
|
||||
uint8_t sensitivity,
|
||||
uint32_t run_time_ms,
|
||||
uint32_t pause_time_ms)
|
||||
{
|
||||
tilt_timing->sensitivity = sensitivity;
|
||||
tilt_timing->move_time_ms = run_time_ms;
|
||||
tilt_timing->pause_time_ms = pause_time_ms;
|
||||
}
|
||||
|
||||
static void _relay_chn_tilt_sensitivity_set(relay_chn_tilt_ctl_t *tilt_ctl, uint8_t sensitivity)
|
||||
{
|
||||
if (sensitivity >= 100) {
|
||||
relay_chn_tilt_set_timing_values(&tilt_ctl->tilt_timing,
|
||||
100,
|
||||
RELAY_CHN_TILT_RUN_MAX_MS,
|
||||
RELAY_CHN_TILT_PAUSE_MAX_MS);
|
||||
}
|
||||
else if (sensitivity == 0) {
|
||||
relay_chn_tilt_set_timing_values(&tilt_ctl->tilt_timing,
|
||||
0,
|
||||
RELAY_CHN_TILT_RUN_MIN_MS,
|
||||
RELAY_CHN_TILT_PAUSE_MIN_MS);
|
||||
}
|
||||
else {
|
||||
// Compute the new timing values from the sensitivity percent value by using linear interpolation
|
||||
uint32_t tilt_run_time_ms = 0, tilt_pause_time_ms = 0;
|
||||
tilt_run_time_ms = RELAY_CHN_TILT_RUN_MIN_MS + (sensitivity * (RELAY_CHN_TILT_RUN_MAX_MS - RELAY_CHN_TILT_RUN_MIN_MS) / 100);
|
||||
tilt_pause_time_ms = RELAY_CHN_TILT_PAUSE_MIN_MS + (sensitivity * (RELAY_CHN_TILT_PAUSE_MAX_MS - RELAY_CHN_TILT_PAUSE_MIN_MS) / 100);
|
||||
|
||||
relay_chn_tilt_set_timing_values(&tilt_ctl->tilt_timing,
|
||||
sensitivity,
|
||||
tilt_run_time_ms,
|
||||
tilt_pause_time_ms);
|
||||
}
|
||||
}
|
||||
|
||||
#if RELAY_CHN_COUNT > 1
|
||||
void relay_chn_tilt_set_sensitivity(uint8_t chn_id, uint8_t sensitivity)
|
||||
{
|
||||
if (!relay_chn_is_channel_id_valid(chn_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (chn_id == RELAY_CHN_ID_ALL) {
|
||||
for (int i = 0; i < RELAY_CHN_COUNT; i++) {
|
||||
_relay_chn_tilt_sensitivity_set(&tilt_ctls[i], sensitivity);
|
||||
}
|
||||
}
|
||||
else {
|
||||
_relay_chn_tilt_sensitivity_set(&tilt_ctls[chn_id], sensitivity);
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_tilt_get_sensitivity(uint8_t chn_id, uint8_t *sensitivity, size_t length)
|
||||
{
|
||||
if (!relay_chn_is_channel_id_valid(chn_id)) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (sensitivity == NULL) {
|
||||
ESP_LOGD(TAG, "relay_chn_tilt_get_sensitivity: sensitivity is NULL");
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (chn_id == RELAY_CHN_ID_ALL) {
|
||||
if (length < RELAY_CHN_COUNT) {
|
||||
ESP_LOGD(TAG, "relay_chn_tilt_get_sensitivity: length is too short to store all sensitivity values");
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
for (int i = 0; i < RELAY_CHN_COUNT; i++) {
|
||||
sensitivity[i] = tilt_ctls[i].tilt_timing.sensitivity;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
*sensitivity = tilt_ctls[chn_id].tilt_timing.sensitivity;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void relay_chn_tilt_set_sensitivity(uint8_t sensitivity)
|
||||
{
|
||||
_relay_chn_tilt_sensitivity_set(&tilt_ctl, sensitivity);
|
||||
}
|
||||
|
||||
uint8_t relay_chn_tilt_get_sensitivity()
|
||||
{
|
||||
return tilt_ctl.tilt_timing.sensitivity;
|
||||
}
|
||||
#endif // RELAY_CHN_COUNT > 1
|
||||
|
||||
void relay_chn_tilt_reset_count(relay_chn_tilt_ctl_t *tilt_ctl)
|
||||
{
|
||||
tilt_ctl->tilt_counter.tilt_forward_count = 0;
|
||||
tilt_ctl->tilt_counter.tilt_reverse_count = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update tilt count automatically and return the current value.
|
||||
*
|
||||
* This helper function updates the relevant tilt count depending on the
|
||||
* last run info and helps the tilt module in deciding whether the requested
|
||||
* tilt should execute or not.
|
||||
* This is useful to control reverse tilting particularly. For example:
|
||||
* - If the channel's last run was FORWARD and a TILT_FORWARD is requested,
|
||||
* then the tilt counter will count up on the
|
||||
* relay_chn_tilt_counter_type::tilt_forward_count and the function will
|
||||
* return the actual count.
|
||||
* - If the channel's last run was FORWARD and a TILT_REVERSE is requested,
|
||||
* then the relay_chn_tilt_counter_type::tilt_forward_count will be checked
|
||||
* against zero first, and then it will count down and return the actual count
|
||||
* if it is greater than 0, else the function will return 0.
|
||||
* - If the tilt command is irrelevant then the function will return 0.
|
||||
* - If the last run is irrelevant then the function will return 0.
|
||||
*
|
||||
* @param tilt_ctl The relay channel handle.
|
||||
* @return uint32_t The actual value of the relevant counter.
|
||||
* @return 0 if:
|
||||
* - related counter is already 0.
|
||||
* - tilt command is irrelevant.
|
||||
* - last run info is irrelevant.
|
||||
*/
|
||||
static uint32_t relay_chn_tilt_count_update(relay_chn_tilt_ctl_t *tilt_ctl)
|
||||
{
|
||||
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) {
|
||||
if (tilt_ctl->cmd == RELAY_CHN_TILT_CMD_FORWARD) {
|
||||
return ++tilt_ctl->tilt_counter.tilt_forward_count;
|
||||
}
|
||||
else if (tilt_ctl->cmd == RELAY_CHN_TILT_CMD_REVERSE) {
|
||||
if (tilt_ctl->tilt_counter.tilt_forward_count > 0) {
|
||||
--tilt_ctl->tilt_counter.tilt_forward_count;
|
||||
// Still should do one more move, return non-zero value
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
relay_chn_tilt_reset_count(tilt_ctl);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else if (last_run_cmd == RELAY_CHN_CMD_REVERSE) {
|
||||
if (tilt_ctl->cmd == RELAY_CHN_TILT_CMD_REVERSE) {
|
||||
return ++tilt_ctl->tilt_counter.tilt_reverse_count;
|
||||
}
|
||||
else if (tilt_ctl->cmd == RELAY_CHN_TILT_CMD_FORWARD) {
|
||||
if (tilt_ctl->tilt_counter.tilt_reverse_count > 0) {
|
||||
--tilt_ctl->tilt_counter.tilt_reverse_count;
|
||||
// Still should do one more move, return non-zero value
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
relay_chn_tilt_reset_count(tilt_ctl);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void relay_chn_tilt_execute_stop(relay_chn_tilt_ctl_t *tilt_ctl)
|
||||
{
|
||||
// Stop the channel's timer if active
|
||||
esp_timer_stop(tilt_ctl->tilt_timer);
|
||||
// Invalidate tilt cmd and step
|
||||
tilt_ctl->cmd = RELAY_CHN_TILT_CMD_NONE;
|
||||
tilt_ctl->step = RELAY_CHN_TILT_STEP_NONE;
|
||||
// Stop the channel
|
||||
if (relay_chn_output_stop(tilt_ctl->chn_ctl->output) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "relay_chn_tilt_execute_stop: Failed to output stop for relay channel #%d!", tilt_ctl->chn_ctl->id);
|
||||
}
|
||||
relay_chn_dispatch_cmd(tilt_ctl->chn_ctl, RELAY_CHN_CMD_STOP);
|
||||
}
|
||||
|
||||
static void relay_chn_tilt_execute_forward(relay_chn_tilt_ctl_t *tilt_ctl)
|
||||
{
|
||||
if (relay_chn_output_reverse(tilt_ctl->chn_ctl->output) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "relay_chn_tilt_execute_forward: Failed to output reverse for relay channel #%d!", tilt_ctl->chn_ctl->id);
|
||||
// Stop tilting because of the error
|
||||
relay_chn_tilt_dispatch_cmd(tilt_ctl, RELAY_CHN_TILT_CMD_STOP);
|
||||
return;
|
||||
}
|
||||
// Set the move time timer
|
||||
relay_chn_start_esp_timer_once(tilt_ctl->tilt_timer, tilt_ctl->tilt_timing.move_time_ms);
|
||||
// Set to pause step
|
||||
tilt_ctl->step = RELAY_CHN_TILT_STEP_PAUSE;
|
||||
}
|
||||
|
||||
static void relay_chn_tilt_execute_reverse(relay_chn_tilt_ctl_t *tilt_ctl)
|
||||
{
|
||||
if (relay_chn_output_forward(tilt_ctl->chn_ctl->output) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "relay_chn_tilt_execute_reverse: Failed to output forward for relay channel #%d!", tilt_ctl->chn_ctl->id);
|
||||
// Stop tilting because of the error
|
||||
relay_chn_tilt_dispatch_cmd(tilt_ctl, RELAY_CHN_TILT_CMD_STOP);
|
||||
return;
|
||||
}
|
||||
// Set the move time timer
|
||||
relay_chn_start_esp_timer_once(tilt_ctl->tilt_timer, tilt_ctl->tilt_timing.move_time_ms);
|
||||
// Set to pause step
|
||||
tilt_ctl->step = RELAY_CHN_TILT_STEP_PAUSE;
|
||||
}
|
||||
|
||||
static void relay_chn_tilt_execute_pause(relay_chn_tilt_ctl_t *tilt_ctl)
|
||||
{
|
||||
// Pause the channel
|
||||
if (relay_chn_output_stop(tilt_ctl->chn_ctl->output) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "relay_chn_tilt_execute_pause: Failed to output stop for relay channel #%d!", tilt_ctl->chn_ctl->id);
|
||||
// Stop tilting because of the error
|
||||
relay_chn_tilt_dispatch_cmd(tilt_ctl, RELAY_CHN_TILT_CMD_STOP);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the tilt counter before the next move and expect the return value to be greater than 0
|
||||
if (relay_chn_tilt_count_update(tilt_ctl) == 0) {
|
||||
ESP_LOGD(TAG, "relay_chn_tilt_execute_pause: Relay channel cannot tilt anymore");
|
||||
// Stop tilting since the tilting limit has been reached
|
||||
relay_chn_tilt_dispatch_cmd(tilt_ctl, RELAY_CHN_TILT_CMD_STOP);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the pause time timer
|
||||
relay_chn_start_esp_timer_once(tilt_ctl->tilt_timer, tilt_ctl->tilt_timing.pause_time_ms);
|
||||
// Set to move step
|
||||
tilt_ctl->step = RELAY_CHN_TILT_STEP_MOVE;
|
||||
}
|
||||
|
||||
static void relay_chn_tilt_event_handler(void *handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
|
||||
{
|
||||
relay_chn_tilt_ctl_t* tilt_ctl = *(relay_chn_tilt_ctl_t**) event_data;
|
||||
ESP_RETURN_VOID_ON_FALSE(tilt_ctl != NULL, TAG, "event_data is NULL");
|
||||
ESP_LOGD(TAG, "relay_chn_event_handler: Command: %s", relay_chn_cmd_str(event_id));
|
||||
|
||||
switch(event_id) {
|
||||
case RELAY_CHN_TILT_CMD_STOP:
|
||||
relay_chn_tilt_execute_stop(tilt_ctl);
|
||||
break;
|
||||
case RELAY_CHN_TILT_CMD_FORWARD:
|
||||
relay_chn_tilt_execute_forward(tilt_ctl);
|
||||
// Update channel state
|
||||
relay_chn_update_state(tilt_ctl->chn_ctl, RELAY_CHN_STATE_TILT_FORWARD);
|
||||
break;
|
||||
case RELAY_CHN_TILT_CMD_REVERSE:
|
||||
relay_chn_tilt_execute_reverse(tilt_ctl);
|
||||
// Update channel state
|
||||
relay_chn_update_state(tilt_ctl->chn_ctl, RELAY_CHN_STATE_TILT_REVERSE);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unexpected relay channel tilt command: %ld!", event_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Timer callback for the relay_chn_tilt_control_t::tilt_timer
|
||||
static void relay_chn_tilt_timer_cb(void *arg)
|
||||
{
|
||||
relay_chn_tilt_ctl_t* tilt_ctl = (relay_chn_tilt_ctl_t*) arg;
|
||||
ESP_RETURN_VOID_ON_FALSE(tilt_ctl != NULL, TAG, "relay_chn_tilt_timer_cb: event_data is NULL");
|
||||
|
||||
switch (tilt_ctl->step)
|
||||
{
|
||||
case RELAY_CHN_TILT_STEP_MOVE:
|
||||
if (tilt_ctl->cmd == RELAY_CHN_TILT_CMD_FORWARD) {
|
||||
relay_chn_tilt_execute_forward(tilt_ctl);
|
||||
}
|
||||
else if (tilt_ctl->cmd == RELAY_CHN_TILT_CMD_REVERSE) {
|
||||
relay_chn_tilt_execute_reverse(tilt_ctl);
|
||||
}
|
||||
break;
|
||||
|
||||
case RELAY_CHN_TILT_STEP_PAUSE:
|
||||
relay_chn_tilt_execute_pause(tilt_ctl);
|
||||
break;
|
||||
|
||||
case RELAY_CHN_TILT_STEP_PENDING:
|
||||
// Just dispatch the pending tilt command
|
||||
relay_chn_tilt_dispatch_cmd(tilt_ctl, tilt_ctl->cmd);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_tilt_ctl_init(relay_chn_tilt_ctl_t *tilt_ctl, relay_chn_ctl_t *chn_ctl)
|
||||
{
|
||||
tilt_ctl->cmd = RELAY_CHN_TILT_CMD_NONE;
|
||||
tilt_ctl->step = RELAY_CHN_TILT_STEP_NONE;
|
||||
tilt_ctl->tilt_timing.sensitivity = RELAY_CHN_TILT_DEFAULT_SENSITIVITY;
|
||||
tilt_ctl->tilt_timing.move_time_ms = RELAY_CHN_TILT_DEFAULT_RUN_MS;
|
||||
tilt_ctl->tilt_timing.pause_time_ms = RELAY_CHN_TILT_DEFAULT_PAUSE_MS;
|
||||
relay_chn_tilt_reset_count(tilt_ctl);
|
||||
|
||||
tilt_ctl->chn_ctl = chn_ctl;
|
||||
tilt_ctl->chn_ctl->tilt_ctl = tilt_ctl; //
|
||||
|
||||
// Create tilt timer for the channel
|
||||
char timer_name[32];
|
||||
snprintf(timer_name, sizeof(timer_name), "relay_chn_%2d_tilt_timer", chn_ctl->id);
|
||||
esp_timer_create_args_t timer_args = {
|
||||
.callback = relay_chn_tilt_timer_cb,
|
||||
.arg = tilt_ctl,
|
||||
.name = timer_name
|
||||
};
|
||||
return esp_timer_create(&timer_args, &tilt_ctl->tilt_timer);
|
||||
}
|
||||
|
||||
esp_err_t relay_chn_tilt_init(relay_chn_ctl_t *chn_ctls)
|
||||
{
|
||||
#if RELAY_CHN_COUNT > 1
|
||||
for (int i = 0; i < RELAY_CHN_COUNT; i++) {
|
||||
relay_chn_tilt_ctl_init(&tilt_ctls[i], &chn_ctls[i]);
|
||||
}
|
||||
#else
|
||||
relay_chn_tilt_ctl_init(&tilt_ctl, chn_ctls);
|
||||
#endif // RELAY_CHN_COUNT > 1
|
||||
|
||||
return esp_event_handler_register_with(relay_chn_event_loop,
|
||||
RELAY_CHN_TILT_CMD_EVENT_BASE,
|
||||
ESP_EVENT_ANY_ID,
|
||||
relay_chn_tilt_event_handler, NULL);
|
||||
}
|
||||
|
||||
void relay_chn_tilt_ctl_deinit(relay_chn_tilt_ctl_t *tilt_ctl)
|
||||
{
|
||||
if (tilt_ctl->tilt_timer != NULL) {
|
||||
esp_timer_delete(tilt_ctl->tilt_timer);
|
||||
tilt_ctl->tilt_timer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void relay_chn_tilt_deinit()
|
||||
{
|
||||
#if RELAY_CHN_COUNT > 1
|
||||
for (int i = 0; i < RELAY_CHN_COUNT; i++) {
|
||||
relay_chn_tilt_ctl_deinit(&tilt_ctls[i]);
|
||||
}
|
||||
#else
|
||||
relay_chn_tilt_ctl_deinit(&tilt_ctl);
|
||||
#endif // RELAY_CHN_COUNT > 1
|
||||
esp_event_handler_unregister_with(relay_chn_event_loop,
|
||||
RELAY_CHN_TILT_CMD_EVENT_BASE,
|
||||
ESP_EVENT_ANY_ID,
|
||||
relay_chn_tilt_event_handler);
|
||||
}
|
||||
Reference in New Issue
Block a user