feat(modbus_tool): Add set functions for max open/close times

Adds new menu options to the Modbus tool to allow setting the maximum
opening and closing times for the valve via Modbus registers.
This commit is contained in:
Eduard Iten 2025-07-10 23:23:00 +02:00
parent bf29061db6
commit 8f89713866
2 changed files with 146 additions and 112 deletions

View File

@ -7,13 +7,13 @@
* safety timeouts for opening and closing operations. * safety timeouts for opening and closing operations.
*/ */
#include <lib/valve.h>
#include <zephyr/device.h> #include <zephyr/device.h>
#include <zephyr/drivers/gpio.h> #include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/misc/vnd7050aj/vnd7050aj.h> #include <zephyr/drivers/misc/vnd7050aj/vnd7050aj.h>
#include <zephyr/kernel.h> #include <zephyr/kernel.h>
#include <zephyr/logging/log.h> #include <zephyr/logging/log.h>
#include <zephyr/settings/settings.h> #include <zephyr/settings/settings.h>
#include <lib/valve.h>
#define VND_NODE DT_ALIAS(vnd7050aj) #define VND_NODE DT_ALIAS(vnd7050aj)
#if !DT_NODE_HAS_STATUS(VND_NODE, okay) #if !DT_NODE_HAS_STATUS(VND_NODE, okay)
@ -27,28 +27,23 @@ static enum valve_state current_state = VALVE_STATE_OPEN;
static enum valve_movement current_movement = VALVE_MOVEMENT_IDLE; static enum valve_movement current_movement = VALVE_MOVEMENT_IDLE;
static uint16_t max_opening_time_s = 10; static uint16_t max_opening_time_s = 10;
static uint16_t max_closing_time_s = 10; static uint16_t max_closing_time_s = 10;
static uint32_t movement_start_time = 0; static struct k_work_delayable valve_work; // Work item for scheduling valve movement timeouts
static struct k_work_delayable static struct k_timer movement_timer;
valve_work; // Work item for scheduling valve movement timeouts
/** /**
* @brief Work handler for valve movement timeouts. * @brief Work handler for end position checks of the valve.
* *
* This function is executed when the valve's movement timer expires. * This function is called periodically to check if the valve has reached its
* It stops the motor to prevent damage and updates the valve's state. * end position. It reads the current load on the motor and determines if the
* valve has reached its target position.
* *
* @param work Pointer to the k_work item. * @param work Pointer to the k_work item.
*/ */
static void valve_work_handler(struct k_work *work) { static void valve_work_handler(struct k_work *work)
{
int current_ma = 0; int current_ma = 0;
uint32_t now;
now = k_uptime_get_32();
if (current_movement == VALVE_MOVEMENT_OPENING) { if (current_movement == VALVE_MOVEMENT_OPENING) {
if (now - movement_start_time > max_opening_time_s * 1000) {
LOG_WRN("Valve opening timeout reached, stopping motor.");
current_movement = VALVE_MOVEMENT_ERROR;
goto work_handler_finish;
}
vnd7050aj_read_load_current(vnd7050aj_dev, VALVE_CHANNEL_OPEN, &current_ma); vnd7050aj_read_load_current(vnd7050aj_dev, VALVE_CHANNEL_OPEN, &current_ma);
if (current_ma > 10) { if (current_ma > 10) {
k_work_schedule(&valve_work, VALVE_ENDPOSITION_CHECK_INTERVAL); k_work_schedule(&valve_work, VALVE_ENDPOSITION_CHECK_INTERVAL);
@ -56,13 +51,7 @@ static void valve_work_handler(struct k_work *work) {
} }
LOG_INF("Valve finished opening"); LOG_INF("Valve finished opening");
} else if (current_movement == VALVE_MOVEMENT_CLOSING) { } else if (current_movement == VALVE_MOVEMENT_CLOSING) {
if (now - movement_start_time > max_closing_time_s * 1000) { vnd7050aj_read_load_current(vnd7050aj_dev, VALVE_CHANNEL_CLOSE, &current_ma);
LOG_WRN("Valve closing timeout reached, stopping motor.");
current_movement = VALVE_MOVEMENT_ERROR;
goto work_handler_finish;
}
vnd7050aj_read_load_current(vnd7050aj_dev, VALVE_CHANNEL_CLOSE,
&current_ma);
if (current_ma > 10) { if (current_ma > 10) {
k_work_schedule(&valve_work, VALVE_ENDPOSITION_CHECK_INTERVAL); k_work_schedule(&valve_work, VALVE_ENDPOSITION_CHECK_INTERVAL);
return; return;
@ -71,95 +60,136 @@ static void valve_work_handler(struct k_work *work) {
LOG_INF("Valve finished closing"); LOG_INF("Valve finished closing");
} }
current_movement = VALVE_MOVEMENT_IDLE; current_movement = VALVE_MOVEMENT_IDLE;
work_handler_finish:
// Reset the movement timer
k_timer_stop(&movement_timer);
vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_OPEN, false); vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_OPEN, false);
vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_CLOSE, false); vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_CLOSE, false);
LOG_INF("Valve work handler finished. Current state: %d, Movement: %d",
current_state, current_movement);
} }
int valve_init(void) { void movement_timeout_handler(struct k_timer *timer)
{
// Stop the end position check if the timer expires
k_work_cancel_delayable(&valve_work);
if (current_movement == VALVE_MOVEMENT_OPENING) {
LOG_WRN("Valve opening timeout reached, stopping motor.");
current_movement = VALVE_MOVEMENT_ERROR;
} else if (current_movement == VALVE_MOVEMENT_CLOSING) {
LOG_WRN("Valve closing timeout reached, stopping motor.");
current_movement = VALVE_MOVEMENT_ERROR;
}
vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_OPEN, false);
vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_CLOSE, false);
current_state = VALVE_STATE_CLOSED;
}
int valve_init(void)
{
if (!device_is_ready(vnd7050aj_dev)) { if (!device_is_ready(vnd7050aj_dev)) {
LOG_ERR("VND7050AJ device is not ready"); LOG_ERR("VND7050AJ device is not ready");
return -ENODEV; return -ENODEV;
} }
k_work_init_delayable(&valve_work, valve_work_handler);
settings_load_one("valve/max_open_time", &max_opening_time_s,
sizeof(max_opening_time_s));
settings_load_one("valve/max_close_time", &max_closing_time_s,
sizeof(max_closing_time_s));
LOG_INF("Valve initialized: max_open=%us, max_close=%us", max_opening_time_s, k_work_init_delayable(&valve_work, valve_work_handler);
k_timer_init(&movement_timer, movement_timeout_handler, NULL);
settings_load_one("valve/max_open_time", &max_opening_time_s, sizeof(max_opening_time_s));
settings_load_one("valve/max_close_time", &max_closing_time_s, sizeof(max_closing_time_s));
LOG_INF("Valve initialized: max_open=%us, max_close=%us",
max_opening_time_s,
max_closing_time_s); max_closing_time_s);
valve_close(); valve_close();
return 0; return 0;
} }
void valve_open(void) { void valve_open(void)
{
vnd7050aj_reset_fault(vnd7050aj_dev); vnd7050aj_reset_fault(vnd7050aj_dev);
vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_CLOSE, false); vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_CLOSE, false);
vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_OPEN, true); vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_OPEN, true);
current_state = VALVE_STATE_OPEN; current_state = VALVE_STATE_OPEN;
current_movement = VALVE_MOVEMENT_OPENING; /* Security: assume valve open as current_movement = VALVE_MOVEMENT_OPENING; /* Security: assume valve open as
soons as it starts opening */ soons as it starts opening */
movement_start_time = k_uptime_get_32(); k_timer_start(&movement_timer, K_SECONDS(max_opening_time_s), K_NO_WAIT);
k_work_schedule(&valve_work, K_MSEC(100)); k_work_schedule(&valve_work, K_MSEC(100));
} }
void valve_close(void) { void valve_close(void)
{
vnd7050aj_reset_fault(vnd7050aj_dev); vnd7050aj_reset_fault(vnd7050aj_dev);
vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_OPEN, false); vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_OPEN, false);
vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_CLOSE, true); vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_CLOSE, true);
movement_start_time = k_uptime_get_32(); k_timer_start(&movement_timer, K_SECONDS(max_closing_time_s), K_NO_WAIT);
current_movement = VALVE_MOVEMENT_CLOSING; current_movement = VALVE_MOVEMENT_CLOSING;
k_work_schedule(&valve_work, VALVE_ENDPOSITION_CHECK_INTERVAL); k_work_schedule(&valve_work, VALVE_ENDPOSITION_CHECK_INTERVAL);
} }
void valve_stop(void) { void valve_stop(void)
{
k_work_cancel_delayable(&valve_work); k_work_cancel_delayable(&valve_work);
k_timer_stop(&movement_timer);
vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_OPEN, false); vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_OPEN, false);
vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_CLOSE, false); vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_CLOSE, false);
current_movement = VALVE_MOVEMENT_IDLE; current_movement = VALVE_MOVEMENT_IDLE;
} }
enum valve_state valve_get_state(void) { return current_state; } enum valve_state valve_get_state(void)
enum valve_movement valve_get_movement(void) { return current_movement; } {
uint16_t valve_get_motor_current(void) { return current_state;
}
enum valve_movement valve_get_movement(void)
{
return current_movement;
}
uint16_t valve_get_motor_current(void)
{
return (current_movement != VALVE_MOVEMENT_IDLE) ? 150 : 10; return (current_movement != VALVE_MOVEMENT_IDLE) ? 150 : 10;
} }
void valve_set_max_open_time(uint16_t seconds) { void valve_set_max_open_time(uint16_t seconds)
{
max_opening_time_s = seconds; max_opening_time_s = seconds;
settings_save_one("valve/max_open_time", &max_opening_time_s, settings_save_one("valve/max_open_time", &max_opening_time_s, sizeof(max_opening_time_s));
sizeof(max_opening_time_s));
} }
void valve_set_max_close_time(uint16_t seconds) { void valve_set_max_close_time(uint16_t seconds)
{
max_closing_time_s = seconds; max_closing_time_s = seconds;
settings_save_one("valve/max_close_time", &max_closing_time_s, settings_save_one("valve/max_close_time", &max_closing_time_s, sizeof(max_closing_time_s));
sizeof(max_closing_time_s)); }
uint16_t valve_get_max_open_time(void)
{
return max_opening_time_s;
}
uint16_t valve_get_max_close_time(void)
{
return max_closing_time_s;
} }
uint16_t valve_get_max_open_time(void) { return max_opening_time_s; }
uint16_t valve_get_max_close_time(void) { return max_closing_time_s; }
int32_t valve_get_opening_current(void) { int32_t valve_get_opening_current(void)
{
int32_t current; int32_t current;
vnd7050aj_read_load_current(vnd7050aj_dev, VALVE_CHANNEL_OPEN, &current); vnd7050aj_read_load_current(vnd7050aj_dev, VALVE_CHANNEL_OPEN, &current);
return current; return current;
} }
int32_t valve_get_closing_current(void) { int32_t valve_get_closing_current(void)
{
int32_t current; int32_t current;
vnd7050aj_read_load_current(vnd7050aj_dev, VALVE_CHANNEL_CLOSE, &current); vnd7050aj_read_load_current(vnd7050aj_dev, VALVE_CHANNEL_CLOSE, &current);
return current; return current;
} }
int32_t valve_get_vnd_temp(void) { int32_t valve_get_vnd_temp(void)
{
int32_t temp_c; int32_t temp_c;
vnd7050aj_read_chip_temp(vnd7050aj_dev, &temp_c); vnd7050aj_read_chip_temp(vnd7050aj_dev, &temp_c);
return temp_c; return temp_c;
} }
int32_t valve_get_vnd_voltage(void) { int32_t valve_get_vnd_voltage(void)
{
int32_t voltage_mv; int32_t voltage_mv;
vnd7050aj_read_supply_voltage(vnd7050aj_dev, &voltage_mv); vnd7050aj_read_supply_voltage(vnd7050aj_dev, &voltage_mv);
return voltage_mv; return voltage_mv;

View File

@ -229,7 +229,7 @@ def main_menu(stdscr, slave_id):
curses.start_color(); curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE); curses.init_pair(2, curses.COLOR_BLUE, curses.COLOR_WHITE); curses.init_pair(3, curses.COLOR_RED, curses.COLOR_BLUE) curses.start_color(); curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE); curses.init_pair(2, curses.COLOR_BLUE, curses.COLOR_WHITE); curses.init_pair(3, curses.COLOR_RED, curses.COLOR_BLUE)
stdscr.bkgd(' ', curses.color_pair(1)) stdscr.bkgd(' ', curses.color_pair(1))
menu = ["Open Valve", "Close Valve", "Stop Valve", "Toggle Output 1", "Toggle Output 2", "Set Watchdog", "Reset Node", "Firmware Update", "Exit"] menu = ["Open Valve", "Close Valve", "Stop Valve", "Toggle Output 1", "Toggle Output 2", "Set Max Open Time", "Set Max Close Time", "Set Watchdog", "Reset Node", "Firmware Update", "Exit"]
current_row_idx = 0 current_row_idx = 0
message, message_time = "", 0 message, message_time = "", 0
input_mode, input_prompt, input_str, input_target_reg = False, "", "", 0 input_mode, input_prompt, input_str, input_target_reg = False, "", "", 0
@ -269,6 +269,10 @@ def main_menu(stdscr, slave_id):
client.write_register(REG_HOLDING_DIGITAL_OUTPUTS_STATE, current_val ^ (1 << bit), slave=slave_id) client.write_register(REG_HOLDING_DIGITAL_OUTPUTS_STATE, current_val ^ (1 << bit), slave=slave_id)
message = f"-> Toggled Output {bit+1}" message = f"-> Toggled Output {bit+1}"
except Exception as e: message = f"-> Error: {e}" except Exception as e: message = f"-> Error: {e}"
elif selected_option == "Set Max Open Time":
input_mode, input_prompt, input_target_reg = True, "Enter Max Open Time (s): ", REG_HOLDING_MAX_OPENING_TIME_S
elif selected_option == "Set Max Close Time":
input_mode, input_prompt, input_target_reg = True, "Enter Max Close Time (s): ", REG_HOLDING_MAX_CLOSING_TIME_S
elif selected_option == "Set Watchdog": elif selected_option == "Set Watchdog":
input_mode, input_prompt, input_target_reg = True, "Enter Watchdog Timeout (s): ", REG_HOLDING_WATCHDOG_TIMEOUT_S input_mode, input_prompt, input_target_reg = True, "Enter Watchdog Timeout (s): ", REG_HOLDING_WATCHDOG_TIMEOUT_S
elif selected_option == "Reset Node": elif selected_option == "Reset Node":