16 Commits

Author SHA1 Message Date
6c1ff0c4df feat(refactor): Restructure project for improved modularity and clarity
This commit introduces a major refactoring of the project structure to align
with Zephyr's recommended multi-application and library organization.

Key changes include:
- Relocation of custom modules from 'software/modules/' to 'software/lib/'.
- Introduction of a central 'software/CMakeLists.txt' to manage application
  and library subdirectories.
- Creation of new Kconfig files for 'software/' and 'software/apps/slave_node/'
  to define project-wide and application-specific configurations.
- Removal of the 'gateway' and 'stm32g431_tests' applications.
- Removal of 'shell_modbus.c' and 'shell_system.c' from 'slave_node' application's
  direct source files, indicating a shift towards library-based shell commands.
- Updates to 'software/apps/slave_node/CMakeLists.txt', 'prj.conf', and
  'boards/bluepill_f103rb.conf' to reflect the new structure and dependencies.
2025-07-03 16:58:43 +02:00
3f0d5a76c6 feat(cdc_acm): Add CDC-ACM support and remove old test applications
- Implemented CDC-ACM (USB Virtual COM Port) support for the slave_node application.
- Removed the now obsolete 'hello_world' and 'stm32g431_tests' applications.
2025-07-03 14:31:17 +02:00
10a770de59 fix(modbus_server): Implement hardcoded firmware version 0.0.1
Set firmware version to 0.0.1 in modbus_server.c for Modbus tool display.
This is a temporary solution until MCUboot integration is complete.
2025-07-03 13:43:15 +02:00
1b0519aadf Resolve merge conflict in modbus_server.c and add hardcoded firmware version. 2025-07-03 13:34:59 +02:00
e429a0874d Revert "feat(slave_node): Refine Modbus UART and add CDC-ACM support"
This reverts commit 3a05c80b25.
2025-07-03 13:34:01 +02:00
3a05c80b25 feat(slave_node): Refine Modbus UART and add CDC-ACM support
- Adjusted Device Tree Overlays for bluepill_f103rb and weact_stm32g431_core
  to correctly define Modbus UART via 'modbus0' subnode with 'zephyr,modbus-serial'
  compatibility, aligning with rtu_server sample.
- Prepared modbus_server.c to use the correct Device Tree node for Modbus UART.
2025-07-03 13:18:47 +02:00
5208f1370d feat(slave_node): Support multi-board build for bluepill_f103rb and weact_stm32g431_core
Refactor slave_node application to support building for both bluepill_f103rb and
weact_stm32g431_core boards.

- Moved RTT-specific console and shell backend configurations from prj.conf
  into board-specific .conf files (bluepill_f103rb.conf).
- Configured USART2 as console/shell for weact_stm32g431_core.
- Added Device Tree Overlay for weact_stm32g431_core to enable USART1 for Modbus
  communication (PA9/PA10).
2025-07-03 10:53:21 +02:00
a59e8518cc Rename hello_world app to stm32g431_tests 2025-07-03 10:02:53 +02:00
2a2890b675 Fix: hello_world prj.conf - set correct board name 2025-07-03 09:21:48 +02:00
38fd3a6aac Add hello_world Zephyr application for stm32g431_core 2025-07-03 09:15:11 +02:00
c3df6565b7 Refactor fwu library into a Zephyr module 2025-07-02 21:30:15 +02:00
140d2baa24 Fix: modbus_tool.py - replace is_connected() with is_socket_open() and fix UnboundLocalError 2025-07-02 21:05:40 +02:00
711341f362 Refactor slave_node application to use Zephyr modules 2025-07-02 20:47:16 +02:00
a5da0a61dd Update modbus_tool.py 2025-07-02 17:10:11 +02:00
b54c73edb1 fix: handle connection loss and re-establish in modbus_tool.py 2025-07-02 10:03:23 +02:00
2418d4e218 fix: resolve build error by moving modbus register enums to header 2025-07-02 10:02:40 +02:00
35 changed files with 325 additions and 143 deletions

5
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,5 @@
{
"files.associations": {
"fwu.h": "c"
}
}

6
lib/fwu/CMakeLists.txt Normal file
View File

@@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.20)
project(fwu)
target_sources(fwu PRIVATE src/fwu.c)
target_include_directories(fwu PUBLIC include)

View File

@@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.20)
project(modbus_server)
target_sources(modbus_server PRIVATE src/modbus_server.c)
target_include_directories(modbus_server PUBLIC include)

6
lib/valve/CMakeLists.txt Normal file
View File

@@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.20)
project(valve)
target_sources(valve PRIVATE src/valve.c)
target_include_directories(valve PUBLIC include)

1
software/Kconfig Normal file
View File

@@ -0,0 +1 @@
rsource "lib/Kconfig"

View File

@@ -1,27 +1,8 @@
cmake_minimum_required(VERSION 3.20)
# Point BOARD_ROOT and DTS_ROOT to the 'software' directory, which contains 'boards'.
list(APPEND BOARD_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../..)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(slave_node)
# Define a variable for the lib directory
set(LIB_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../lib)
# Add the include directories for the libraries
target_include_directories(app PRIVATE
${LIB_DIR}/valve
${LIB_DIR}/modbus_server
${LIB_DIR}/fwu
)
# Add the source files from the app and the libraries
target_sources(app PRIVATE
src/main.c
src/shell_modbus.c
src/shell_system.c
${LIB_DIR}/valve/valve.c
${LIB_DIR}/modbus_server/modbus_server.c
${LIB_DIR}/fwu/fwu.c
)
project(slave_node LANGUAGES C)
zephyr_include_directories(../../include)
add_subdirectory(../../lib lib)
target_sources(app PRIVATE src/main.c)

View File

@@ -0,0 +1,2 @@
rsource "../../lib/Kconfig"
source "Kconfig.zephyr"

View File

@@ -0,0 +1,7 @@
# Disable UART console
CONFIG_UART_CONSOLE=n
# Enable RTT console
CONFIG_RTT_CONSOLE=y
CONFIG_USE_SEGGER_RTT=y
CONFIG_SHELL_BACKEND_RTT=y

View File

@@ -0,0 +1,9 @@
&usart1 {
modbus0 {
compatible = "zephyr,modbus-serial";
status = "okay";
};
status = "okay";
pinctrl-0 = <&usart1_tx_pa9 &usart1_rx_pa10>;
pinctrl-names = "default";
};

View File

@@ -0,0 +1,10 @@
&zephyr_udc0 {
cdc_acm_uart0: cdc_acm_uart0 {
compatible = "zephyr,cdc-acm-uart";
modbus0 {
compatible = "zephyr,modbus-serial";
status = "okay";
};
};
};

View File

@@ -0,0 +1,4 @@
CONFIG_USB_DEVICE_STACK=y
CONFIG_USB_DEVICE_PRODUCT="Modbus slave node"
CONFIG_UART_LINE_CTRL=y
CONFIG_USB_DEVICE_INITIALIZE_AT_BOOT=n

View File

@@ -2,16 +2,8 @@
CONFIG_CONSOLE=y
CONFIG_LOG=y
# Disable UART console
CONFIG_UART_CONSOLE=n
# Enable RTT console
CONFIG_RTT_CONSOLE=y
CONFIG_USE_SEGGER_RTT=y
# Enable Shell
CONFIG_SHELL=y
CONFIG_SHELL_BACKEND_RTT=y
CONFIG_REBOOT=y
# Enable Settings Subsystem
@@ -28,3 +20,4 @@ CONFIG_UART_INTERRUPT_DRIVEN=y
CONFIG_MODBUS=y
CONFIG_MODBUS_ROLE_SERVER=y
CONFIG_MODBUS_BUFFER_SIZE=256

View File

@@ -1,9 +1,9 @@
#include <zephyr/kernel.h>
#include <zephyr/settings/settings.h>
#include <zephyr/logging/log.h>
#include "modbus_server.h"
#include "valve.h"
#include "fwu.h"
#include <lib/modbus_server.h>
#include <lib/valve.h>
#include <lib/fwu.h>
LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);

View File

@@ -0,0 +1,52 @@
#ifndef MODBUS_SERVER_H
#define MODBUS_SERVER_H
#include <stdint.h>
/**
* @brief Modbus Input Register Addresses.
*/
enum {
/* Valve Control & Status */
REG_INPUT_VALVE_STATE_MOVEMENT = 0x0000,
REG_INPUT_MOTOR_CURRENT_MA = 0x0001,
/* Digital Inputs */
REG_INPUT_DIGITAL_INPUTS_STATE = 0x0020,
REG_INPUT_BUTTON_EVENTS = 0x0021,
/* System Config & Status */
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,
/* Firmware Update */
REG_INPUT_FWU_LAST_CHUNK_CRC = 0x0100,
};
/**
* @brief Modbus Holding Register Addresses.
*/
enum {
/* Valve Control */
REG_HOLDING_VALVE_COMMAND = 0x0000,
REG_HOLDING_MAX_OPENING_TIME_S = 0x0001,
REG_HOLDING_MAX_CLOSING_TIME_S = 0x0002,
/* Digital Outputs */
REG_HOLDING_DIGITAL_OUTPUTS_STATE = 0x0010,
/* System Config */
REG_HOLDING_WATCHDOG_TIMEOUT_S = 0x00F0,
REG_HOLDING_DEVICE_RESET = 0x00F1,
/* Firmware Update */
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,
};
int modbus_server_init(void);
int modbus_reconfigure(uint32_t baudrate, uint8_t unit_id);
uint32_t modbus_get_baudrate(void);
uint8_t modbus_get_unit_id(void);
#endif // MODBUS_SERVER_H

View File

@@ -0,0 +1,5 @@
add_subdirectory_ifdef(CONFIG_LIB_FWU fwu)
add_subdirectory_ifdef(CONFIG_LIB_MODBUS_SERVER modbus_server)
add_subdirectory_ifdef(CONFIG_LIB_VALVE valve)
add_subdirectory_ifdef(CONFIG_SHELL_SYSTEM shell_system)
add_subdirectory_ifdef(CONFIG_SHELL_MODBUS shell_modbus)

8
software/lib/Kconfig Normal file
View File

@@ -0,0 +1,8 @@
menu "Irrigation system software libraries"
rsource "fwu/Kconfig"
rsource "modbus_server/Kconfig"
rsource "valve/Kconfig"
rsource "shell_system/Kconfig"
rsource "shell_modbus/Kconfig"
endmenu

View File

@@ -0,0 +1 @@
zephyr_library_sources(fwu.c)

5
software/lib/fwu/Kconfig Normal file
View File

@@ -0,0 +1,5 @@
config LIB_FWU
bool "Enable Firmware Update Library"
default y
help
Enable the Firmware Update Library.

View File

@@ -1,8 +1,8 @@
#include "fwu.h"
#include <zephyr/kernel.h>
#include <zephyr/sys/crc.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/logging/log.h>
#include <lib/fwu.h>
LOG_MODULE_REGISTER(fwu, LOG_LEVEL_INF);

View File

@@ -0,0 +1 @@
zephyr_library_sources(modbus_server.c)

View File

@@ -0,0 +1,5 @@
config LIB_MODBUS_SERVER
bool "Enable Modbus Server Library"
default y
help
Enable the Modbus Server Library.

View File

@@ -1,60 +1,23 @@
#include <zephyr/kernel.h>
#include "modbus_server.h"
#include <zephyr/drivers/uart.h>
#include <zephyr/device.h>
#include <zephyr/modbus/modbus.h>
#include <zephyr/logging/log.h>
#include <zephyr/settings/settings.h>
#include <zephyr/sys/reboot.h>
#include "valve.h"
#include "fwu.h"
#include <lib/modbus_server.h>
#include <lib/valve.h>
#include <lib/fwu.h>
#include <zephyr/usb/usb_device.h>
LOG_MODULE_REGISTER(modbus_server, LOG_LEVEL_INF);
/**
* @brief Modbus Input Register Addresses.
*/
enum {
/* Valve Control & Status */
REG_INPUT_VALVE_STATE_MOVEMENT = 0x0000,
REG_INPUT_MOTOR_CURRENT_MA = 0x0001,
/* Digital Inputs */
REG_INPUT_DIGITAL_INPUTS_STATE = 0x0020,
REG_INPUT_BUTTON_EVENTS = 0x0021,
/* System Config & Status */
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,
/* Firmware Update */
REG_INPUT_FWU_LAST_CHUNK_CRC = 0x0100,
};
/**
* @brief Modbus Holding Register Addresses.
*/
enum {
/* Valve Control */
REG_HOLDING_VALVE_COMMAND = 0x0000,
REG_HOLDING_MAX_OPENING_TIME_S = 0x0001,
REG_HOLDING_MAX_CLOSING_TIME_S = 0x0002,
/* Digital Outputs */
REG_HOLDING_DIGITAL_OUTPUTS_STATE = 0x0010,
/* System Config */
REG_HOLDING_WATCHDOG_TIMEOUT_S = 0x00F0,
/* Firmware Update */
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,
};
static int modbus_iface;
static struct modbus_iface_param server_param = {
.mode = MODBUS_MODE_RTU,
.server = { .user_cb = NULL, .unit_id = 1 },
.serial = { .baud = 19200, .parity = UART_CFG_PARITY_NONE },
.server = {.user_cb = NULL, .unit_id = 1},
.serial = {.baud = 19200, .parity = UART_CFG_PARITY_NONE},
};
static uint16_t watchdog_timeout_s = 0;
@@ -68,7 +31,8 @@ static void watchdog_timer_handler(struct k_timer *timer_id)
static inline void reset_watchdog(void)
{
if (watchdog_timeout_s > 0) {
if (watchdog_timeout_s > 0)
{
k_timer_start(&watchdog_timer, K_SECONDS(watchdog_timeout_s), K_NO_WAIT);
}
}
@@ -76,11 +40,20 @@ static inline void reset_watchdog(void)
static int holding_reg_rd(uint16_t addr, uint16_t *reg)
{
reset_watchdog();
switch (addr) {
case REG_HOLDING_MAX_OPENING_TIME_S: *reg = valve_get_max_open_time(); break;
case REG_HOLDING_MAX_CLOSING_TIME_S: *reg = valve_get_max_close_time(); break;
case REG_HOLDING_WATCHDOG_TIMEOUT_S: *reg = watchdog_timeout_s; break;
default: *reg = 0; break;
switch (addr)
{
case REG_HOLDING_MAX_OPENING_TIME_S:
*reg = valve_get_max_open_time();
break;
case REG_HOLDING_MAX_CLOSING_TIME_S:
*reg = valve_get_max_close_time();
break;
case REG_HOLDING_WATCHDOG_TIMEOUT_S:
*reg = watchdog_timeout_s;
break;
default:
*reg = 0;
break;
}
return 0;
}
@@ -88,11 +61,21 @@ static int holding_reg_rd(uint16_t addr, uint16_t *reg)
static int holding_reg_wr(uint16_t addr, uint16_t reg)
{
reset_watchdog();
switch (addr) {
switch (addr)
{
case REG_HOLDING_VALVE_COMMAND:
if (reg == 1) { valve_open(); }
else if (reg == 2) { valve_close(); }
else if (reg == 0) { valve_stop(); }
if (reg == 1)
{
valve_open();
}
else if (reg == 2)
{
valve_close();
}
else if (reg == 0)
{
valve_stop();
}
break;
case REG_HOLDING_MAX_OPENING_TIME_S:
valve_set_max_open_time(reg);
@@ -102,23 +85,27 @@ static int holding_reg_wr(uint16_t addr, uint16_t reg)
break;
case REG_HOLDING_WATCHDOG_TIMEOUT_S:
watchdog_timeout_s = reg;
if (watchdog_timeout_s > 0) {
if (watchdog_timeout_s > 0)
{
LOG_INF("Watchdog enabled with %u s timeout.", watchdog_timeout_s);
reset_watchdog();
} else {
}
else
{
LOG_INF("Watchdog disabled.");
k_timer_stop(&watchdog_timer);
}
break;
case REG_HOLDING_DEVICE_RESET:
if (reg == 1) {
if (reg == 1)
{
LOG_WRN("Modbus reset command received. Rebooting...");
sys_reboot(SYS_REBOOT_WARM);
}
break;
default:
fwu_handler(addr, reg);
break;
default:
fwu_handler(addr, reg);
break;
}
return 0;
}
@@ -127,13 +114,32 @@ static int input_reg_rd(uint16_t addr, uint16_t *reg)
{
reset_watchdog();
uint32_t uptime_s = k_uptime_get_32() / 1000;
switch (addr) {
case REG_INPUT_VALVE_STATE_MOVEMENT: *reg = (valve_get_movement() << 8) | (valve_get_state() & 0xFF); break;
case REG_INPUT_MOTOR_CURRENT_MA: *reg = valve_get_motor_current(); break;
case REG_INPUT_UPTIME_SECONDS_LOW: *reg = (uint16_t)(uptime_s & 0xFFFF); break;
case REG_INPUT_UPTIME_SECONDS_HIGH: *reg = (uint16_t)(uptime_s >> 16); break;
case REG_INPUT_FWU_LAST_CHUNK_CRC: *reg = fwu_get_last_chunk_crc(); break;
default: *reg = 0; break;
switch (addr)
{
case REG_INPUT_VALVE_STATE_MOVEMENT:
*reg = (valve_get_movement() << 8) | (valve_get_state() & 0xFF);
break;
case REG_INPUT_MOTOR_CURRENT_MA:
*reg = valve_get_motor_current();
break;
case REG_INPUT_UPTIME_SECONDS_LOW:
*reg = (uint16_t)(uptime_s & 0xFFFF);
break;
case REG_INPUT_UPTIME_SECONDS_HIGH:
*reg = (uint16_t)(uptime_s >> 16);
break;
case REG_INPUT_FWU_LAST_CHUNK_CRC:
*reg = fwu_get_last_chunk_crc();
break;
case REG_INPUT_FIRMWARE_VERSION_MAJOR_MINOR:
*reg = (0 << 8) | 0;
break;
case REG_INPUT_FIRMWARE_VERSION_PATCH:
*reg = 2;
break;
default:
*reg = 0;
break;
}
return 0;
}
@@ -150,8 +156,28 @@ int modbus_server_init(void)
{
k_timer_init(&watchdog_timer, watchdog_timer_handler, NULL);
const char iface_name[] = {DEVICE_DT_NAME(MODBUS_NODE)};
#if DT_NODE_HAS_COMPAT(DT_PARENT(MODBUS_NODE), zephyr_cdc_acm_uart)
const struct device *const dev = DEVICE_DT_GET(DT_PARENT(MODBUS_NODE));
uint32_t dtr = 0;
if (!device_is_ready(dev) || usb_enable(NULL))
{
return 0;
}
while (!dtr)
{
uart_line_ctrl_get(dev, UART_LINE_CTRL_DTR, &dtr);
k_sleep(K_MSEC(100));
}
LOG_INF("Client connected to server on %s", dev->name);
#endif
modbus_iface = modbus_iface_get_by_name(iface_name);
if (modbus_iface < 0) { return modbus_iface; }
if (modbus_iface < 0)
{
return modbus_iface;
}
server_param.server.user_cb = &mbs_cbs;
return modbus_init_server(modbus_iface, server_param);
}
@@ -163,12 +189,13 @@ int modbus_reconfigure(uint32_t baudrate, uint8_t unit_id)
int ret = modbus_init_server(modbus_iface, server_param);
if (ret == 0) {
settings_save_one("modbus/baudrate", &baudrate, sizeof(baudrate));
settings_save_one("modbus/unit_id", &unit_id, sizeof(unit_id));
}
if (ret == 0)
{
settings_save_one("modbus/baudrate", &baudrate, sizeof(baudrate));
settings_save_one("modbus/unit_id", &unit_id, sizeof(unit_id));
}
return ret;
return ret;
}
uint32_t modbus_get_baudrate(void) { return server_param.serial.baud; }

View File

@@ -1,11 +0,0 @@
#ifndef MODBUS_SERVER_H
#define MODBUS_SERVER_H
#include <stdint.h>
int modbus_server_init(void);
int modbus_reconfigure(uint32_t baudrate, uint8_t unit_id);
uint32_t modbus_get_baudrate(void);
uint8_t modbus_get_unit_id(void);
#endif // MODBUS_SERVER_H

View File

@@ -0,0 +1 @@
zephyr_library_sources(shell_modbus.c)

View File

@@ -0,0 +1,5 @@
config SHELL_MODBUS
bool "Enable Shell Modbus"
default y
help
Enable the modnbus shell commands.

View File

@@ -1,7 +1,7 @@
#include <zephyr/shell/shell.h>
#include <stdlib.h>
#include "modbus_server.h"
#include "valve.h"
#include <lib/modbus_server.h>
#include <lib/valve.h>
static int cmd_modbus_set_baud(const struct shell *sh, size_t argc, char **argv)
{

View File

@@ -0,0 +1 @@
zephyr_library_sources(shell_system.c)

View File

@@ -0,0 +1,5 @@
config SHELL_SYSTEM
bool "Enable Shell System"
default y
help
Enable the system commands.

View File

@@ -0,0 +1 @@
zephyr_library_sources(valve.c)

View File

@@ -0,0 +1,5 @@
config LIB_VALVE
bool "Enable Valve Library"
default y
help
Enable the Valve Library.

View File

@@ -1,7 +1,7 @@
#include "valve.h"
#include <zephyr/kernel.h>
#include <zephyr/settings/settings.h>
#include <zephyr/logging/log.h>
#include <lib/valve.h>
LOG_MODULE_REGISTER(valve, LOG_LEVEL_INF);

View File

@@ -54,33 +54,74 @@ def format_uptime(seconds):
def poll_status(slave_id, interval):
global status_data
reconnect_attempts = 0
max_reconnect_attempts = 5
reconnect_delay = 1 # seconds
while not stop_event.is_set():
if update_status["running"]: time.sleep(interval); continue
new_data = {"error": None}
if update_status["running"]:
time.sleep(interval)
continue
new_data = {}
try:
if not client.is_socket_open():
reconnect_attempts += 1
if reconnect_attempts >= max_reconnect_attempts:
new_data["error"] = f"Failed to reconnect after {max_reconnect_attempts} attempts. Exiting."
stop_event.set()
break
# Attempt to connect
if client.connect():
reconnect_attempts = 0
new_data["error"] = None # Clear error on successful reconnect
else:
new_data["error"] = f"Connection lost. Attempting to reconnect ({reconnect_attempts}/{max_reconnect_attempts})..."
time.sleep(reconnect_delay)
continue
# If connected, try to read data
ir_valve = client.read_input_registers(REG_INPUT_VALVE_STATE_MOVEMENT, 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=5, slave=slave_id)
hr_valve = client.read_holding_registers(REG_HOLDING_MAX_OPENING_TIME_S, count=2, 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)
for res in [ir_valve, ir_dig, ir_sys, hr_valve, hr_dig, hr_sys]:
if res.isError(): raise ModbusException(str(res))
if res.isError():
raise ModbusException(str(res))
valve_state_raw = ir_valve.registers[0]
movement_map = {0: "Idle", 1: "Opening", 2: "Closing", 3: "Error"}; state_map = {0: "Closed", 1: "Open"}
new_data["movement"] = movement_map.get(valve_state_raw >> 8, 'Unknown'); new_data["state"] = state_map.get(valve_state_raw & 0xFF, 'Unknown')
new_data["motor_current"] = f"{ir_valve.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["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}"
movement_map = {0: "Idle", 1: "Opening", 2: "Closing", 3: "Error"}
state_map = {0: "Closed", 1: "Open"}
new_data["movement"] = movement_map.get(valve_state_raw >> 8, 'Unknown')
new_data["state"] = state_map.get(valve_state_raw & 0xFF, 'Unknown')
new_data["motor_current"] = f"{ir_valve.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["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}"
fw_major = ir_sys.registers[0] >> 8; fw_minor = ir_sys.registers[0] & 0xFF; fw_patch = ir_sys.registers[1]
fw_major = ir_sys.registers[0] >> 8
fw_minor = ir_sys.registers[0] & 0xFF
fw_patch = ir_sys.registers[1]
uptime_seconds = (ir_sys.registers[4] << 16) | ir_sys.registers[3]
new_data["firmware"] = f"v{fw_major}.{fw_minor}.{fw_patch}"; new_data["device_status"] = "OK" if ir_sys.registers[2] == 0 else "ERROR"
new_data["uptime"] = format_uptime(uptime_seconds); new_data["watchdog"] = f"{hr_sys.registers[0]}s"
new_data["firmware"] = f"v{fw_major}.{fw_minor}.{fw_patch}"
new_data["device_status"] = "OK" if ir_sys.registers[2] == 0 else "ERROR"
new_data["uptime"] = format_uptime(uptime_seconds)
new_data["watchdog"] = f"{hr_sys.registers[0]}s"
new_data["error"] = None # Clear any previous error on successful read
reconnect_attempts = 0 # Reset attempts on successful communication
except Exception as e:
new_data["error"] = f"Error: {e}"
with status_lock: status_data = new_data
time.sleep(interval)
new_data["error"] = f"Communication Error: {e}. Closing connection."
client.close() # Close connection to force reconnect attempt in next loop
finally:
with status_lock:
status_data = new_data
time.sleep(interval)
def firmware_update_thread(slave_id, filepath):
global update_status
@@ -225,11 +266,11 @@ def main_menu(stdscr, slave_id):
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":
client.write_register(REG_HOLDING_DEVICE_RESET, 1, slave=slave_id)
message = "-> Sent RESET command"
elif selected_option == "Firmware Update":
client.write_register(REG_HOLDING_DEVICE_RESET, 1, slave=slave_id)
message = "-> Sent RESET command"
try:
client.write_register(REG_HOLDING_DEVICE_RESET, 1, slave=slave_id)
message = "-> Sent RESET command. Node should reboot."
except Exception as e:
message = f"-> Error sending reset: {e}"
elif selected_option == "Firmware Update":
filepath = file_browser(stdscr)
if filepath:
@@ -245,9 +286,9 @@ def main_menu(stdscr, slave_id):
stdscr.addstr(h // 2 + 1, w // 2 - 25, msg.ljust(50))
else:
with status_lock: current_data = status_data.copy()
bold, normal = curses.color_pair(1) | curses.A_BOLD, curses.color_pair(1)
if current_data.get("error"): stdscr.addstr(0, 0, current_data["error"], curses.color_pair(3) | curses.A_BOLD)
else:
bold, normal = curses.color_pair(1) | curses.A_BOLD, curses.color_pair(1)
col1, col2, col3, col4 = 2, 30, 58, 88
stdscr.addstr(1, col1, "State:", bold); stdscr.addstr(1, col1 + 18, str(current_data.get('state', 'N/A')), normal)
stdscr.addstr(2, col1, "Movement:", bold); stdscr.addstr(2, col1 + 18, str(current_data.get('movement', 'N/A')), normal)