canbus, modbus working, valve has to be implemented in real

This commit is contained in:
Eduard Iten 2025-06-19 16:56:21 +02:00
parent 852c5c72be
commit e9fc370094
33 changed files with 1261 additions and 870 deletions

10
software/.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,10 @@
{
"recommendations": [
"ms-vscode.cpptools-extension-pack",
"ms-python.python",
"ms-vscode.vscode-embedded-tools",
"ms-vscode.vscode-serial-monitor",
"marus25.cortex-debug",
"donjayamanne.python-environment-manager"
]
}

28
software/.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,28 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch",
"device": "STM32F103RB",
"cwd": "${workspaceFolder}",
"executable": "build/zephyr/zephyr.elf",
"request": "launch",
"type": "cortex-debug",
//"runToEntryPoint": "main",
"servertype": "jlink",
"gdbPath": "${userHome}/zephyr-sdk-0.17.1/arm-zephyr-eabi/bin/arm-zephyr-eabi-gdb",
"preLaunchTask": "West Build"
},
{
"name": "Attach",
"device": "nRF52840_xxAA",
"cwd": "${workspaceFolder}",
"executable": "build/zephyr/zephyr.elf",
"request": "attach",
"type": "cortex-debug",
// "runToEntryPoint": "main",
"servertype": "jlink",
"gdbPath": "${userHome}/zephyr-sdk-0.17.1/arm-zephyr-eabi/bin/arm-zephyr-eabi-gdb"
},
]
}

View File

@ -1,23 +1,12 @@
{ {
"files.associations": { // Hush CMake
"log.h": "c", "cmake.configureOnOpen": false,
"modbus.h": "c",
"array": "c", // IntelliSense
"string": "c", "C_Cpp.default.compilerPath": "${userHome}/zephyr-sdk-0.17.1/arm-zephyr-eabi/bin/arm-zephyr-eabi-gcc.exe",
"string_view": "c", "C_Cpp.default.compileCommands": "${workspaceFolder}/build/compile_commands.json",
"canbus.h": "c",
"kernel.h": "c", // File Associations
"settings.h": "c", "files.associations": {
"can.h": "c", }
"stdlib.h": "c",
"reboot.h": "c"
},
"C_Cpp.default.compileCommands": [
"build/compile_commands.json",
"../build/compile_commands.json"
],
"cmake.sourceDirectory": "/home/edi/zephyrproject/projects/irrigation_system/software/test_canbus",
"nrf-connect.applications": [
"${workspaceFolder}/test_canbus"
],
} }

96
software/.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,96 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "West Build",
"type": "shell",
"group": {
"kind": "build",
"isDefault": true
},
"linux": {
"command": "${userHome}/zephyrproject/.venv/bin/west"
},
"windows": {
"command": "${userHome}/zephyrproject/.venv/Scripts/west.exe"
},
"osx": {
"command": "${userHome}/zephyrproject/.venv/bin/west"
},
"args": [
"build",
"-p",
"auto",
"-b",
"valve_node"
],
"problemMatcher": [
"$gcc"
]
},
{
"label": "West Configurable Build",
"type": "shell",
"group": {
"kind": "build",
},
"linux": {
"command": "${userHome}/zephyrproject/.venv/bin/west"
},
"windows": {
"command": "${userHome}/zephyrproject/.venv/Scripts/west.exe"
},
"osx": {
"command": "${userHome}/zephyrproject/.venv/bin/west"
},
"args": [
"build",
"-p",
"${input:pristine}",
"-b",
"${input:board}"
],
"problemMatcher": [
"$gcc"
]
},
{
"label": "West Flash",
"type": "shell",
"linux": {
"command": "${userHome}/zephyrproject/.venv/bin/west"
},
"windows": {
"command": "${userHome}/zephyrproject/.venv/Scripts/west.exe"
},
"osx": {
"command": "${userHome}/zephyrproject/.venv/bin/west"
},
"args": [
"flash"
],
"problemMatcher": [
"$gcc"
]
}
],
"inputs": [
{
"id": "board",
"type": "promptString",
"default": "valve-node",
"description": "See https://docs.zephyrproject.org/latest/boards/index.html"
},
{
"id": "pristine",
"type": "pickString",
"description": "Choose when to run a pristine build",
"default": "auto",
"options": [
"auto",
"always",
"never"
]
}
]
}

25
software/CMakeLists.txt Normal file
View File

@ -0,0 +1,25 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
list(APPEND BOARD_ROOT ${CMAKE_CURRENT_SOURCE_DIR})
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(valve_node)
target_sources(app PRIVATE src/main.c)
target_sources(app PRIVATE lib/canbus.c)
# source files for modbus waterlevel sensor
zephyr_library_sources_ifdef(CONFIG_HAS_MODBUS_WATERLEVEL_SENSOR
lib/waterlevel_sensor.c
)
#source files for valve
zephyr_library_sources_ifdef(CONFIG_HAS_VALVE
lib/valve.c
)
zephyr_include_directories(
lib
)

View File

@ -1,7 +1,7 @@
config BOARD_VALVE_NODE config BOARD_VALVE_NODE
select SOC_STM32F103XB select SOC_STM32F103XB
mainmenu "Controller Area Network sample application" mainmenu "APP CAN Settings"
config LOOPBACK_MODE config LOOPBACK_MODE
bool "Loopback LOOPBACK_MODE" bool "Loopback LOOPBACK_MODE"
default n default n
@ -9,6 +9,7 @@ config LOOPBACK_MODE
Set the can controller to loopback mode. Set the can controller to loopback mode.
This allows testing without a second board. This allows testing without a second board.
mainmenu "APP Logging Settings"
config LOG_CAN_LEVEL config LOG_CAN_LEVEL
int "Log level for CAN" int "Log level for CAN"
default 3 default 3
@ -23,4 +24,35 @@ config LOG_SETTINGS_LEVEL
range 0 7 range 0 7
help help
Set the log level for CAN messages. Set the log level for CAN messages.
0 = None, 1 = Error, 2 = Warning, 3 = Info, 4 = Debug, 5 = Trace, 6 = Debug2, 7 = Debug3 0 = None, 1 = Error, 2 = Warning, 3 = Info, 4 = Debug, 5 = Trace, 6 = Debug2, 7 = Debug3
config LOG_WATERLEVELSENSOR_LEVEL
int "Log level for waterlevel sensor"
default 3
range 0 7
help
Set the log level for CAN messages.
0 = None, 1 = Error, 2 = Warning, 3 = Info, 4 = Debug, 5 = Trace, 6 = Debug2, 7 = Debug3
config LOG_VALVE_LEVEL
int "Log level for valve control"
default 3
range 0 7
help
Set the log level for valve control messages.
0 = None, 1 = Error, 2 = Warning, 3 = Info, 4 = Debug, 5 = Trace, 6 = Debug2, 7 = Debug3
mainmenu "Irrigation controller node configuration"
config HAS_MODBUS_WATERLEVEL_SENSOR
bool "Has modbus water level sensor"
default n
help
Enable modbus water level sensor support.
This allows reading the water level from a modbus device.
config HAS_VALVE
bool "Has valve control"
default n
help
Enable valve control support.
This allows controlling valves via CAN messages.

View File

@ -8,3 +8,4 @@ board_runner_args(jlink "--device=STM32F103RB" "--speed=4000")
include(${ZEPHYR_BASE}/boards/common/stm32cubeprogrammer.board.cmake) include(${ZEPHYR_BASE}/boards/common/stm32cubeprogrammer.board.cmake)
include(${ZEPHYR_BASE}/boards/common/openocd-stm32.board.cmake) include(${ZEPHYR_BASE}/boards/common/openocd-stm32.board.cmake)
include(${ZEPHYR_BASE}/boards/common/jlink.board.cmake) include(${ZEPHYR_BASE}/boards/common/jlink.board.cmake)
include(${ZEPHYR_BASE}/boards/common/blackmagicprobe.board.cmake)

View File

@ -184,7 +184,7 @@
pinctrl-0 = <&can_rx_pa11 &can_tx_pa12>; pinctrl-0 = <&can_rx_pa11 &can_tx_pa12>;
pinctrl-names = "default"; pinctrl-names = "default";
status= "okay"; status= "okay";
bus-speed = <500000>; bus-speed = <125000>;
}; };
&exti { &exti {

View File

@ -2,140 +2,151 @@
#include <zephyr/logging/log.h> #include <zephyr/logging/log.h>
#include <zephyr/kernel.h> #include <zephyr/kernel.h>
#include <zephyr/sys/byteorder.h> #include <zephyr/sys/byteorder.h>
#include "config.h" #include <zephyr/drivers/can.h>
#include "modbus.h" #include <stdlib.h>
int canbus_node_id = 1; // Default node ID for CAN bus #ifdef CONFIG_HAS_MODBUS_WATERLEVEL_SENSOR
#include "waterlevel_sensor.h"
#endif // CONFIG_HAS_MODBUS_WATERLEVEL_SENSOR
#ifdef CONFIG_HAS_VALVE
#include "valve.h"
#endif // CONFIG_HAS_VALVE
const struct device *const can_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_canbus)); const struct device *const can_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_canbus));
struct k_thread rx_thread_data;
K_THREAD_STACK_DEFINE(rx_thread_stack, CANBUS_RX_THREAD_STACK_SIZE);
CAN_MSGQ_DEFINE(commands_msq, CANBUS_RX_MSGQ_SIZE);
LOG_MODULE_REGISTER(canbus, CONFIG_LOG_CAN_LEVEL); LOG_MODULE_REGISTER(canbus, CONFIG_LOG_CAN_LEVEL);
static void rx_thread(void *arg1, void *arg2, void *arg3) K_MSGQ_DEFINE(canbus_msgq, sizeof(struct can_frame), CANBUS_RX_MSGQ_SIZE, 4);
K_THREAD_STACK_DEFINE(canbus_rx_stack, CANBUS_RX_THREAD_STACK_SIZE);
uint8_t node_id = 0; // Default node ID, can be set later
uint8_t can_msg_id = 0;
k_tid_t canbus_rx_thread_id;
struct k_thread canbus_rx_thread_data;
int canbus_rx_filter_id = -1;
void canbus_rx_callback(const struct device *dev, struct can_frame *frame, void *user_data)
{
int rc;
ARG_UNUSED(dev);
ARG_UNUSED(user_data);
if (frame->id >> 8 != node_id) // Check if the frame ID matches the node ID
{
LOG_DBG("Received CAN frame with ID %d, but it does not match node ID %d", frame->id >> 8, node_id);
return; // Ignore frames that do not match the node ID
}
// Process the received CAN frame
rc = k_msgq_put(&canbus_msgq, frame, K_NO_WAIT);
if (rc != 0)
{
LOG_ERR("Failed to put CAN frame into message queue: %d", rc);
}
}
void canbus_thread(void *arg1, void *arg2, void *arg3)
{ {
ARG_UNUSED(arg1); ARG_UNUSED(arg1);
ARG_UNUSED(arg2); ARG_UNUSED(arg2);
ARG_UNUSED(arg3); ARG_UNUSED(arg3);
const struct can_filter filter = { LOG_INF("CAN bus thread started");
.flags = 0, // No special flags,
.id = (canbus_node_id << 8) | 0x00, // Standard ID with node ID in the first byte
.mask = CAN_STD_ID_MASK & 0xFFFFFF00U // Mask for standard ID, ignoring the last byte
};
struct can_frame frame;
int filter_id;
extern struct k_msgq modbus_msgq;
filter_id = can_add_rx_filter_msgq(can_dev, &commands_msq, &filter);
LOG_DBG("RX thread started. Filter ID: %d, flags: 0x%02x, id: 0x%08x, mask: 0x%08x", filter_id, filter.flags, filter.id, filter.mask);
// Main loop for CAN bus operations
while (1) while (1)
{ {
k_msgq_get(&commands_msq, &frame, K_FOREVER); int rc;
LOG_DBG("Received CAN frame: ID=0x%08x, DLC=%u, Flags=0x%02x", struct can_frame frame;
frame.id, frame.dlc, frame.flags); k_msgq_get(&canbus_msgq, &frame, K_FOREVER); // Wait for a message from the queue
LOG_DBG("Received CAN frame with ID: 0x%02x, DLC: %d, RTR: %d",
uint8_t target_node = (frame.id >> 8) & 0xFF; // Extract the target node from the ID frame.id, frame.dlc, (uint8_t)(frame.flags & CAN_FRAME_RTR));
if (target_node != canbus_node_id) LOG_HEXDUMP_DBG(frame.data, frame.dlc, "CAN frame data");
uint8_t reg = frame.id & 0xFF; // Extract register from the frame ID
bool is_rtr = (frame.flags & CAN_FRAME_RTR) != 0; // Check if it's a remote transmission request
switch (reg)
{ {
LOG_WRN("Received frame for different node ID: %d, expected: %d", target_node, canbus_node_id); #ifdef CONFIG_HAS_MODBUS_WATERLEVEL_SENSOR
continue; // Ignore frames not meant for this node case CANBUS_REG_WATERLEVEL_LEVEL:
} case CANBUS_REG_WATERLEVEL_ZERO_POINT:
uint8_t target_register = frame.id & 0xFF; // Extract the register address from the ID case CANBUS_REG_WATERLEVEL_MAX_POINT:
waterlevel_command_t command;
if (IS_ENABLED(CONFIG_CAN_ACCEPT_RTR) && (frame.flags & CAN_FRAME_RTR) != 0U) command.cmd = is_rtr ? WATERLEVEL_CMD_GET : WATERLEVEL_CMD_SET; // Determine command type based on RTR
{ command.reg = reg; // Set the register ID
LOG_DBG("Received RTR frame for register address: 0x%02x", target_register); int16_t value = 0; // Initialize value to 0
switch (target_register) if (!is_rtr) // If it's not a remote request, extract the value from the frame data
{ {
case CANBUS_REGISTER_WATER_LEVEL_MM: if (frame.dlc < sizeof(command.data))
case CANBUS_REGISTER_WATER_MINIMUM: { LOG_ERR("Received frame with insufficient data length: %d", frame.dlc);
case CANBUS_REGISTER_WATER_MAXIMUM: continue; // Skip processing if data length is insufficient
LOG_DBG("Handling water sensor request for node %d", target_node); }
modbus_register_t reg_max; value = sys_be16_to_cpu(*(uint16_t *)frame.data); // Convert data from big-endian to host byte order
reg_max.reg = target_register; command.data = value; // Set the data field
reg_max.command = CANBUS_REGISTER_COMMAND_GET; // Set command to GET LOG_DBG("Setting command data to: %d", value);
k_msgq_put(&modbus_msgq, &reg_max, K_NO_WAIT);
break;
default:
LOG_WRN("Received RTR frame for unknown register address: 0x%02x", target_register);
} }
continue; extern struct k_msgq waterlevel_sensor_msgq; // Declare the water level sensor message queue
} k_msgq_put(&waterlevel_sensor_msgq, &command, K_NO_WAIT);
// Log the received frame details break;
LOG_DBG("Received CAN frame: ID=0x%08x, DLC=%u, Flags=0x%02x, Target Node=%d, Register Address=0x%02x", #endif // CONFIG_HAS_MODBUS_WATERLEVEL_SENSOR
frame.id, frame.dlc, frame.flags, target_node, target_register); #ifdef CONFIG_HAS_VALVE
switch (target_register) case CANBUS_REG_VALVE_STATUS:
{ case CANBUS_REG_VALVE_OPERATION:
case CANBUS_REGISTER_WATER_MINIMUM: if (is_rtr)
case CANBUS_REGISTER_WATER_MAXIMUM:
LOG_DBG("Handling water sensor data for node %d", target_node);
modbus_register_t reg;
reg.reg = target_register;
reg.command = CANBUS_REGISTER_COMMAND_SET; // Set command to SET
if (frame.dlc >= sizeof(reg.value)) // Ensure enough data length for value
{ {
reg.value = sys_get_be16(frame.data); // Convert received data to big-endian format LOG_DBG("Received remote request for valve status or operation");
LOG_DBG("Received value: %d, 0x%04x", reg.value, reg.value); if (reg == CANBUS_REG_VALVE_STATUS)
{
valve_send_status(); // Send the current valve status
}
else if (reg == CANBUS_REG_VALVE_OPERATION)
{
valve_send_operation(); // Send the current valve operation state
} else {
LOG_ERR("Unknown valve register: 0x%02x", reg);
continue; // Skip processing if the register is unknown
}
} }
else else
{ {
LOG_ERR("Received frame with insufficient data length: %u", frame.dlc); LOG_ERR("Received CAN frame with data for valve status or operation, but RTR is not set");
continue; // Skip processing if data length is invalid continue; // Skip processing if RTR is not set for valve status or operation
}
break;
case CANBUS_REG_VALVE_COMMAND:
{
if (is_rtr) {
LOG_ERR("Received remote request for valve command, but this is not supported");
continue; // Skip processing if RTR is set for valve command
} else {
// Extract the command from the frame data
if (frame.dlc < sizeof(uint8_t)) {
LOG_ERR("Received frame with insufficient data length for valve command: %d", frame.dlc);
continue; // Skip processing if data length is insufficient
}
uint8_t command = frame.data[0]; // Get the command from the first byte of data
LOG_DBG("Received valve command: 0x%02x", command);
rc = valve_cmd(command); // Send the command to the valve
if (rc < 0) {
LOG_ERR("Failed to send valve command: %d", rc);
continue; // Skip processing if sending the command failed
}
} }
k_msgq_put(&modbus_msgq, &reg, K_NO_WAIT);
break; break;
} }
LOG_HEXDUMP_DBG(frame.data, sizeof(frame.data) * sizeof(frame.data[0]), "Frame data:"); default:
LOG_DBG("Received CAN frame with unknown register: 0x%02x", reg);
break;
}
#endif // CONFIG_HAS_VALVE
} }
} }
static void canbus_tx_callback(const struct device *dev, int error, void *user_data)
{
ARG_UNUSED(dev);
ARG_UNUSED(user_data);
if (error != 0)
{
LOG_ERR("CAN transmission error: %d", error);
}
else
{
LOG_DBG("CAN message sent successfully");
}
}
int canbus_init(void) int canbus_init(void)
{ {
k_tid_t rx_tid; int rc = 0;
int rc;
rc = settings_get_val_len("canbus/node_id");
if (rc < 0)
{
LOG_ERR("Failed to check CAN bus settings: %d", rc);
return rc;
}
else if (rc == 0)
{
LOG_WRN("No CAN bus node id found in settings, using default (%d)", canbus_node_id);
settings_save_one("canbus/node_id", &canbus_node_id, sizeof(canbus_node_id));
if (rc < 0)
{
LOG_ERR("Failed to save default CAN bus node id: %d", rc);
return rc;
}
}
else
{
LOG_DBG("CAN bus node ID found in settings: %d", canbus_node_id);
}
if (!device_is_ready(can_dev)) if (!device_is_ready(can_dev))
{ {
@ -151,6 +162,7 @@ int canbus_init(void)
return rc; return rc;
} }
#endif #endif
rc = can_start(can_dev); rc = can_start(can_dev);
if (rc != 0) if (rc != 0)
{ {
@ -158,71 +170,117 @@ int canbus_init(void)
return 0; return 0;
} }
LOG_DBG("CAN device %s is ready", can_dev->name); LOG_DBG("CAN device %s is ready", can_dev->name);
// Initialize the CAN RX thread
canbus_rx_thread_id = k_thread_create(&canbus_rx_thread_data, canbus_rx_stack,
K_THREAD_STACK_SIZEOF(canbus_rx_stack), canbus_thread,
NULL, NULL, NULL,
CANBUS_RX_THREAD_PRIORITY, 0, K_NO_WAIT);
k_thread_name_set(canbus_rx_thread_id, "canbus_rx");
LOG_DBG("Trying to start rx thread"); const struct can_filter filter = {
.id = node_id << 8, // Standard ID with node ID in the first byte
// Create the RX thread for handling incoming CAN messages .mask = 0x700, // Mask to match the first byte of the ID
rx_tid = k_thread_create(&rx_thread_data, rx_thread_stack, .flags = 0, // No special flags
K_THREAD_STACK_SIZEOF(rx_thread_stack), };
rx_thread, NULL, NULL, NULL, canbus_rx_filter_id = can_add_rx_filter(can_dev, canbus_rx_callback, NULL, &filter);
CANBUS_RX_THREAD_PRIORITY, 0, K_NO_WAIT); LOG_DBG("CAN RX filter added for node ID %d", canbus_rx_filter_id);
if (rx_tid == NULL)
{
LOG_ERR("Failed to create CAN RX thread");
return -ENOMEM; // Not enough memory to create thread
}
LOG_INF("CAN bus initialized with node ID: %d,", canbus_node_id);
return 0; // Return 0 on success
}
int canbus_send_register(uint8_t destination_node, uint8_t register_address, uint8_t *data, size_t data_length)
{
struct can_frame frame;
if (data_length > sizeof(frame.data))
{
LOG_ERR("Data length exceeds maximum CAN frame size");
return -EINVAL; // Invalid argument
}
frame.id = (destination_node << 8) | register_address; // Standard ID with node ID in the first byte
frame.dlc = data_length;
frame.flags = 0; // No special flags
// Copy data into the frame
memcpy(frame.data, data, data_length);
// Send the CAN frame
int rc = can_send(can_dev, &frame, K_MSEC(100), canbus_tx_callback, NULL);
if (rc < 0)
{
LOG_ERR("Failed to send CAN message: %d", rc);
return rc; // Return error code
}
LOG_DBG("CAN message sent: ID=0x%08x, DLC=%u", frame.id, frame.dlc);
return 0; return 0;
} }
int canbus_request_register(uint8_t node_id, uint8_t register_address) void canbus_tx8_callback(const struct device *dev, int error, void *user_data)
{ {
int rc; ARG_UNUSED(dev);
struct can_frame frame; uint8_t frame_id = *(uint8_t *)user_data; // Retrieve the frame ID from user data
// Prepare the CAN frame for the request if (error != 0)
frame.id = (node_id << 8) | register_address; // Standard ID with node ID in the first byte
frame.dlc = 0; // No data for request
frame.flags = CAN_FRAME_RTR; // Set RTR flag for request
// Send the CAN frame
rc = can_send(can_dev, &frame, K_MSEC(100), canbus_tx_callback, NULL);
if (rc < 0)
{ {
LOG_ERR("Failed to send CAN request: %d", rc); LOG_ERR("CAN transmission error. Error code: %d, Frame ID: %d", error, frame_id);
return rc; // Return error code
} }
else
{
LOG_DBG("CAN message with Frame ID %d sent successfully", frame_id);
}
free(user_data); // Free the allocated memory for frame ID
}
LOG_DBG("CAN request sent: ID=0x%08x", frame.id); int canbus_send8(uint16_t reg, uint8_t value)
{
int rc = 0;
struct can_frame frame = {
.id = (node_id << 8) | reg, // Standard ID with node ID in the first byte
.dlc = sizeof(value), // Data Length Code (DLC)
};
frame.data[0] = value; // Set the value in the first byte of the data
uint8_t *frame_id = malloc(sizeof(uint8_t)); // Allocate memory for frame ID
LOG_DBG("Preparing to send 8-bit value 0x%02x to register 0x%02x on node %d", value, reg, node_id);
if (frame_id == NULL)
{
LOG_ERR("Failed to allocate memory for frame ID");
return -ENOMEM; // Not enough memory
}
*frame_id = can_msg_id++; // Increment message ID for uniqueness
LOG_DBG("Using frame ID: %d", *frame_id);
rc = can_send(can_dev, &frame, CANBUS_TX_TIMEOUT, canbus_tx8_callback, frame_id);
// Send the CAN frame with a timeout and callback
if (rc != 0)
{
LOG_ERR("Failed to queue CAN frame: %d", rc);
free(frame_id); // Free the allocated memory for frame ID
return rc;
}
return 0; return 0;
} }
void canbus_tx16_callback(const struct device *dev, int error, void *user_data)
{
ARG_UNUSED(dev);
uint8_t frame_id = *(uint8_t *)user_data; // Retrieve the frame ID from user data
if (error != 0)
{
LOG_ERR("CAN transmission error. Error code: %d, Frame ID: %d", error, frame_id);
}
else
{
LOG_DBG("CAN message with Frame ID %d sent successfully", frame_id);
}
free(user_data); // Free the allocated memory for frame ID
}
int canbus_send16(uint16_t reg, uint16_t value)
{
int rc = 0;
union data_type
{
int16_t value;
uint8_t bytes[2];
} data;
data.value = sys_cpu_to_be16(value); // Convert value to big-endian format
struct can_frame frame = {
.id = (node_id << 8) | reg, // Standard ID with node ID in the first byte
.dlc = sizeof(data), // Data Length Code (DLC)
};
memcpy(frame.data, data.bytes, sizeof(data)); // Copy data into the frame
uint8_t *frame_id = malloc(sizeof(uint8_t)); // Allocate memory for frame ID
LOG_DBG("Preparing to send 16-bit value 0x%04x to register 0x%02x on node %d", value, reg, node_id);
if (frame_id == NULL)
{
LOG_ERR("Failed to allocate memory for frame ID");
return -ENOMEM; // Not enough memory
}
*frame_id = can_msg_id++; // Increment message ID for uniqueness
LOG_DBG("Using frame ID: %d", *frame_id);
rc = can_send(can_dev, &frame, CANBUS_TX_TIMEOUT, canbus_tx16_callback, frame_id);
// Send the CAN frame with a timeout and callback
if (rc != 0)
{
LOG_ERR("Failed to queue CAN frame: %d", rc);
free(frame_id); // Free the allocated memory for frame ID
return rc;
}
LOG_DBG("Queued 16-bit value 0x%04x to register 0x%02x on node %d, frame ID: %d",
value, reg, node_id, *frame_id);
return 0;
}

View File

@ -1,32 +1,16 @@
#ifndef __CANBUS_H__ #ifndef __CANBUS_H__
#define __CANBUS_H__ #define __CANBUS_H__
#include <zephyr/drivers/can.h> #include <stdint.h>
#include <zephyr/kernel.h>
#include "canbus_registers.h" #include "canbus_registers.h"
#define CANBUS_RX_THREAD_STACK_SIZE (512) #define CANBUS_RX_THREAD_STACK_SIZE (512) // Stack size for the CAN RX thread
#define CANBUS_RX_THREAD_PRIORITY (5) #define CANBUS_RX_THREAD_PRIORITY (5) // Priority for the CAN RX thread
#define CANBUS_RX_MSGQ_SIZE (5) #define CANBUS_RX_MSGQ_SIZE (5) // Size of the message queue for CAN RX thread
#define CANBUS_TX_TIMEOUT K_MSEC(100) // Timeout for sending CAN messages in milliseconds
typedef struct can_frame_t{
/** Standard (11-bit) or extended (29-bit) CAN identifier. */
uint32_t id;
/** Data Length Code (DLC) indicating data length in bytes. */
uint8_t dlc;
/** Flags. */
uint8_t flags;
/** The frame payload data. */
union {
/** Payload data accessed as unsigned 8 bit values. */
uint8_t data[8];
uint16_t data_16[4];
uint32_t data_32[2];
} data;
} can_frame_t;
int canbus_init(void); int canbus_init(void);
int canbus_send_register(uint8_t node_id, uint8_t register_address, uint8_t *data, size_t data_length); int canbus_send8(uint16_t reg, uint8_t value);
int canbus_request_register(uint8_t node_id, uint8_t register_address); int canbus_send16(uint16_t reg, uint16_t value);
#endif // __CANBUS_H__ #endif // __CANBUS_H__

View File

@ -1,18 +1,42 @@
#ifndef __CANBUS_REGISTERS_H__ #ifndef __CANBUS_REGISTERS_H__
#define __CANBUS_REGISTERS_H__ #define __CANBUS_REGISTERS_H__
typedef enum { enum canbus_registers {
CANBUS_REGISTER_VALVE_COMMAND = 0x00, CANBUS_REG_REBOOT = 0x00,
CANBUS_REGISTER_VALVE_STATE = 0x01, CANBUS_REG_STATE = 0x01,
CANBUS_REG_ERROR = 0x02,
CANBUS_REGISTER_WATER_LEVEL_MM = 0x10,
CANBUS_REGISTER_WATER_MINIMUM = 0x11,
CANBUS_REGISTER_WATER_MAXIMUM = 0x12,
} canbus_registers_t;
typedef enum { CANBUS_REG_VALVE_STATUS = 0x10,
CANBUS_REGISTER_COMMAND_SET = 0x00, CANBUS_REG_VALVE_OPERATION = 0x11,
CANBUS_REGISTER_COMMAND_GET = 0x01, CANBUS_REG_VALVE_COMMAND = 0x12,
} canbus_register_command_t;
CANBUS_REG_WATERLEVEL_STATE = 0x20,
CANBUS_REG_WATERLEVEL_LEVEL = 0x21,
CANBUS_REG_WATERLEVEL_ZERO_POINT = 0x22,
CANBUS_REG_WATERLEVEL_MAX_POINT = 0x23,
};
enum valve_status {
VALVE_STATE_CLOSED = 0x00,
VALVE_STATE_OPEN = 0x01,
VALVE_STATE_ERROR = 0x02,
VALVE_STATE_UNKNOWN = 0x03,
};
enum valve_operation_state {
VALVE_OPERATION_IDLE = 0x00,
VALVE_OPERATION_OPENING = 0x01,
VALVE_OPERATION_CLOSING = 0x02,
};
enum valve_command {
VALVE_COMMAND_STOP = 0x00,
VALVE_COMMAND_OPEN = 0x01,
VALVE_COMMAND_CLOSE = 0x02,
};
enum waterlevel_state {
WATERLEVEL_STATE_OK = 0x00,
WATERLEVEL_STATE_MODBUS_ERROR = 0x02,
};
#endif // __CANBUS_REGISTERS_H__ #endif // __CANBUS_REGISTERS_H__

View File

@ -1,133 +0,0 @@
#include "config.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(config, CONFIG_LOG_SETTINGS_LEVEL);
extern int canbus_node_id; // Default node ID for CAN bus
// Only compile shell commands if CONFIG_SHELL is enabled
// and CONFIG_REBOOT is enabled, as rebooting is required to apply changes
#ifdef CONFIG_SHELL
#ifndef CONFIG_REBOOT
#error You must enable CONFIG_REBOOT to use the shell commands
#endif // CONFIG_REBOOT
#include <zephyr/shell/shell.h>
#include <zephyr/sys/reboot.h>
#include <stdlib.h>
int reboot_system(const struct shell *shell, size_t argc, char **argv)
{
// Reboot the system
shell_print(shell, "Rebooting node in 1 second...");
k_sleep(K_MSEC(1000)); // Wait for 1 second before rebooting
sys_reboot(SYS_REBOOT_COLD); // Perform a cold reboot
return 0; // Return 0 on success
}
int shell_print_config(const struct shell *shell, size_t argc, char **argv)
{
// Print the current settings for the CAN bus node ID
shell_print(shell, "Current configuration settings:");
shell_print(shell, "%26s <%d>", "CANBUS node ID:", canbus_node_id);
return 0;
}
int shell_set_canbus_node_id(const struct shell *shell, size_t argc, char **argv)
{
shell_print(shell, "argument count: %zu", argc);
if (argc != 2)
{
shell_error(shell, "Usage: config set_nodeid <value>");
return -EINVAL; // Invalid argument
}
int new_node_id = atoi(argv[1]);
if (new_node_id < 1 || new_node_id > 7)
{
shell_error(shell, "Invalid CAN bus node ID: <%d>. Must be between 1 and 7.", new_node_id);
return -EINVAL; // Invalid argument
}
canbus_node_id = new_node_id;
LOG_INF("Set CAN bus node ID to <%d>", canbus_node_id);
// Save the new node ID to settings
int rc = settings_save_one("canbus/node_id", &canbus_node_id, sizeof(canbus_node_id));
if (rc < 0)
{
shell_error(shell, "Failed to save CAN bus node ID: %d", rc);
return rc; // Save error
}
shell_print(shell, "CAN bus node ID set to <%d> and saved to settings", canbus_node_id);
shell_warn(shell, "Reboot the node (command: 'reboot') to apply changes.");
return 0; // Return 0 on success
}
SHELL_STATIC_SUBCMD_SET_CREATE(
config_cmds,
SHELL_CMD_ARG(print, NULL, "Print current configuration settings", shell_print_config, 0, 0),
SHELL_CMD_ARG(set_nodeid, NULL, "Set canbus node id", shell_set_canbus_node_id, 2, 0),
SHELL_SUBCMD_SET_END
);
SHELL_CMD_REGISTER(config, &config_cmds, "Configuration commands", NULL);
SHELL_CMD_REGISTER(reboot, NULL, "Reboot the node", reboot_system);
#endif // CONFIG_SHELL
static int settings_canbus(const char *key, size_t len, settings_read_cb read_cb, void *cb_arg)
{
const char *next;
int rc;
// Handle setting values for CAN bus configuration
LOG_DBG("Trying to read CAN bus configuration: key=%s, len=%zu", key, len);
if (settings_name_steq(key, "node_id", &next) && next == NULL)
{
if (len != sizeof(canbus_node_id))
{
LOG_ERR("Invalid length for node_id setting: %zu", len);
return -EINVAL; // Invalid argument
}
rc = read_cb(cb_arg, &canbus_node_id, sizeof(canbus_node_id));
if (rc < 0)
{
LOG_ERR("Failed to read node_id setting: %d", rc);
return rc; // Read error
}
LOG_INF("Set CAN bus node ID to <%d> from settings", canbus_node_id);
}
return 0; // Return 0 on success
}
static struct settings_handler settings_handler_canbus = {
.name = "canbus",
.h_set = settings_canbus, // No settings set handler
};
int config_init(void)
{
int rc;
rc=settings_subsys_init();
if (rc < 0)
{
LOG_ERR("Failed to initialize settings subsystem: %d", rc);
return rc; // Initialization error
}
rc = settings_register(&settings_handler_canbus);
if (rc < 0)
{
LOG_ERR("Failed to register settings handler: %d", rc);
return rc; // Registration error
}
rc = settings_load();
if (rc < 0)
{
LOG_ERR("Failed to load settings: %d", rc);
return rc; // Load error
}
return 0; // Return 0 on success
}

View File

@ -1,11 +0,0 @@
#ifndef __SETTINGS_H__
#define __SETTINGS_H__
#include <zephyr/settings/settings.h>
#include <errno.h>
#include <zephyr/sys/printk.h>
int config_init(void);
#endif // __SETTINGS_H__

View File

@ -1,163 +0,0 @@
#include "modbus.h"
#include <zephyr/kernel.h>
#include <zephyr/sys/util.h>
#include <zephyr/modbus/modbus.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(mbc, CONFIG_LOG_DEFAULT_LEVEL);
#define MODBUS_NODE DT_COMPAT_GET_ANY_STATUS_OKAY(zephyr_modbus_serial)
static int client_iface;
static struct
{
int level; // Water level value
int minimum; // Minimum value
int maximum; // Maximum value
int factor; // Factor for unit conversion
} measurement;
int mb_init_client(void)
{
const char iface_name[] = {DEVICE_DT_NAME(MODBUS_NODE)};
client_iface = modbus_iface_get_by_name(iface_name);
LOG_DBG("Modbus client interface: %d", client_iface);
if (client_iface < 0)
{
LOG_ERR("Failed to get Modbus interface by name: %s", iface_name);
return client_iface;
}
return modbus_init_client(client_iface, client_param);
}
int mb_read_holding_registers(int node, uint16_t reg_addr, uint16_t *data, size_t len)
{
return modbus_read_holding_regs(client_iface, node, reg_addr, data, len);
}
int mb_read()
{
int rc;
int16_t data[5] = {0};
rc = mb_read_holding_registers(1, 0x0002, data, sizeof(data) / sizeof(data[0]));
if (rc < 0)
{
LOG_ERR("Failed to read holding registers: %d", rc);
return rc;
}
LOG_HEXDUMP_DBG(data, sizeof(data), "Holding Registers Data");
int unit, decimals;
unit = data[0];
decimals = data[1];
int factor;
switch (unit)
{
case 1: // cm
factor = 10;
break;
case 2: // mm
factor = 1;
break;
default:
LOG_ERR("Unknown unit: %d", unit);
return -EINVAL;
}
switch(decimals)
{
case 0: // no decimals
factor /= 1;
break;
case 1: // one decimal
factor /= 10;
break;
case 2: // two decimals
factor /= 100;
break;
default:
LOG_ERR("Unknown decimals: %d", decimals);
return -EINVAL;
}
measurement.factor = factor;
measurement.level = data[2] * factor;
measurement.minimum = data[3] * factor;
measurement.maximum = data[4] * factor;
LOG_DBG("Water level: %dmm, Minimum: %dmm, Maximum: %dmm",
measurement.level, measurement.minimum, measurement.maximum);
return 0;
}
int mb_read_water_level(double *mb_read_water_level)
{
int rc = mb_read();
if (rc < 0)
{
LOG_ERR("Failed to read water level: %d", rc);
return rc;
}
*mb_read_water_level = (double)measurement.level / 1000.0; // Convert to meters
return 0;
}
int mb_read_water_level_mm(int *mb_read_water_level)
{
int rc = mb_read();
if (rc < 0)
{
LOG_ERR("Failed to read water level: %d", rc);
return rc;
}
*mb_read_water_level = measurement.level;
return 0;
}
int mb_read_minimum_mm(int *mb_read_minimum)
{
int rc = mb_read();
if (rc < 0)
{
LOG_ERR("Failed to read water level: %d", rc);
return rc;
}
*mb_read_minimum = measurement.minimum;
return 0;
}
int mb_read_maximum_mm(int *mb_read_maximum)
{
int rc = mb_read();
if (rc < 0)
{
LOG_ERR("Failed to read water level: %d", rc);
return rc;
}
*mb_read_maximum = measurement.maximum;
return 0;
}
int mb_write_minimum_mm(int minimum)
{
int rc = mb_read();
if (rc < 0)
{
LOG_ERR("Failed to read water level: %d", rc);
return rc;
}
modbus_write_holding_reg(client_iface, 1, 0x0005, minimum / measurement.factor);
return 0;
}
int mb_write_maximum_mm(int maximum)
{
int rc = mb_read();
if (rc < 0)
{
LOG_ERR("Failed to read water level: %d", rc);
return rc;
}
modbus_write_holding_reg(client_iface, 1, 0x0006, maximum / measurement.factor);
return 0;
}

View File

@ -1,34 +0,0 @@
#ifndef __waterlevel_h
#define __waterlevel_h
#include <zephyr/modbus/modbus.h>
#define MODBUS_THREAD_STACK_SIZE (512)
#define MODBUS_THREAD_PRIORITY (6)
#define MODBUS_MSGQ_SIZE (5)
const static struct modbus_iface_param client_param = {
.mode = MODBUS_MODE_RTU,
.rx_timeout = 50000,
.serial = {
.baud = 9600,
.parity = UART_CFG_PARITY_NONE,
},
};
typedef struct {
uint8_t reg;
uint8_t command; // 0 for set, 1 for get
int16_t value;
} modbus_register_t;
int mb_init_client(void);
int mb_read_holding_registers(int node, uint16_t reg_addr, uint16_t *data, size_t len);
int mb_read_water_level(double *mb_read_water_level);
int mb_read_water_level_mm(int *mb_read_water_level_mm);
int mb_read_minimum_mm(int *mb_read_minimum);
int mb_read_maximum_mm(int *mb_read_maximum);
int mb_write_minimum_mm(int minimum);
int mb_write_maximum_mm(int maximum);
#endif

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

@ -0,0 +1,163 @@
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include "canbus.h"
#include "canbus_registers.h"
#include "valve.h"
LOG_MODULE_REGISTER(valve, CONFIG_LOG_VALVE_LEVEL);
K_THREAD_STACK_DEFINE(valve_thread_stack, VALVE_THREAD_STACK_SIZE);
K_MSGQ_DEFINE(valve_msgq, sizeof(int), VALVE_MSGQ_SIZE, 4);
k_tid_t valve_thread_id;
struct k_thread valve_thread_data;
valve_status_t valve_status_data = {
.valve_state = VALVE_STATE_UNKNOWN,
.valve_operation = VALVE_OPERATION_IDLE,
};
int valve_start_thread(void)
{
int rc;
// Initialize the valve
rc = valve_init();
if (rc < 0)
{
LOG_ERR("Failed to initialize valve: %d", rc);
return rc;
}
// Create the valve thread
valve_thread_id = k_thread_create(&valve_thread_data, valve_thread_stack,
K_THREAD_STACK_SIZEOF(valve_thread_stack),
(k_thread_entry_t)valve_cmd, NULL, NULL, NULL,
VALVE_THREAD_PRIORITY, 0, K_NO_WAIT);
k_thread_name_set(valve_thread_id, "valve");
LOG_INF("Valve thread started successfully");
while (1)
{
// Wait for commands from the message queue
int cmd;
rc = k_msgq_get(&valve_msgq, &cmd, VALVE_STATE_INTERVAL);
if (rc == 0)
{
// Process the command
rc = valve_cmd(cmd);
if (rc < 0)
{
LOG_ERR("Failed to process valve command: %d", rc);
}
}
else
{
valve_send_status(); // Send current valve status periodically
}
}
return 0;
}
int valve_init(void)
{
return 0;
}
int valve_cmd(int cmd)
{
switch (cmd)
{
case VALVE_COMMAND_OPEN:
if (valve_status_data.valve_state != VALVE_STATE_OPEN)
{
valve_status_data.valve_state = VALVE_STATE_OPEN;
valve_status_data.valve_operation = VALVE_OPERATION_OPENING;
valve_send_status(); // Send updated status before opening
valve_send_operation(); // Send updated operation state before opening
k_sleep(VALVE_OPENING_TIME); // Simulate opening time
valve_status_data.valve_operation = VALVE_OPERATION_IDLE; // Set operation to idle after opening
valve_send_status(); // Send updated status after opening
valve_send_operation(); // Send updated operation state after opening
}
break;
case VALVE_COMMAND_CLOSE:
if (valve_status_data.valve_state != VALVE_STATE_CLOSED)
{
valve_status_data.valve_operation = VALVE_OPERATION_CLOSING;
valve_send_operation(); // Send updated operation state before closing
k_sleep(VALVE_CLOSING_TIME); // Simulate closing time
valve_status_data.valve_state = VALVE_STATE_CLOSED; // Set valve state to closed after closing
valve_status_data.valve_operation = VALVE_OPERATION_IDLE; // Set operation to idle after closing
valve_send_status(); // Send updated status after closing
valve_send_operation(); // Send updated operation state after closing
}
break;
case VALVE_COMMAND_STOP:
valve_status_data.valve_operation = VALVE_OPERATION_IDLE;
break;
default:
LOG_ERR("Unknown valve command: %d", cmd);
return -EINVAL; // Invalid command
}
return 0;
}
int valve_send_status(void)
{
int rc = canbus_send8(CANBUS_REG_VALVE_STATUS, valve_status_data.valve_state);
if (rc != 0)
{
LOG_ERR("Failed to send valve status: %d", rc);
return rc;
}
char *state_str;
switch (valve_status_data.valve_state)
{
case VALVE_STATE_CLOSED:
state_str = "CLOSED";
break;
case VALVE_STATE_OPEN:
state_str = "OPEN";
break;
case VALVE_STATE_ERROR:
state_str = "ERROR";
break;
case VALVE_STATE_UNKNOWN:
state_str = "UNKNOWN";
break;
default:
state_str = "INVALID";
break;
}
LOG_INF("Valve status sent: %s", state_str);
return 0;
}
int valve_send_operation(void)
{
int rc = canbus_send8(CANBUS_REG_VALVE_OPERATION, valve_status_data.valve_operation);
if (rc != 0)
{
LOG_ERR("Failed to send valve operation: %d", rc);
return rc;
}
char *operation_str;
switch (valve_status_data.valve_operation)
{
case VALVE_OPERATION_IDLE:
operation_str = "IDLE";
break;
case VALVE_OPERATION_OPENING:
operation_str = "OPENING";
break;
case VALVE_OPERATION_CLOSING:
operation_str = "CLOSING";
break;
default:
operation_str = "UNKNOWN";
break;
}
LOG_INF("Valve operation sent: %s", operation_str);
return 0;
}

27
software/lib/valve.h Normal file
View File

@ -0,0 +1,27 @@
#ifndef __VALVE_H__
#define __VALVE_H__
#define VALVE_OPENING_TIME K_MSEC(4500) // Time to open the valve
#define VALVE_CLOSING_TIME K_MSEC(4500) // Time to close the valve
#define VALVE_MAX_OPENING_TIME K_MSEC(5000) // Maximum time to open the valve
#define VALVE_MAX_CLOSING_TIME K_MSEC(5000) // Maximum time to close the valve
#define VALVE_STATE_INTERVAL K_SECONDS(5 * 60) // Interval to check the valve state
#define VALVE_THREAD_STACK_SIZE (512) // Stack size for the valve thread
#define VALVE_THREAD_PRIORITY (2) // Priority for the valve thread
#define VALVE_MSGQ_SIZE (5) // Size of the message queue for valve operations
#include <stdint.h>
#include "canbus_registers.h"
typedef struct {
uint8_t valve_state;
uint8_t valve_operation;
} valve_status_t;
int valve_init(void);
int valve_cmd(int cmd);
int valve_send_status(void);
int valve_send_operation(void);
#endif // __VALVE_H__

View File

@ -0,0 +1,384 @@
#include "waterlevel_sensor.h"
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/modbus/modbus.h>
#include <stdlib.h>
#include "canbus.h"
LOG_MODULE_REGISTER(wls, CONFIG_LOG_WATERLEVELSENSOR_LEVEL);
#define MODBUS_NODE DT_COMPAT_GET_ANY_STATUS_OKAY(zephyr_modbus_serial)
struct k_thread waterlevel_sensor_thread_data;
K_THREAD_STACK_DEFINE(waterlevel_sensor_stack, WATERLEVEL_SENSOR_STACK_SIZE);
K_MSGQ_DEFINE(waterlevel_sensor_msgq, sizeof(waterlevel_command_t), WATERLEVEL_MESSAGE_QUEUE_SIZE, 4);
static int modbus_client_iface;
volatile static struct
{
int level; // Water level value
int zeropoint; // Minimum value
int maxpoint; // Maximum value
int factor; // Factor for unit conversion
bool factor_set; // Flag to indicate if factor is set
} waterlevel_measurement = {
.factor_set = false,
};
struct modbus_iface_param client_param = {
.mode = MODBUS_MODE_RTU,
.rx_timeout = 50000, // Timeout for receiving data in milliseconds
.serial = {
.baud = 9600,
.parity = UART_CFG_PARITY_NONE,
.stop_bits_client = UART_CFG_STOP_BITS_1, // 1 stop bit for Modbus RTU
},
};
static int waterlevel_modbus_init() {
const char iface_name[] = {DEVICE_DT_NAME(MODBUS_NODE)};
modbus_client_iface = modbus_iface_get_by_name(iface_name);
if (modbus_client_iface < 0)
{
LOG_ERR("Failed to get Modbus interface by name: %s", iface_name);
return modbus_client_iface;
}
LOG_DBG("Initializing modbus client interface: %s", iface_name);
return modbus_init_client(modbus_client_iface, client_param);
}
static int waterlevel_modbus_read(void) {
int rc;
union
{
struct
{
int16_t unit; // Unit of measurement (e.g., cm, mm)
int16_t decimals; // Number of decimal places for the measurement
int16_t level; // Water level value
int16_t zeropoint; // Zero point for the measurement
int16_t maxpoint; // Maximum point for the measurement
};
int16_t data[5]; // Data array for holding registers
} waterlevel_modbus_data;
rc = modbus_read_holding_regs(modbus_client_iface, WATERLEVEL_SENSOR_MODBUS_NODE_ID, 0x0002, waterlevel_modbus_data.data, sizeof(waterlevel_modbus_data.data) / sizeof(waterlevel_modbus_data.data[0]));
if (rc < 0)
{
LOG_ERR("Failed to read holding registers, node <%d>, returncode: %d", WATERLEVEL_SENSOR_MODBUS_NODE_ID, rc);
return rc;
}
LOG_DBG("Got values. Unit: %d, Decimals: %d, Level: %d, Zero Point: %d, Max Point: %d",
waterlevel_modbus_data.unit,
waterlevel_modbus_data.decimals,
waterlevel_modbus_data.level,
waterlevel_modbus_data.zeropoint,
waterlevel_modbus_data.maxpoint);
LOG_HEXDUMP_DBG(waterlevel_modbus_data.data, sizeof(waterlevel_modbus_data.data), "Waterlevel Sensor Holding Registers Data");
switch (waterlevel_modbus_data.unit)
{
case 1: // cm
waterlevel_measurement.factor = 10;
break;
case 2: // mm
waterlevel_measurement.factor = 1;
break;
default:
LOG_ERR("Unknown unit: %d", waterlevel_modbus_data.unit);
waterlevel_measurement.factor_set = false;
return -EINVAL;
}
switch (waterlevel_modbus_data.decimals)
{
case 0: // no decimals
waterlevel_measurement.factor /= 1;
break;
case 1: // one decimal
waterlevel_measurement.factor /= 10;
break;
case 2: // two decimals
waterlevel_measurement.factor /= 100;
break;
default:
LOG_ERR("Unknown decimals: %d", waterlevel_modbus_data.decimals);
waterlevel_measurement.factor_set = false;
return -EINVAL;
}
waterlevel_measurement.factor_set = true;
waterlevel_measurement.level = waterlevel_modbus_data.level * waterlevel_measurement.factor;
waterlevel_measurement.zeropoint = waterlevel_modbus_data.zeropoint * waterlevel_measurement.factor;
waterlevel_measurement.maxpoint = waterlevel_modbus_data.maxpoint * waterlevel_measurement.factor;
LOG_DBG("Water level: %dmm, zero point: %dmm, maximum point: %dmm",
waterlevel_measurement.level,
waterlevel_measurement.zeropoint,
waterlevel_measurement.maxpoint);
LOG_HEXDUMP_DBG(waterlevel_modbus_data.data, sizeof(waterlevel_modbus_data.data), "Waterlevel Sensor Holding Registers Data");
return 0;
}
static int waterlevel_send_level(void) {
if (!waterlevel_measurement.factor_set) {
LOG_ERR("Factor not set, cannot send water level");
return -EINVAL;
}
LOG_INF("Sending water level: %dmm", waterlevel_measurement.level);
canbus_send16(CANBUS_REG_WATERLEVEL_LEVEL, waterlevel_measurement.level);
return 0;
}
static int waterlevel_send_zero_point(void) {
if (!waterlevel_measurement.factor_set) {
LOG_ERR("Factor not set, cannot send zero point");
return -EINVAL;
}
LOG_INF("Sending water zero point: %dmm", waterlevel_measurement.zeropoint);
canbus_send16(CANBUS_REG_WATERLEVEL_ZERO_POINT, waterlevel_measurement.zeropoint);
return 0;
}
static int waterlevel_send_max_point(void) {
if (!waterlevel_measurement.factor_set) {
LOG_ERR("Factor not set, cannot send maximum point");
return -EINVAL;
}
LOG_INF("Sending water maximum point: %dmm", waterlevel_measurement.maxpoint);
canbus_send16(CANBUS_REG_WATERLEVEL_MAX_POINT, waterlevel_measurement.maxpoint);
return 0;
}
static int waterlevel_set_zero_point(int zeropoint) {
if (!waterlevel_measurement.factor_set) {
LOG_ERR("Factor not set, cannot set zero point");
return -EINVAL;
}
int16_t zeropoint_modbus = zeropoint / waterlevel_measurement.factor;
int rc = modbus_write_holding_regs(modbus_client_iface, WATERLEVEL_SENSOR_MODBUS_NODE_ID, 0x0005, &zeropoint_modbus, 1);
if (rc < 0) {
LOG_ERR("Failed to write zero point: %d", rc);
return rc;
}
waterlevel_measurement.zeropoint = zeropoint; // Update the local measurement structure
LOG_INF("Zero point set to: %dmm", waterlevel_measurement.zeropoint);
rc = waterlevel_send_zero_point();
if (rc < 0) {
LOG_ERR("Failed to send zero point: %d", rc);
return rc;
}
return 0;
}
static int waterlevel_set_max_point(int maxpoint) {
if (!waterlevel_measurement.factor_set) {
LOG_ERR("Factor not set, cannot set maximum point");
return -EINVAL;
}
int16_t maxpoint_modbus = maxpoint / waterlevel_measurement.factor;
int rc = modbus_write_holding_regs(modbus_client_iface, WATERLEVEL_SENSOR_MODBUS_NODE_ID, 0x0006, &maxpoint_modbus, 1);
if (rc < 0) {
LOG_ERR("Failed to write maximum point: %d", rc);
return rc;
}
waterlevel_measurement.maxpoint = maxpoint; // Update the local measurement structure
LOG_INF("Maximum point set to: %dmm", waterlevel_measurement.maxpoint);
rc = waterlevel_send_max_point();
if (rc < 0) {
LOG_ERR("Failed to send maximum point: %d", rc);
return rc;
}
return 0;
}
void waterlevel_sensor_thread(void *arg1, void *arg2, void *arg3)
{
ARG_UNUSED(arg1);
ARG_UNUSED(arg2);
ARG_UNUSED(arg3);
// Initialize the Modbus client
int rc = waterlevel_modbus_init();
if (rc < 0)
{
LOG_ERR("Failed to initialize Modbus client: %d", rc);
return;
}
rc = waterlevel_modbus_read();
if (rc < 0) {
LOG_ERR("Failed to read initial water level: %d", rc);
return;
}
waterlevel_send_level();
waterlevel_send_zero_point();
waterlevel_send_max_point();
// Initialize the last transmission time and level
// Use k_uptime_get_32() to get the current uptime in milliseconds
// and store the initial water level measurement.
// This will be used to determine when to send updates.
uint32_t last_transmission_time_ms = k_uptime_get_32();
int32_t last_transmission_level = waterlevel_measurement.level;
while (1)
{
uint32_t current_time_ms = k_uptime_get_32();
uint32_t delta_time = current_time_ms-last_transmission_time_ms;
waterlevel_command_t command;
rc = waterlevel_modbus_read();
if (rc < 0)
{
LOG_ERR("Failed to read water level: %d", rc);
continue;
}
if (delta_time >= WATERLEVEL_SENSOR_MAX_UPDATE_INTERVAL_MS ||
abs(waterlevel_measurement.level - last_transmission_level) >= WATERLEVEL_SENSOR_MIN_DELTA) {
rc = waterlevel_send_level();
if (rc < 0) {
LOG_ERR("Failed to send water level: %d", rc);
} else {
last_transmission_time_ms = current_time_ms;
last_transmission_level = waterlevel_measurement.level;
}
}
while (k_msgq_get(&waterlevel_sensor_msgq, &command, K_NO_WAIT) == 0)
{
switch(command.cmd)
{
case WATERLEVEL_CMD_SET:
switch (command.reg)
{
case CANBUS_REG_WATERLEVEL_ZERO_POINT: // Set zero point
rc = waterlevel_set_zero_point(command.data);
if (rc < 0) {
LOG_ERR("Failed to set zero point: %d", rc);
}
break;
case CANBUS_REG_WATERLEVEL_MAX_POINT: // Set maximum point
rc = waterlevel_set_max_point(command.data);
if (rc < 0) {
LOG_ERR("Failed to set maximum point: %d", rc);
}
break;
default:
LOG_ERR("Unknown register for SET command: 0x%02X", command.reg);
break;
}
break;
case WATERLEVEL_CMD_GET:
switch (command.reg)
{
case CANBUS_REG_WATERLEVEL_LEVEL: // Get water level
waterlevel_send_level();
break;
case CANBUS_REG_WATERLEVEL_ZERO_POINT: // Get zero point
waterlevel_send_zero_point();
break;
case CANBUS_REG_WATERLEVEL_MAX_POINT: // Get maximum point
waterlevel_send_max_point();
break;
default:
LOG_ERR("Unknown register for GET command: 0x%02X", command.reg);
break;
}
break;
default:
LOG_ERR("Unknown command type: %d", command.cmd);
break;
}
}
}
}
int waterlevel_sensor_start_thread(void)
{
k_tid_t waterlevel_sensor_thread_id;
// Start the thread
waterlevel_sensor_thread_id = k_thread_create(&waterlevel_sensor_thread_data, waterlevel_sensor_stack,
K_THREAD_STACK_SIZEOF(waterlevel_sensor_stack), waterlevel_sensor_thread,
NULL, NULL, NULL,
WATERLEVEL_SENSOR_THREAD_PRIORITY, 0, K_NO_WAIT);
if (waterlevel_sensor_thread_id == NULL)
{
LOG_ERR("Failed to create water level sensor thread");
return -ENOMEM;
}
k_thread_name_set(waterlevel_sensor_thread_id, "waterlevel_sensor");
LOG_INF("Water level sensor thread started successfully");
return 0;
}
#ifdef CONFIG_SHELL
#include <zephyr/shell/shell.h>
void waterlevel_set_zero_point_shell(const struct shell *shell, size_t argc, char **argv) {
if (argc != 2) {
shell_error(shell, "Usage: waterlevel_sensor set_zero_point <zeropoint>");
return;
}
int zeropoint = atoi(argv[1]);
int rc = waterlevel_set_zero_point(zeropoint);
if (rc < 0) {
shell_error(shell, "Failed to set zero point: %d", rc);
} else {
shell_print(shell, "Zero point set to: %dmm", zeropoint);
}
}
void waterlevel_set_max_point_shell(const struct shell *shell, size_t argc, char **argv) {
if (argc != 2) {
shell_error(shell, "Usage: waterlevel_sensor set_max_point <maxpoint>");
return;
}
int maxpoint = atoi(argv[1]);
int rc = waterlevel_set_max_point(maxpoint);
if (rc < 0) {
shell_error(shell, "Failed to set maximum point: %d", rc);
} else {
shell_print(shell, "Maximum point set to: %dmm", maxpoint);
}
}
void waterlevel_sensor_print_shell(const struct shell *shell, size_t argc, char **argv) {
ARG_UNUSED(argc);
ARG_UNUSED(argv);
waterlevel_modbus_read();
if (!waterlevel_measurement.factor_set) {
shell_error(shell, "Factor not set, cannot print water level");
return;
}
shell_print(shell, "Current water level: %4dmm", waterlevel_measurement.level);
shell_print(shell, "Zero point: %4dmm", waterlevel_measurement.zeropoint);
shell_print(shell, "Maximum point: %4dmm", waterlevel_measurement.maxpoint);
}
// Define the shell commands for the water level sensor
SHELL_STATIC_SUBCMD_SET_CREATE(
waterlevel_sensor_cmds,
SHELL_CMD(print, NULL, "Print the current water level, zero point, and maximum point", waterlevel_sensor_print_shell),
SHELL_CMD(setzero, NULL, "Set the zero point for the water level sensor", waterlevel_set_zero_point_shell),
SHELL_CMD(setmax, NULL, "Set the maximum point for the water level sensor", waterlevel_set_max_point_shell),
SHELL_SUBCMD_SET_END);
SHELL_CMD_REGISTER(wls, &waterlevel_sensor_cmds, "Water level sensor commands", NULL);
#endif // CONFIG_SHELL

View File

@ -0,0 +1,27 @@
#ifndef __WATERLEVEL_SENSOR_H__
#define __WATERLEVEL_SENSOR_H__
#define WATERLEVEL_SENSOR_STACK_SIZE (512)
#define WATERLEVEL_SENSOR_THREAD_PRIORITY (2)
#define WATERLEVEL_MESSAGE_QUEUE_SIZE (5) // Size of the message queue for water level sensor thread
#define WATERLEVEL_SENSOR_READ_INTERVAL_MS (5000) // Interval for reading the water level sensor in milliseconds
#define WATERLEVEL_SENSOR_MIN_DELTA (2) // Minimum change in water level to trigger an update
#define WATERLEVEL_SENSOR_MAX_UPDATE_INTERVAL_MS (600000) // Maximum interval for updating the water level in milliseconds
#define WATERLEVEL_SENSOR_MODBUS_NODE_ID (0x01) // Modbus node ID for the water level sensor
#define WATERLEVEL_SENSOR_MODBUS_BAUD_RATE (9600) // Baud rate for Modbus communication
#include <inttypes.h>
int waterlevel_sensor_start_thread(void);
typedef struct {
uint8_t reg;
enum {
WATERLEVEL_CMD_SET,
WATERLEVEL_CMD_GET,
} cmd;
int16_t data; // Data to be set
} waterlevel_command_t;
#endif // __WATERLEVEL_SENSOR_H__

View File

@ -1,20 +1,17 @@
CONFIG_HAS_MODBUS_WATERLEVEL_SENSOR=y
CONFIG_HAS_VALVE=y
CONFIG_LOG=y CONFIG_LOG=y
CONFIG_LOG_DEFAULT_LEVEL=3 CONFIG_LOG_DEFAULT_LEVEL=3
# CONFIG_LOG_SETTINGS_LEVEL=4
# CONFIG_LOG_CAN_LEVEL=4 # CONFIG_LOG_CAN_LEVEL=4
# CONFIG_LOG_WATERLEVELSENSOR_LEVEL=4
# CONFIG_LOG_VALVE_LEVEL=4
CONFIG_CBPRINTF_FP_SUPPORT=y CONFIG_CBPRINTF_FP_SUPPORT=y
CONFIG_UART_CONSOLE=y # Console on USART1 CONFIG_UART_CONSOLE=y # Console on USART1
#CONFIG_RTT_CONSOLE=y
#CONFIG_USE_SEGGER_RTT=y
# CAN loopback mode for testing # CAN loopback mode for testing
CONFIG_LOOPBACK_MODE=y #CONFIG_LOOPBACK_MODE=y
CONFIG_SHELL=y CONFIG_SHELL=y
CONFIG_CAN_SHELL=y CONFIG_CAN_SHELL=y
CONFIG_GPIO_SHELL=y CONFIG_GPIO_SHELL=y
CONFIG_REBOOT=y CONFIG_REBOOT=y
# CONFIG_USE_SEGGER_RTT=y
# CONFIG_SHELL_BACKEND_RTT=y
# CONFIG_SHELL_BACKEND_SERIAL=n

53
software/src/main.c Normal file
View File

@ -0,0 +1,53 @@
#include <zephyr/logging/log.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/shell/shell.h>
#include "canbus.h"
#include "canbus_registers.h"
#ifdef CONFIG_HAS_MODBUS_WATERLEVEL_SENSOR
// Include the water level sensor header file when the feature is enabled
#include "waterlevel_sensor.h"
#endif // CONFIG_HAS_MODBUS_WATERLEVEL_SENSOR
#ifdef CONFIG_HAS_VALVE
#include "valve.h"
#endif // CONFIG_HAS_VALVE
LOG_MODULE_REGISTER(main, CONFIG_LOG_DEFAULT_LEVEL);
int main(void)
{
LOG_INF("Starting main application...");
canbus_init();
k_sleep(K_MSEC(3000)); // Allow some time for CAN initialization
#ifdef CONFIG_HAS_MODBUS_WATERLEVEL_SENSOR
int rc = waterlevel_sensor_start_thread();
if (rc < 0)
{
LOG_ERR("Failed to start water level sensor thread: %d", rc);
return rc;
}
#endif // CONFIG_HAS_MODBUS_WATERLEVEL_SENSOR
valve_cmd(VALVE_COMMAND_CLOSE); // Ensure the valve is closed at startup
LOG_INF("Main application started successfully.");
return 0; // Return 0 on success
}
#ifdef CONFIG_SHELL
#include <zephyr/shell/shell.h>
#include <zephyr/sys/reboot.h>
static int reboot_shell_cmd(const struct shell *shell, size_t argc, char **argv)
{
ARG_UNUSED(argc);
ARG_UNUSED(argv);
shell_print(shell, "Rebooting the node in 1 second...");
k_sleep(K_SECONDS(1));
sys_reboot(SYS_REBOOT_COLD);
return 0;
}
SHELL_CMD_REGISTER(reboot, NULL, "Reboot the node", reboot_shell_cmd);
#endif // CONFIG_SHELL

View File

@ -1 +0,0 @@
build

View File

@ -1,16 +0,0 @@
{
"configurations": [
{
"name": "Win32",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE"
]
}
],
"version": 4
}

View File

@ -1,15 +0,0 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
list(APPEND BOARD_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/..)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(hello_world)
target_sources(app PRIVATE src/main.c)
target_sources(app PRIVATE ../lib/canbus.c)
target_sources(app PRIVATE ../lib/config.c)
target_include_directories(app PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/../lib
)

View File

@ -1,9 +0,0 @@
&usart1 {
status = "okay";
current-speed = <9600>;
modbus0 {
compatible = "zephyr,modbus-serial";
status = "okay";
// de-gpios = <&arduino_header 15 GPIO_ACTIVE_LOW>; /* D9 */
};
};

View File

@ -1,132 +0,0 @@
/* Testing MODBUS functionality
This code initializes a Modbus client, sets the zero point and the 2m point for water level,
and reads the water level in both meters and millimeters.
It uses the Zephyr RTOS and its Modbus library to communicate with a Modbus server.
*/
#include <zephyr/logging/log.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/shell/shell.h>
#include "canbus.h"
#include "modbus.h"
#include "config.h"
LOG_MODULE_REGISTER(main, CONFIG_LOG_DEFAULT_LEVEL);
K_THREAD_STACK_DEFINE(fake_modbus_stack, MODBUS_THREAD_STACK_SIZE);
K_MSGQ_DEFINE(modbus_msgq, sizeof(modbus_register_t), 10, 4);
int water_min = 0;
int water_max = 2000; // Maximum water level in mm
void fake_modbus_thread(void *arg1, void *arg2, void *arg3)
{
modbus_register_t reg;
ARG_UNUSED(arg1);
ARG_UNUSED(arg2);
ARG_UNUSED(arg3);
LOG_INF("Fake Modbus thread started");
while (1)
{
k_msgq_get(&modbus_msgq, &reg, K_FOREVER);
LOG_INF("Received Modbus register: 0x%02x, command: %s, value: %d", reg.reg, reg.command ? "GET" : "SET", reg.value);
switch (reg.command)
{
case CANBUS_REGISTER_COMMAND_SET:
switch (reg.reg)
{
case CANBUS_REGISTER_WATER_MINIMUM:
water_min = reg.value;
LOG_INF("Set water minimum to %d mm", water_min);
break;
case CANBUS_REGISTER_WATER_MAXIMUM:
water_max = reg.value;
LOG_INF("Set water maximum to %d mm", water_max);
break;
default:
LOG_INF("Received unknown command: 0x%02x for register: 0x%02x", reg.command, reg.reg);
break;
}
case CANBUS_REGISTER_COMMAND_GET:
switch (reg.reg)
{
case CANBUS_REGISTER_WATER_LEVEL_MM:
// Simulate reading water level in mm
reg.value = (water_min + water_max) / 2; // Example: average of min and max
LOG_INF("Read water level: %d mm", reg.value);
break;
case CANBUS_REGISTER_WATER_MINIMUM:
reg.value = water_min;
LOG_INF("Read water minimum: %d mm", reg.value);
break;
case CANBUS_REGISTER_WATER_MAXIMUM:
reg.value = water_max;
LOG_INF("Read water maximum: %d mm", reg.value);
break;
default:
LOG_INF("Received unknown command: 0x%02x for register: 0x%02x", reg.command, reg.reg);
break;
}
break;
}
}
}
int main(void)
{
int rc;
LOG_INF("Starting CAN bus initialization...");
// Initialize the configuration and CAN bus
rc = config_init();
if (rc != 0)
{
LOG_ERR("Failed to initialize configuration: %d", rc);
return rc;
}
rc = canbus_init();
if (rc != 0)
{
LOG_ERR("Failed to initialize CAN bus: %d", rc);
return rc;
}
LOG_INF("CAN bus initialized successfully");
struct k_thread fake_modbus_thread_data;
k_tid_t fake_modbus_tid = k_thread_create(&fake_modbus_thread_data, fake_modbus_stack,
K_THREAD_STACK_SIZEOF(fake_modbus_stack), fake_modbus_thread, NULL, NULL, NULL,
MODBUS_THREAD_PRIORITY, 0, K_NO_WAIT);
if (fake_modbus_tid == NULL)
{
LOG_ERR("Failed to create fake Modbus thread");
return -ENOMEM; // Not enough memory to create thread
}
LOG_INF("Fake Modbus thread created successfully");
while (1)
{
canbus_request_register(0x01, CANBUS_REGISTER_WATER_LEVEL_MM); // Request water level in mm
k_sleep(K_MSEC(150)); // Wait for 150 ms to allow the request to be processed
canbus_request_register(0x01, CANBUS_REGISTER_WATER_MINIMUM); // Request water minimum
k_sleep(K_MSEC(150)); // Wait for 150 ms to allow the request to be processed
int water_tmp = -100;
uint8_t data[2];
sys_put_be16(water_tmp, data); // Convert water_tmp to big-endian format
rc = canbus_send_register(0x01, CANBUS_REGISTER_WATER_MINIMUM, data, sizeof(data));
k_sleep(K_MSEC(150)); // Wait for 150 ms to allow the request to be processed
canbus_request_register(0x01, CANBUS_REGISTER_WATER_LEVEL_MM); // Request water level in mm
k_sleep(K_MSEC(150)); // Wait for 150 ms to allow the request to be processed
canbus_request_register(0x01, CANBUS_REGISTER_WATER_MINIMUM); // Request water minimum
k_sleep(K_SECONDS(5)); // Sleep for 5 second before next iteration
return 0; // Exit the loop after one iteration for testing purposes
// In a real application, you would remove this return statement
// to keep the loop running indefinitely.
}
return 0;
}

View File

@ -1 +0,0 @@
build

View File

@ -1,16 +0,0 @@
{
"configurations": [
{
"name": "Win32",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE"
]
}
],
"version": 4
}

View File

@ -1,14 +0,0 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
list(APPEND BOARD_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/..)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(hello_world)
target_sources(app PRIVATE src/main.c)
target_sources(app PRIVATE ../lib/modbus.c)
target_include_directories(app PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/../lib
)

View File

@ -1,9 +0,0 @@
&usart1 {
status = "okay";
current-speed = <9600>;
modbus0 {
compatible = "zephyr,modbus-serial";
status = "okay";
// de-gpios = <&arduino_header 15 GPIO_ACTIVE_LOW>; /* D9 */
};
};

View File

@ -1,7 +0,0 @@
CONFIG_LOG=y
CONFIG_LOG_DEFAULT_LEVEL=3
CONFIG_CBPRINTF_FP_SUPPORT=y
CONFIG_UART_CONSOLE=y # Console on USART1
#CONFIG_RTT_CONSOLE=y
#CONFIG_USE_SEGGER_RTT=y

View File

@ -1,78 +0,0 @@
/* Testing MODBUS functionality
This code initializes a Modbus client, sets the zero point and the 2m point for water level,
and reads the water level in both meters and millimeters.
It uses the Zephyr RTOS and its Modbus library to communicate with a Modbus server.
*/
#include <zephyr/logging/log.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include "modbus.h"
LOG_MODULE_REGISTER(main, CONFIG_LOG_DEFAULT_LEVEL);
/* 1000 msec = 1 sec */
#define SLEEP_TIME_MS 1000
int main(void)
{
int rc;
rc = mb_init_client();
if (rc != 0)
{
LOG_ERR("Failed to initialize Modbus client: %d", rc);
return rc;
}
LOG_INF("Modbus client initialized successfully");
double water_level = 0.0;
int water_level_mm, water_level_min_mm, water_level_max_mm = 0;
rc = mb_write_minimum_mm(42); // Set the zero point for water level
if (rc < 0)
{
LOG_ERR("Failed to write minimum water level: %d", rc);
return rc;
}
rc = mb_write_maximum_mm(2000); // Set the 2m point for water level
if (rc < 0)
{
LOG_ERR("Failed to write maximum water level: %d", rc);
return rc;
}
rc = mb_read_minimum_mm(&water_level_min_mm);
if (rc < 0)
{
LOG_ERR("Failed to read minimum water level: %d", rc);
return rc;
}
rc = mb_read_maximum_mm(&water_level_max_mm);
if (rc < 0)
{
LOG_ERR("Failed to read maximum water level: %d", rc);
return rc;
}
LOG_INF("Water zero point is set to: %dmm", water_level_min_mm);
LOG_INF("Water 2m point is set to: %dmm", water_level_max_mm);
/* Read water level */
rc = mb_read_water_level(&water_level);
if (rc < 0)
{
LOG_ERR("Failed to read water level: %d", rc);
return rc;
}
rc = mb_read_water_level_mm(&water_level_mm);
if (rc < 0)
{
LOG_ERR("Failed to read water level: %d", rc);
return rc;
}
LOG_INF("Water level: %.3fm", water_level);
LOG_INF("Water level: %dmm", water_level_mm);
LOG_INF("Modbus test completed successfully");
return 0;
}

View File

@ -0,0 +1,132 @@
{
"folders": [
{
"path": "."
}
],
"settings": {
// Hush CMake
"cmake.configureOnOpen": false,
// IntelliSense
"C_Cpp.default.compilerPath": "${userHome}/zephyr-sdk-0.17.1/arm-zephyr-eabi/bin/arm-zephyr-eabi-gcc",
"C_Cpp.default.compileCommands": "${workspaceFolder}/build/compile_commands.json",
// File Associations
"files.associations": {
"waterlevel_sensor.h": "c",
"shell.h": "c",
"can.h": "c"
}
},
"tasks": {
"version": "2.0.0",
"tasks": [
{
"label": "West Build",
"type": "shell",
"group": {
"kind": "build",
"isDefault": true
},
"command": "${userHome}/zephyrproject/.venv/bin/west",
"args": [
"build",
"-p",
"auto",
"-b",
"valve_node"
],
"problemMatcher": [
"$gcc"
],
},
{
"label": "West Configurable Build",
"type": "shell",
"group": {
"kind": "build",
},
"command": "${userHome}/zephyrproject/.venv/bin/west",
"args": [
"build",
"-p",
"${input:pristine}",
"-b",
"${input:board}"
],
"problemMatcher": [
"$gcc"
]
},
{
"label": "West Flash",
"type": "shell",
"command": "${userHome}/zephyrproject/.venv/bin/west",
"args": [
"flash"
],
"problemMatcher": [
"$gcc"
]
}
],
"inputs": [
{
"id": "board",
"type": "promptString",
"default": "vave_node",
"description": "See https://docs.zephyrproject.org/latest/boards/index.html"
},
{
"id": "pristine",
"type": "pickString",
"description": "Choose when to run a pristine build",
"default": "auto",
"options": [
"auto",
"always",
"never"
]
}
]
},
"launch": {
"version": "0.2.0",
"configurations": [
{
"name": "Launch",
"device": "STM32F103RB",
"cwd": "${workspaceFolder}",
"executable": "build/zephyr/zephyr.elf",
"request": "launch",
"type": "cortex-debug",
//"runToEntryPoint": "main",
"servertype": "jlink",
"gdbPath": "${userHome}/zephyr-sdk-0.17.1/arm-zephyr-eabi/bin/arm-zephyr-eabi-gdb",
"preLaunchTask": "West Build"
},
{
"name": "Attach",
"device": "STM32F103RB",
"cwd": "${workspaceFolder}",
"executable": "build/zephyr/zephyr.elf",
"request": "attach",
"type": "cortex-debug",
//"runToEntryPoint": "main",
"servertype": "jlink",
"gdbPath": "${userHome}/zephyr-sdk-0.17.1/arm-zephyr-eabi/bin/arm-zephyr-eabi-gdb"
},
]
},
"extensions": {
"recommendations": [
"ms-vscode.cpptools-extension-pack",
"ms-python.python",
"ms-vscode.vscode-embedded-tools",
"ms-vscode.vscode-serial-monitor",
"marus25.cortex-debug",
"donjayamanne.python-environment-manager"
]
}
}