13 Commits

Author SHA1 Message Date
2c21f1f9cb feat: Revert ADC changes and set channel 12 for VREFINT 2025-07-06 15:27:14 +02:00
a2afec52e2 fix(adc_test): correct ADC reference configuration
- Revert ADC_GAIN to ADC_GAIN_1 due to driver limitation.
- Revert ADC_REF to ADC_REF_INTERNAL due to driver limitation.
- Set zephyr,vref-mv to 3300 to match observed hardware behavior.
2025-07-06 10:24:37 +02:00
2cc258e8e2 feat(adc_test): use devicetree for adc configuration
- Use named adc channel 'multisense' from devicetree
- Enable adc calibration
2025-07-06 09:55:10 +02:00
a77298b3a6 Implement VND7050AJ supply voltage reading function
- Added devicetree overlay for VND7050AJ with GPIO and ADC configuration
- Created custom devicetree binding for VND7050AJ valve controller
- Implemented valve_get_supply_voltage() function with proper pin control:
  - RST=HIGH to enable VND7050AJ
  - S0=1, S1=1 for supply voltage sensing mode
  - SEN=1 to enable MULTISENSE output
  - ADC reading on PA0 (ADC1_IN1) with 12-bit resolution
- Fixed supply voltage calculation (VCC/8 per datasheet)
- Added comprehensive debug logging for all steps
- Tested and verified ADC functionality
- Current reading: 5.1V (may be limited by hardware power supply)

Files modified:
- software/lib/valve/valve.c: Main implementation
- software/apps/slave_node/boards/weact_stm32g431_core.overlay: DT config
- software/apps/slave_node/dts/bindings/vnd7050aj-valve-controller.yaml: DT binding
- software/apps/slave_node/src/main.c: Test code
- software/apps/slave_node/prj.conf: ADC driver enablement
2025-07-04 08:54:47 +02:00
45d011952f fix(valve): Correct VND7050AJ initialization and pin configuration
- Initialize RST pin as active to keep VND7050AJ out of reset state
- Clarify S0/S1 pins as output select pins with descriptive comments
- Add initialization logging to show configured max open/close times
- Ensure proper valve controller startup sequence
2025-07-03 19:04:20 +02:00
bb25134b6c feat(modbus): Implement persistent and improved reconfiguration for Modbus server
This commit enhances the Modbus server's configuration handling by:

- Loading saved baudrate and unit ID settings during initialization, ensuring persistence across reboots.
- Providing improved feedback during `modbus_reconfigure`, including logging for successful changes and informing the user when a device restart is required for changes to take effect.
- Saving new configuration settings even if immediate reinitialization fails, allowing them to be applied on the next boot.
2025-07-03 18:59:01 +02:00
9f96384aa5 fix(cdc-acm): Correct CDC ACM overlay configuration
This commit fixes an issue in the `cdc-acm.overlay` file.
2025-07-03 18:57:06 +02:00
b543579393 feat(modbus): Add supply voltage register and display in tool
This commit introduces a new Modbus input register for the system's supply voltage.

- The `modbus-registers.de.md` documentation is updated to include the `SUPPLY_VOLTAGE_MV` register at address `0x00F5` within the system block.
- The `modbus_server.h` header defines the new register.
- The `modbus_server.c` implementation provides a fixed value (12300 mV) for this register.
- The `modbus_tool.py` script is updated to read and display this new supply voltage value in the UI.

This lays the groundwork for integrating actual voltage measurements in the future.
2025-07-03 18:47:48 +02:00
69cf7e9511 feat(valve): Implement GPIO control for VND7050AJ
This commit implements the real valve control using the GPIOs connected to the VND7050AJ driver.

- The `weact_stm32g431_core.overlay` is updated with a specific compatible string and a device tree label for the valve controller.
- `valve.h` is extended to include GPIO device specifications.
- `valve.c` now initializes and controls the GPIOs for opening and closing the valve, including the reset logic. The IN0 and IN1 pins are interlocked to prevent simultaneous activation. The RST pin is activated before each movement and deactivated afterward.

This replaces the previous virtual/simulated valve logic with actual hardware control.
2025-07-03 18:17:31 +02:00
8df7aef51b Removed unused lib dir 2025-07-03 17:47:48 +02:00
f6ee0a5122 feat(weact_stm32g431_core): Configure VND7050AJ driver pins in overlay
Updated the weact_stm32g431_core.overlay to define the GPIO and ADC
pin assignments for the VND7050AJ driver. This includes:
- Digital I/O pins (IN0, IN1, RST, S0, S1, SEN) configured as GPIOs.
- Analog input pin (MULTISENSE/PA0) configured for ADC1.
2025-07-03 17:39:04 +02:00
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
54 changed files with 781 additions and 228 deletions

View File

@@ -0,0 +1,7 @@
feat(modbus): Implement persistent and improved reconfiguration for Modbus server
This commit enhances the Modbus server's configuration handling by:
- Loading saved baudrate and unit ID settings during initialization, ensuring persistence across reboots.
- Providing improved feedback during `modbus_reconfigure`, including logging for successful changes and informing the user when a device restart is required for changes to take effect.
- Saving new configuration settings even if immediate reinitialization fails, allowing them to be applied on the next boot.

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

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

View File

@@ -38,6 +38,7 @@ Alle Register sind in einer einzigen, durchgehenden Liste pro Register-Typ (`Inp
| **0x00F2** | `DEVICE_STATUS` | System | `0`=OK, `1`=Allgemeiner Fehler. |
| **0x00F3** | `UPTIME_SECONDS_LOW` | System | Untere 16 Bit der Uptime in Sekunden. |
| **0x00F4** | `UPTIME_SECONDS_HIGH` | System | Obere 16 Bit der Uptime. |
| **0x00F5** | `SUPPLY_VOLTAGE_MV` | System | Aktuelle Versorgungsspannung in Millivolt (mV). |
| **0x0100** | `FWU_LAST_CHUNK_CRC` | Firmware-Update | Enthält den CRC16 des zuletzt im Puffer empfangenen Daten-Chunks. |
## 3. Holding Registers (4xxxx, Read/Write)

View File

@@ -1,8 +0,0 @@
cmake_minimum_required(VERSION 3.13.1)
project(software)
add_subdirectory(modules/modbus_server)
add_subdirectory(modules/valve)
add_subdirectory(modules/fwu)
add_subdirectory(apps/stm32g431_tests)

1
software/Kconfig Normal file
View File

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

View File

@@ -0,0 +1,34 @@
/{
zephyr,user {
io-channels = <&adc1 1>, <&adc1 12>;
};
};
&adc1 {
status = "okay";
pinctrl-0 = <&adc1_in1_pa0>;
pinctrl-names = "default";
st,adc-clock-source = "SYNC";
st,adc-prescaler = <4>;
#address-cells = <1>;
#size-cells = <0>;
channel@1 {
reg = <1>;
zephyr,gain = "ADC_GAIN_1";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
zephyr,resolution = <12>;
};
channel@c {
reg = <0xc>;
zephyr,gain = "ADC_GAIN_1";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
zephyr,resolution = <12>;
};
};

View File

@@ -0,0 +1,102 @@
/*
* Copyright (c) 2020 Libre Solar Technologies GmbH
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <inttypes.h>
#include <stddef.h>
#include <stdint.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys/util.h>
#if !DT_NODE_EXISTS(DT_PATH(zephyr_user)) || \
!DT_NODE_HAS_PROP(DT_PATH(zephyr_user), io_channels)
#error "No suitable devicetree overlay specified"
#endif
#define DT_SPEC_AND_COMMA(node_id, prop, idx) \
ADC_DT_SPEC_GET_BY_IDX(node_id, idx),
/* Data of ADC io-channels specified in devicetree. */
static const struct adc_dt_spec adc_channels[] = {
DT_FOREACH_PROP_ELEM(DT_PATH(zephyr_user), io_channels,
DT_SPEC_AND_COMMA)
};
int main(void)
{
int err;
uint32_t count = 0;
uint16_t buf;
struct adc_sequence sequence = {
.buffer = &buf,
/* buffer size in bytes, not number of samples */
.buffer_size = sizeof(buf),
};
/* Configure channels individually prior to sampling. */
for (size_t i = 0U; i < ARRAY_SIZE(adc_channels); i++) {
if (!adc_is_ready_dt(&adc_channels[i])) {
printk("ADC controller device %s not ready\n", adc_channels[i].dev->name);
return 0;
}
err = adc_channel_setup_dt(&adc_channels[i]);
if (err < 0) {
printk("Could not setup channel #%d (%d)\n", i, err);
return 0;
}
}
#ifndef CONFIG_COVERAGE
while (1) {
#else
for (int k = 0; k < 10; k++) {
#endif
printk("ADC reading[%u]:\n", count++);
for (size_t i = 0U; i < ARRAY_SIZE(adc_channels); i++) {
int32_t val_mv;
printk("- %s, channel %d: ",
adc_channels[i].dev->name,
adc_channels[i].channel_id);
(void)adc_sequence_init_dt(&adc_channels[i], &sequence);
err = adc_read_dt(&adc_channels[i], &sequence);
if (err < 0) {
printk("Could not read (%d)\n", err);
continue;
}
/*
* If using differential mode, the 16 bit value
* in the ADC sample buffer should be a signed 2's
* complement value.
*/
if (adc_channels[i].channel_cfg.differential) {
val_mv = (int32_t)((int16_t)buf);
} else {
val_mv = (int32_t)buf;
}
printk("%"PRId32, val_mv);
err = adc_raw_to_millivolts_dt(&adc_channels[i],
&val_mv);
/* conversion to mV may not be supported, skip if not */
if (err < 0) {
printk(" (value in mV not available)\n");
} else {
printk(" = %"PRId32" mV\n", val_mv);
}
}
k_sleep(K_MSEC(1000));
}
return 0;
}

View File

@@ -0,0 +1,31 @@
/ {
zephyr,user {
io-channels = <&adc1 1>;
io-channel-names = "multisense";
};
};
&adc1 {
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
st,adc-clock-source = "SYNC";
st,adc-prescaler = <4>;
pinctrl-0 = <&adc1_in1_pa0>;
pinctrl-names = "default";
channel@1 {
reg = <1>;
zephyr,gain = "ADC_GAIN_1";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_MAX>;
zephyr,resolution = <12>;
zephyr,vref-mv = <3300>;
};
};
&pinctrl {
adc1_in1_pa0: adc1_in1_pa0 {
pinmux = <STM32_PINMUX('A', 0, ANALOG)>;
};
};

View File

@@ -0,0 +1,3 @@
CONFIG_ADC=y
CONFIG_ADC_STM32=y
CONFIG_LOG=y

View File

@@ -0,0 +1,62 @@
/*
* Copyright (c) 2024 Your Name
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(adc_test, LOG_LEVEL_DBG);
#if !DT_NODE_EXISTS(DT_PATH(zephyr_user))
#error "zephyr,user node not found"
#endif
static const struct adc_dt_spec adc_channel = ADC_DT_SPEC_GET_BY_NAME(DT_PATH(zephyr_user), multisense);
int main(void)
{
int err;
if (!device_is_ready(adc_channel.dev)) {
LOG_ERR("ADC device not found: %s", adc_channel.dev->name);
return 0;
}
err = adc_channel_setup_dt(&adc_channel);
if (err < 0) {
LOG_ERR("Could not setup channel #%d, error %d", adc_channel.channel_id, err);
return 0;
}
while (1) {
int16_t buffer[1];
struct adc_sequence sequence = {
.channels = BIT(adc_channel.channel_id),
.buffer = buffer,
.buffer_size = sizeof(buffer),
.resolution = adc_channel.resolution,
.calibrate = true,
};
err = adc_read(adc_channel.dev, &sequence);
if (err < 0) {
LOG_ERR("Could not read ADC, error %d", err);
continue;
}
int32_t millivolts = buffer[0];
err = adc_raw_to_millivolts_dt(&adc_channel, &millivolts);
if (err < 0) {
LOG_ERR("Could not convert to millivolts (%d)", err);
continue;
}
LOG_INF("ADC raw: %d, mV: %d", buffer[0], millivolts);
k_msleep(500);
}
return 0;
}

View File

@@ -1,6 +0,0 @@
cmake_minimum_required(VERSION 3.20)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(hello_world)
target_sources(app PRIVATE src/main.c)

View File

@@ -1,4 +0,0 @@
CONFIG_CONSOLE=y
CONFIG_LOG=y
CONFIG_UART_CONSOLE=y

View File

@@ -1,9 +0,0 @@
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
int main(void)
{
printk("Hello World! %s\n", CONFIG_BOARD);
return 0;
}

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)
list(APPEND KCONFIG_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/../../modules/modbus_server/Kconfig)
list(APPEND KCONFIG_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/../../modules/valve/Kconfig)
list(APPEND KCONFIG_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/../../modules/fwu/Kconfig)
target_include_directories(app PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/../../modules/valve/include
${CMAKE_CURRENT_SOURCE_DIR}/../../modules/modbus_server/include
${CMAKE_CURRENT_SOURCE_DIR}/../../modules/fwu/include
)
# 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
${CMAKE_CURRENT_SOURCE_DIR}/../../modules/valve/src/valve.c
${CMAKE_CURRENT_SOURCE_DIR}/../../modules/modbus_server/src/modbus_server.c
${CMAKE_CURRENT_SOURCE_DIR}/../../modules/fwu/src/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

@@ -4,3 +4,4 @@ CONFIG_UART_CONSOLE=n
# Enable RTT console
CONFIG_RTT_CONSOLE=y
CONFIG_USE_SEGGER_RTT=y
CONFIG_SHELL_BACKEND_RTT=y

View File

@@ -1,9 +1,50 @@
/ {
vnd7050aj: vnd7050aj {
compatible = "vnd7050aj-valve-controller";
status = "okay";
// VND7050AJ GPIO pin definitions
in0-gpios = <&gpiob 7 GPIO_ACTIVE_HIGH>; // IN0 (PB7) - Input 0 control signal
in1-gpios = <&gpiob 9 GPIO_ACTIVE_HIGH>; // IN1 (PB9) - Input 1 control signal
rst-gpios = <&gpiob 3 GPIO_ACTIVE_HIGH>; // RST (PB3) - Reset pin for VND7050AJ
sen-gpios = <&gpiob 4 GPIO_ACTIVE_HIGH>; // SEN (PB4) - Sense Enable for current monitoring
s0-gpios = <&gpiob 6 GPIO_ACTIVE_HIGH>; // S0 (PB6) - Status/Select 0 output from VND7050AJ
s1-gpios = <&gpiob 5 GPIO_ACTIVE_HIGH>; // S1 (PB5) - Status/Select 1 output from VND7050AJ
};
};
&usart1 {
modbus0 {
compatible = "zephyr,modbus-serial";
status = "okay";
};
status = "okay";
pinctrl-0 = <&usart1_tx_pa9 &usart1_rx_pa10>;
pinctrl-0 = <&usart1_tx_pa9 &usart1_rx_pa10>; // PA9=TX, PA10=RX for Modbus communication
pinctrl-names = "default";
};
&adc1 {
status = "okay";
pinctrl-0 = <&adc1_in1_pa0>;
pinctrl-names = "default";
st,adc-clock-source = "SYNC";
st,adc-prescaler = <4>;
#address-cells = <1>;
#size-cells = <0>;
channel@1 {
reg = <1>;
zephyr,gain = "ADC_GAIN_1";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
zephyr,resolution = <12>;
zephyr,vref-mv = <3300>;
};
};
&pinctrl {
// Pinmux für PA0 als ADC1_IN1 (Analogmodus)
adc1_in1_pa0: adc1_in1_pa0 {
pinmux = <STM32_PINMUX('A', 0, ANALOG)>; // PA0 in den Analogmodus setzen
};
};

View File

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

View File

@@ -0,0 +1,35 @@
# VND7050AJ Valve Controller binding
description: VND7050AJ valve controller GPIO configuration
compatible: "vnd7050aj-valve-controller"
properties:
in0-gpios:
type: phandle-array
description: GPIO for IN0 control signal
required: true
in1-gpios:
type: phandle-array
description: GPIO for IN1 control signal
required: true
rst-gpios:
type: phandle-array
description: GPIO for reset pin
required: true
sen-gpios:
type: phandle-array
description: GPIO for sense enable pin
required: true
s0-gpios:
type: phandle-array
description: GPIO for select 0 pin
required: true
s1-gpios:
type: phandle-array
description: GPIO for select 1 pin
required: true

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

@@ -4,7 +4,6 @@ CONFIG_LOG=y
# Enable Shell
CONFIG_SHELL=y
CONFIG_SHELL_BACKEND_RTT=y
CONFIG_REBOOT=y
# Enable Settings Subsystem
@@ -22,3 +21,7 @@ CONFIG_MODBUS=y
CONFIG_MODBUS_ROLE_SERVER=y
CONFIG_MODBUS_BUFFER_SIZE=256
# Enable ADC driver
CONFIG_ADC=y
CONFIG_ADC_STM32=y

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);
@@ -23,6 +23,10 @@ int main(void)
return 0;
}
// Test supply voltage reading
uint16_t supply_voltage = valve_get_supply_voltage();
LOG_INF("Supply voltage: %u mV", supply_voltage);
LOG_INF("Irrigation System Slave Node started successfully");
return 0;
}

View File

@@ -1,6 +0,0 @@
cmake_minimum_required(VERSION 3.20)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(stm32g431_tests)
target_sources(app PRIVATE src/main.c)

View File

@@ -1,4 +0,0 @@
CONFIG_CONSOLE=y
CONFIG_LOG=y
CONFIG_UART_CONSOLE=y

View File

@@ -1,9 +0,0 @@
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
int main(void)
{
printk("Hello World! %s\n", CONFIG_BOARD);
return 0;
}

View File

@@ -0,0 +1,54 @@
#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,
REG_INPUT_SUPPLY_VOLTAGE_MV = 0x00F5,
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

@@ -2,8 +2,21 @@
#define VALVE_H
#include <stdint.h>
#include <zephyr/drivers/gpio.h>
enum valve_state { VALVE_STATE_CLOSED, VALVE_STATE_OPEN };
struct valve_gpios {
const struct gpio_dt_spec in0;
const struct gpio_dt_spec in1;
const struct gpio_dt_spec rst;
const struct gpio_dt_spec sen;
const struct gpio_dt_spec s0;
const struct gpio_dt_spec s1;
};
enum valve_state {
VALVE_STATE_CLOSED,
VALVE_STATE_OPEN,
};
enum valve_movement { VALVE_MOVEMENT_IDLE, VALVE_MOVEMENT_OPENING, VALVE_MOVEMENT_CLOSING, VALVE_MOVEMENT_ERROR };
void valve_init(void);
@@ -14,6 +27,7 @@ void valve_stop(void);
enum valve_state valve_get_state(void);
enum valve_movement valve_get_movement(void);
uint16_t valve_get_motor_current(void);
uint16_t valve_get_supply_voltage(void);
void valve_set_max_open_time(uint16_t seconds);
void valve_set_max_close_time(uint16_t seconds);

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)

View File

@@ -1,5 +1,5 @@
config FWU
config LIB_FWU
bool "Enable Firmware Update Library"
default y
help
Enable the Firmware Update module.
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

@@ -5,9 +5,9 @@
#include <zephyr/logging/log.h>
#include <zephyr/settings/settings.h>
#include <zephyr/sys/reboot.h>
#include "modbus_server.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>
@@ -128,6 +128,9 @@ static int input_reg_rd(uint16_t addr, uint16_t *reg)
case REG_INPUT_UPTIME_SECONDS_HIGH:
*reg = (uint16_t)(uptime_s >> 16);
break;
case REG_INPUT_SUPPLY_VOLTAGE_MV:
*reg = 12300;
break;
case REG_INPUT_FWU_LAST_CHUNK_CRC:
*reg = fwu_get_last_chunk_crc();
break;
@@ -135,7 +138,7 @@ static int input_reg_rd(uint16_t addr, uint16_t *reg)
*reg = (0 << 8) | 0;
break;
case REG_INPUT_FIRMWARE_VERSION_PATCH:
*reg = 1;
*reg = 2;
break;
default:
*reg = 0;
@@ -155,6 +158,17 @@ static struct modbus_user_callbacks mbs_cbs = {
int modbus_server_init(void)
{
k_timer_init(&watchdog_timer, watchdog_timer_handler, NULL);
// Load saved settings
uint32_t saved_baudrate = 19200;
uint8_t saved_unit_id = 1;
settings_load_one("modbus/baudrate", &saved_baudrate, sizeof(saved_baudrate));
settings_load_one("modbus/unit_id", &saved_unit_id, sizeof(saved_unit_id));
// Apply loaded settings
server_param.serial.baud = saved_baudrate;
server_param.server.unit_id = saved_unit_id;
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));
@@ -179,20 +193,37 @@ int modbus_server_init(void)
return modbus_iface;
}
server_param.server.user_cb = &mbs_cbs;
LOG_INF("Starting Modbus server: baudrate=%u, unit_id=%u", saved_baudrate, saved_unit_id);
return modbus_init_server(modbus_iface, server_param);
}
int modbus_reconfigure(uint32_t baudrate, uint8_t unit_id)
{
// Update parameters
server_param.serial.baud = baudrate;
server_param.server.unit_id = unit_id;
// Try to reinitialize - this should work for most cases
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));
LOG_INF("Modbus reconfigured: baudrate=%u, unit_id=%u", baudrate, unit_id);
}
else
{
LOG_ERR("Failed to reconfigure Modbus: %d", ret);
LOG_INF("Modbus reconfiguration requires restart to take effect");
// Save settings for next boot
settings_save_one("modbus/baudrate", &baudrate, sizeof(baudrate));
settings_save_one("modbus/unit_id", &unit_id, sizeof(unit_id));
LOG_INF("Settings saved. Type 'reset' to restart the device and apply the change.");
return 0; // Return success since settings are saved
}
return ret;

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

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

178
software/lib/valve/valve.c Normal file
View File

@@ -0,0 +1,178 @@
#include <zephyr/kernel.h>
#include <zephyr/settings/settings.h>
#include <zephyr/logging/log.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/adc.h>
#include <lib/valve.h>
LOG_MODULE_REGISTER(valve, LOG_LEVEL_DBG);
// ADC configuration for MULTISENSE (PA0)
static const struct device *adc_dev = DEVICE_DT_GET(DT_NODELABEL(adc1));
static const struct adc_channel_cfg adc_channel_cfg = {
.gain = ADC_GAIN_1,
.reference = ADC_REF_INTERNAL,
.acquisition_time = ADC_ACQ_TIME_DEFAULT,
.channel_id = 1, // ADC1_IN1 (PA0)
};
static const struct valve_gpios valve_gpios = {
.in0 = GPIO_DT_SPEC_GET(DT_NODELABEL(vnd7050aj), in0_gpios),
.in1 = GPIO_DT_SPEC_GET(DT_NODELABEL(vnd7050aj), in1_gpios),
.rst = GPIO_DT_SPEC_GET(DT_NODELABEL(vnd7050aj), rst_gpios),
.sen = GPIO_DT_SPEC_GET(DT_NODELABEL(vnd7050aj), sen_gpios),
.s0 = GPIO_DT_SPEC_GET(DT_NODELABEL(vnd7050aj), s0_gpios),
.s1 = GPIO_DT_SPEC_GET(DT_NODELABEL(vnd7050aj), s1_gpios),
};
static enum valve_state current_state = VALVE_STATE_CLOSED;
static enum valve_movement current_movement = VALVE_MOVEMENT_IDLE;
static uint16_t max_opening_time_s = 60;
static uint16_t max_closing_time_s = 60;
static struct k_work_delayable valve_work;
static void valve_work_handler(struct k_work *work)
{
gpio_pin_set_dt(&valve_gpios.in0, 0);
gpio_pin_set_dt(&valve_gpios.in1, 0);
gpio_pin_set_dt(&valve_gpios.rst, 0);
if (current_movement == VALVE_MOVEMENT_OPENING) {
LOG_INF("Valve finished opening");
} else if (current_movement == VALVE_MOVEMENT_CLOSING) {
current_state = VALVE_STATE_CLOSED;
LOG_INF("Valve finished closing");
}
current_movement = VALVE_MOVEMENT_IDLE;
}
void valve_init(void)
{
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));
// Initialize ADC for MULTISENSE
if (!device_is_ready(adc_dev)) {
LOG_ERR("ADC device not ready");
return;
}
int ret = adc_channel_setup(adc_dev, &adc_channel_cfg);
if (ret < 0) {
LOG_ERR("Could not setup ADC channel (%d)", ret);
return;
}
gpio_pin_configure_dt(&valve_gpios.in0, GPIO_OUTPUT_INACTIVE); // IN0 control pin - output, deactivate
gpio_pin_configure_dt(&valve_gpios.in1, GPIO_OUTPUT_INACTIVE); // IN1 control pin - output, deactivate
gpio_pin_configure_dt(&valve_gpios.rst, GPIO_OUTPUT_INACTIVE); // Keep VND7050AJ in reset
gpio_pin_configure_dt(&valve_gpios.sen, GPIO_OUTPUT_INACTIVE); // Sensor enable pin - output, inactive
// S0 and S1 pins are used for selecting the valve state, they are initially inactive
// and will be set to active when the valve is opened or closed.
gpio_pin_configure_dt(&valve_gpios.s0, GPIO_OUTPUT_INACTIVE); // S0 select pin - output
gpio_pin_configure_dt(&valve_gpios.s1, GPIO_OUTPUT_INACTIVE); // S1 select pin - output
LOG_INF("Valve initialized: max_open=%us, max_close=%us", max_opening_time_s, max_closing_time_s);
}
void valve_open(void)
{
if (current_state == VALVE_STATE_CLOSED) {
gpio_pin_set_dt(&valve_gpios.rst, 1);
gpio_pin_set_dt(&valve_gpios.in1, 0);
gpio_pin_set_dt(&valve_gpios.in0, 1);
current_state = VALVE_STATE_OPEN;
current_movement = VALVE_MOVEMENT_OPENING;
k_work_schedule(&valve_work, K_MSEC(max_opening_time_s * 1000 * 0.9));
}
}
void valve_close(void)
{
if (current_state == VALVE_STATE_OPEN) {
gpio_pin_set_dt(&valve_gpios.rst, 1);
gpio_pin_set_dt(&valve_gpios.in0, 0);
gpio_pin_set_dt(&valve_gpios.in1, 1);
current_movement = VALVE_MOVEMENT_CLOSING;
k_work_schedule(&valve_work, K_MSEC(max_closing_time_s * 1000 * 0.9));
}
}
void valve_stop(void)
{
k_work_cancel_delayable(&valve_work);
current_movement = VALVE_MOVEMENT_IDLE;
}
enum valve_state valve_get_state(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; }
uint16_t valve_get_supply_voltage(void)
{
LOG_DBG("Starting supply voltage measurement");
// Ensure VND7050AJ is enabled (RST=HIGH)
LOG_DBG("Enabling VND7050AJ (RST=1)");
gpio_pin_set_dt(&valve_gpios.rst, 1);
// Wait for VND7050AJ to power up and stabilize
k_msleep(50);
int16_t buf;
struct adc_sequence sequence = {
.buffer = &buf,
.buffer_size = sizeof(buf),
.channels = BIT(adc_channel_cfg.channel_id),
.resolution = 12,
};
// Configure VND7050AJ to output supply voltage on MULTISENSE
// According to VND7050AJ datasheet page 20:
// S0=1, S1=1: Supply voltage sensing mode
LOG_DBG("Setting S0=1, S1=1 for supply voltage sensing");
gpio_pin_set_dt(&valve_gpios.s0, 1);
gpio_pin_set_dt(&valve_gpios.s1, 1);
// Enable sensing
LOG_DBG("Enabling MULTISENSE (SEN=1)");
gpio_pin_set_dt(&valve_gpios.sen, 1);
// Wait for voltage to stabilize
k_msleep(10);
// Read ADC value
LOG_DBG("Reading ADC channel %d", adc_channel_cfg.channel_id);
int ret = adc_read(adc_dev, &sequence);
if (ret < 0) {
LOG_ERR("Could not read ADC (%d)", ret);
gpio_pin_set_dt(&valve_gpios.sen, 0);
return 0;
}
// Disable sensing to save power
LOG_DBG("Disabling MULTISENSE (SEN=0)");
gpio_pin_set_dt(&valve_gpios.sen, 0);
// Convert ADC value to millivolts
// VDD = 3.3V, ADC resolution = 12-bit (4096 steps)
// ADC voltage = (buf / 4096) * 3300 mV
int32_t val_mv = ((int32_t)buf * 3300) / 4096;
// VND7050AJ MULTISENSE voltage divider:
// According to datasheet page 35, MULTISENSE = VCC / 8 (8:1 voltage divider)
// So actual supply voltage = MULTISENSE * 8
uint16_t supply_voltage_mv = (uint16_t)(val_mv * 8);
LOG_INF("Supply voltage: %u mV (ADC raw: %d, ADC mV: %d)",
supply_voltage_mv, buf, (int)val_mv);
return supply_voltage_mv;
}
void valve_set_max_open_time(uint16_t seconds) { max_opening_time_s = seconds; settings_save_one("valve/max_open_time", &max_opening_time_s, sizeof(max_opening_time_s)); }
void valve_set_max_close_time(uint16_t seconds) { max_closing_time_s = seconds; settings_save_one("valve/max_close_time", &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; }

View File

@@ -1,7 +0,0 @@
cmake_minimum_required(VERSION 3.13.1)
include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE)
project(fwu)
target_sources(app PRIVATE src/fwu.c)
target_include_directories(app PUBLIC include)

View File

@@ -1,7 +0,0 @@
cmake_minimum_required(VERSION 3.13.1)
include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE)
project(modbus_server)
target_sources(app PRIVATE src/modbus_server.c)
target_include_directories(app PUBLIC include)

View File

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

View File

@@ -1,52 +0,0 @@
#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

@@ -1,7 +0,0 @@
cmake_minimum_required(VERSION 3.13.1)
include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE)
project(valve)
target_sources(app PRIVATE src/valve.c)
target_include_directories(app PUBLIC include)

View File

@@ -1,62 +0,0 @@
#include "valve.h"
#include <zephyr/kernel.h>
#include <zephyr/settings/settings.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(valve, LOG_LEVEL_INF);
static enum valve_state current_state = VALVE_STATE_CLOSED;
static enum valve_movement current_movement = VALVE_MOVEMENT_IDLE;
static uint16_t max_opening_time_s = 60;
static uint16_t max_closing_time_s = 60;
static struct k_work_delayable valve_work;
static void valve_work_handler(struct k_work *work)
{
if (current_movement == VALVE_MOVEMENT_OPENING) {
LOG_INF("Virtual valve finished opening");
} else if (current_movement == VALVE_MOVEMENT_CLOSING) {
current_state = VALVE_STATE_CLOSED;
LOG_INF("Virtual valve finished closing");
}
current_movement = VALVE_MOVEMENT_IDLE;
}
void valve_init(void)
{
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));
}
void valve_open(void)
{
if (current_state == VALVE_STATE_CLOSED) {
current_state = VALVE_STATE_OPEN;
current_movement = VALVE_MOVEMENT_OPENING;
k_work_schedule(&valve_work, K_SECONDS(max_opening_time_s));
}
}
void valve_close(void)
{
if (current_state == VALVE_STATE_OPEN) {
current_movement = VALVE_MOVEMENT_CLOSING;
k_work_schedule(&valve_work, K_SECONDS(max_closing_time_s));
}
}
void valve_stop(void)
{
k_work_cancel_delayable(&valve_work);
current_movement = VALVE_MOVEMENT_IDLE;
}
enum valve_state valve_get_state(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; }
void valve_set_max_open_time(uint16_t seconds) { max_opening_time_s = seconds; settings_save_one("valve/max_open_time", &max_opening_time_s, sizeof(max_opening_time_s)); }
void valve_set_max_close_time(uint16_t seconds) { max_closing_time_s = seconds; settings_save_one("valve/max_close_time", &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; }

View File

@@ -0,0 +1,39 @@
#!/usr/bin/env python3
import serial
import time
import sys
def monitor_serial():
try:
# Open serial connection
ser = serial.Serial('/dev/ttyACM3', 115200, timeout=1)
print("Connected to /dev/ttyACM3")
# Send reset command
ser.write(b'reset\n')
print("Sent reset command")
# Wait a bit and then read output
time.sleep(0.5)
# Read output for 10 seconds
start_time = time.time()
while time.time() - start_time < 10:
if ser.in_waiting > 0:
data = ser.read(ser.in_waiting)
try:
text = data.decode('utf-8', errors='ignore')
print(text, end='')
except:
print(f"Raw bytes: {data}")
time.sleep(0.1)
ser.close()
print("\nSerial monitor closed")
except Exception as e:
print(f"Error: {e}")
sys.exit(1)
if __name__ == "__main__":
monitor_serial()

View File

@@ -0,0 +1,55 @@
#!/usr/bin/env python3
import serial
import time
import sys
def monitor_serial_with_reset():
try:
# Open serial port
ser = serial.Serial('/dev/ttyACM3', 115200, timeout=1)
print("Serial port opened successfully")
# Clear any existing data
ser.flushInput()
ser.flushOutput()
# Send reset command
print("Sending reset command...")
ser.write(b"reset\n")
time.sleep(0.1)
# Read output for 10 seconds
print("Reading serial output...")
start_time = time.time()
output_lines = []
while time.time() - start_time < 10:
if ser.in_waiting > 0:
try:
line = ser.readline().decode('utf-8', errors='replace').strip()
if line:
print(f"[{time.time() - start_time:.3f}s] {line}")
output_lines.append(line)
except Exception as e:
print(f"Error reading line: {e}")
time.sleep(0.01)
ser.close()
print("\nSerial monitoring complete")
# Summary
print("\n=== SUMMARY ===")
supply_voltage_lines = [line for line in output_lines if "Supply voltage" in line]
if supply_voltage_lines:
print("Supply voltage readings:")
for line in supply_voltage_lines:
print(f" {line}")
else:
print("No supply voltage readings found")
except Exception as e:
print(f"Error: {e}")
sys.exit(1)
if __name__ == "__main__":
monitor_serial_with_reset()

View File

@@ -19,6 +19,7 @@ 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
@@ -84,7 +85,7 @@ def poll_status(slave_id, interval):
# 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)
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_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)
@@ -109,9 +110,11 @@ def poll_status(slave_id, interval):
fw_minor = ir_sys.registers[0] & 0xFF
fw_patch = ir_sys.registers[1]
uptime_seconds = (ir_sys.registers[4] << 16) | ir_sys.registers[3]
supply_voltage_mv = ir_sys.registers[5]
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["supply_voltage"] = f"{supply_voltage_mv / 1000.0:.2f} V"
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
@@ -302,6 +305,7 @@ def main_menu(stdscr, slave_id):
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(5, 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)