feat: Make valve obstacle detection parameters configurable via settings and shell

This commit introduces configurable obstacle detection thresholds for the valve, allowing them to be set and persisted via the Zephyr settings subsystem and controlled through the shell and Modbus tool.

- `software/lib/valve/Kconfig`: Added new Kconfig options `VALVE_OBSTACLE_THRESHOLD_OPEN_MA` and `VALVE_OBSTACLE_THRESHOLD_CLOSE_MA` for compile-time configuration and default values.
- `software/include/lib/valve.h`: Removed hardcoded defines and added API functions for setting and getting obstacle thresholds.
- `software/lib/valve/valve.c`:
    - Updated `valve_work_handler` to use the new configurable obstacle thresholds.
    - Integrated loading and saving of obstacle thresholds via the settings subsystem in `valve_init`.
    - Implemented the new setter and getter functions for obstacle thresholds.
    - Updated the `LOG_INF` message in `valve_init` to display the new obstacle threshold values.
- `software/apps/slave_node/prj.conf`: Added default values for the new Kconfig options.
- `software/lib/shell_valve/shell_valve.c`: Added new shell commands `valve set_obstacle_open` and `valve set_obstacle_close` to modify the obstacle thresholds, and updated `valve show` to display them.
- `software/tools/modbus_tool/modbus_tool.py`:
    - Defined new Modbus holding registers (`REG_HOLDING_OBSTACLE_THRESHOLD_OPEN_MA`, `REG_HOLDING_OBSTACLE_THRESHOLD_CLOSE_MA`).
    - Updated `poll_status` to read these new registers.
    - Modified the `main_menu` to include "Set Obstacle Open" and "Set Obstacle Close" options in the settings menu, allowing users to view and modify these parameters.
- `software/lib/modbus_server/modbus_server.c`:
    - Updated `holding_reg_rd` to read the new obstacle threshold registers.
    - Updated `holding_reg_wr` to write to the new obstacle threshold registers.
    - Removed incorrect `REG_HOLDING_END_CURRENT_THRESHOLD_OPEN_MA` and `REG_HOLDING_END_CURRENT_THRESHOLD_CLOSE_MA` cases from `input_reg_rd`.
- `software/include/lib/modbus_registers.h`: Created a new header file to centralize Modbus register definitions, which were previously hardcoded in `modbus_tool.py`.
Signed-off-by: Eduard Iten <eduard@iten.pro>
This commit is contained in:
Eduard Iten 2025-07-11 09:15:19 +02:00
parent fc0add8583
commit 8467b3e347
8 changed files with 193 additions and 42 deletions

View File

@ -26,4 +26,6 @@ CONFIG_MODBUS_BUFFER_SIZE=256
# Enable VND7050AJ
CONFIG_VND7050AJ=y
CONFIG_LOG_VALVE_LEVEL=4
CONFIG_LOG_VALVE_LEVEL=4
CONFIG_VALVE_OBSTACLE_THRESHOLD_OPEN_MA=200
CONFIG_VALVE_OBSTACLE_THRESHOLD_CLOSE_MA=200

View File

@ -0,0 +1,35 @@
#ifndef MODBUS_REGISTERS_H
#define MODBUS_REGISTERS_H
// Input Registers (Read-Only)
#define REG_INPUT_VALVE_STATE_MOVEMENT 0x0000
#define REG_INPUT_MOTOR_OPEN_CURRENT_MA 0x0001
#define REG_INPUT_MOTOR_CLOSE_CURRENT_MA 0x0002
#define REG_INPUT_DIGITAL_INPUTS_STATE 0x0020
#define REG_INPUT_BUTTON_EVENTS 0x0021
#define REG_INPUT_FIRMWARE_VERSION_MAJOR_MINOR 0x00F0
#define REG_INPUT_FIRMWARE_VERSION_PATCH 0x00F1
#define REG_INPUT_DEVICE_STATUS 0x00F2
#define REG_INPUT_UPTIME_SECONDS_LOW 0x00F3
#define REG_INPUT_UPTIME_SECONDS_HIGH 0x00F4
#define REG_INPUT_SUPPLY_VOLTAGE_MV 0x00F5
#define REG_INPUT_FWU_LAST_CHUNK_CRC 0x0100
// Holding Registers (Read-Write)
#define REG_HOLDING_VALVE_COMMAND 0x0000
#define REG_HOLDING_MAX_OPENING_TIME_S 0x0001
#define REG_HOLDING_MAX_CLOSING_TIME_S 0x0002
#define REG_HOLDING_END_CURRENT_THRESHOLD_OPEN_MA 0x0003
#define REG_HOLDING_END_CURRENT_THRESHOLD_CLOSE_MA 0x0004
#define REG_HOLDING_OBSTACLE_THRESHOLD_OPEN_MA 0x0005
#define REG_HOLDING_OBSTACLE_THRESHOLD_CLOSE_MA 0x0006
#define REG_HOLDING_DIGITAL_OUTPUTS_STATE 0x0010
#define REG_HOLDING_WATCHDOG_TIMEOUT_S 0x00F0
#define REG_HOLDING_DEVICE_RESET 0x00F1
#define REG_HOLDING_FWU_COMMAND 0x0100
#define REG_HOLDING_FWU_CHUNK_OFFSET_LOW 0x0101
#define REG_HOLDING_FWU_CHUNK_OFFSET_HIGH 0x0102
#define REG_HOLDING_FWU_CHUNK_SIZE 0x0103
#define REG_HOLDING_FWU_DATA_BUFFER 0x0180
#endif // MODBUS_REGISTERS_H

View File

@ -17,9 +17,6 @@
#define VALVE_CHANNEL_CLOSE 1
#define VALVE_ENDPOSITION_CHECK_INTERVAL K_MSEC(100)
#define VALVE_OBSTACLE_THRESHOLD_OPEN_MA 200
#define VALVE_OBSTACLE_THRESHOLD_CLOSE_MA 200
/**
* @brief Represents the static state of the valve (open or closed).
*/
@ -164,4 +161,33 @@ int32_t valve_get_vnd_temp(void);
* @return The voltage in millivolts.
*/
int32_t valve_get_vnd_voltage(void);
#endif // VALVE_H
/**
* @brief Sets the current threshold for obstacle detection during opening.
*
* @param current_ma The current threshold in milliamps.
*/
void valve_set_obstacle_threshold_open(uint16_t current_ma);
/**
* @brief Sets the current threshold for obstacle detection during closing.
*
* @param current_ma The current threshold in milliamps.
*/
void valve_set_obstacle_threshold_close(uint16_t current_ma);
/**
* @brief Gets the current threshold for obstacle detection during opening.
*
* @return The current threshold in milliamps.
*/
uint16_t valve_get_obstacle_threshold_open(void);
/**
* @brief Gets the current threshold for obstacle detection during closing.
*
* @return The current threshold in milliamps.
*/
uint16_t valve_get_obstacle_threshold_close(void);
#endif // VALVE_H

View File

@ -18,6 +18,7 @@
#include <zephyr/usb/usb_device.h>
#include <app_version.h>
#include <lib/fwu.h>
#include <lib/modbus_registers.h>
#include <lib/modbus_server.h>
#include <lib/valve.h>
@ -87,6 +88,12 @@ static int holding_reg_rd(uint16_t addr, uint16_t *reg)
case REG_HOLDING_END_CURRENT_THRESHOLD_CLOSE_MA:
*reg = valve_get_end_current_threshold_close();
break;
case REG_HOLDING_OBSTACLE_THRESHOLD_OPEN_MA:
*reg = valve_get_obstacle_threshold_open();
break;
case REG_HOLDING_OBSTACLE_THRESHOLD_CLOSE_MA:
*reg = valve_get_obstacle_threshold_close();
break;
default:
*reg = 0;
break;
@ -126,6 +133,12 @@ static int holding_reg_wr(uint16_t addr, uint16_t reg)
case REG_HOLDING_END_CURRENT_THRESHOLD_CLOSE_MA:
valve_set_end_current_threshold_close(reg);
break;
case REG_HOLDING_OBSTACLE_THRESHOLD_OPEN_MA:
valve_set_obstacle_threshold_open(reg);
break;
case REG_HOLDING_OBSTACLE_THRESHOLD_CLOSE_MA:
valve_set_obstacle_threshold_close(reg);
break;
case REG_HOLDING_WATCHDOG_TIMEOUT_S:
watchdog_timeout_s = reg;
if (watchdog_timeout_s > 0) {
@ -188,12 +201,6 @@ static int input_reg_rd(uint16_t addr, uint16_t *reg)
case REG_INPUT_FIRMWARE_VERSION_PATCH:
*reg = APP_PATCHLEVEL;
break;
case REG_HOLDING_END_CURRENT_THRESHOLD_OPEN_MA:
*reg = valve_get_end_current_threshold_open();
break;
case REG_HOLDING_END_CURRENT_THRESHOLD_CLOSE_MA:
*reg = valve_get_end_current_threshold_close();
break;
default:
*reg = 0;
break;

View File

@ -55,6 +55,32 @@ static int cmd_valve_set_end_curr_close(const struct shell *sh, size_t argc, cha
return 0;
}
static int cmd_valve_set_obstacle_open(const struct shell *sh, size_t argc, char **argv)
{
if (argc != 2) {
shell_print(sh, "Usage: valve set_obstacle_open <milliamps>");
return -EINVAL;
}
uint16_t current_ma = (uint16_t)atoi(argv[1]);
valve_set_obstacle_threshold_open(current_ma);
shell_print(sh, "Obstacle threshold (open) set to %u mA.", current_ma);
return 0;
}
static int cmd_valve_set_obstacle_close(const struct shell *sh, size_t argc, char **argv)
{
if (argc != 2) {
shell_print(sh, "Usage: valve set_obstacle_close <milliamps>");
return -EINVAL;
}
uint16_t current_ma = (uint16_t)atoi(argv[1]);
valve_set_obstacle_threshold_close(current_ma);
shell_print(sh, "Obstacle threshold (close) set to %u mA.", current_ma);
return 0;
}
static int cmd_valve_show(const struct shell *sh, size_t argc, char **argv)
{
const int label_width = 30;
@ -72,6 +98,16 @@ static int cmd_valve_show(const struct shell *sh, size_t argc, char **argv)
label_width,
"End Current Threshold (Close):",
valve_get_end_current_threshold_close());
shell_print(sh,
"%*s %u mA",
label_width,
"Obstacle Threshold (Open):",
valve_get_obstacle_threshold_open());
shell_print(sh,
"%*s %u mA",
label_width,
"Obstacle Threshold (Close):",
valve_get_obstacle_threshold_close());
return 0;
}
@ -86,6 +122,14 @@ SHELL_STATIC_SUBCMD_SET_CREATE(sub_valve_settings,
NULL,
"Set end current threshold for closing (mA)",
cmd_valve_set_end_curr_close),
SHELL_CMD(set_obstacle_open,
NULL,
"Set obstacle threshold for opening (mA)",
cmd_valve_set_obstacle_open),
SHELL_CMD(set_obstacle_close,
NULL,
"Set obstacle threshold for closing (mA)",
cmd_valve_set_obstacle_close),
SHELL_CMD(show, NULL, "Show valve configuration", cmd_valve_show),
SHELL_SUBCMD_SET_END);

View File

@ -11,4 +11,21 @@ config LOG_VALVE_LEVEL
help
Set the log level for the Valve Library.
0 = None, 1 = Error, 2 = Warning, 3 = Info, 4 = Debug
config VALVE_OBSTACLE_THRESHOLD_OPEN_MA
int "Obstacle Threshold Open (mA)"
default 200
help
Set the current threshold in milliamps for obstacle detection
during valve opening. If the motor current exceeds this value,
an obstacle is detected and the valve stops.
config VALVE_OBSTACLE_THRESHOLD_CLOSE_MA
int "Obstacle Threshold Close (mA)"
default 200
help
Set the current threshold in milliamps for obstacle detection
during valve closing. If the motor current exceeds this value,
an obstacle is detected and the valve stops.
endif # LIB_VALVE

View File

@ -29,6 +29,8 @@ static uint16_t max_opening_time_s = 10;
static uint16_t max_closing_time_s = 10;
static uint16_t end_current_threshold_open_ma = 10;
static uint16_t end_current_threshold_close_ma = 10;
static uint16_t obstacle_threshold_open_ma = CONFIG_VALVE_OBSTACLE_THRESHOLD_OPEN_MA;
static uint16_t obstacle_threshold_close_ma = CONFIG_VALVE_OBSTACLE_THRESHOLD_CLOSE_MA;
static struct k_work_delayable valve_work;
static struct k_timer movement_timer;
@ -48,7 +50,7 @@ static void valve_work_handler(struct k_work *work)
if (current_movement == VALVE_MOVEMENT_OPENING) {
vnd7050aj_read_load_current(vnd7050aj_dev, VALVE_CHANNEL_OPEN, &current_ma);
LOG_DBG("Current load during opening: %d mA", current_ma);
if (current_ma > VALVE_OBSTACLE_THRESHOLD_OPEN_MA) {
if (current_ma > obstacle_threshold_open_ma) {
LOG_ERR(
"Obstacle detected during opening (current: %d mA), stopping motor.",
current_ma);
@ -63,7 +65,7 @@ static void valve_work_handler(struct k_work *work)
} else if (current_movement == VALVE_MOVEMENT_CLOSING) {
vnd7050aj_read_load_current(vnd7050aj_dev, VALVE_CHANNEL_CLOSE, &current_ma);
LOG_DBG("Current load during closing: %d mA", current_ma);
if (current_ma > VALVE_OBSTACLE_THRESHOLD_CLOSE_MA) {
if (current_ma > obstacle_threshold_close_ma) {
LOG_ERR(
"Obstacle detected during closing (current: %d mA), stopping motor.",
current_ma);
@ -125,13 +127,21 @@ int valve_init(void)
settings_load_one("valve/end_current_threshold_close",
&end_current_threshold_close_ma,
sizeof(end_current_threshold_close_ma));
settings_load_one("valve/obstacle_threshold_open",
&obstacle_threshold_open_ma,
sizeof(obstacle_threshold_open_ma));
settings_load_one("valve/obstacle_threshold_close",
&obstacle_threshold_close_ma,
sizeof(obstacle_threshold_close_ma));
LOG_INF("Valve initialized: max_open=%us, max_close=%us, end_curr_open=%umA, "
"end_curr_close=%umA",
"end_curr_close=%umA, obs_open=%umA, obs_close=%umA",
max_opening_time_s,
max_closing_time_s,
end_current_threshold_open_ma,
end_current_threshold_close_ma);
end_current_threshold_close_ma,
obstacle_threshold_open_ma,
obstacle_threshold_close_ma);
valve_close();
return 0;
}
@ -255,3 +265,29 @@ int32_t valve_get_vnd_voltage(void)
vnd7050aj_read_supply_voltage(vnd7050aj_dev, &voltage_mv);
return voltage_mv;
}
void valve_set_obstacle_threshold_open(uint16_t current_ma)
{
obstacle_threshold_open_ma = current_ma;
settings_save_one("valve/obstacle_threshold_open",
&obstacle_threshold_open_ma,
sizeof(obstacle_threshold_open_ma));
}
void valve_set_obstacle_threshold_close(uint16_t current_ma)
{
obstacle_threshold_close_ma = current_ma;
settings_save_one("valve/obstacle_threshold_close",
&obstacle_threshold_close_ma,
sizeof(obstacle_threshold_close_ma));
}
uint16_t valve_get_obstacle_threshold_open(void)
{
return obstacle_threshold_open_ma;
}
uint16_t valve_get_obstacle_threshold_close(void)
{
return obstacle_threshold_close_ma;
}

View File

@ -10,31 +10,7 @@ from pymodbus.exceptions import ModbusException
# --- Register Definitions ---
# (omitted for brevity, no changes here)
REG_INPUT_VALVE_STATE_MOVEMENT = 0x0000
REG_INPUT_MOTOR_OPEN_CURRENT_MA = 0x0001
REG_INPUT_MOTOR_CLOSE_CURRENT_MA = 0x0002
REG_INPUT_DIGITAL_INPUTS_STATE = 0x0020
REG_INPUT_BUTTON_EVENTS = 0x0021
REG_INPUT_FIRMWARE_VERSION_MAJOR_MINOR = 0x00F0
REG_INPUT_FIRMWARE_VERSION_PATCH = 0x00F1
REG_INPUT_DEVICE_STATUS = 0x00F2
REG_INPUT_UPTIME_SECONDS_LOW = 0x00F3
REG_INPUT_UPTIME_SECONDS_HIGH = 0x00F4
REG_INPUT_SUPPLY_VOLTAGE_MV = 0x00F5
REG_INPUT_FWU_LAST_CHUNK_CRC = 0x0100
REG_HOLDING_VALVE_COMMAND = 0x0000
REG_HOLDING_MAX_OPENING_TIME_S = 0x0001
REG_HOLDING_MAX_CLOSING_TIME_S = 0x0002
REG_HOLDING_END_CURRENT_THRESHOLD_OPEN_MA = 0x0003
REG_HOLDING_END_CURRENT_THRESHOLD_CLOSE_MA = 0x0004
REG_HOLDING_DIGITAL_OUTPUTS_STATE = 0x0010
REG_HOLDING_WATCHDOG_TIMEOUT_S = 0x00F0
REG_HOLDING_DEVICE_RESET = 0x00F1
REG_HOLDING_FWU_COMMAND = 0x0100
REG_HOLDING_FWU_CHUNK_OFFSET_LOW = 0x0101
REG_HOLDING_FWU_CHUNK_OFFSET_HIGH = 0x0102
REG_HOLDING_FWU_CHUNK_SIZE = 0x0103
REG_HOLDING_FWU_DATA_BUFFER = 0x0180
# --- Global State ---
@ -92,7 +68,7 @@ def poll_status(slave_id, interval):
ir_current = client.read_input_registers(REG_INPUT_MOTOR_OPEN_CURRENT_MA, count=2, slave=slave_id)
ir_dig = client.read_input_registers(REG_INPUT_DIGITAL_INPUTS_STATE, count=2, slave=slave_id)
ir_sys = client.read_input_registers(REG_INPUT_FIRMWARE_VERSION_MAJOR_MINOR, count=6, slave=slave_id)
hr_valve = client.read_holding_registers(REG_HOLDING_MAX_OPENING_TIME_S, count=4, slave=slave_id)
hr_valve = client.read_holding_registers(REG_HOLDING_MAX_OPENING_TIME_S, count=6, slave=slave_id)
hr_dig = client.read_holding_registers(REG_HOLDING_DIGITAL_OUTPUTS_STATE, count=1, slave=slave_id)
hr_sys = client.read_holding_registers(REG_HOLDING_WATCHDOG_TIMEOUT_S, count=1, slave=slave_id)
@ -111,6 +87,8 @@ def poll_status(slave_id, interval):
new_data["close_time"] = f"{hr_valve.registers[1]}s"
new_data["end_curr_open"] = f"{hr_valve.registers[2]}mA"
new_data["end_curr_close"] = f"{hr_valve.registers[3]}mA"
new_data["obstacle_open"] = f"{hr_valve.registers[4]}mA"
new_data["obstacle_close"] = f"{hr_valve.registers[5]}mA"
new_data["digital_inputs"] = f"0x{ir_dig.registers[0]:04X}"
new_data["button_events"] = f"0x{ir_dig.registers[1]:04X}"
new_data["digital_outputs"] = f"0x{hr_dig.registers[0]:04X}"
@ -236,7 +214,7 @@ def main_menu(stdscr, slave_id):
stdscr.bkgd(' ', curses.color_pair(1))
menu = ["Open Valve", "Close Valve", "Stop Valve", "Settings", "Reset Node", "Firmware Update", "Exit"]
settings_menu = ["Set Max Open Time", "Set Max Close Time", "Set End Current Open", "Set End Current Close", "Set Watchdog", "Back"]
settings_menu = ["Set Max Open Time", "Set Max Close Time", "Set End Current Open", "Set End Current Close", "Set Obstacle Open", "Set Obstacle Close", "Set Watchdog", "Back"]
current_menu = menu
current_row_idx = 0
message, message_time = "", 0
@ -282,6 +260,10 @@ def main_menu(stdscr, slave_id):
input_mode, input_prompt, input_target_reg = True, "Enter End Current Threshold Close (mA): ", REG_HOLDING_END_CURRENT_THRESHOLD_CLOSE_MA
elif selected_option == "Set Watchdog":
input_mode, input_prompt, input_target_reg = True, "Enter Watchdog Timeout (s): ", REG_HOLDING_WATCHDOG_TIMEOUT_S
elif selected_option == "Set Obstacle Open":
input_mode, input_prompt, input_target_reg = True, "Enter Obstacle Threshold Open (mA): ", REG_HOLDING_OBSTACLE_THRESHOLD_OPEN_MA
elif selected_option == "Set Obstacle Close":
input_mode, input_prompt, input_target_reg = True, "Enter Obstacle Threshold Close (mA): ", REG_HOLDING_OBSTACLE_THRESHOLD_CLOSE_MA
elif selected_option == "Reset Node":
try:
client.write_register(REG_HOLDING_DEVICE_RESET, 1, slave=slave_id)
@ -319,6 +301,8 @@ def main_menu(stdscr, slave_id):
stdscr.addstr(3, col3, "Watchdog:", bold); stdscr.addstr(3, col3 + 16, str(current_data.get('watchdog', 'N/A')), normal)
stdscr.addstr(4, col3, "End Curr Open:", bold); stdscr.addstr(4, col3 + 16, str(current_data.get('end_curr_open', 'N/A')), normal)
stdscr.addstr(5, col3, "End Curr Close:", bold); stdscr.addstr(5, col3 + 16, str(current_data.get('end_curr_close', 'N/A')), normal)
stdscr.addstr(6, col3, "Obstacle Open:", bold); stdscr.addstr(6, col3 + 16, str(current_data.get('obstacle_open', 'N/A')), normal)
stdscr.addstr(7, col3, "Obstacle Close:", bold); stdscr.addstr(7, col3 + 16, str(current_data.get('obstacle_close', 'N/A')), normal)
stdscr.addstr(1, col4, "Firmware:", bold); stdscr.addstr(1, col4 + 14, str(current_data.get('firmware', 'N/A')), normal)
stdscr.addstr(2, col4, "Uptime:", bold); stdscr.addstr(2, col4 + 14, str(current_data.get('uptime', 'N/A')), normal)
stdscr.addstr(3, col4, "Dev. Status:", bold); stdscr.addstr(3, col4 + 14, str(current_data.get('device_status', 'N/A')), normal)