feat(valve): Make end-position current thresholds configurable

Introduces separate Modbus holding registers for configurable end-position
current thresholds for both opening and closing valve movements.

- Added REG_HOLDING_VALVE_END_CURRENT_THRESHOLD_OPEN_MA and
  REG_HOLDING_VALVE_END_CURRENT_THRESHOLD_CLOSE_MA to modbus_server.h.
- Modified valve.c to use these new thresholds and save/load them via settings.
- Added new setter functions to valve.h.
- Created new shell_valve library with commands to set/get these thresholds.
- Updated modbus_tool.py to include new menu options for setting thresholds.
- Updated docs/modbus-registers.de.md to document the new registers.

This enhances the flexibility and calibration of the valve control system.
This commit is contained in:
2025-07-10 23:42:41 +02:00
parent bd8a7a766c
commit 92bb171e85
10 changed files with 198 additions and 12 deletions

View File

@@ -25,6 +25,8 @@ 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
@@ -88,7 +90,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=2, slave=slave_id)
hr_valve = client.read_holding_registers(REG_HOLDING_MAX_OPENING_TIME_S, count=4, 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)
@@ -105,6 +107,8 @@ def poll_status(slave_id, interval):
new_data["motor_current_close"] = f"{ir_current.registers[1]} mA"
new_data["open_time"] = f"{hr_valve.registers[0]}s"
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["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}"
@@ -229,7 +233,9 @@ 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)
stdscr.bkgd(' ', curses.color_pair(1))
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"]
menu = ["Open Valve", "Close Valve", "Stop Valve", "Toggle Output 1", "Toggle Output 2", "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"]
current_menu = menu
current_row_idx = 0
message, message_time = "", 0
input_mode, input_prompt, input_str, input_target_reg = False, "", "", 0
@@ -253,12 +259,14 @@ def main_menu(stdscr, slave_id):
elif key == curses.KEY_BACKSPACE or key == 127: input_str = input_str[:-1]
elif key != -1 and chr(key).isprintable(): input_str += chr(key)
else:
if key == curses.KEY_UP: current_row_idx = (current_row_idx - 1) % len(menu)
elif key == curses.KEY_DOWN: current_row_idx = (current_row_idx + 1) % len(menu)
if key == curses.KEY_UP: current_row_idx = (current_row_idx - 1) % len(current_menu)
elif key == curses.KEY_DOWN: current_row_idx = (current_row_idx + 1) % len(current_menu)
elif key == curses.KEY_ENTER or key in [10, 13]:
selected_option = menu[current_row_idx]
selected_option = current_menu[current_row_idx]
message_time = time.time()
if selected_option == "Exit": stop_event.set(); continue
elif selected_option == "Back": current_menu = menu; current_row_idx = 0; continue
elif selected_option == "Settings": current_menu = settings_menu; current_row_idx = 0; continue
elif selected_option == "Open Valve": client.write_register(REG_HOLDING_VALVE_COMMAND, 1, slave=slave_id); message = "-> Sent OPEN command"
elif selected_option == "Close Valve": client.write_register(REG_HOLDING_VALVE_COMMAND, 2, slave=slave_id); message = "-> Sent CLOSE command"
elif selected_option == "Stop Valve": client.write_register(REG_HOLDING_VALVE_COMMAND, 0, slave=slave_id); message = "-> Sent STOP command"
@@ -273,6 +281,10 @@ def main_menu(stdscr, slave_id):
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 End Current Open":
input_mode, input_prompt, input_target_reg = True, "Enter End Current Threshold Open (mA): ", REG_HOLDING_END_CURRENT_THRESHOLD_OPEN_MA
elif selected_option == "Set End Current Close":
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 == "Reset Node":
@@ -310,13 +322,15 @@ def main_menu(stdscr, slave_id):
stdscr.addstr(1, col3, "Max Open Time:", bold); stdscr.addstr(1, col3 + 16, str(current_data.get('open_time', 'N/A')), normal)
stdscr.addstr(2, col3, "Max Close Time:", bold); stdscr.addstr(2, col3 + 16, str(current_data.get('close_time', 'N/A')), normal)
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(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)
stdscr.addstr(4, col4, "Supply V:", bold); stdscr.addstr(4, col4 + 14, str(current_data.get('supply_voltage', 'N/A')), normal)
stdscr.addstr(6, 0, "" * (w - 1), normal)
for idx, row in enumerate(menu):
draw_button(stdscr, h // 2 - len(menu) + (idx * 2), w // 2 - len(row) // 2, row, idx == current_row_idx)
for idx, row in enumerate(current_menu):
draw_button(stdscr, h // 2 - len(current_menu) + (idx * 2), w // 2 - len(row) // 2, row, idx == current_row_idx)
if time.time() - message_time < 2.0: stdscr.addstr(h - 2, 0, message.ljust(w - 1), curses.color_pair(1) | curses.A_BOLD)
if input_mode:
curses.curs_set(1); stdscr.addstr(h - 2, 0, (input_prompt + input_str).ljust(w-1), curses.color_pair(2)); stdscr.move(h - 2, len(input_prompt) + len(input_str))