Compare commits
No commits in common. "d76b897eb2586268ae119e697c533cfa0bc85ba8" and "6f304efb57d0ef31ee059b10806d9a4e988345e0" have entirely different histories.
d76b897eb2
...
6f304efb57
|
|
@ -1,3 +0,0 @@
|
||||||
[submodule "software/modules/zephyr_vnd7050aj_driver"]
|
|
||||||
path = software/modules/zephyr_vnd7050aj_driver
|
|
||||||
url = https://gitea.iten.pro/edi/zephyr_vnd7050aj_driver.git
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
{
|
|
||||||
"version": "2.0.0",
|
|
||||||
"tasks": [
|
|
||||||
{
|
|
||||||
"type": "shell",
|
|
||||||
"label": "Build Zephyr app",
|
|
||||||
"command": "west build -b weact_stm32g431_core .",
|
|
||||||
"group": "build",
|
|
||||||
"problemMatcher": [
|
|
||||||
"$gcc"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
cmake_minimum_required(VERSION 3.20.5)
|
cmake_minimum_required(VERSION 3.20)
|
||||||
|
|
||||||
|
# Include the main 'software' directory as a module to find boards, libs, etc.
|
||||||
|
list(APPEND ZEPHYR_EXTRA_MODULES ${CMAKE_CURRENT_SOURCE_DIR}/../..)
|
||||||
|
|
||||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||||
|
|
||||||
project(gateway)
|
project(gateway)
|
||||||
target_sources(app PRIVATE src/main.c)
|
|
||||||
|
|
||||||
target_include_directories(app PRIVATE include)
|
target_sources(app PRIVATE src/main.c)
|
||||||
|
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
# README for the Hello World Zephyr Application
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
This is a minimal Hello World application built using the Zephyr RTOS. The application demonstrates basic logging functionality by printing a message every 5 seconds, including the version number of the application.
|
|
||||||
|
|
||||||
## Project Structure
|
|
||||||
|
|
||||||
The project consists of the following files:
|
|
||||||
|
|
||||||
- `src/main.c`: The entry point of the application that initializes logging and sets up a timer.
|
|
||||||
- `include/app_version.h`: Header file that defines the application version.
|
|
||||||
- `VERSION`: A text file containing the version number of the application.
|
|
||||||
- `prj.conf`: Configuration file for the Zephyr project, specifying necessary options.
|
|
||||||
- `CMakeLists.txt`: Build configuration file for CMake.
|
|
||||||
- `README.md`: Documentation for the project.
|
|
||||||
|
|
||||||
## Building the Application
|
|
||||||
|
|
||||||
To build the application, follow these steps:
|
|
||||||
|
|
||||||
1. Ensure you have the Zephyr development environment set up.
|
|
||||||
2. Navigate to the `apps/gateway` directory.
|
|
||||||
3. Run the following command to build the application:
|
|
||||||
|
|
||||||
```
|
|
||||||
west build -b <your_board> .
|
|
||||||
```
|
|
||||||
|
|
||||||
Replace `<your_board>` with the appropriate board name.
|
|
||||||
|
|
||||||
## Running the Application
|
|
||||||
|
|
||||||
After building the application, you can flash it to your board using:
|
|
||||||
|
|
||||||
```
|
|
||||||
west flash
|
|
||||||
```
|
|
||||||
|
|
||||||
Once the application is running, you will see log messages printed every 5 seconds, including the version number.
|
|
||||||
|
|
||||||
## Version
|
|
||||||
|
|
||||||
The version of this application can be found in the `VERSION` file and is also included in the log messages.
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
VERSION_MAJOR = 0
|
|
||||||
VERSION_MINOR = 0
|
|
||||||
PATCHLEVEL = 1
|
|
||||||
VERSION_TWEAK = 0
|
|
||||||
EXTRAVERSION = devel
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
&flash0 {
|
|
||||||
reg = <0x0 0x400000>; /* 4MB flash */
|
|
||||||
};
|
|
||||||
|
|
||||||
#include "espressif/partitions_0x0_default_4M.dtsi"
|
|
||||||
|
|
||||||
/ {
|
|
||||||
chosen {
|
|
||||||
zephyr,shell-uart = &uart0;
|
|
||||||
zephyr,uart-mcumgr = &usb_serial;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
&usb_serial {
|
|
||||||
status = "okay";
|
|
||||||
};
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
#include "common_4MB.dtsi"
|
|
||||||
|
|
@ -1,47 +1,2 @@
|
||||||
# -------------------
|
# Gateway Configuration
|
||||||
# Logging and Console
|
CONFIG_NETWORKING=y
|
||||||
# -------------------
|
|
||||||
CONFIG_LOG=y
|
|
||||||
CONFIG_UART_CONSOLE=y
|
|
||||||
|
|
||||||
# -------------
|
|
||||||
# Zephyr Shell
|
|
||||||
# -------------
|
|
||||||
CONFIG_SHELL=y
|
|
||||||
CONFIG_KERNEL_SHELL=y
|
|
||||||
CONFIG_REBOOT=y
|
|
||||||
|
|
||||||
# -------------------
|
|
||||||
# MCUmgr OS Management
|
|
||||||
# -------------------
|
|
||||||
CONFIG_MCUMGR=y
|
|
||||||
CONFIG_MCUMGR_GRP_OS=y
|
|
||||||
CONFIG_MCUMGR_TRANSPORT_UART=y
|
|
||||||
|
|
||||||
# -------------------
|
|
||||||
# MCUmgr Filesystem Group
|
|
||||||
# -------------------
|
|
||||||
CONFIG_MCUMGR_GRP_FS=y
|
|
||||||
|
|
||||||
# -------------------
|
|
||||||
# LittleFS and Flash
|
|
||||||
# -------------------
|
|
||||||
CONFIG_FILE_SYSTEM=y
|
|
||||||
CONFIG_FILE_SYSTEM_LITTLEFS=y
|
|
||||||
CONFIG_FLASH=y
|
|
||||||
CONFIG_FLASH_MAP=y
|
|
||||||
|
|
||||||
# -------------------
|
|
||||||
# Settings Subsystem
|
|
||||||
# -------------------
|
|
||||||
CONFIG_SETTINGS=y
|
|
||||||
CONFIG_SETTINGS_FILE=y
|
|
||||||
CONFIG_SETTINGS_FILE_PATH="/lfs/settings.bin"
|
|
||||||
|
|
||||||
# -------------------
|
|
||||||
# Dependencies
|
|
||||||
# -------------------
|
|
||||||
CONFIG_NET_BUF=y
|
|
||||||
CONFIG_ZCBOR=y
|
|
||||||
CONFIG_CRC=y
|
|
||||||
CONFIG_BASE64=y
|
|
||||||
|
|
|
||||||
|
|
@ -1,136 +1,13 @@
|
||||||
#include <zephyr/fs/fs.h>
|
/*
|
||||||
#include <zephyr/fs/littlefs.h>
|
* Copyright (c) 2025 Eduard Iten
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
#include <zephyr/kernel.h>
|
#include <zephyr/kernel.h>
|
||||||
#include <zephyr/logging/log.h>
|
|
||||||
#include <zephyr/settings/settings.h>
|
|
||||||
#include <zephyr/shell/shell.h>
|
|
||||||
#include <app_version.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
LOG_MODULE_REGISTER(hello_world);
|
|
||||||
|
|
||||||
/* LittleFS mount configuration for 'storage_partition' partition */
|
|
||||||
FS_LITTLEFS_DECLARE_DEFAULT_CONFIG(storage_partition);
|
|
||||||
static struct fs_mount_t littlefs_mnt = {
|
|
||||||
.type = FS_LITTLEFS,
|
|
||||||
.mnt_point = "/lfs",
|
|
||||||
.fs_data = &storage_partition, // default config macro
|
|
||||||
.storage_dev = (void *)FIXED_PARTITION_ID(storage_partition),
|
|
||||||
};
|
|
||||||
|
|
||||||
static char my_setting[32] = "default";
|
|
||||||
|
|
||||||
static int my_settings_set(const char *name, size_t len, settings_read_cb read_cb, void *cb_arg)
|
|
||||||
{
|
|
||||||
if (strcmp(name, "value") == 0) {
|
|
||||||
if (len > sizeof(my_setting) - 1) {
|
|
||||||
len = sizeof(my_setting) - 1;
|
|
||||||
}
|
|
||||||
if (read_cb(cb_arg, my_setting, len) == len) {
|
|
||||||
my_setting[len] = '\0';
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
return -ENOENT;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int my_settings_export(int (*export_func)(const char *, const void *, size_t))
|
|
||||||
{
|
|
||||||
return export_func("my/setting/value", my_setting, strlen(my_setting));
|
|
||||||
}
|
|
||||||
|
|
||||||
SETTINGS_STATIC_HANDLER_DEFINE(my, "my/setting", NULL, my_settings_set, NULL, my_settings_export);
|
|
||||||
|
|
||||||
static int cmd_my_get(const struct shell *shell, size_t argc, char **argv)
|
|
||||||
{
|
|
||||||
shell_print(shell, "my_setting = '%s'", my_setting);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int cmd_my_reset(const struct shell *shell, size_t argc, char **argv)
|
|
||||||
{
|
|
||||||
strcpy(my_setting, "default");
|
|
||||||
settings_save();
|
|
||||||
shell_print(shell, "my_setting reset to default");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Improved set command: join all arguments for whitespace support
|
|
||||||
static int cmd_my_set(const struct shell *shell, size_t argc, char **argv)
|
|
||||||
{
|
|
||||||
if (argc < 2) {
|
|
||||||
shell_error(shell, "Usage: my set <value>");
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
// Join all argv[1..] with spaces
|
|
||||||
size_t i, pos = 0;
|
|
||||||
my_setting[0] = '\0';
|
|
||||||
for (i = 1; i < argc; ++i) {
|
|
||||||
size_t left = sizeof(my_setting) - 1 - pos;
|
|
||||||
if (left == 0)
|
|
||||||
break;
|
|
||||||
strncat(my_setting, argv[i], left);
|
|
||||||
pos = strlen(my_setting);
|
|
||||||
if (i < argc - 1 && left > 1) {
|
|
||||||
strncat(my_setting, " ", left - 1);
|
|
||||||
pos = strlen(my_setting);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
my_setting[sizeof(my_setting) - 1] = '\0';
|
|
||||||
settings_save();
|
|
||||||
shell_print(shell, "my_setting set to '%s'", my_setting);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
SHELL_STATIC_SUBCMD_SET_CREATE(my_subcmds,
|
|
||||||
SHELL_CMD(get, NULL, "Get my_setting", cmd_my_get),
|
|
||||||
SHELL_CMD(set, NULL, "Set my_setting (supports spaces)", cmd_my_set),
|
|
||||||
SHELL_CMD(reset, NULL, "Reset my_setting to default and compact settings file", cmd_my_reset),
|
|
||||||
SHELL_SUBCMD_SET_END);
|
|
||||||
|
|
||||||
SHELL_CMD_REGISTER(my, &my_subcmds, "My settings commands", NULL);
|
|
||||||
|
|
||||||
static void compact_settings_file(void)
|
|
||||||
{
|
|
||||||
struct fs_file_t file;
|
|
||||||
fs_file_t_init(&file);
|
|
||||||
int rc = fs_open(&file, "/lfs/settings.bin", FS_O_WRITE | FS_O_CREATE | FS_O_TRUNC);
|
|
||||||
if (rc == 0) {
|
|
||||||
fs_close(&file);
|
|
||||||
LOG_INF("Settings file compacted (truncated and recreated)");
|
|
||||||
} else if (rc == -ENOENT) {
|
|
||||||
LOG_INF("Settings file did not exist, created new");
|
|
||||||
} else {
|
|
||||||
LOG_ERR("Failed to compact settings file (%d)", rc);
|
|
||||||
}
|
|
||||||
settings_save();
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
int rc = fs_mount(&littlefs_mnt);
|
printk("Hello from Gateway!\n");
|
||||||
if (rc < 0) {
|
|
||||||
LOG_ERR("Error mounting LittleFS [%d]", rc);
|
|
||||||
} else {
|
|
||||||
LOG_INF("LittleFS mounted at /lfs");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Initialize settings subsystem */
|
|
||||||
settings_subsys_init();
|
|
||||||
LOG_INF("Settings subsystem initialized");
|
|
||||||
|
|
||||||
/* Load settings from storage */
|
|
||||||
rc = settings_load();
|
|
||||||
if (rc == 0) {
|
|
||||||
LOG_INF("Settings loaded: my_setting='%s'", my_setting);
|
|
||||||
} else {
|
|
||||||
LOG_ERR("Failed to load settings (%d)", rc);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Compact settings file on each start */
|
|
||||||
compact_settings_file();
|
|
||||||
|
|
||||||
LOG_INF("Hello World! Version: %s", APP_VERSION_EXTENDED_STRING);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
SB_CONFIG_BOOTLOADER_MCUBOOT=y
|
|
||||||
SB_CONFIG_MCUBOOT_MODE_SINGLE_APP=y
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
#include "../boards/common_4MB.dtsi"
|
|
||||||
|
|
||||||
/* Application Configuration - Firmware goes to slot0_partition (0x20000) */
|
|
||||||
/ {
|
|
||||||
chosen {
|
|
||||||
zephyr,code-partition = &slot0_partition;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
CONFIG_LOG=y
|
|
||||||
CONFIG_MCUBOOT_LOG_LEVEL_INF=y
|
|
||||||
CONFIG_UART_CONSOLE=n
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
#include "../boards/common_4MB.dtsi"
|
|
||||||
|
|
||||||
/* MCUboot Configuration - Bootloader goes to boot_partition (0x0) */
|
|
||||||
/ {
|
|
||||||
chosen {
|
|
||||||
zephyr,code-partition = &boot_partition;
|
|
||||||
};
|
|
||||||
aliases {
|
|
||||||
mcuboot-button0 = &user_button1;
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
@ -3,8 +3,6 @@ cmake_minimum_required(VERSION 3.20)
|
||||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||||
|
|
||||||
project(slave_node LANGUAGES C)
|
project(slave_node LANGUAGES C)
|
||||||
|
|
||||||
zephyr_include_directories(../../include)
|
zephyr_include_directories(../../include)
|
||||||
add_subdirectory(../../lib lib)
|
add_subdirectory(../../lib lib)
|
||||||
|
|
||||||
target_sources(app PRIVATE src/main.c)
|
target_sources(app PRIVATE src/main.c)
|
||||||
|
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
/ {
|
|
||||||
aliases {
|
|
||||||
vnd7050aj = &vnd7050aj;
|
|
||||||
};
|
|
||||||
|
|
||||||
vnd7050aj: vnd7050aj {
|
|
||||||
compatible = "st,vnd7050aj";
|
|
||||||
status = "okay";
|
|
||||||
|
|
||||||
input0-gpios = <&gpio0 1 GPIO_ACTIVE_HIGH>;
|
|
||||||
input1-gpios = <&gpio0 2 GPIO_ACTIVE_HIGH>;
|
|
||||||
select0-gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>;
|
|
||||||
select1-gpios = <&gpio0 4 GPIO_ACTIVE_HIGH>;
|
|
||||||
sense-enable-gpios = <&gpio0 5 GPIO_ACTIVE_HIGH>;
|
|
||||||
fault-reset-gpios = <&gpio0 6 GPIO_ACTIVE_LOW>;
|
|
||||||
io-channels = <&adc0 0>;
|
|
||||||
r-sense-ohms = <1500>;
|
|
||||||
k-vcc = <4000>;
|
|
||||||
};
|
|
||||||
|
|
||||||
modbus_uart: uart_2 {
|
|
||||||
compatible = "zephyr,native-pty-uart";
|
|
||||||
status = "okay";
|
|
||||||
current-speed = <19200>;
|
|
||||||
|
|
||||||
modbus0: modbus0 {
|
|
||||||
compatible = "zephyr,modbus-serial";
|
|
||||||
status = "okay";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
&adc0 {
|
|
||||||
#address-cells = <1>;
|
|
||||||
#size-cells = <0>;
|
|
||||||
ref-internal-mv = <3300>;
|
|
||||||
ref-external1-mv = <5000>;
|
|
||||||
|
|
||||||
channel@0 {
|
|
||||||
reg = <0>;
|
|
||||||
zephyr,gain = "ADC_GAIN_1";
|
|
||||||
zephyr,reference = "ADC_REF_INTERNAL";
|
|
||||||
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
|
|
||||||
zephyr,resolution = <12>;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
# Copyright (c) 2024, Eduard Iten
|
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
description: |
|
|
||||||
STMicroelectronics VND7050AJ dual-channel high-side driver.
|
|
||||||
This is a GPIO and ADC controlled device.
|
|
||||||
|
|
||||||
compatible: "st,vnd7050aj"
|
|
||||||
|
|
||||||
include: base.yaml
|
|
||||||
|
|
||||||
properties:
|
|
||||||
input0-gpios:
|
|
||||||
type: phandle-array
|
|
||||||
required: true
|
|
||||||
description: GPIO to control output channel 0.
|
|
||||||
|
|
||||||
input1-gpios:
|
|
||||||
type: phandle-array
|
|
||||||
required: true
|
|
||||||
description: GPIO to control output channel 1.
|
|
||||||
|
|
||||||
select0-gpios:
|
|
||||||
type: phandle-array
|
|
||||||
required: true
|
|
||||||
description: GPIO for MultiSense selection bit 0.
|
|
||||||
|
|
||||||
select1-gpios:
|
|
||||||
type: phandle-array
|
|
||||||
required: true
|
|
||||||
description: GPIO for MultiSense selection bit 1.
|
|
||||||
|
|
||||||
sense-enable-gpios:
|
|
||||||
type: phandle-array
|
|
||||||
required: true
|
|
||||||
description: GPIO to enable the MultiSense output.
|
|
||||||
|
|
||||||
fault-reset-gpios:
|
|
||||||
type: phandle-array
|
|
||||||
required: true
|
|
||||||
description: GPIO to reset a latched fault (active-low).
|
|
||||||
|
|
||||||
io-channels:
|
|
||||||
type: phandle-array
|
|
||||||
required: true
|
|
||||||
description: |
|
|
||||||
ADC channel connected to the MultiSense pin. This should be an
|
|
||||||
io-channels property pointing to the ADC controller and channel number.
|
|
||||||
|
|
||||||
r-sense-ohms:
|
|
||||||
type: int
|
|
||||||
required: true
|
|
||||||
description: |
|
|
||||||
Value of the external sense resistor connected from the MultiSense
|
|
||||||
pin to GND, specified in Ohms. This is critical for correct
|
|
||||||
conversion of the analog readings.
|
|
||||||
|
|
||||||
k-factor:
|
|
||||||
type: int
|
|
||||||
default: 1500
|
|
||||||
description: |
|
|
||||||
Factor between PowerMOS and SenseMOS.
|
|
||||||
|
|
||||||
k-vcc:
|
|
||||||
type: int
|
|
||||||
default: 8000
|
|
||||||
description: |
|
|
||||||
VCC sense ratio multiplied by 1000. Used for supply voltage calculation.
|
|
||||||
|
|
||||||
t-sense-0:
|
|
||||||
type: int
|
|
||||||
default: 25
|
|
||||||
description: |
|
|
||||||
Temperature sense reference temperature in degrees Celsius.
|
|
||||||
|
|
||||||
v-sense-0:
|
|
||||||
type: int
|
|
||||||
default: 2070
|
|
||||||
description: |
|
|
||||||
Temperature sense reference voltage in millivolts.
|
|
||||||
|
|
||||||
k-tchip:
|
|
||||||
type: int
|
|
||||||
default: -5500
|
|
||||||
description: |
|
|
||||||
Temperature sense gain coefficient multiplied by 1000.
|
|
||||||
Used for chip temperature calculation.
|
|
||||||
|
|
||||||
|
|
@ -22,7 +22,7 @@ CONFIG_SETTINGS_LOG_LEVEL_DBG=y
|
||||||
CONFIG_UART_INTERRUPT_DRIVEN=y
|
CONFIG_UART_INTERRUPT_DRIVEN=y
|
||||||
CONFIG_MODBUS=y
|
CONFIG_MODBUS=y
|
||||||
CONFIG_MODBUS_ROLE_SERVER=y
|
CONFIG_MODBUS_ROLE_SERVER=y
|
||||||
CONFIG_MODBUS_LOG_LEVEL_DBG=y
|
CONFIG_MODBUS_BUFFER_SIZE=256
|
||||||
|
|
||||||
# Enable VND7050AJ
|
# Enable VND7050AJ
|
||||||
CONFIG_VND7050AJ=y
|
CONFIG_VND7050AJ=y
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
int rc;
|
|
||||||
LOG_INF("Starting Irrigation System Slave Node");
|
LOG_INF("Starting Irrigation System Slave Node");
|
||||||
|
|
||||||
if (settings_subsys_init() || settings_load()) {
|
if (settings_subsys_init() || settings_load()) {
|
||||||
|
|
@ -19,10 +18,9 @@ int main(void)
|
||||||
valve_init();
|
valve_init();
|
||||||
fwu_init();
|
fwu_init();
|
||||||
|
|
||||||
rc = modbus_server_init();
|
if (modbus_server_init()) {
|
||||||
if (rc) {
|
LOG_ERR("Modbus RTU server initialization failed");
|
||||||
LOG_ERR("Modbus server initialization failed: %d", rc);
|
return 0;
|
||||||
return rc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_INF("Irrigation System Slave Node started successfully");
|
LOG_INF("Irrigation System Slave Node started successfully");
|
||||||
|
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
SB_CONFIG_BOOTLOADER_MCUBOOT=y
|
|
||||||
SB_CONFIG_MCUBOOT_MODE_SINGLE_APP=y
|
|
||||||
|
|
||||||
CONFIG_LOG=y
|
|
||||||
CONFIG_MCUBOOT_LOG_LEVEL_INF=y
|
|
||||||
|
|
@ -1,11 +1,3 @@
|
||||||
# ===================================================================
|
|
||||||
# ESPHome Configuration - Final Version
|
|
||||||
#
|
|
||||||
# This version corrects the C++ function call inside the valve actions
|
|
||||||
# to use the correct `send` method from the ModbusDevice base class,
|
|
||||||
# which is compatible with the esp-idf framework.
|
|
||||||
# ===================================================================
|
|
||||||
|
|
||||||
esphome:
|
esphome:
|
||||||
name: irrigation-system
|
name: irrigation-system
|
||||||
friendly_name: Bewässerung
|
friendly_name: Bewässerung
|
||||||
|
|
@ -13,7 +5,7 @@ esphome:
|
||||||
esp32:
|
esp32:
|
||||||
board: esp32-c6-devkitm-1
|
board: esp32-c6-devkitm-1
|
||||||
framework:
|
framework:
|
||||||
type: esp-idf # Set to esp-idf as required by the ESP32-C6 board
|
type: esp-idf
|
||||||
|
|
||||||
wifi:
|
wifi:
|
||||||
ssid: !secret wifi_ssid
|
ssid: !secret wifi_ssid
|
||||||
|
|
@ -32,135 +24,88 @@ logger:
|
||||||
|
|
||||||
web_server:
|
web_server:
|
||||||
|
|
||||||
# ===================================================================
|
# UART-Bus für Modbus
|
||||||
# HARDWARE SETUP - COMPLETE
|
|
||||||
# ===================================================================
|
|
||||||
|
|
||||||
# --- UART for RS485 Communication ---
|
|
||||||
uart:
|
uart:
|
||||||
id: uart_bus
|
id: uart_bus
|
||||||
tx_pin: GPIO1
|
tx_pin: GPIO1
|
||||||
rx_pin: GPIO2
|
rx_pin: GPIO2
|
||||||
baud_rate: 9600
|
baud_rate: 9600
|
||||||
data_bits: 8
|
|
||||||
stop_bits: 1
|
stop_bits: 1
|
||||||
parity: NONE
|
parity: NONE
|
||||||
|
|
||||||
# --- Base Modbus component for the bus ---
|
# Modbus-Komponente (der Hub)
|
||||||
modbus:
|
modbus:
|
||||||
- id: modbus_hub
|
- id: modbus1
|
||||||
uart_id: uart_bus
|
uart_id: uart_bus
|
||||||
|
|
||||||
# --- Modbus Controller for the specific valve device ---
|
|
||||||
modbus_controller:
|
modbus_controller:
|
||||||
- id: valve_controller
|
- id: valve_device
|
||||||
modbus_id: modbus_hub
|
address: 0x01
|
||||||
address: 0 # The Modbus address of your valve. Change if not 0.
|
modbus_id: modbus1
|
||||||
# update_interval: 1s
|
|
||||||
|
|
||||||
# ===================================================================
|
number:
|
||||||
# SENSORS - COMPLETE
|
|
||||||
# ===================================================================
|
|
||||||
sensor:
|
|
||||||
# This sensor reads the raw 16-bit value from the valve's input register.
|
|
||||||
- platform: modbus_controller
|
- platform: modbus_controller
|
||||||
modbus_controller_id: valve_controller
|
modbus_controller_id: valve_device
|
||||||
name: "Valve Raw Status"
|
id: valve_controller_command
|
||||||
id: valve_raw_status
|
name: "Valve Control"
|
||||||
internal: true # Hide from Home Assistant UI
|
address: 0x01
|
||||||
register_type: read # 'read' is the valid type for input registers
|
value_type: U_WORD
|
||||||
address: 0x0000 # The address of the register to read
|
# min_value: 0
|
||||||
value_type: U_WORD # Read the full 16-bit unsigned word
|
# max_value: 2
|
||||||
- platform: modbus_controller
|
# step: 1
|
||||||
modbus_controller_id: valve_controller
|
|
||||||
name: "VDD"
|
|
||||||
id: valve_vdd
|
|
||||||
register_type: read # 'read' is the valid type for input registers
|
|
||||||
address: 0x00FC # The address of the register to read
|
|
||||||
value_type: U_WORD # Read the full 16-bit unsigned word
|
|
||||||
entity_category: diagnostic # Mark as diagnostic
|
|
||||||
unit_of_measurement: "V"
|
|
||||||
accuracy_decimals: 2 # Show two decimal places
|
|
||||||
# Apply filters to convert the raw value to volts and update periodically
|
|
||||||
filters:
|
|
||||||
- lambda: |-
|
|
||||||
// Convert the raw VDD value to volts
|
|
||||||
return x / 1000.0; // Assuming the value is in millivolts
|
|
||||||
- heartbeat: 60s # Update every 60 seconds
|
|
||||||
- delta: 200 # Only update if the value changes by more than 200 mV
|
|
||||||
|
|
||||||
# ===================================================================
|
globals:
|
||||||
# TEXT SENSORS FOR HUMAN-READABLE STATUS
|
- id: my_valve_is_open
|
||||||
# ===================================================================
|
type: bool
|
||||||
text_sensor:
|
restore_value: false
|
||||||
# 1. This text sensor extracts the HIGH BYTE for the operation status.
|
initial_value: 'true'
|
||||||
- platform: template
|
|
||||||
name: "Valve Operation"
|
|
||||||
id: valve_operation_status
|
|
||||||
icon: "mdi:state-machine"
|
|
||||||
lambda: |-
|
|
||||||
// Extract the high byte from the raw status sensor
|
|
||||||
// using a bitwise right shift.
|
|
||||||
int operation_code = (int)id(valve_raw_status).state >> 8;
|
|
||||||
switch (operation_code) {
|
|
||||||
case 0: return {"Idle"};
|
|
||||||
case 1: return {"Opening"};
|
|
||||||
case 2: return {"Closing"};
|
|
||||||
case 3: return {"Obstacle Detected"};
|
|
||||||
case 4: return {"End Position Not Reached"};
|
|
||||||
default: return {"Unknown Operation"};
|
|
||||||
}
|
|
||||||
|
|
||||||
# 2. This text sensor extracts the LOW BYTE for the current valve state.
|
|
||||||
- platform: template
|
|
||||||
name: "Valve Position"
|
|
||||||
id: valve_position_status
|
|
||||||
icon: "mdi:valve"
|
|
||||||
lambda: |-
|
|
||||||
// Extract the low byte from the raw status sensor
|
|
||||||
// using a bitwise AND mask.
|
|
||||||
int state_code = (int)id(valve_raw_status).state & 0xFF;
|
|
||||||
switch (state_code) {
|
|
||||||
case 0: return {"Closed"};
|
|
||||||
case 1: return {"Open"};
|
|
||||||
default: return {"Unknown"};
|
|
||||||
}
|
|
||||||
|
|
||||||
# ===================================================================
|
|
||||||
# THE MAIN VALVE COMPONENT
|
|
||||||
# ===================================================================
|
|
||||||
valve:
|
valve:
|
||||||
- platform: template
|
- platform: template
|
||||||
name: "Modbus Controlled Valve"
|
name: "Modbus Ventil"
|
||||||
id: modbus_valve
|
id: my_modbus_valve
|
||||||
optimistic: false
|
|
||||||
# The lambda determines the current state (open or closed) of the valve.
|
# Lambda, um den aktuellen Zustand zu bestimmen
|
||||||
|
# Liest den Zustand aus der globalen Variable
|
||||||
lambda: |-
|
lambda: |-
|
||||||
int state_code = (int)id(valve_raw_status).state & 0xFF;
|
return id(my_valve_is_open);
|
||||||
if (state_code == 1) {
|
|
||||||
return true; // Open
|
# Aktion beim Drücken auf "Öffnen"
|
||||||
} else if (state_code == 0) {
|
|
||||||
return false; // Closed
|
|
||||||
} else {
|
|
||||||
return {}; // Unknown
|
|
||||||
}
|
|
||||||
# Action to execute when the "OPEN" button is pressed.
|
|
||||||
open_action:
|
open_action:
|
||||||
- lambda: |-
|
- number.set:
|
||||||
// Use the send() command inherited from ModbusDevice
|
id: valve_controller_command
|
||||||
// Function 0x06: Write Single Register
|
value: 1
|
||||||
// Payload for value 1 is {0x00, 0x01}
|
- globals.set:
|
||||||
const uint8_t data[] = {0x00, 0x01};
|
id: my_valve_is_open
|
||||||
id(valve_controller).send(0x06, 0x0000, 1, 2, data);
|
value: 'true'
|
||||||
# Action to execute when the "CLOSE" button is pressed.
|
|
||||||
|
# Aktion beim Drücken auf "Schliessen"
|
||||||
close_action:
|
close_action:
|
||||||
- lambda: |-
|
- number.set:
|
||||||
// Payload for value 2 is {0x00, 0x02}
|
id: valve_controller_command
|
||||||
const uint8_t data[] = {0x00, 0x02};
|
value: 2
|
||||||
id(valve_controller).send(0x06, 0x0000, 1, 2, data);
|
- globals.set:
|
||||||
# Action to execute when the "STOP" button is pressed.
|
id: my_valve_is_open
|
||||||
|
value: 'false'
|
||||||
|
|
||||||
|
# (Optional) Aktion beim Drücken auf "Stopp"
|
||||||
stop_action:
|
stop_action:
|
||||||
- lambda: |-
|
- number.set:
|
||||||
// Payload for value 3 is {0x00, 0x03}
|
id: valve_controller_command
|
||||||
const uint8_t data[] = {0x00, 0x03};
|
value: 0
|
||||||
id(valve_controller).send(0x06, 0x0000, 1, 2, data);
|
|
||||||
|
sensor:
|
||||||
|
- platform: modbus_controller
|
||||||
|
modbus_controller_id: valve_device
|
||||||
|
name: "Supply Voltage"
|
||||||
|
register_type: read
|
||||||
|
device_class: voltage
|
||||||
|
entity_category: diagnostic
|
||||||
|
accuracy_decimals: 2
|
||||||
|
filters:
|
||||||
|
- lambda: |-
|
||||||
|
return x / 1000.0;
|
||||||
|
address: 0x00F5
|
||||||
|
unit_of_measurement: "V"
|
||||||
|
value_type: U_WORD
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2025, Eduard Iten
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef ZEPHYR_INCLUDE_DRIVERS_MISC_VND7050AJ_H_
|
|
||||||
#define ZEPHYR_INCLUDE_DRIVERS_MISC_VND7050AJ_H_
|
|
||||||
|
|
||||||
#include <zephyr/device.h>
|
|
||||||
#include <zephyr/kernel.h>
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Channel identifiers for the VND7050AJ.
|
|
||||||
*/
|
|
||||||
#define VND7050AJ_CHANNEL_0 0
|
|
||||||
#define VND7050AJ_CHANNEL_1 1
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Sets the state of a specific output channel.
|
|
||||||
*
|
|
||||||
* @param dev Pointer to the device structure for the driver instance.
|
|
||||||
* @param channel The channel to control (VND7050AJ_CHANNEL_0 or VND7050AJ_CHANNEL_1).
|
|
||||||
* @param state The desired state (true for ON, false for OFF).
|
|
||||||
* @return 0 on success, negative error code on failure.
|
|
||||||
*/
|
|
||||||
int vnd7050aj_set_output_state(const struct device *dev, uint8_t channel, bool state);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Reads the load current for a specific channel.
|
|
||||||
*
|
|
||||||
* @param dev Pointer to the device structure for the driver instance.
|
|
||||||
* @param channel The channel to measure (VND7050AJ_CHANNEL_0 or VND7050AJ_CHANNEL_1).
|
|
||||||
* @param[out] current_ma Pointer to store the measured current in milliamperes (mA).
|
|
||||||
* @return 0 on success, negative error code on failure.
|
|
||||||
*/
|
|
||||||
int vnd7050aj_read_load_current(const struct device *dev, uint8_t channel, int32_t *current_ma);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Reads the VCC supply voltage.
|
|
||||||
*
|
|
||||||
* @param dev Pointer to the device structure for the driver instance.
|
|
||||||
* @param[out] voltage_mv Pointer to store the measured voltage in millivolts (mV).
|
|
||||||
* @return 0 on success, negative error code on failure.
|
|
||||||
*/
|
|
||||||
int vnd7050aj_read_supply_voltage(const struct device *dev, int32_t *voltage_mv);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Reads the internal chip temperature.
|
|
||||||
*
|
|
||||||
* @param dev Pointer to the device structure for the driver instance.
|
|
||||||
* @param[out] temp_c Pointer to store the measured temperature in degrees Celsius (°C).
|
|
||||||
* @return 0 on success, negative error code on failure.
|
|
||||||
*/
|
|
||||||
int vnd7050aj_read_chip_temp(const struct device *dev, int32_t *temp_c);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Resets a latched fault condition.
|
|
||||||
*
|
|
||||||
* This function sends a low pulse to the FaultRST pin.
|
|
||||||
*
|
|
||||||
* @param dev Pointer to the device structure for the driver instance.
|
|
||||||
* @return 0 on success, negative error code on failure.
|
|
||||||
*/
|
|
||||||
int vnd7050aj_reset_fault(const struct device *dev);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* ZEPHYR_INCLUDE_DRIVERS_MISC_VND7050AJ_H_ */
|
|
||||||
|
|
@ -3,5 +3,4 @@ add_subdirectory_ifdef(CONFIG_LIB_MODBUS_SERVER modbus_server)
|
||||||
add_subdirectory_ifdef(CONFIG_LIB_VALVE valve)
|
add_subdirectory_ifdef(CONFIG_LIB_VALVE valve)
|
||||||
add_subdirectory_ifdef(CONFIG_SHELL_SYSTEM shell_system)
|
add_subdirectory_ifdef(CONFIG_SHELL_SYSTEM shell_system)
|
||||||
add_subdirectory_ifdef(CONFIG_SHELL_MODBUS shell_modbus)
|
add_subdirectory_ifdef(CONFIG_SHELL_MODBUS shell_modbus)
|
||||||
add_subdirectory_ifdef(CONFIG_SHELL_VALVE shell_valve)
|
add_subdirectory_ifdef(CONFIG_SHELL_VALVE shell_valve)
|
||||||
add_subdirectory_ifdef(CONFIG_VND7050AJ vnd7050aj)
|
|
||||||
|
|
@ -6,5 +6,4 @@ rsource "valve/Kconfig"
|
||||||
rsource "shell_system/Kconfig"
|
rsource "shell_system/Kconfig"
|
||||||
rsource "shell_modbus/Kconfig"
|
rsource "shell_modbus/Kconfig"
|
||||||
rsource "shell_valve/Kconfig"
|
rsource "shell_valve/Kconfig"
|
||||||
rsource "vnd7050aj/Kconfig"
|
|
||||||
endmenu
|
endmenu
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <zephyr/device.h>
|
#include <zephyr/device.h>
|
||||||
|
#include <zephyr/drivers/misc/vnd7050aj/vnd7050aj.h>
|
||||||
#include <zephyr/drivers/uart.h>
|
#include <zephyr/drivers/uart.h>
|
||||||
#include <zephyr/kernel.h>
|
#include <zephyr/kernel.h>
|
||||||
#include <zephyr/logging/log.h>
|
#include <zephyr/logging/log.h>
|
||||||
|
|
@ -19,7 +20,6 @@
|
||||||
#include <lib/fwu.h>
|
#include <lib/fwu.h>
|
||||||
#include <lib/modbus_server.h>
|
#include <lib/modbus_server.h>
|
||||||
#include <lib/valve.h>
|
#include <lib/valve.h>
|
||||||
#include <lib/vnd7050aj.h>
|
|
||||||
|
|
||||||
LOG_MODULE_REGISTER(modbus_server, LOG_LEVEL_INF);
|
LOG_MODULE_REGISTER(modbus_server, LOG_LEVEL_INF);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@
|
||||||
|
|
||||||
#include <zephyr/shell/shell.h>
|
#include <zephyr/shell/shell.h>
|
||||||
#include <lib/modbus_server.h>
|
#include <lib/modbus_server.h>
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,11 @@
|
||||||
|
|
||||||
#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/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>
|
#include <lib/valve.h>
|
||||||
#include <lib/vnd7050aj.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)
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
zephyr_library_sources(vnd7050aj.c)
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
config VND7050AJ
|
|
||||||
bool "VND7050AJ driver"
|
|
||||||
default n
|
|
||||||
select ADC
|
|
||||||
select GPIO
|
|
||||||
help
|
|
||||||
Enable support for the VND7050AJ high-side driver.
|
|
||||||
|
|
||||||
if VND7050AJ
|
|
||||||
config VND7050AJ_INIT_PRIORITY
|
|
||||||
int "VND7050AJ initialization priority"
|
|
||||||
default 80
|
|
||||||
help
|
|
||||||
VND7050AJ driver initialization priority. This should be set to a value
|
|
||||||
that ensures the driver is initialized after the required subsystems
|
|
||||||
(like GPIO, ADC) but before application code runs.
|
|
||||||
|
|
||||||
config VND7050AJ_LOG_LEVEL
|
|
||||||
int "VND7050AJ Log level"
|
|
||||||
depends on LOG
|
|
||||||
default 3
|
|
||||||
range 0 4
|
|
||||||
help
|
|
||||||
Sets log level for VND7050AJ driver.
|
|
||||||
Levels are:
|
|
||||||
0 OFF, do not write
|
|
||||||
1 ERROR, only write LOG_ERR
|
|
||||||
2 WARNING, write LOG_WRN in addition to previous level
|
|
||||||
3 INFO, write LOG_INF in addition to previous levels
|
|
||||||
4 DEBUG, write LOG_DBG in addition to previous levels
|
|
||||||
endif # VND7050AJ
|
|
||||||
|
|
@ -1,333 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2025, Eduard Iten
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define DT_DRV_COMPAT st_vnd7050aj
|
|
||||||
|
|
||||||
#include <zephyr/device.h>
|
|
||||||
#include <zephyr/drivers/adc.h>
|
|
||||||
#include <zephyr/drivers/gpio.h>
|
|
||||||
#include <zephyr/kernel.h>
|
|
||||||
#include <zephyr/logging/log.h>
|
|
||||||
#include <zephyr/sys/util.h>
|
|
||||||
|
|
||||||
#include <lib/vnd7050aj.h>
|
|
||||||
|
|
||||||
LOG_MODULE_REGISTER(VND7050AJ, CONFIG_VND7050AJ_LOG_LEVEL);
|
|
||||||
|
|
||||||
/* Diagnostic selection modes */
|
|
||||||
enum vnd7050aj_diag_mode {
|
|
||||||
DIAG_CURRENT_CH0,
|
|
||||||
DIAG_CURRENT_CH1,
|
|
||||||
DIAG_VCC,
|
|
||||||
DIAG_TEMP,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct vnd7050aj_config {
|
|
||||||
struct gpio_dt_spec input0_gpio;
|
|
||||||
struct gpio_dt_spec input1_gpio;
|
|
||||||
struct gpio_dt_spec sel0_gpio;
|
|
||||||
struct gpio_dt_spec sel1_gpio;
|
|
||||||
struct gpio_dt_spec sen_gpio;
|
|
||||||
struct gpio_dt_spec fault_reset_gpio;
|
|
||||||
struct adc_dt_spec io_channels;
|
|
||||||
uint32_t r_sense_ohms;
|
|
||||||
uint32_t k_factor; /* Current sense ratio */
|
|
||||||
uint32_t k_vcc; /* VCC sense ratio * 1000 */
|
|
||||||
int32_t t_sense_0; /* Temp sense reference temperature in °C */
|
|
||||||
uint32_t v_sense_0; /* Temp sense reference voltage in mV */
|
|
||||||
uint32_t k_tchip; /* Temp sense gain in °C/mV * 1000 */
|
|
||||||
};
|
|
||||||
|
|
||||||
struct vnd7050aj_data {
|
|
||||||
struct k_mutex lock;
|
|
||||||
};
|
|
||||||
|
|
||||||
static int vnd7050aj_init(const struct device *dev)
|
|
||||||
{
|
|
||||||
const struct vnd7050aj_config *config = dev->config;
|
|
||||||
struct vnd7050aj_data *data = dev->data;
|
|
||||||
int err;
|
|
||||||
|
|
||||||
k_mutex_init(&data->lock);
|
|
||||||
|
|
||||||
LOG_DBG("Initializing VND7050AJ device %s", dev->name);
|
|
||||||
|
|
||||||
/* --- Check if all required devices are ready --- */
|
|
||||||
if (!gpio_is_ready_dt(&config->input0_gpio)) {
|
|
||||||
LOG_ERR("Input0 GPIO port is not ready");
|
|
||||||
return -ENODEV;
|
|
||||||
}
|
|
||||||
if (!gpio_is_ready_dt(&config->input1_gpio)) {
|
|
||||||
LOG_ERR("Input1 GPIO port is not ready");
|
|
||||||
return -ENODEV;
|
|
||||||
}
|
|
||||||
if (!gpio_is_ready_dt(&config->sel0_gpio)) {
|
|
||||||
LOG_ERR("Select0 GPIO port is not ready");
|
|
||||||
return -ENODEV;
|
|
||||||
}
|
|
||||||
if (!gpio_is_ready_dt(&config->sel1_gpio)) {
|
|
||||||
LOG_ERR("Select1 GPIO port is not ready");
|
|
||||||
return -ENODEV;
|
|
||||||
}
|
|
||||||
if (!gpio_is_ready_dt(&config->sen_gpio)) {
|
|
||||||
LOG_ERR("Sense GPIO port is not ready");
|
|
||||||
return -ENODEV;
|
|
||||||
}
|
|
||||||
if (!gpio_is_ready_dt(&config->fault_reset_gpio)) {
|
|
||||||
LOG_ERR("Fault Reset GPIO port is not ready");
|
|
||||||
return -ENODEV;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!adc_is_ready_dt(&config->io_channels)) {
|
|
||||||
LOG_ERR("ADC controller not ready");
|
|
||||||
return -ENODEV;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Configure GPIOs to their initial states --- */
|
|
||||||
err = gpio_pin_configure_dt(&config->input0_gpio, GPIO_OUTPUT_INACTIVE);
|
|
||||||
if (err) {
|
|
||||||
LOG_ERR("Failed to configure input0 GPIO: %d", err);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
err = gpio_pin_configure_dt(&config->input1_gpio, GPIO_OUTPUT_INACTIVE);
|
|
||||||
if (err) {
|
|
||||||
LOG_ERR("Failed to configure input1 GPIO: %d", err);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
err = gpio_pin_configure_dt(&config->sel0_gpio, GPIO_OUTPUT_INACTIVE);
|
|
||||||
if (err) {
|
|
||||||
LOG_ERR("Failed to configure select0 GPIO: %d", err);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
err = gpio_pin_configure_dt(&config->sel1_gpio, GPIO_OUTPUT_INACTIVE);
|
|
||||||
if (err) {
|
|
||||||
LOG_ERR("Failed to configure select1 GPIO: %d", err);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
err = gpio_pin_configure_dt(&config->sen_gpio, GPIO_OUTPUT_INACTIVE);
|
|
||||||
if (err) {
|
|
||||||
LOG_ERR("Failed to configure sense GPIO: %d", err);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
err = gpio_pin_configure_dt(
|
|
||||||
&config->fault_reset_gpio, GPIO_OUTPUT_ACTIVE); /* Active-low, so init high */
|
|
||||||
if (err) {
|
|
||||||
LOG_ERR("Failed to configure fault reset GPIO: %d", err);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Configure the ADC channel --- */
|
|
||||||
err = adc_channel_setup_dt(&config->io_channels);
|
|
||||||
if (err) {
|
|
||||||
LOG_ERR("Failed to setup ADC channel: %d", err);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DBG("Device %s initialized", dev->name);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define VND7050AJ_DEFINE(inst) \
|
|
||||||
static struct vnd7050aj_data vnd7050aj_data_##inst; \
|
|
||||||
\
|
|
||||||
static const struct vnd7050aj_config vnd7050aj_config_##inst = { \
|
|
||||||
.input0_gpio = GPIO_DT_SPEC_INST_GET(inst, input0_gpios), \
|
|
||||||
.input1_gpio = GPIO_DT_SPEC_INST_GET(inst, input1_gpios), \
|
|
||||||
.sel0_gpio = GPIO_DT_SPEC_INST_GET(inst, select0_gpios), \
|
|
||||||
.sel1_gpio = GPIO_DT_SPEC_INST_GET(inst, select1_gpios), \
|
|
||||||
.sen_gpio = GPIO_DT_SPEC_INST_GET(inst, sense_enable_gpios), \
|
|
||||||
.fault_reset_gpio = GPIO_DT_SPEC_INST_GET(inst, fault_reset_gpios), \
|
|
||||||
.io_channels = ADC_DT_SPEC_INST_GET(inst), \
|
|
||||||
.r_sense_ohms = DT_INST_PROP(inst, r_sense_ohms), \
|
|
||||||
.k_factor = DT_INST_PROP(inst, k_factor), \
|
|
||||||
.k_vcc = DT_INST_PROP(inst, k_vcc), \
|
|
||||||
.t_sense_0 = DT_INST_PROP(inst, t_sense_0), \
|
|
||||||
.v_sense_0 = DT_INST_PROP(inst, v_sense_0), \
|
|
||||||
.k_tchip = DT_INST_PROP(inst, k_tchip), \
|
|
||||||
}; \
|
|
||||||
\
|
|
||||||
DEVICE_DT_INST_DEFINE(inst, \
|
|
||||||
vnd7050aj_init, \
|
|
||||||
NULL, /* No PM support yet */ \
|
|
||||||
&vnd7050aj_data_##inst, \
|
|
||||||
&vnd7050aj_config_##inst, \
|
|
||||||
POST_KERNEL, \
|
|
||||||
CONFIG_VND7050AJ_INIT_PRIORITY, \
|
|
||||||
NULL); /* No API struct needed for custom API */
|
|
||||||
|
|
||||||
DT_INST_FOREACH_STATUS_OKAY(VND7050AJ_DEFINE)
|
|
||||||
|
|
||||||
int vnd7050aj_set_output_state(const struct device *dev, uint8_t channel, bool state)
|
|
||||||
{
|
|
||||||
const struct vnd7050aj_config *config = dev->config;
|
|
||||||
|
|
||||||
if (channel != VND7050AJ_CHANNEL_0 && channel != VND7050AJ_CHANNEL_1) {
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
const struct gpio_dt_spec *gpio =
|
|
||||||
(channel == VND7050AJ_CHANNEL_0) ? &config->input0_gpio : &config->input1_gpio;
|
|
||||||
|
|
||||||
return gpio_pin_set_dt(gpio, (int)state);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int vnd7050aj_read_sense_voltage(
|
|
||||||
const struct device *dev, enum vnd7050aj_diag_mode mode, int32_t *voltage_mv)
|
|
||||||
{
|
|
||||||
const struct vnd7050aj_config *config = dev->config;
|
|
||||||
struct vnd7050aj_data *data = dev->data;
|
|
||||||
int err = 0;
|
|
||||||
|
|
||||||
/* Initialize the buffer to zero */
|
|
||||||
*voltage_mv = 0;
|
|
||||||
|
|
||||||
struct adc_sequence sequence = {
|
|
||||||
.buffer = voltage_mv,
|
|
||||||
.buffer_size = sizeof(*voltage_mv),
|
|
||||||
#ifdef CONFIG_ADC_CALIBRATION
|
|
||||||
.calibrate = true,
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
adc_sequence_init_dt(&config->io_channels, &sequence);
|
|
||||||
|
|
||||||
k_mutex_lock(&data->lock, K_FOREVER);
|
|
||||||
|
|
||||||
/* Step 1: Select diagnostic mode */
|
|
||||||
switch (mode) {
|
|
||||||
case DIAG_CURRENT_CH0:
|
|
||||||
gpio_pin_set_dt(&config->sel0_gpio, 0);
|
|
||||||
gpio_pin_set_dt(&config->sel1_gpio, 0);
|
|
||||||
gpio_pin_set_dt(&config->sen_gpio, 1);
|
|
||||||
break;
|
|
||||||
case DIAG_CURRENT_CH1:
|
|
||||||
gpio_pin_set_dt(&config->sel0_gpio, 0);
|
|
||||||
gpio_pin_set_dt(&config->sel1_gpio, 1);
|
|
||||||
gpio_pin_set_dt(&config->sen_gpio, 1);
|
|
||||||
break;
|
|
||||||
case DIAG_TEMP:
|
|
||||||
gpio_pin_set_dt(&config->sel0_gpio, 1);
|
|
||||||
gpio_pin_set_dt(&config->sel1_gpio, 0);
|
|
||||||
gpio_pin_set_dt(&config->sen_gpio, 1);
|
|
||||||
break;
|
|
||||||
case DIAG_VCC:
|
|
||||||
gpio_pin_set_dt(&config->sel1_gpio, 1);
|
|
||||||
gpio_pin_set_dt(&config->sel0_gpio, 1);
|
|
||||||
gpio_pin_set_dt(&config->sen_gpio, 1);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
err = -ENOTSUP;
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Allow time for GPIO changes to settle and ADC input to stabilize */
|
|
||||||
k_msleep(1);
|
|
||||||
|
|
||||||
/* Initialize buffer before read */
|
|
||||||
*voltage_mv = 0;
|
|
||||||
err = adc_read_dt(&config->io_channels, &sequence);
|
|
||||||
if (err) {
|
|
||||||
LOG_ERR("ADC read failed: %d", err);
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DBG("ADC read completed, raw value: %d", *voltage_mv);
|
|
||||||
err = adc_raw_to_millivolts_dt(&config->io_channels, voltage_mv);
|
|
||||||
if (err) {
|
|
||||||
LOG_ERR("ADC raw to millivolts conversion failed: %d", err);
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
LOG_DBG("ADC Reading (without processing) %dmV", *voltage_mv);
|
|
||||||
|
|
||||||
cleanup:
|
|
||||||
/* Deactivate sense enable to save power */
|
|
||||||
gpio_pin_set_dt(&config->sen_gpio, 0);
|
|
||||||
k_mutex_unlock(&data->lock);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
int vnd7050aj_read_load_current(const struct device *dev, uint8_t channel, int32_t *current_ma)
|
|
||||||
{
|
|
||||||
const struct vnd7050aj_config *config = dev->config;
|
|
||||||
int32_t sense_mv;
|
|
||||||
int err;
|
|
||||||
|
|
||||||
if (channel != VND7050AJ_CHANNEL_0 && channel != VND7050AJ_CHANNEL_1) {
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum vnd7050aj_diag_mode mode =
|
|
||||||
(channel == VND7050AJ_CHANNEL_0) ? DIAG_CURRENT_CH0 : DIAG_CURRENT_CH1;
|
|
||||||
|
|
||||||
err = vnd7050aj_read_sense_voltage(dev, mode, &sense_mv);
|
|
||||||
if (err) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Formula according to datasheet: I_OUT = (V_SENSE / R_SENSE) * K_IL */
|
|
||||||
/* To avoid floating point, we calculate in microamps and then convert to milliamps */
|
|
||||||
int64_t current_ua = ((int64_t)sense_mv * 1000 * config->k_factor) / config->r_sense_ohms;
|
|
||||||
*current_ma = (int32_t)(current_ua / 1000);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int vnd7050aj_read_chip_temp(const struct device *dev, int32_t *temp_c)
|
|
||||||
{
|
|
||||||
const struct vnd7050aj_config *config = dev->config;
|
|
||||||
int32_t sense_mv;
|
|
||||||
int err;
|
|
||||||
|
|
||||||
err = vnd7050aj_read_sense_voltage(dev, DIAG_TEMP, &sense_mv);
|
|
||||||
if (err) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Calculate temperature difference in kelvin first to avoid overflow */
|
|
||||||
int32_t voltage_diff = sense_mv - (int32_t)config->v_sense_0;
|
|
||||||
int32_t temp_diff_kelvin = (voltage_diff * 1000) / (int32_t)config->k_tchip;
|
|
||||||
|
|
||||||
*temp_c = config->t_sense_0 + temp_diff_kelvin;
|
|
||||||
|
|
||||||
LOG_DBG("Voltage diff: %d mV, Temp diff: %d milli°C, Final temp: %d °C",
|
|
||||||
voltage_diff,
|
|
||||||
temp_diff_kelvin,
|
|
||||||
*temp_c);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int vnd7050aj_read_supply_voltage(const struct device *dev, int32_t *voltage_mv)
|
|
||||||
{
|
|
||||||
const struct vnd7050aj_config *config = dev->config;
|
|
||||||
int32_t sense_mv;
|
|
||||||
int err;
|
|
||||||
|
|
||||||
err = vnd7050aj_read_sense_voltage(dev, DIAG_VCC, &sense_mv);
|
|
||||||
if (err) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Formula from datasheet: VCC = V_SENSE * K_VCC */
|
|
||||||
*voltage_mv = (sense_mv * config->k_vcc) / 1000;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int vnd7050aj_reset_fault(const struct device *dev)
|
|
||||||
{
|
|
||||||
const struct vnd7050aj_config *config = dev->config;
|
|
||||||
int err;
|
|
||||||
|
|
||||||
/* Pulse the active-low fault reset pin */
|
|
||||||
err = gpio_pin_set_dt(&config->fault_reset_gpio, 0);
|
|
||||||
if (err) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
k_msleep(1); /* Short pulse */
|
|
||||||
err = gpio_pin_set_dt(&config->fault_reset_gpio, 1);
|
|
||||||
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
source /home/edi/zephyrproject/.venv/bin/activate && source /home/edi/zephyrproject/zephyr/zephyr-env.sh && rm -r build ;west build -p always -b esp32c6_devkitc/esp32c6/hpcore apps/gateway -D CMAKE_OBJCOPY=/home/edi/zephyr-sdk-0.17.1/riscv64-zephyr-elf/bin/riscv64-zephyr-elf-objcopy;
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
source /home/edi/zephyrproject/.venv/bin/activate && \
|
|
||||||
source /home/edi/zephyrproject/zephyr/zephyr-env.sh && \
|
|
||||||
rm -r build ;west build --sysbuild -p always -b esp32c6_devkitc/esp32c6/hpcore apps/gateway -D CMAKE_OBJCOPY=/home/edi/zephyr-sdk-0.17.1/riscv64-zephyr-elf/bin/riscv64-zephyr-elf-objcopy;
|
|
||||||
Binary file not shown.
|
|
@ -1,216 +0,0 @@
|
||||||
import argparse
|
|
||||||
import time
|
|
||||||
import logging
|
|
||||||
import threading
|
|
||||||
import serial
|
|
||||||
import asyncio
|
|
||||||
from pymodbus.server import StartSerialServer
|
|
||||||
from pymodbus.datastore import ModbusSequentialDataBlock, ModbusSlaveContext, ModbusServerContext
|
|
||||||
from pymodbus.framer.rtu import FramerRTU
|
|
||||||
|
|
||||||
# --- Configuration ---
|
|
||||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[logging.FileHandler('communication_debug.log', 'w')])
|
|
||||||
log = logging.getLogger()
|
|
||||||
logging.getLogger("pymodbus").setLevel(logging.DEBUG)
|
|
||||||
|
|
||||||
# --- Constants from Documentation ---
|
|
||||||
# Input Registers (3xxxx)
|
|
||||||
REG_INPUT_VALVE_STATE_MOVEMENT = 0x0000
|
|
||||||
REG_INPUT_MOTOR_OPEN_CURRENT_MA = 0x0001
|
|
||||||
REG_INPUT_MOTOR_CLOSE_CURRENT_MA = 0x0002
|
|
||||||
REG_INPUT_SUPPLY_VOLTAGE_MV = 0x00F5
|
|
||||||
|
|
||||||
# Holding Registers (4xxxx)
|
|
||||||
REG_HOLDING_VALVE_COMMAND = 0x0000
|
|
||||||
|
|
||||||
# --- Simulation Parameters ---
|
|
||||||
SUPPLY_VOLTAGE = 12345 # mV
|
|
||||||
MOTOR_CURRENT_IDLE = 0 # mA
|
|
||||||
MOTOR_CURRENT_MOVING = 80 # mA
|
|
||||||
VALVE_TRAVEL_TIME = 4.5 # seconds
|
|
||||||
|
|
||||||
# --- Valve Logic ---
|
|
||||||
class ValveController:
|
|
||||||
"""Holds the state and logic for the simulated valve based on documentation."""
|
|
||||||
def __init__(self, node_id):
|
|
||||||
self.node_id = node_id
|
|
||||||
self.lock = threading.Lock()
|
|
||||||
|
|
||||||
# Internal State
|
|
||||||
self.movement = 0 # 0=Idle, 1=Öffnet, 2=Schliesst
|
|
||||||
self.state = 0 # 0=Geschlossen, 1=Geöffnet
|
|
||||||
self.is_moving = False
|
|
||||||
self.movement_start_time = 0
|
|
||||||
self.target_state = 0 # 0 for close, 1 for open
|
|
||||||
|
|
||||||
def start_movement(self, command):
|
|
||||||
"""Initiates a valve movement based on a holding register command."""
|
|
||||||
with self.lock:
|
|
||||||
if self.is_moving:
|
|
||||||
log.warning(f"[Node {self.node_id}] Valve is already moving. Ignoring command.")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Command 1: Open
|
|
||||||
if command == 1 and self.state == 0:
|
|
||||||
log.info(f"[Node {self.node_id}] Received command to OPEN valve.")
|
|
||||||
self.movement = 1 # Öffnet
|
|
||||||
self.target_state = 1 # Geöffnet
|
|
||||||
# Command 2: Close
|
|
||||||
elif command == 2 and self.state == 1:
|
|
||||||
log.info(f"[Node {self.node_id}] Received command to CLOSE valve.")
|
|
||||||
self.movement = 2 # Schliesst
|
|
||||||
self.target_state = 0 # Geschlossen
|
|
||||||
# Command 0: Stop
|
|
||||||
elif command == 0:
|
|
||||||
if self.is_moving:
|
|
||||||
log.info(f"[Node {self.node_id}] Received command to STOP valve.")
|
|
||||||
self.is_moving = False
|
|
||||||
self.movement = 0 # Idle
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
log.info(f"[Node {self.node_id}] Valve is already in the requested state or command is invalid.")
|
|
||||||
return
|
|
||||||
|
|
||||||
self.is_moving = True
|
|
||||||
self.movement_start_time = time.time()
|
|
||||||
|
|
||||||
def update_state(self):
|
|
||||||
"""Updates the valve's position and state if it's moving."""
|
|
||||||
with self.lock:
|
|
||||||
if not self.is_moving:
|
|
||||||
return
|
|
||||||
|
|
||||||
elapsed_time = time.time() - self.movement_start_time
|
|
||||||
|
|
||||||
if elapsed_time >= VALVE_TRAVEL_TIME:
|
|
||||||
# Movement is complete
|
|
||||||
self.is_moving = False
|
|
||||||
self.state = self.target_state
|
|
||||||
self.movement = 0 # Idle
|
|
||||||
log.info(f"[Node {self.node_id}] Valve movement finished. State: {'Open' if self.state == 1 else 'Closed'}")
|
|
||||||
|
|
||||||
# --- Modbus Datastore Blocks ---
|
|
||||||
class CustomDataBlock(ModbusSequentialDataBlock):
|
|
||||||
def __init__(self, controller):
|
|
||||||
self.controller = controller
|
|
||||||
# Initialize registers to a safe default size, they will be dynamically updated.
|
|
||||||
super().__init__(0, [0] * 256)
|
|
||||||
|
|
||||||
def setValues(self, address, values):
|
|
||||||
# Handle writes to the VALVE_COMMAND register
|
|
||||||
if address == REG_HOLDING_VALVE_COMMAND:
|
|
||||||
if values:
|
|
||||||
self.controller.start_movement(values[0])
|
|
||||||
else:
|
|
||||||
log.info(f"[Node {self.controller.node_id}] Write to unhandled holding register 0x{address:04X} with value(s): {values}")
|
|
||||||
super().setValues(address, values)
|
|
||||||
|
|
||||||
def getValues(self, address, count=1):
|
|
||||||
self.controller.update_state() # Update valve state before returning values
|
|
||||||
log.debug(f"getValues: requested address={address}")
|
|
||||||
|
|
||||||
# Handle specific input registers
|
|
||||||
if (address - 1) == REG_INPUT_VALVE_STATE_MOVEMENT:
|
|
||||||
valve_state_movement = (self.controller.movement << 8) | self.controller.state
|
|
||||||
return [valve_state_movement]
|
|
||||||
elif (address - 1) == REG_INPUT_MOTOR_OPEN_CURRENT_MA:
|
|
||||||
motor_current = MOTOR_CURRENT_MOVING if self.controller.movement == 1 else MOTOR_CURRENT_IDLE
|
|
||||||
return [motor_current]
|
|
||||||
elif (address - 1) == REG_INPUT_MOTOR_CLOSE_CURRENT_MA:
|
|
||||||
motor_current = MOTOR_CURRENT_MOVING if self.controller.movement == 2 else MOTOR_CURRENT_IDLE
|
|
||||||
return [motor_current]
|
|
||||||
elif (address - 1) == REG_INPUT_SUPPLY_VOLTAGE_MV:
|
|
||||||
return [SUPPLY_VOLTAGE]
|
|
||||||
else:
|
|
||||||
# For any other register, return 0xbeaf
|
|
||||||
return [0xbeaf] * count
|
|
||||||
|
|
||||||
# --- Main Server ---
|
|
||||||
async def run_server(port, node_id, baudrate):
|
|
||||||
"""Sets up and runs the Modbus RTU server."""
|
|
||||||
controller = ValveController(node_id)
|
|
||||||
datablock = CustomDataBlock(controller)
|
|
||||||
|
|
||||||
store = ModbusSlaveContext(
|
|
||||||
di=datablock, # Input Registers
|
|
||||||
co=None, # Coils (not used)
|
|
||||||
hr=datablock, # Holding Registers
|
|
||||||
ir=datablock, # Re-using for simplicity, maps to the same logic
|
|
||||||
)
|
|
||||||
context = ModbusServerContext(slaves={node_id: store}, single=False)
|
|
||||||
|
|
||||||
log.info(f"Starting Modbus RTU Valve Simulator on {port}")
|
|
||||||
log.info(f"Node ID: {node_id}, Baudrate: {baudrate}")
|
|
||||||
log.info("--- Register Map ---")
|
|
||||||
log.info("Input Registers (Read-Only):")
|
|
||||||
log.info(f" 0x{REG_INPUT_VALVE_STATE_MOVEMENT:04X}: VALVE_STATE_MOVEMENT")
|
|
||||||
log.info(f" 0x{REG_INPUT_MOTOR_OPEN_CURRENT_MA:04X}: MOTOR_OPEN_CURRENT_MA")
|
|
||||||
log.info(f" 0x{REG_INPUT_MOTOR_CLOSE_CURRENT_MA:04X}: MOTOR_CLOSE_CURRENT_MA")
|
|
||||||
log.info(f" 0x{REG_INPUT_SUPPLY_VOLTAGE_MV:04X}: SUPPLY_VOLTAGE_MV")
|
|
||||||
log.info("Holding Registers (Read/Write):")
|
|
||||||
log.info(f" 0x{REG_HOLDING_VALVE_COMMAND:04X}: VALVE_COMMAND (1=Open, 2=Close, 0=Stop)")
|
|
||||||
|
|
||||||
log.info("Server listening.")
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
data = ser.read(ser.in_waiting or 1) # Read available data or wait for 1 byte
|
|
||||||
if data:
|
|
||||||
# Process the request
|
|
||||||
request = framer.processIncomingPacket(data)
|
|
||||||
if request:
|
|
||||||
log_line = "<-- Reg: "
|
|
||||||
reg_addr_hex = f"0x{request.address:04X}" if hasattr(request, 'address') else "N/A"
|
|
||||||
rw_indicator = ""
|
|
||||||
reg_type_indicator = ""
|
|
||||||
data_info = ""
|
|
||||||
|
|
||||||
if request.function_code in [0x01, 0x02, 0x03, 0x04]: # Read operations
|
|
||||||
rw_indicator = "r"
|
|
||||||
if request.function_code == 0x03: reg_type_indicator = "Hold."
|
|
||||||
elif request.function_code == 0x04: reg_type_indicator = "Input"
|
|
||||||
log_line += f"{reg_addr_hex}{rw_indicator}"
|
|
||||||
elif request.function_code in [0x05, 0x06, 0x0F, 0x10]: # Write operations
|
|
||||||
rw_indicator = "w"
|
|
||||||
if request.function_code == 0x06 or request.function_code == 0x10: reg_type_indicator = "Hold."
|
|
||||||
elif request.function_code == 0x05 or request.function_code == 0x0F: reg_type_indicator = "Coil"
|
|
||||||
|
|
||||||
if hasattr(request, 'value') and request.value is not None: # For single write (0x05, 0x06)
|
|
||||||
data_info = f" Data: 0x{request.value:04X}"
|
|
||||||
elif hasattr(request, 'values') and request.values is not None: # For multiple write (0x0F, 0x10)
|
|
||||||
data_info = " Data: 0x" + "".join([f"{val:04X}" for val in request.values])
|
|
||||||
elif hasattr(request, 'bits') and request.bits is not None: # For multiple coil write (0x0F)
|
|
||||||
data_info = " Data: 0x" + "".join([f"{int(bit):X}" for bit in request.bits])
|
|
||||||
|
|
||||||
log_line += f"{reg_addr_hex}{rw_indicator} Type: {reg_type_indicator}{data_info}"
|
|
||||||
else:
|
|
||||||
log_line = f"<-- Func: 0x{request.function_code:02X} Raw: {data.hex()}"
|
|
||||||
|
|
||||||
print(log_line)
|
|
||||||
sys.stdout.flush()
|
|
||||||
|
|
||||||
response = request.execute(context)
|
|
||||||
if response:
|
|
||||||
pdu = framer.buildPacket(response)
|
|
||||||
ser.write(pdu)
|
|
||||||
|
|
||||||
response_reg_addr = f"0x{request.address:04X}" if hasattr(request, 'address') else "N/A"
|
|
||||||
response_data_hex = ""
|
|
||||||
response_data_dec = ""
|
|
||||||
|
|
||||||
if hasattr(response, 'registers') and response.registers is not None:
|
|
||||||
response_data_hex = "".join([f"{val:04X}" for val in response.registers])
|
|
||||||
response_data_dec = ", ".join([str(val) for val in response.registers])
|
|
||||||
elif hasattr(response, 'bits') and response.bits is not None:
|
|
||||||
response_data_hex = "".join([f"{int(bit):X}" for bit in response.bits])
|
|
||||||
response_data_dec = ", ".join([str(int(bit)) for bit in response.bits])
|
|
||||||
elif hasattr(response, 'value') and response.value is not None: # For single write response
|
|
||||||
response_data_hex = f"{response.value:04X}"
|
|
||||||
response_data_dec = str(response.value)
|
|
||||||
|
|
||||||
print(f"--> Reg: {response_reg_addr} Data: 0x{response_data_hex} (Dec: {response_data_dec})")
|
|
||||||
sys.stdout.flush()
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error during serial communication: {e}")
|
|
||||||
sys.stderr.flush()
|
|
||||||
await asyncio.sleep(0.001) # Small delay to prevent busy-waiting
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
pymodbus
|
|
||||||
pyserial
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
#
|
|
||||||
# This script creates a pair of virtual serial ports (pseudo-terminals)
|
|
||||||
# that are linked together, allowing two applications to communicate
|
|
||||||
# as if they were connected by a physical null-modem cable.
|
|
||||||
#
|
|
||||||
# It uses `socat`, a powerful command-line utility for data transfer.
|
|
||||||
|
|
||||||
# --- Check for socat ---
|
|
||||||
if ! command -v socat &> /dev/null
|
|
||||||
then
|
|
||||||
echo "Error: 'socat' is not installed. It is required to create virtual serial ports."
|
|
||||||
echo "Please install it using your package manager."
|
|
||||||
echo "For Debian/Ubuntu: sudo apt-get update && sudo apt-get install socat"
|
|
||||||
echo "For Fedora: sudo dnf install socat"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- Configuration ---
|
|
||||||
# The script will create symlinks to the virtual ports in the current directory
|
|
||||||
# for easy access.
|
|
||||||
PORT1="./vcom_a"
|
|
||||||
PORT2="./vcom_b"
|
|
||||||
|
|
||||||
# --- Cleanup function ---
|
|
||||||
# This function will be called when the script exits to remove the symlinks.
|
|
||||||
trap 'cleanup' EXIT
|
|
||||||
|
|
||||||
cleanup() {
|
|
||||||
echo -e "\nCleaning up..."
|
|
||||||
rm -f "$PORT1" "$PORT2"
|
|
||||||
echo "Removed symlinks '$PORT1' and '$PORT2'."
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# --- Main Execution ---
|
|
||||||
echo "=================================================="
|
|
||||||
echo " Virtual Serial Port Pair Setup"
|
|
||||||
echo "=================================================="
|
|
||||||
echo
|
|
||||||
echo "Creating a linked pair of virtual serial ports."
|
|
||||||
echo " - Port A will be available at: $PORT1"
|
|
||||||
echo " - Port B will be available at: $PORT2"
|
|
||||||
echo
|
|
||||||
echo "You can now connect the simulator to one port and your client script to the other."
|
|
||||||
echo "Example:"
|
|
||||||
echo " Terminal 1: python modbus_valve_simulator.py $PORT1"
|
|
||||||
echo " Terminal 2: python your_client_script.py $PORT2"
|
|
||||||
echo
|
|
||||||
echo "Press [Ctrl+C] to shut down the virtual ports and exit."
|
|
||||||
echo "--------------------------------------------------"
|
|
||||||
|
|
||||||
# The core command.
|
|
||||||
# -d -d: Increases verbosity to show data transfer.
|
|
||||||
# pty: Creates a pseudo-terminal (virtual port).
|
|
||||||
# raw,echo=0: Puts the terminal in raw mode, suitable for serial data.
|
|
||||||
# link=<path>: Creates a symbolic link to the PTY device for a stable name.
|
|
||||||
socat -d -d pty,raw,echo=0,link="$PORT1" pty,raw,echo=0,link="$PORT2"
|
|
||||||
Loading…
Reference in New Issue