61 Commits

Author SHA1 Message Date
d48281436e Fix ADC devicetree compilation error for voltage divider
- Fix voltage divider devicetree configuration to reference ADC controller directly instead of channel node
- Switch from ADC API to sensor API for voltage divider usage
- Add required sensor and voltage divider configuration options
- Remove unnecessary zephyr,user node that was causing compilation issues
- The voltage divider now properly uses sensor framework and builds successfully

Hardware setup:
- Uses ADC1 channel 1 on pin PA0
- Voltage divider with 2.2kΩ output and 3.2kΩ total resistance
- Provides voltage readings through sensor API accounting for divider ratio
2025-07-07 13:36:44 +02:00
dcb73c0a25 Working DT ADC sample WITHOUT resistor divider 2025-07-07 12:32:53 +02:00
2c21f1f9cb feat: Revert ADC changes and set channel 12 for VREFINT 2025-07-06 15:27:14 +02:00
a2afec52e2 fix(adc_test): correct ADC reference configuration
- Revert ADC_GAIN to ADC_GAIN_1 due to driver limitation.
- Revert ADC_REF to ADC_REF_INTERNAL due to driver limitation.
- Set zephyr,vref-mv to 3300 to match observed hardware behavior.
2025-07-06 10:24:37 +02:00
2cc258e8e2 feat(adc_test): use devicetree for adc configuration
- Use named adc channel 'multisense' from devicetree
- Enable adc calibration
2025-07-06 09:55:10 +02:00
a77298b3a6 Implement VND7050AJ supply voltage reading function
- Added devicetree overlay for VND7050AJ with GPIO and ADC configuration
- Created custom devicetree binding for VND7050AJ valve controller
- Implemented valve_get_supply_voltage() function with proper pin control:
  - RST=HIGH to enable VND7050AJ
  - S0=1, S1=1 for supply voltage sensing mode
  - SEN=1 to enable MULTISENSE output
  - ADC reading on PA0 (ADC1_IN1) with 12-bit resolution
- Fixed supply voltage calculation (VCC/8 per datasheet)
- Added comprehensive debug logging for all steps
- Tested and verified ADC functionality
- Current reading: 5.1V (may be limited by hardware power supply)

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

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

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

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

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

This replaces the previous virtual/simulated valve logic with actual hardware control.
2025-07-03 18:17:31 +02:00
8df7aef51b Removed unused lib dir 2025-07-03 17:47:48 +02:00
f6ee0a5122 feat(weact_stm32g431_core): Configure VND7050AJ driver pins in overlay
Updated the weact_stm32g431_core.overlay to define the GPIO and ADC
pin assignments for the VND7050AJ driver. This includes:
- Digital I/O pins (IN0, IN1, RST, S0, S1, SEN) configured as GPIOs.
- Analog input pin (MULTISENSE/PA0) configured for ADC1.
2025-07-03 17:39:04 +02:00
6c1ff0c4df feat(refactor): Restructure project for improved modularity and clarity
This commit introduces a major refactoring of the project structure to align
with Zephyr's recommended multi-application and library organization.

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

- Moved RTT-specific console and shell backend configurations from prj.conf
  into board-specific .conf files (bluepill_f103rb.conf).
- Configured USART2 as console/shell for weact_stm32g431_core.
- Added Device Tree Overlay for weact_stm32g431_core to enable USART1 for Modbus
  communication (PA9/PA10).
2025-07-03 10:53:21 +02:00
a59e8518cc Rename hello_world app to stm32g431_tests 2025-07-03 10:02:53 +02:00
2a2890b675 Fix: hello_world prj.conf - set correct board name 2025-07-03 09:21:48 +02:00
38fd3a6aac Add hello_world Zephyr application for stm32g431_core 2025-07-03 09:15:11 +02:00
c3df6565b7 Refactor fwu library into a Zephyr module 2025-07-02 21:30:15 +02:00
140d2baa24 Fix: modbus_tool.py - replace is_connected() with is_socket_open() and fix UnboundLocalError 2025-07-02 21:05:40 +02:00
711341f362 Refactor slave_node application to use Zephyr modules 2025-07-02 20:47:16 +02:00
a5da0a61dd Update modbus_tool.py 2025-07-02 17:10:11 +02:00
b54c73edb1 fix: handle connection loss and re-establish in modbus_tool.py 2025-07-02 10:03:23 +02:00
2418d4e218 fix: resolve build error by moving modbus register enums to header 2025-07-02 10:02:40 +02:00
2b4890f052 fix: correct modbus_tool.py update for reset command 2025-07-02 09:58:19 +02:00
85d493f24a feat: implement modbus reset command and update docs/tool 2025-07-02 09:55:42 +02:00
f486d4c4ab cleanup: remove unused CMakeLists.txt and empty modbus directory 2025-07-02 09:54:01 +02:00
6cfd4b8b4d refactor: restructure slave_node into libraries 2025-07-02 09:45:22 +02:00
0088030d66 docs: replace svg logo with png version 2025-07-02 09:22:37 +02:00
4d828b41f1 docs: Add project logo to all markdown files
Added the new project logo to the header of all relevant markdown documentation files to improve brand consistency and visual appeal.
2025-07-01 23:37:30 +02:00
95f435923f feat(main): Add detailed source code documentation
Add comprehensive Doxygen-style comments to all functions, enums, and macros in `main.c`. This improves code clarity and maintainability. The Doxygen configuration itself was removed after deciding against generating a separate HTML manual, but the in-code comments provide significant value on their own.
2025-07-01 23:29:26 +02:00
33f2a15cf3 docs(slave_node): Add Doxygen comments to main.c
- Add Doxygen-compliant comments to functions, enums, and state variables in `main.c`.
- This provides a foundation for automatically generating source code documentation.
- Remove the separate, now redundant, `firmware-manual.de.md` file.
2025-07-01 22:56:30 +02:00
c4e87a3125 docs: Add firmware manual and update main README
- Create a new firmware manual (`firmware-manual.de.md`) to document the current features of the slave node.
- Add a linked table of contents to the new manual.
- Update the main `README.de.md` to link to the new firmware manual and the existing Modbus tool README.
2025-07-01 22:51:17 +02:00
773027f6b0 feat(slave_node): Implement Modbus watchdog timer
- Add a fail-safe watchdog using a Zephyr kernel timer.
- The timer is reset on any successful Modbus communication.
- If the timer expires (no communication within the configured timeout), the valve is automatically closed as a safety measure.
- The watchdog is enabled by writing a non-zero value to the `WATCHDOG_TIMEOUT_S` register and disabled by writing 0.
2025-07-01 22:46:57 +02:00
461cce7a48 fix(modbus_tool): Adjust UI layout for alignment
- Shorten "Device Status" label to "Dev. Status".
- Realign the rightmost column for better readability.
2025-07-01 22:38:52 +02:00
23b88ada83 feat(modbus_tool): Add interactive file browser for firmware updates
- Implement a simple, curses-based file browser to allow selecting firmware files from the filesystem.
- The selected file path is used for the firmware update process.
- Fix a visual bug where the progress bar would not reach 100% upon completion.
- Remove a leftover  function that was causing a NameError.
2025-07-01 22:15:44 +02:00
c2916662e2 feat(modbus_tool): Implement simulated firmware update
- Add a new thread to handle the firmware update process, preventing the UI from freezing.
- The UI now displays a progress bar and status messages during the update.
- The tool reads a  file and sends it to the slave in chunks.
- Add a dummy  for testing purposes.
- Fix Modbus communication issues by reducing the chunk size to a safe value (248 bytes) and sending data in smaller bursts to improve stability.
- Update the README with the new features and instructions.
2025-07-01 21:55:19 +02:00
24087f5622 fix(slave_node): Increase Modbus buffer size
- Set CONFIG_MODBUS_BUFFER_SIZE to 256 to ensure the slave can handle larger data packets sent by the client during firmware updates.
2025-07-01 21:55:01 +02:00
95fd88e93e feat(modbus_tool): Adapt UI to full register map
- Update the TUI to display all new registers from the slave, including digital I/O and system status.
- Add new menu buttons to control digital outputs and set the watchdog timer.
- Add a placeholder button for the firmware update process.
- Fix various bugs, including incorrect argument passing in Modbus calls and a module import error.
2025-07-01 21:36:28 +02:00
21797d8507 feat(slave_node): Implement full Modbus register map
- Implement all remaining Modbus registers as defined in the documentation v1.0.
- Add support for digital I/O, system status, and a simulated watchdog.
- Implement a placeholder for the firmware update mechanism, including CRC calculation for received data chunks.
- Remove the input simulation timer; digital inputs are now static and ready for real hardware.
2025-07-01 21:36:10 +02:00
6dcb11ae0c fix(modbus_tool): Improve UI stability and readability
- Refactor the curses drawing loop to be state-based, eliminating screen flicker after user input.
- Add a helper function to format uptime from seconds into a human-readable string (d/h/m/s).
2025-07-01 21:17:30 +02:00
38d6dbe95a feat(modbus_tool): Add interactive TUI and documentation
- Replace the basic command-line prompt with a full-screen, interactive TUI using the  library.
- The new UI provides a real-time, tabular status display and intuitive, button-style menu navigation.
- Implement a consistent blue background theme for better aesthetics.
- Add a detailed  with installation and usage instructions.
- Fix several bugs in the Modbus communication logic.
2025-07-01 20:59:47 +02:00
b100a8acf7 feat(shell): Add commands for valve timing
- Add shell commands 'valve set_open_time' and 'valve set_close_time' to configure the virtual valve.
- Extend the 'show_config' command to display the new timing values.
- The new settings are persisted to flash storage.
2025-07-01 18:24:20 +02:00
269e9e88a1 feat(valve): Implement safe virtual valve control
- Implement virtual valve logic with time-based movement simulation.
- The valve state is now set to 'OPEN' immediately when the opening process starts, ensuring a safe and correct state representation.
- The state is only set to 'CLOSED' after the closing process has finished.
- Add persistence for max opening and closing times.
2025-07-01 18:15:54 +02:00
8cab3eecc1 feat(settings): Implement persistent Modbus configuration
- Integrate the Zephyr Settings subsystem to persist Modbus parameters.
- Use NVS (Non-Volatile Storage) as the backend with a dedicated flash partition.
- Modbus baudrate and slave ID are now loaded at startup.
- Changes made via the shell are saved to flash and survive a reboot.
- Add a 'reset' command to the shell for easier testing.
- Fix all compiler and devicetree warnings for a clean build.
2025-07-01 16:44:32 +02:00
6a9e4773ea feat(shell): Add commands to configure Modbus
- Implement a new 'modbus' command in the shell.
- Add sub-commands 'set_baud', 'set_id', and 'show'.
- Add validation for baud rate and slave ID inputs.
- The new parameters are applied to the Modbus server at runtime, allowing for live reconfiguration of the communication settings.
- The shell backend is set to RTT.
2025-07-01 15:27:57 +02:00
b836f9a2f4 feat(slave_node): Implement system registers
- Add read support for system-level input registers:
  - FIRMWARE_VERSION_MAJOR_MINOR (0xF0)
  - FIRMWARE_VERSION_PATCH (0xF1)
  - DEVICE_STATUS (0xF2)
- Use enums for register addresses to improve readability.
- Implement a workaround for a Kconfig issue by hardcoding the firmware version.
- Stabilize multi-register reads by returning 0 for unhandled input registers instead of an exception.
- NOTE: Writing to holding registers is currently unstable with mbpoll and will be addressed separately.
2025-07-01 14:56:56 +02:00
032ddf2cc0 feat(slave_node): Implement uptime registers
- Add a callback for reading Modbus input registers.
- Implement logic to provide the system uptime in seconds, split across two 16-bit registers (UPTIME_SECONDS_LOW at 0x00F3 and UPTIME_SECONDS_HIGH at 0x00F4) as per documentation.
- Return 0 for unhandled registers to prevent "Invalid data" errors with certain Modbus masters.
2025-07-01 14:38:10 +02:00
1067796df4 fix(slave_node): Stabilize Modbus RTU communication
The Modbus server was previously unstable, leading to intermittent CRC errors when polled by a master. This was caused by the main thread exiting after initialization, which created timing and race condition issues for the interrupt-driven Modbus stack.

This fix ensures the main thread continues to run in a low-power sleep loop (). This provides a stable context for the Modbus server, resolving the CRC errors and ensuring reliable communication.
2025-07-01 14:30:22 +02:00
6f81e84541 feat(slave_node): Implement initial Modbus RTU server
- Add a basic Modbus RTU server implementation based on Zephyr samples.
- Configure usart1 for Modbus via a board overlay.
- The server initializes and runs, but polling with mbpoll results in a timeout.
- This commit captures a functional but non-working state for further debugging.
2025-07-01 13:41:22 +02:00
0d3696bf93 feat(slave_node): Switch to RTT console output
- Reconfigure the project to use SEGGER RTT for console output instead of UART.
- Update the main loop with a new printk message for testing purposes.
2025-07-01 12:36:11 +02:00
b005fd5c11 feat(slave_node): Enable UART console and printk
- Enable the console subsystem and printk for debugging output.
- Configure the console to use the UART peripheral (usart1 on PA9/PA10).
- Disable RTT to ensure UART is the active console.
2025-07-01 12:13:59 +02:00
6c15b7021f refactor(slave_node): Clean up initial configuration
- Remove redundant DTS_ROOT from CMakeLists.txt as it's inferred from BOARD_ROOT.
- Clear the project configuration (prj.conf) to start with a minimal baseline.
2025-07-01 12:09:36 +02:00
842b204d36 refactor(build): Streamline multi-app CMake configuration
Remove the top-level  and configure each application to directly include the  directory as a module or root. This simplifies the build process by making each application more self-contained while still allowing access to shared boards and libraries.
2025-07-01 12:05:18 +02:00
5ce96a662d feat: Establish functional multi-app structure
This commit captures a working multi-app build where the board definition is located in the 'software' directory and explicitly included by the slave_node application. This serves as a stable baseline.
2025-07-01 11:05:12 +02:00
fbeaa916b9 feat(project): Restructure software for multi-app setup
- Reorganize the software directory to support multiple Zephyr applications (gateway, slave_node).

- Create a clear separation between applications and shared libraries.

- Add placeholder files for gateway and slave_node applications.
2025-07-01 08:20:25 +02:00
98 changed files with 2278 additions and 1581 deletions

View File

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

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

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

View File

@@ -1,3 +1,5 @@
<img src="./docs/img/logo.png" alt="Logo" width="100"/>
🇩🇪 Deutsch | [🇬🇧 English](README.md) | [🇫🇷 Français](README.fr.md) | [🇪🇸 Español](README.es.md) 🇩🇪 Deutsch | [🇬🇧 English](README.md) | [🇫🇷 Français](README.fr.md) | [🇪🇸 Español](README.es.md)
# Modulares Bewässerungssystem # Modulares Bewässerungssystem
@@ -11,6 +13,8 @@ Die detaillierte Dokumentation befindet sich im Verzeichnis [`docs/`](./docs/):
* **[Konzept](./docs/concept.de.md)**: Beschreibt die Systemarchitektur, die verwendeten Komponenten und die grundlegenden Design-Entscheidungen. * **[Konzept](./docs/concept.de.md)**: Beschreibt die Systemarchitektur, die verwendeten Komponenten und die grundlegenden Design-Entscheidungen.
* **[MODBUS Register](./docs/modbus-registers.de.md)**: Definiert die Register-Map für die Kommunikation mit den Slave-Nodes. * **[MODBUS Register](./docs/modbus-registers.de.md)**: Definiert die Register-Map für die Kommunikation mit den Slave-Nodes.
* **[Projektplan](./docs/planning.de.md)**: Enthält den Entwicklungs- und Implementierungsplan. * **[Projektplan](./docs/planning.de.md)**: Enthält den Entwicklungs- und Implementierungsplan.
* **[Firmware-Handbuch](./docs/firmware-manual.de.md)**: Beschreibt den Funktionsumfang und die Bedienung der Slave-Node-Firmware.
* **[Modbus Test-Tool](./software/tools/modbus_tool/README.de.md)**: Anleitung für das Python-basierte Kommandozeilen-Tool zum Testen der Slaves.
## Schnellstart ## Schnellstart

View File

@@ -1,3 +1,5 @@
<img src="./docs/img/logo.png" alt="Logo" width="100"/>
[🇩🇪 Deutsch](README.de.md) | [🇬🇧 English](README.md) | [🇫🇷 Français](README.fr.md) | 🇪🇸 Español [🇩🇪 Deutsch](README.de.md) | [🇬🇧 English](README.md) | [🇫🇷 Français](README.fr.md) | 🇪🇸 Español
# Sistema de riego modular # Sistema de riego modular

View File

@@ -1,3 +1,5 @@
<img src="./docs/img/logo.png" alt="Logo" width="100"/>
[🇩🇪 Deutsch](README.de.md) | [🇬🇧 English](README.md) | 🇫🇷 Français | [🇪🇸 Español](README.es.md) [🇩🇪 Deutsch](README.de.md) | [🇬🇧 English](README.md) | 🇫🇷 Français | [🇪🇸 Español](README.es.md)
# Système d'irrigation modulaire # Système d'irrigation modulaire

View File

@@ -1,3 +1,5 @@
<img src="./docs/img/logo.png" alt="Logo" width="100"/>
[🇩🇪 Deutsch](README.de.md) | 🇬🇧 English | [🇫🇷 Français](README.fr.md) | [🇪🇸 Español](README.es.md) [🇩🇪 Deutsch](README.de.md) | 🇬🇧 English | [🇫🇷 Français](README.fr.md) | [🇪🇸 Español](README.es.md)
# Modular Irrigation System # Modular Irrigation System

View File

@@ -1,3 +1,5 @@
<img src="./img/logo.png" alt="Logo" width="100"/>
🇩🇪 Deutsch | [🇬🇧 English](concept.en.md) | [🇫🇷 Français](concept.fr.md) | [🇪🇸 Español](concept.es.md) 🇩🇪 Deutsch | [🇬🇧 English](concept.en.md) | [🇫🇷 Français](concept.fr.md) | [🇪🇸 Español](concept.es.md)
# Konzept: Modulares Bewässerungssystem # Konzept: Modulares Bewässerungssystem

View File

@@ -1,3 +1,5 @@
<img src="./img/logo.png" alt="Logo" width="100"/>
[🇩🇪 Deutsch](concept.de.md) | 🇬🇧 English | [🇫🇷 Français](concept.fr.md) | [🇪🇸 Español](concept.es.md) [🇩🇪 Deutsch](concept.de.md) | 🇬🇧 English | [🇫🇷 Français](concept.fr.md) | [🇪🇸 Español](concept.es.md)
# Concept: Modular Irrigation System # Concept: Modular Irrigation System

View File

@@ -1,3 +1,5 @@
<img src="./img/logo.png" alt="Logo" width="100"/>
[🇩🇪 Deutsch](concept.de.md) | [🇬🇧 English](concept.en.md) | [🇫🇷 Français](concept.fr.md) | 🇪🇸 Español [🇩🇪 Deutsch](concept.de.md) | [🇬🇧 English](concept.en.md) | [🇫🇷 Français](concept.fr.md) | 🇪🇸 Español
# Concepto: Sistema de riego modular # Concepto: Sistema de riego modular

View File

@@ -1,3 +1,5 @@
<img src="./img/logo.png" alt="Logo" width="100"/>
[🇩🇪 Deutsch](concept.de.md) | [🇬🇧 English](concept.en.md) | 🇫🇷 Français | [🇪🇸 Español](concept.es.md) [🇩🇪 Deutsch](concept.de.md) | [🇬🇧 English](concept.en.md) | 🇫🇷 Français | [🇪🇸 Español](concept.es.md)
# Concept : Système d'irrigation modulaire # Concept : Système d'irrigation modulaire

BIN
docs/img/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

16
docs/img/logo.svg Normal file
View File

@@ -0,0 +1,16 @@
<svg width="100" height="120" viewBox="0 0 100 120" xmlns="http://www.w3.org/2000/svg" aria-labelledby="logoTitle">
<title id="logoTitle">Logo: Wassertropfen mit integriertem Chip</title>
<path
d="M50 115 C 85 85, 95 65, 95 45 A 45 45 0 1 0 5 45 C 5 65, 15 85, 50 115 Z"
fill="#2563EB"
/>
<g fill="#FFFFFF">
<rect x="25" y="35" width="50" height="8" rx="2"/>
<rect x="25" y="50" width="35" height="8" rx="2"/>
<rect x="70" y="50" width="5" height="8" rx="2"/>
<rect x="25" y="65" width="50" height="8" rx="2"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 568 B

View File

@@ -1,3 +1,5 @@
<img src="./img/logo.png" alt="Logo" width="100"/>
🇩🇪 Deutsch | [🇬🇧 English](modbus-registers.en.md) | [🇫🇷 Français](modbus-registers.fr.md) | [🇪🇸 Español](modbus-registers.es.md) 🇩🇪 Deutsch | [🇬🇧 English](modbus-registers.en.md) | [🇫🇷 Français](modbus-registers.fr.md) | [🇪🇸 Español](modbus-registers.es.md)
# MODBUS Register Map Definition v1.0 # MODBUS Register Map Definition v1.0
@@ -36,6 +38,7 @@ Alle Register sind in einer einzigen, durchgehenden Liste pro Register-Typ (`Inp
| **0x00F2** | `DEVICE_STATUS` | System | `0`=OK, `1`=Allgemeiner Fehler. | | **0x00F2** | `DEVICE_STATUS` | System | `0`=OK, `1`=Allgemeiner Fehler. |
| **0x00F3** | `UPTIME_SECONDS_LOW` | System | Untere 16 Bit der Uptime in Sekunden. | | **0x00F3** | `UPTIME_SECONDS_LOW` | System | Untere 16 Bit der Uptime in Sekunden. |
| **0x00F4** | `UPTIME_SECONDS_HIGH` | System | Obere 16 Bit der Uptime. | | **0x00F4** | `UPTIME_SECONDS_HIGH` | System | Obere 16 Bit der Uptime. |
| **0x00F5** | `SUPPLY_VOLTAGE_MV` | System | Aktuelle Versorgungsspannung in Millivolt (mV). |
| **0x0100** | `FWU_LAST_CHUNK_CRC` | Firmware-Update | Enthält den CRC16 des zuletzt im Puffer empfangenen Daten-Chunks. | | **0x0100** | `FWU_LAST_CHUNK_CRC` | Firmware-Update | Enthält den CRC16 des zuletzt im Puffer empfangenen Daten-Chunks. |
## 3. Holding Registers (4xxxx, Read/Write) ## 3. Holding Registers (4xxxx, Read/Write)
@@ -47,6 +50,7 @@ Alle Register sind in einer einzigen, durchgehenden Liste pro Register-Typ (`Inp
| **0x0002** | `MAX_SCHLIESSZEIT_S` | Ventil | Sicherheits-Timeout in Sekunden für den Schliessen-Vorgang. | | **0x0002** | `MAX_SCHLIESSZEIT_S` | Ventil | Sicherheits-Timeout in Sekunden für den Schliessen-Vorgang. |
| **0x0010** | `DIGITAL_AUSGAENGE_ZUSTAND` | Ausgänge | Bitmaske zum Lesen und Schreiben der Ausgänge. Bit 0: Ausgang 1, Bit 1: Ausgang 2. `1`=AN, `0`=AUS. | | **0x0010** | `DIGITAL_AUSGAENGE_ZUSTAND` | Ausgänge | Bitmaske zum Lesen und Schreiben der Ausgänge. Bit 0: Ausgang 1, Bit 1: Ausgang 2. `1`=AN, `0`=AUS. |
| **0x00F0** | `WATCHDOG_TIMEOUT_S` | System | Timeout des Fail-Safe-Watchdogs in Sekunden. `0`=Deaktiviert. | | **0x00F0** | `WATCHDOG_TIMEOUT_S` | System | Timeout des Fail-Safe-Watchdogs in Sekunden. `0`=Deaktiviert. |
| **0x00F1** | `DEVICE_RESET` | System | Schreibt `1` um das Gerät neu zu starten. |
| **0x0100** | `FWU_COMMAND` | Firmware-Update | `1`: **Verify Chunk**: Der zuletzt übertragene Chunk wurde vom Client als gültig befunden. Der Slave soll ihn nun ins Flash schreiben. `2`: **Finalize Update**: Alle Chunks sind übertragen. Installation abschliessen und neu starten. | | **0x0100** | `FWU_COMMAND` | Firmware-Update | `1`: **Verify Chunk**: Der zuletzt übertragene Chunk wurde vom Client als gültig befunden. Der Slave soll ihn nun ins Flash schreiben. `2`: **Finalize Update**: Alle Chunks sind übertragen. Installation abschliessen und neu starten. |
| **0x0101** | `FWU_CHUNK_OFFSET_LOW` | Firmware-Update | Untere 16 Bit des 32-Bit-Offsets, an den der nächste Chunk geschrieben werden soll. | | **0x0101** | `FWU_CHUNK_OFFSET_LOW` | Firmware-Update | Untere 16 Bit des 32-Bit-Offsets, an den der nächste Chunk geschrieben werden soll. |
| **0x0102** | `FWU_CHUNK_OFFSET_HIGH` | Firmware-Update | Obere 16 Bit des 32-Bit-Offsets. | | **0x0102** | `FWU_CHUNK_OFFSET_HIGH` | Firmware-Update | Obere 16 Bit des 32-Bit-Offsets. |

View File

@@ -1,3 +1,5 @@
<img src="./img/logo.png" alt="Logo" width="100"/>
[🇩🇪 Deutsch](modbus-registers.de.md) | 🇬🇧 English | [🇫🇷 Français](modbus-registers.fr.md) | [🇪🇸 Español](modbus-registers.es.md) [🇩🇪 Deutsch](modbus-registers.de.md) | 🇬🇧 English | [🇫🇷 Français](modbus-registers.fr.md) | [🇪🇸 Español](modbus-registers.es.md)
# MODBUS Register Map Definition v1.0 # MODBUS Register Map Definition v1.0

View File

@@ -1,3 +1,5 @@
<img src="./img/logo.png" alt="Logo" width="100"/>
[🇩🇪 Deutsch](modbus-registers.de.md) | [🇬🇧 English](modbus-registers.en.md) | [🇫🇷 Français](modbus-registers.fr.md) | 🇪🇸 Español [🇩🇪 Deutsch](modbus-registers.de.md) | [🇬🇧 English](modbus-registers.en.md) | [🇫🇷 Français](modbus-registers.fr.md) | 🇪🇸 Español
# Definición del mapa de registros MODBUS v1.0 # Definición del mapa de registros MODBUS v1.0

View File

@@ -1,3 +1,5 @@
<img src="./img/logo.png" alt="Logo" width="100"/>
[🇩🇪 Deutsch](modbus-registers.de.md) | [🇬🇧 English](modbus-registers.en.md) | 🇫🇷 Français | [🇪🇸 Español](modbus-registers.es.md) [🇩🇪 Deutsch](modbus-registers.de.md) | [🇬🇧 English](modbus-registers.en.md) | 🇫🇷 Français | [🇪🇸 Español](modbus-registers.es.md)
# Définition de la carte des registres MODBUS v1.0 # Définition de la carte des registres MODBUS v1.0

View File

@@ -1,3 +1,5 @@
<img src="./img/logo.png" alt="Logo" width="100"/>
🇩🇪 Deutsch | [🇬🇧 English](planning.en.md) | [🇫🇷 Français](planning.fr.md) | [🇪🇸 Español](planning.es.md) 🇩🇪 Deutsch | [🇬🇧 English](planning.en.md) | [🇫🇷 Français](planning.fr.md) | [🇪🇸 Español](planning.es.md)
# Projektplan: Modulares Bewässerungssystem # Projektplan: Modulares Bewässerungssystem

View File

@@ -1,3 +1,5 @@
<img src="./img/logo.png" alt="Logo" width="100"/>
[🇩🇪 Deutsch](planning.de.md) | 🇬🇧 English | [🇫🇷 Français](planning.fr.md) | [🇪🇸 Español](planning.es.md) [🇩🇪 Deutsch](planning.de.md) | 🇬🇧 English | [🇫🇷 Français](planning.fr.md) | [🇪🇸 Español](planning.es.md)
# Project Plan: Modular Irrigation System # Project Plan: Modular Irrigation System

View File

@@ -1,3 +1,5 @@
<img src="./img/logo.png" alt="Logo" width="100"/>
[🇩🇪 Deutsch](planning.de.md) | [🇬🇧 English](planning.en.md) | [🇫🇷 Français](planning.fr.md) | 🇪🇸 Español [🇩🇪 Deutsch](planning.de.md) | [🇬🇧 English](planning.en.md) | [🇫🇷 Français](planning.fr.md) | 🇪🇸 Español
# Plan del proyecto: Sistema de riego modular # Plan del proyecto: Sistema de riego modular

View File

@@ -1,3 +1,5 @@
<img src="./img/logo.png" alt="Logo" width="100"/>
[🇩🇪 Deutsch](planning.de.md) | [🇬🇧 English](planning.en.md) | 🇫🇷 Français | [🇪🇸 Español](planning.es.md) [🇩🇪 Deutsch](planning.de.md) | [🇬🇧 English](planning.en.md) | 🇫🇷 Français | [🇪🇸 Español](planning.es.md)
# Plan de projet : Système d'irrigation modulaire # Plan de projet : Système d'irrigation modulaire

View File

@@ -1,14 +0,0 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
# This line should ideally be after project() and find_package(Zephyr)
# target_include_directories(app PRIVATE ${ZEPHYR_BASE}/include/zephyr/drivers) # <-- WRONG POSITION
list(APPEND BOARD_ROOT ${CMAKE_CURRENT_SOURCE_DIR})
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(valve_node)
target_include_directories(app PRIVATE ${ZEPHYR_BASE}/include/zephyr/drivers)
target_sources(app PRIVATE src/main2.c)

View File

@@ -1,25 +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(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
)

1
software/Kconfig Normal file
View File

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

View File

@@ -0,0 +1,8 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(ADC)
target_sources(app PRIVATE src/main.c)

View File

@@ -0,0 +1,62 @@
.. zephyr:code-sample:: adc_dt
:name: Analog-to-Digital Converter (ADC) with devicetree
:relevant-api: adc_interface
Read analog inputs from ADC channels.
Overview
********
This sample demonstrates how to use the :ref:`ADC driver API <adc_api>`.
Depending on the target board, it reads ADC samples from one or more channels
and prints the readings on the console. If voltage of the used reference can
be obtained, the raw readings are converted to millivolts.
The pins of the ADC channels are board-specific. Please refer to the board
or MCU datasheet for further details.
Building and Running
********************
The ADC peripheral and pinmux is configured in the board's ``.dts`` file. Make
sure that the ADC is enabled (``status = "okay";``).
In addition to that, this sample requires an ADC channel specified in the
``io-channels`` property of the ``zephyr,user`` node. This is usually done with
a devicetree overlay. The example overlay in the ``boards`` subdirectory for
the ``nucleo_l073rz`` board can be easily adjusted for other boards.
Configuration of channels (settings like gain, reference, or acquisition time)
also needs to be specified in devicetree, in ADC controller child nodes. Also
the ADC resolution and oversampling setting (if used) need to be specified
there. See :zephyr_file:`boards/nrf52840dk_nrf52840.overlay
<samples/drivers/adc/adc_dt/boards/nrf52840dk_nrf52840.overlay>` for an example of
such setup.
Building and Running for ST Nucleo L073RZ
=========================================
The sample can be built and executed for the
:zephyr:board:`nucleo_l073rz` as follows:
.. zephyr-app-commands::
:zephyr-app: samples/drivers/adc/adc_dt
:board: nucleo_l073rz
:goals: build flash
:compact:
To build for another board, change "nucleo_l073rz" above to that board's name
and provide a corresponding devicetree overlay.
Sample output
=============
You should get a similar output as below, repeated every second:
.. code-block:: console
ADC reading:
- ADC_0, channel 7: 36 = 65mV
.. note:: If the ADC is not supported, the output will be an error message.

View File

@@ -0,0 +1,38 @@
/ {
vdd_sense: voltage-divider {
compatible = "voltage-divider";
/*
* This reference must provide one argument (the channel number)
* because of the "#io-channel-cells = <1>" in the &adc1 node.
*/
io-channels = <&adc1 1>;
output-ohms = <2200>;
full-ohms = <3200>;
};
};
&adc1 {
status = "okay";
pinctrl-0 = <&adc1_in1_pa0>;
pinctrl-names = "default";
st,adc-clock-source = "SYNC";
st,adc-prescaler = <4>;
#address-cells = <1>;
#size-cells = <0>;
/*
* This line is required by the st,stm32-adc driver binding.
* It declares that references to its channels need one extra argument.
*/
#io-channel-cells = <1>;
adc_channel_1: channel@1 {
reg = <1>;
zephyr,gain = "ADC_GAIN_1";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
zephyr,resolution = <12>;
};
};

View File

@@ -0,0 +1,4 @@
CONFIG_ADC=y
CONFIG_SENSOR=y
CONFIG_VOLTAGE_DIVIDER=y
CONFIG_LOG=y

View File

@@ -0,0 +1,53 @@
sample:
name: ADC devicetree driver sample
tests:
sample.drivers.adc.adc_dt:
tags:
- adc
depends_on: adc
platform_allow:
- nucleo_l073rz
- disco_l475_iot1
- cc3220sf_launchxl
- cc3235sf_launchxl
- cy8cproto_063_ble
- stm32l496g_disco
- stm32h735g_disco
- nrf51dk/nrf51822
- nrf52840dk/nrf52840
- nrf54l15dk/nrf54l15/cpuapp
- nrf54h20dk/nrf54h20/cpuapp
- ophelia4ev/nrf54l15/cpuapp
- mec172xevb_assy6906
- gd32f350r_eval
- gd32f450i_eval
- gd32vf103v_eval
- gd32f403z_eval
- esp32_devkitc/esp32/procpu
- esp32s2_saola
- esp32c3_devkitm
- gd32l233r_eval
- lpcxpresso55s36
- mr_canhubk3
- longan_nano
- longan_nano/gd32vf103/lite
- rd_rw612_bga
- frdm_mcxn947/mcxn947/cpu0
- mcx_n9xx_evk/mcxn947/cpu0
- frdm_mcxc242
- ucans32k1sic
- xg24_rb4187c
- xg29_rb4412a
- raytac_an54l15q_db/nrf54l15/cpuapp
- frdm_mcxa166
- frdm_mcxa276
integration_platforms:
- nucleo_l073rz
- nrf52840dk/nrf52840
harness: console
timeout: 10
harness_config:
type: multi_line
regex:
- "ADC reading\\[\\d+\\]:"
- "- .+, channel \\d+: -?\\d+"

View File

@@ -0,0 +1,25 @@
/*
* Copyright (c) 2022 Wolter HV <wolterhv@gmx.de>
*
* SPDX-License-Identifier: Apache-2.0
*/
/ {
zephyr,user {
io-channels = <&adc0 0>;
};
};
&adc0 {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
channel@0 {
reg = <0>;
zephyr,gain = "ADC_GAIN_1_4";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
zephyr,resolution = <12>;
};
};

View File

@@ -0,0 +1,25 @@
/*
* Copyright (c) 2022 Wolter HV <wolterhv@gmx.de>
*
* SPDX-License-Identifier: Apache-2.0
*/
/ {
zephyr,user {
io-channels = <&adc0 0>;
};
};
&adc0 {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
channel@0 {
reg = <0>;
zephyr,gain = "ADC_GAIN_1_4";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
zephyr,resolution = <12>;
};
};

View File

@@ -0,0 +1,25 @@
/*
* Copyright (c) 2022 Wolter HV <wolterhv@gmx.de>
*
* SPDX-License-Identifier: Apache-2.0
*/
/ {
zephyr,user {
io-channels = <&adc0 0>;
};
};
&adc0 {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
channel@0 {
reg = <0>;
zephyr,gain = "ADC_GAIN_1_4";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
zephyr,resolution = <12>;
};
};

View File

@@ -0,0 +1,25 @@
/*
* Copyright (c) 2022 Wolter HV <wolterhv@gmx.de>
*
* SPDX-License-Identifier: Apache-2.0
*/
/ {
zephyr,user {
io-channels = <&adc0 0>;
};
};
&adc0 {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
channel@0 {
reg = <0>;
zephyr,gain = "ADC_GAIN_1_4";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
zephyr,resolution = <12>;
};
};

View File

@@ -0,0 +1,45 @@
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(adc_dt_example, LOG_LEVEL_DBG);
/* Get the voltage divider device */
#define VOLTAGE_DIVIDER_NODE DT_NODELABEL(vdd_sense)
int main(void)
{
const struct device *vdd_dev = DEVICE_DT_GET(VOLTAGE_DIVIDER_NODE);
struct sensor_value val;
int err;
if (!device_is_ready(vdd_dev)) {
LOG_ERR("Voltage divider device not ready");
return 0;
}
LOG_INF("Voltage divider device ready!");
while (1) {
err = sensor_sample_fetch(vdd_dev);
if (err < 0) {
LOG_ERR("Could not fetch sample (%d)", err);
k_sleep(K_MSEC(1000));
continue;
}
err = sensor_channel_get(vdd_dev, SENSOR_CHAN_VOLTAGE, &val);
if (err < 0) {
LOG_ERR("Could not get channel (%d)", err);
k_sleep(K_MSEC(1000));
continue;
}
LOG_INF("Voltage reading: %d.%06d V", val.val1, val.val2);
k_sleep(K_MSEC(1000));
}
return 0;
}

View File

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

View File

@@ -0,0 +1,8 @@
&adc1 {
pinctrl-0 = <&adc1_in1_pa0>;
pinctrl-names = "default";
status = "okay";
st,adc-clock-source = "SYNC";
st,adc-prescaler = <4>;
};

View File

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

View File

@@ -0,0 +1,73 @@
#include <zephyr/kernel.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/device.h>
#include <zephyr/sys/printk.h>
// ADC-Knoten holen
static const struct device *adc_dev = DEVICE_DT_GET(DT_NODELABEL(adc1));
// Kanaldefinitionen
#define MY_SIGNAL_CHANNEL 1 // PA0
#define ADC_VREFINT_CHANNEL 18 // Intern
// Puffer für ZWEI Messwerte
static int16_t sample_buffer[2];
void main(void)
{
int err;
// Die VREFINT-Spannung in mV aus dem Datenblatt deines Controllers
#define VREFINT_MV 1212
printk("*** ADC Ratiometric Measurement (Single Sequence) ***\n");
if (!device_is_ready(adc_dev)) {
printk("ADC device not ready!\n");
return;
}
// --- Einmaliges Setup der beiden Kanäle ---
const struct adc_channel_cfg signal_channel_cfg = {
.gain = ADC_GAIN_1,
.reference = ADC_REF_INTERNAL,
.acquisition_time = ADC_ACQ_TIME_DEFAULT, // Kurz für niederohmige Quellen
.channel_id = MY_SIGNAL_CHANNEL,
};
const struct adc_channel_cfg vrefint_channel_cfg = {
.gain = ADC_GAIN_1,
.reference = ADC_REF_INTERNAL,
.acquisition_time = ADC_ACQ_TIME_MAX, // Lang für VREFINT
.channel_id = ADC_VREFINT_CHANNEL,
};
adc_channel_setup(adc_dev, &signal_channel_cfg);
adc_channel_setup(adc_dev, &vrefint_channel_cfg);
// --- EINE Sequenz, die BEIDE Kanäle enthält ---
const struct adc_sequence sequence = {
.channels = BIT(MY_SIGNAL_CHANNEL) | BIT(ADC_VREFINT_CHANNEL),
.buffer = sample_buffer,
.buffer_size = sizeof(sample_buffer),
.resolution = 12,
};
while (1) {
err = adc_read(adc_dev, &sequence);
if (err != 0) {
printk("ADC read failed with code %d\n", err);
} else {
// Die Ergebnisse sind in der Reihenfolge der Kanalnummern im Puffer
// Kanal 1 (MY_SIGNAL_CHANNEL) kommt vor Kanal 18 (ADC_VREFINT_CHANNEL)
int16_t signal_raw = sample_buffer[0];
int16_t vrefint_raw = sample_buffer[1];
// Ratiometrische Berechnung
int32_t signal_mv = (int32_t)signal_raw * VREFINT_MV / vrefint_raw;
printk("Signal: raw=%4d | VREFINT: raw=%4d | Calculated Voltage: %d mV\n",
signal_raw, vrefint_raw, signal_mv);
}
k_msleep(2000);
}
}

View File

@@ -0,0 +1,80 @@
#include <zephyr/kernel.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/device.h>
// Definiere die Kanäle
#define ADC_VREFINT_CHANNEL 18 // Muss mit dem DTS übereinstimmen
#define MY_SIGNAL_CHANNEL 1 // Muss mit dem pinctrl im DTS übereinstimmen
// ADC Device
static const struct device *adc_dev = DEVICE_DT_GET(DT_NODELABEL(adc1));
// ADC Kanal Konfigurationen
static const struct adc_channel_cfg vrefint_channel_cfg = {
.gain = ADC_GAIN_1,
.reference = ADC_REF_INTERNAL, // Bedeutet VDDA
.acquisition_time = ADC_ACQ_TIME_MAX,
.channel_id = ADC_VREFINT_CHANNEL,
.differential = 0,
};
static const struct adc_channel_cfg signal_channel_cfg = {
.gain = ADC_GAIN_1,
.reference = ADC_REF_INTERNAL, // Bedeutet VDDA
.acquisition_time = ADC_ACQ_TIME_MAX,
.channel_id = MY_SIGNAL_CHANNEL,
.differential = 0,
};
// Puffer für die Messwerte
#define BUFFER_SIZE 1
static int16_t sample_buffer[BUFFER_SIZE];
// Sequenz für die Messungen
struct adc_sequence sequence_vrefint = {
.channels = BIT(ADC_VREFINT_CHANNEL),
.buffer = sample_buffer,
.buffer_size = sizeof(sample_buffer),
.resolution = 12, // STM32G4 hat 12-bit
};
struct adc_sequence sequence_signal = {
.channels = BIT(MY_SIGNAL_CHANNEL),
.buffer = sample_buffer,
.buffer_size = sizeof(sample_buffer),
.resolution = 12,
};
void main(void) {
if (!device_is_ready(adc_dev)) {
printk("ADC device not found\n");
return;
}
// Kanäle konfigurieren
adc_channel_setup(adc_dev, &vrefint_channel_cfg);
adc_channel_setup(adc_dev, &signal_channel_cfg);
while (1) {
// 1. VREFINT messen zur Kalibrierung
adc_read(adc_dev, &sequence_vrefint);
int16_t vrefint_raw = sample_buffer[0];
// 2. Dein eigentliches Signal messen
adc_read(adc_dev, &sequence_signal);
int16_t signal_raw = sample_buffer[0];
// 3. Spannung berechnen
// VREFINT Wert für STM32G431 bei 3.0V Vdda ist typ. 1.212V (1212 mV)
// Überprüfe den genauen Wert im Datenblatt für deinen Controller!
#define VREFINT_MV 1212
int32_t signal_mv = (int32_t)signal_raw * VREFINT_MV / vrefint_raw;
printk("VREFINT raw: %d, Signal raw: %d, Calculated Voltage: %d mV\n",
vrefint_raw, signal_raw, signal_mv);
k_msleep(1000);
}
}

View File

@@ -0,0 +1,38 @@
#include <zephyr.h>
#include <drivers/adc.h>
#define PA0_PIN 0x04
#define ADC_CHANNEL 0x03
int main(void) {
int16_t adc_value = 0;
// Initialize the ADC
adc_config_t adc_config;
adc_config.mode = ADC_MODE_SINGLE_SHOT;
adc_config.channel = ADC_CHANNEL_PA0;
adc_config.sampling_rate = ADC_SAMP_RATE_1MS;
adc_config.data_rate = ADC_DATA_RATE_4MS;
adc_config.aux = ADC_AUX_ALL;
adc_config.atten = ADC_ATTEN_DB_11;
adc_config.ref = ADC_REF_INTERNAL;
adc_config.cal = ADC_CAL_ALL;
if (adc_config_data(&adc_config, &adc_context) < 0) {
zephyr_printf("Failed to configure ADC\n");
return -1;
}
// Read the analog input value
if (adc_read(&adc_context, &adc_value) < 0) {
zephyr_printf("Failed to read ADC value\n");
return -1;
}
zephyr_printf("ADC Value: %d\n", adc_value);
return 0;
}

View File

@@ -0,0 +1,9 @@
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})
project(gateway)
target_sources(app PRIVATE src/main.c)

View File

@@ -0,0 +1,2 @@
# Gateway Configuration
CONFIG_NETWORKING=y

View File

@@ -0,0 +1,13 @@
/*
* Copyright (c) 2025 Eduard Iten
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
int main(void)
{
printk("Hello from Gateway!\n");
return 0;
}

View File

@@ -0,0 +1,8 @@
cmake_minimum_required(VERSION 3.20)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(slave_node LANGUAGES C)
zephyr_include_directories(../../include)
add_subdirectory(../../lib lib)
target_sources(app PRIVATE src/main.c)

View File

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

View File

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

View File

@@ -0,0 +1,43 @@
/ {
chosen {
zephyr,console = &rtt;
zephyr,shell = &rtt;
zephyr,settings-partition = &storage_partition;
};
rtt: rtt {
compatible = "segger,rtt-uart";
#address-cells = <1>;
#size-cells = <0>;
label = "RTT";
status = "okay";
};
};
&flash0 {
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
/* Application partition starts at the beginning of flash */
slot0_partition: partition@0 {
label = "image-0";
reg = <0x00000000 DT_SIZE_K(120)>;
};
/* Use the last 8K for settings */
storage_partition: partition@1E000 {
label = "storage";
reg = <0x0001E000 DT_SIZE_K(8)>;
};
};
};
&usart1 {
modbus0 {
compatible = "zephyr,modbus-serial";
status = "okay";
};
status = "okay";
};

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,27 @@
# Enable Console and printk for logging
CONFIG_CONSOLE=y
CONFIG_LOG=y
# Enable Shell
CONFIG_SHELL=y
CONFIG_REBOOT=y
# Enable Settings Subsystem
CONFIG_SETTINGS=y
CONFIG_SETTINGS_NVS=y
CONFIG_NVS=y
CONFIG_FLASH=y
CONFIG_FLASH_MAP=y
CONFIG_FLASH_PAGE_LAYOUT=y
CONFIG_SETTINGS_LOG_LEVEL_DBG=y
# Config modbus
CONFIG_UART_INTERRUPT_DRIVEN=y
CONFIG_MODBUS=y
CONFIG_MODBUS_ROLE_SERVER=y
CONFIG_MODBUS_BUFFER_SIZE=256
# Enable ADC driver
CONFIG_ADC=y
CONFIG_ADC_STM32=y

View File

@@ -0,0 +1,35 @@
#include <zephyr/kernel.h>
#include <zephyr/settings/settings.h>
#include <zephyr/logging/log.h>
#include <lib/modbus_server.h>
#include <lib/valve.h>
#include <lib/fwu.h>
LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);
int main(void)
{
LOG_INF("Starting Irrigation System Slave Node");
if (settings_subsys_init() || settings_load()) {
LOG_ERR("Settings initialization or loading failed");
}
valve_init();
fwu_init();
if (modbus_server_init()) {
LOG_ERR("Modbus RTU server initialization failed");
return 0;
}
// Test supply voltage reading periodically
while (1) {
uint16_t supply_voltage = valve_get_supply_voltage();
LOG_INF("Supply voltage: %u mV", supply_voltage);
k_msleep(5000); // Read every 5 seconds
}
LOG_INF("Irrigation System Slave Node started successfully");
return 0;
}

View File

@@ -0,0 +1,5 @@
# Copyright (c) 2025 Eduard Iten
# SPDX-License-Identifier: Apache-2.0
config BOARD_BLUEPILL_F103RB
select SOC_STM32F103XB

View File

@@ -0,0 +1,106 @@
/*
* Copyright (c) 2025 Eduard Iten
*
* SPDX-License-Identifier: Apache-2.0
*/
/dts-v1/;
#include <st/f1/stm32f1.dtsi>
#include <st/f1/stm32f103Xb.dtsi>
#include <st/f1/stm32f103r(8-b)tx-pinctrl.dtsi>
#include <zephyr/dt-bindings/input/input-event-codes.h>
/ {
model = "Blue-Pill STM32F103RB";
compatible = "iten,bluepill-f103rb";
chosen {
zephyr,console = &usart1;
zephyr,shell-uart = &usart1;
zephyr,sram = &sram0;
zephyr,flash = &flash0;
};
leds {
compatible = "gpio-leds";
led_status: led_status {
gpios = <&gpioc 13 GPIO_ACTIVE_LOW>;
label = "User LED";
};
};
aliases {
led0 = &led_status;
watchdog0 = &iwdg;
};
};
&clk_lsi {
status = "okay";
};
&clk_hse {
clock-frequency = <DT_FREQ_M(8)>;
status = "okay";
};
&pll {
mul = <9>;
clocks = <&clk_hse>;
status = "okay";
};
&rcc {
clocks = <&pll>;
clock-frequency = <DT_FREQ_M(72)>;
ahb-prescaler = <1>;
apb1-prescaler = <2>;
apb2-prescaler = <1>;
adc-prescaler = <6>;
};
&usart1 {
pinctrl-0 = <&usart1_tx_pa9 &usart1_rx_pa10>;
pinctrl-names = "default";
current-speed = <115200>;
status = "okay";
};
&iwdg {
status = "okay";
};
&clk_lsi {
status = "okay";
};
&clk_hse {
clock-frequency = <DT_FREQ_M(8)>;
status = "okay";
};
&pll {
mul = <9>;
clocks = <&clk_hse>;
status = "okay";
};
&rcc {
clocks = <&pll>;
clock-frequency = <DT_FREQ_M(72)>;
ahb-prescaler = <1>;
apb1-prescaler = <2>;
apb2-prescaler = <1>;
adc-prescaler = <6>;
};
&exti {
status = "okay";
};
&dma1 {
status = "okay";
};

View File

@@ -0,0 +1,4 @@
# Copyright (c) 2025 Eduard Iten
# SPDX-License-Identifier: Apache-2.0
CONFIG_SERIAL=y
CONFIG_GPIO=y

View File

@@ -1,11 +1,12 @@
# Copyright (c) 2025 Eduard Iten
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
# keep first # keep first
board_runner_args(stm32cubeprogrammer "--port=swd" "--reset-mode=hw")
board_runner_args(jlink "--device=STM32F103RB" "--speed=4000") board_runner_args(jlink "--device=STM32F103RB" "--speed=4000")
board_runner_args(stm32cubeprogrammer "--port=swd" "--reset-mode=hw")
# keep first # keep first
include(${ZEPHYR_BASE}/boards/common/jlink.board.cmake)
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/blackmagicprobe.board.cmake) include(${ZEPHYR_BASE}/boards/common/blackmagicprobe.board.cmake)

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2025 Eduard Iten
# SPDX-License-Identifier: Apache-2.0
board:
name: bluepill_f103rb
vendor: iten
socs:
- name: stm32f103xb

View File

@@ -1,58 +0,0 @@
config BOARD_VALVE_NODE
select SOC_STM32F103XB
mainmenu "APP CAN Settings"
config LOOPBACK_MODE
bool "Loopback LOOPBACK_MODE"
default n
help
Set the can controller to loopback mode.
This allows testing without a second board.
mainmenu "APP Logging Settings"
config LOG_CAN_LEVEL
int "Log level for CAN"
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_SETTINGS_LEVEL
int "Log level for settings"
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_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

@@ -1,10 +0,0 @@
board:
name: valve_node
full_name: Irrigation system CANbus valve node
socs:
- name: stm32f103xb
# revision:
# format: number
# default: "1"
# revisions:
# -name: "1"

View File

@@ -1,206 +0,0 @@
/*
* Copyright (c) 2017 Linaro Limited
*
* SPDX-License-Identifier: Apache-2.0
*/
/dts-v1/;
#include <st/f1/stm32f1.dtsi>
#include <st/f1/stm32f103Xb.dtsi>
#include <st/f1/stm32f103r(8-b)tx-pinctrl.dtsi>
#include <zephyr/dt-bindings/input/input-event-codes.h>
/ {
model = "Iten engineering Valve Node";
compatible = "st,stm32f103rb";
can_loopback0: can_loopback0 {
status = "disabled";
compatible = "zephyr,can-loopback";
};
chosen {
zephyr,console = &usart1;
zephyr,shell-uart = &usart1;
zephyr,sram = &sram0;
zephyr,flash = &flash0;
zephyr,canbus = &can1;
};
leds: leds {
compatible = "gpio-leds";
green_led_2: led_2 {
gpios = <&gpiob 2 GPIO_ACTIVE_HIGH>;
label = "User LD2";
};
};
gpio_keys {
compatible = "gpio-keys";
user_button: button {
label = "User";
gpios = <&gpioc 13 GPIO_ACTIVE_LOW>;
zephyr,code = <INPUT_KEY_0>;
};
endstopopen: endstop_open {
gpios = <&gpiob 4 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>;
label = "Endstop Open";
};
endstopclose: endstop_closed {
gpios = <&gpiob 5 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>;
label = "Endstop Close";
};
statusopen: status_open {
gpios = <&gpiob 14 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>;
label = "Status Open";
};
statusclose: status_close {
gpios = <&gpioa 8 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>;
label = "Status Close";
};
};
aliases {
led0 = &green_led_2;
sw0 = &user_button;
watchdog0 = &iwdg;
die-temp0 = &die_temp;
adc-motor-current = &motor_current_channel;
adc-vref = &vref_channel;
};
};
&clk_lsi {
status = "okay";
};
&clk_hse {
clock-frequency = <DT_FREQ_M(8)>;
status = "okay";
};
&pll {
mul = <9>;
clocks = <&clk_hse>;
status = "okay";
};
&rcc {
clocks = <&pll>;
clock-frequency = <DT_FREQ_M(72)>;
ahb-prescaler = <1>;
apb1-prescaler = <2>;
apb2-prescaler = <1>;
adc-prescaler = <6>;
};
&usart1 {
pinctrl-0 = <&usart1_tx_pa9 &usart1_rx_pa10>;
pinctrl-names = "default";
status = "okay";
current-speed = <115200>;
};
&usart2 {
pinctrl-0 = <&usart2_tx_pa2 &usart2_rx_pa3>;
current-speed = <9600>;
pinctrl-names = "default";
status = "okay";
modbus0 {
compatible = "zephyr,modbus-serial";
status = "okay";
};
};
&usart3 {
pinctrl-0 = <&usart3_tx_pb10 &usart3_rx_pb11>;
current-speed = <115200>;
pinctrl-names = "default";
};
&i2c1 {
pinctrl-0 = <&i2c1_scl_remap1_pb8 &i2c1_sda_remap1_pb9>;
pinctrl-names = "default";
status = "okay";
clock-frequency = <I2C_BITRATE_FAST>;
};
&iwdg {
status = "okay";
};
&rtc {
clocks = <&rcc STM32_CLOCK_BUS_APB1 0x10000000>,
<&rcc STM32_SRC_LSI RTC_SEL(2)>;
status = "okay";
};
&adc1 {
pinctrl-0 = <&adc_pb1_pins>;
pinctrl-names = "default";
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
motor_current_channel: channel@9 {
reg = <0x9>;
zephyr,gain = "ADC_GAIN_1";
zephyr,reference = "ADC_REF_VDD_1";
zephyr,acquisition-time = <49159>;
zephyr,resolution = <12>;
};
vref_channel: channel@11 { /* 17 dezimal = 11 hex */
reg = <0x11>;
zephyr,gain = "ADC_GAIN_1";
zephyr,reference = "ADC_REF_VDD_1";
zephyr,acquisition-time = <49159>;
zephyr,resolution = <12>;
};
};
&die_temp {
status = "okay";
};
&dma1 {
status = "okay";
};
&flash0 {
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
storage_partition: partition@1f800 {
label = "storage";
reg = <0x0001f800 DT_SIZE_K(2)>;
};
};
};
&can1 {
pinctrl-0 = <&can_rx_pa11 &can_tx_pa12>;
pinctrl-names = "default";
status= "okay";
bitrate = <125000>;
};
&exti {
status = "okay";
};
&pinctrl {
adc_pb1_pins: adc_pb1_pins {
pinmux = <STM32F1_PINMUX('B', 1, ANALOG, NO_REMAP)>;
};
};

View File

@@ -1,31 +0,0 @@
# SPDX-License-Identifier: Apache-2.0
# enable uart driver
CONFIG_SERIAL=y
# enable console
CONFIG_CONSOLE=y
# enable GPIO
CONFIG_GPIO=y
# modbus config
CONFIG_UART_INTERRUPT_DRIVEN=y
CONFIG_UART_LINE_CTRL=n
CONFIG_MODBUS=y
CONFIG_MODBUS_ROLE_CLIENT=y
# can config
CONFIG_CAN=y
CONFIG_CAN_INIT_PRIORITY=80
#CONFIG_CAN_MAX_FILTER=5
CONFIG_CAN_ACCEPT_RTR=y
# settings
CONFIG_FLASH=y
CONFIG_FLASH_MAP=y
CONFIG_SETTINGS=y
CONFIG_SETTINGS_RUNTIME=y
CONFIG_NVS=y
CONFIG_SETTINGS_NVS=y
CONFIG_HEAP_MEM_POOL_SIZE=256
CONFIG_MPU_ALLOW_FLASH_WRITE=y

View File

@@ -0,0 +1,10 @@
#ifndef FWU_H
#define FWU_H
#include <stdint.h>
void fwu_init(void);
void fwu_handler(uint16_t addr, uint16_t reg);
uint16_t fwu_get_last_chunk_crc(void);
#endif // FWU_H

View File

@@ -0,0 +1,54 @@
#ifndef MODBUS_SERVER_H
#define MODBUS_SERVER_H
#include <stdint.h>
/**
* @brief Modbus Input Register Addresses.
*/
enum
{
/* Valve Control & Status */
REG_INPUT_VALVE_STATE_MOVEMENT = 0x0000,
REG_INPUT_MOTOR_CURRENT_MA = 0x0001,
/* Digital Inputs */
REG_INPUT_DIGITAL_INPUTS_STATE = 0x0020,
REG_INPUT_BUTTON_EVENTS = 0x0021,
/* System Config & Status */
REG_INPUT_FIRMWARE_VERSION_MAJOR_MINOR = 0x00F0,
REG_INPUT_FIRMWARE_VERSION_PATCH = 0x00F1,
REG_INPUT_DEVICE_STATUS = 0x00F2,
REG_INPUT_UPTIME_SECONDS_LOW = 0x00F3,
REG_INPUT_UPTIME_SECONDS_HIGH = 0x00F4,
REG_INPUT_SUPPLY_VOLTAGE_MV = 0x00F5,
REG_INPUT_FWU_LAST_CHUNK_CRC = 0x0100
};
/**
* @brief Modbus Holding Register Addresses.
*/
enum
{
/* Valve Control */
REG_HOLDING_VALVE_COMMAND = 0x0000,
REG_HOLDING_MAX_OPENING_TIME_S = 0x0001,
REG_HOLDING_MAX_CLOSING_TIME_S = 0x0002,
/* Digital Outputs */
REG_HOLDING_DIGITAL_OUTPUTS_STATE = 0x0010,
/* System Config */
REG_HOLDING_WATCHDOG_TIMEOUT_S = 0x00F0,
REG_HOLDING_DEVICE_RESET = 0x00F1,
/* Firmware Update */
REG_HOLDING_FWU_COMMAND = 0x0100,
REG_HOLDING_FWU_CHUNK_OFFSET_LOW = 0x0101,
REG_HOLDING_FWU_CHUNK_OFFSET_HIGH = 0x0102,
REG_HOLDING_FWU_CHUNK_SIZE = 0x0103,
REG_HOLDING_FWU_DATA_BUFFER = 0x0180,
};
int modbus_server_init(void);
int modbus_reconfigure(uint32_t baudrate, uint8_t unit_id);
uint32_t modbus_get_baudrate(void);
uint8_t modbus_get_unit_id(void);
#endif // MODBUS_SERVER_H

View File

@@ -0,0 +1,37 @@
#ifndef VALVE_H
#define VALVE_H
#include <stdint.h>
#include <zephyr/drivers/gpio.h>
struct valve_gpios {
const struct gpio_dt_spec in0;
const struct gpio_dt_spec in1;
const struct gpio_dt_spec rst;
const struct gpio_dt_spec sen;
const struct gpio_dt_spec s0;
const struct gpio_dt_spec s1;
};
enum valve_state {
VALVE_STATE_CLOSED,
VALVE_STATE_OPEN,
};
enum valve_movement { VALVE_MOVEMENT_IDLE, VALVE_MOVEMENT_OPENING, VALVE_MOVEMENT_CLOSING, VALVE_MOVEMENT_ERROR };
void valve_init(void);
void valve_open(void);
void valve_close(void);
void valve_stop(void);
enum valve_state valve_get_state(void);
enum valve_movement valve_get_movement(void);
uint16_t valve_get_motor_current(void);
uint16_t valve_get_supply_voltage(void);
void valve_set_max_open_time(uint16_t seconds);
void valve_set_max_close_time(uint16_t seconds);
uint16_t valve_get_max_open_time(void);
uint16_t valve_get_max_close_time(void);
#endif // VALVE_H

View File

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

8
software/lib/Kconfig Normal file
View File

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

View File

@@ -1,286 +0,0 @@
#include "canbus.h"
#include <zephyr/logging/log.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/drivers/can.h>
#include <stdlib.h>
#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));
LOG_MODULE_REGISTER(canbus, CONFIG_LOG_CAN_LEVEL);
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(arg2);
ARG_UNUSED(arg3);
LOG_INF("CAN bus thread started");
// Main loop for CAN bus operations
while (1)
{
int rc;
struct can_frame frame;
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",
frame.id, frame.dlc, (uint8_t)(frame.flags & CAN_FRAME_RTR));
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)
{
#ifdef CONFIG_HAS_MODBUS_WATERLEVEL_SENSOR
case CANBUS_REG_WATERLEVEL_LEVEL:
case CANBUS_REG_WATERLEVEL_ZERO_POINT:
case CANBUS_REG_WATERLEVEL_MAX_POINT:
waterlevel_command_t command;
command.cmd = is_rtr ? WATERLEVEL_CMD_GET : WATERLEVEL_CMD_SET; // Determine command type based on RTR
command.reg = reg; // Set the register ID
int16_t value = 0; // Initialize value to 0
if (!is_rtr) // If it's not a remote request, extract the value from the frame data
{
if (frame.dlc < sizeof(command.data))
{ LOG_ERR("Received frame with insufficient data length: %d", frame.dlc);
continue; // Skip processing if data length is insufficient
}
value = sys_be16_to_cpu(*(uint16_t *)frame.data); // Convert data from big-endian to host byte order
command.data = value; // Set the data field
LOG_DBG("Setting command data to: %d", value);
}
extern struct k_msgq waterlevel_sensor_msgq; // Declare the water level sensor message queue
k_msgq_put(&waterlevel_sensor_msgq, &command, K_NO_WAIT);
break;
#endif // CONFIG_HAS_MODBUS_WATERLEVEL_SENSOR
#ifdef CONFIG_HAS_VALVE
case CANBUS_REG_VALVE_STATUS:
case CANBUS_REG_VALVE_OPERATION:
if (is_rtr)
{
LOG_DBG("Received remote request for valve status or operation");
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
{
LOG_ERR("Received CAN frame with data for valve status or operation, but RTR is not set");
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
}
}
break;
}
default:
LOG_DBG("Received CAN frame with unknown register: 0x%02x", reg);
break;
}
#endif // CONFIG_HAS_VALVE
}
}
int canbus_init(void)
{
int rc = 0;
if (!device_is_ready(can_dev))
{
LOG_ERR("CAN device %s is not ready", can_dev->name);
return -ENODEV;
}
#ifdef CONFIG_LOOPBACK_MODE
rc = can_set_mode(can_dev, CAN_MODE_LOOPBACK);
if (rc != 0)
{
LOG_ERR("Failed to set CAN loopback mode: %d", rc);
return rc;
}
#endif
rc = can_start(can_dev);
if (rc != 0)
{
printf("Error starting CAN controller [%d]", rc);
return 0;
}
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");
const struct can_filter filter = {
.id = node_id << 8, // Standard ID with node ID in the first byte
.mask = 0x700, // Mask to match the first byte of the ID
.flags = 0, // No special flags
};
canbus_rx_filter_id = can_add_rx_filter(can_dev, canbus_rx_callback, NULL, &filter);
LOG_DBG("CAN RX filter added for node ID %d", canbus_rx_filter_id);
return 0;
}
void canbus_tx8_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_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;
}
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,16 +0,0 @@
#ifndef __CANBUS_H__
#define __CANBUS_H__
#include <stdint.h>
#include "canbus_registers.h"
#define CANBUS_RX_THREAD_STACK_SIZE (512) // Stack size for the CAN RX thread
#define CANBUS_RX_THREAD_PRIORITY (5) // Priority for the CAN RX thread
#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
int canbus_init(void);
int canbus_send8(uint16_t reg, uint8_t value);
int canbus_send16(uint16_t reg, uint16_t value);
#endif // __CANBUS_H__

View File

@@ -1,42 +0,0 @@
#ifndef __CANBUS_REGISTERS_H__
#define __CANBUS_REGISTERS_H__
enum canbus_registers {
CANBUS_REG_REBOOT = 0x00,
CANBUS_REG_STATE = 0x01,
CANBUS_REG_ERROR = 0x02,
CANBUS_REG_VALVE_STATUS = 0x10,
CANBUS_REG_VALVE_OPERATION = 0x11,
CANBUS_REG_VALVE_COMMAND = 0x12,
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__

View File

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

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

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

45
software/lib/fwu/fwu.c Normal file
View File

@@ -0,0 +1,45 @@
#include <zephyr/kernel.h>
#include <zephyr/sys/crc.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/logging/log.h>
#include <lib/fwu.h>
LOG_MODULE_REGISTER(fwu, LOG_LEVEL_INF);
#define FWU_BUFFER_SIZE 256
static uint8_t fwu_buffer[FWU_BUFFER_SIZE];
static uint32_t fwu_chunk_offset = 0;
static uint16_t fwu_chunk_size = 0;
static uint16_t fwu_last_chunk_crc = 0;
void fwu_init(void) {}
void fwu_handler(uint16_t addr, uint16_t reg)
{
// This is a simplified handler. In a real scenario, you would have a proper mapping
// between register addresses and actions.
if (addr == 0x0100) { // FWU_COMMAND
if (reg == 1) { LOG_INF("FWU: Chunk at offset %u (size %u) verified.", fwu_chunk_offset, fwu_chunk_size); }
else if (reg == 2) { LOG_INF("FWU: Finalize command received. Rebooting (simulated)."); }
} else if (addr == 0x0101) { // FWU_CHUNK_OFFSET_LOW
fwu_chunk_offset = (fwu_chunk_offset & 0xFFFF0000) | reg;
} else if (addr == 0x0102) { // FWU_CHUNK_OFFSET_HIGH
fwu_chunk_offset = (fwu_chunk_offset & 0x0000FFFF) | ((uint32_t)reg << 16);
} else if (addr == 0x0103) { // FWU_CHUNK_SIZE
fwu_chunk_size = (reg > FWU_BUFFER_SIZE) ? FWU_BUFFER_SIZE : reg;
} else if (addr >= 0x0180 && addr < (0x0180 + (FWU_BUFFER_SIZE / 2))) {
uint16_t index = (addr - 0x0180) * 2;
if (index < sizeof(fwu_buffer)) {
sys_put_be16(reg, &fwu_buffer[index]);
if (index + 2 >= fwu_chunk_size) {
fwu_last_chunk_crc = crc16_ccitt(0xffff, fwu_buffer, fwu_chunk_size);
LOG_INF("FWU: Chunk received, CRC is 0x%04X", fwu_last_chunk_crc);
}
}
}
}
uint16_t fwu_get_last_chunk_crc(void)
{
return fwu_last_chunk_crc;
}

View File

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

View File

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

View File

@@ -0,0 +1,233 @@
#include <zephyr/kernel.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/device.h>
#include <zephyr/modbus/modbus.h>
#include <zephyr/logging/log.h>
#include <zephyr/settings/settings.h>
#include <zephyr/sys/reboot.h>
#include <lib/modbus_server.h>
#include <lib/valve.h>
#include <lib/fwu.h>
#include <zephyr/usb/usb_device.h>
LOG_MODULE_REGISTER(modbus_server, LOG_LEVEL_INF);
static int modbus_iface;
static struct modbus_iface_param server_param = {
.mode = MODBUS_MODE_RTU,
.server = {.user_cb = NULL, .unit_id = 1},
.serial = {.baud = 19200, .parity = UART_CFG_PARITY_NONE},
};
static uint16_t watchdog_timeout_s = 0;
static struct k_timer watchdog_timer;
static void watchdog_timer_handler(struct k_timer *timer_id)
{
LOG_WRN("Modbus watchdog expired! Closing valve as a fail-safe.");
valve_close();
}
static inline void reset_watchdog(void)
{
if (watchdog_timeout_s > 0)
{
k_timer_start(&watchdog_timer, K_SECONDS(watchdog_timeout_s), K_NO_WAIT);
}
}
static int holding_reg_rd(uint16_t addr, uint16_t *reg)
{
reset_watchdog();
switch (addr)
{
case REG_HOLDING_MAX_OPENING_TIME_S:
*reg = valve_get_max_open_time();
break;
case REG_HOLDING_MAX_CLOSING_TIME_S:
*reg = valve_get_max_close_time();
break;
case REG_HOLDING_WATCHDOG_TIMEOUT_S:
*reg = watchdog_timeout_s;
break;
default:
*reg = 0;
break;
}
return 0;
}
static int holding_reg_wr(uint16_t addr, uint16_t reg)
{
reset_watchdog();
switch (addr)
{
case REG_HOLDING_VALVE_COMMAND:
if (reg == 1)
{
valve_open();
}
else if (reg == 2)
{
valve_close();
}
else if (reg == 0)
{
valve_stop();
}
break;
case REG_HOLDING_MAX_OPENING_TIME_S:
valve_set_max_open_time(reg);
break;
case REG_HOLDING_MAX_CLOSING_TIME_S:
valve_set_max_close_time(reg);
break;
case REG_HOLDING_WATCHDOG_TIMEOUT_S:
watchdog_timeout_s = reg;
if (watchdog_timeout_s > 0)
{
LOG_INF("Watchdog enabled with %u s timeout.", watchdog_timeout_s);
reset_watchdog();
}
else
{
LOG_INF("Watchdog disabled.");
k_timer_stop(&watchdog_timer);
}
break;
case REG_HOLDING_DEVICE_RESET:
if (reg == 1)
{
LOG_WRN("Modbus reset command received. Rebooting...");
sys_reboot(SYS_REBOOT_WARM);
}
break;
default:
fwu_handler(addr, reg);
break;
}
return 0;
}
static int input_reg_rd(uint16_t addr, uint16_t *reg)
{
reset_watchdog();
uint32_t uptime_s = k_uptime_get_32() / 1000;
switch (addr)
{
case REG_INPUT_VALVE_STATE_MOVEMENT:
*reg = (valve_get_movement() << 8) | (valve_get_state() & 0xFF);
break;
case REG_INPUT_MOTOR_CURRENT_MA:
*reg = valve_get_motor_current();
break;
case REG_INPUT_UPTIME_SECONDS_LOW:
*reg = (uint16_t)(uptime_s & 0xFFFF);
break;
case REG_INPUT_UPTIME_SECONDS_HIGH:
*reg = (uint16_t)(uptime_s >> 16);
break;
case REG_INPUT_SUPPLY_VOLTAGE_MV:
*reg = 12300;
break;
case REG_INPUT_FWU_LAST_CHUNK_CRC:
*reg = fwu_get_last_chunk_crc();
break;
case REG_INPUT_FIRMWARE_VERSION_MAJOR_MINOR:
*reg = (0 << 8) | 0;
break;
case REG_INPUT_FIRMWARE_VERSION_PATCH:
*reg = 2;
break;
default:
*reg = 0;
break;
}
return 0;
}
static struct modbus_user_callbacks mbs_cbs = {
.holding_reg_rd = holding_reg_rd,
.holding_reg_wr = holding_reg_wr,
.input_reg_rd = input_reg_rd,
};
#define MODBUS_NODE DT_COMPAT_GET_ANY_STATUS_OKAY(zephyr_modbus_serial)
int modbus_server_init(void)
{
k_timer_init(&watchdog_timer, watchdog_timer_handler, NULL);
// Load saved settings
uint32_t saved_baudrate = 19200;
uint8_t saved_unit_id = 1;
settings_load_one("modbus/baudrate", &saved_baudrate, sizeof(saved_baudrate));
settings_load_one("modbus/unit_id", &saved_unit_id, sizeof(saved_unit_id));
// Apply loaded settings
server_param.serial.baud = saved_baudrate;
server_param.server.unit_id = saved_unit_id;
const char iface_name[] = {DEVICE_DT_NAME(MODBUS_NODE)};
#if DT_NODE_HAS_COMPAT(DT_PARENT(MODBUS_NODE), zephyr_cdc_acm_uart)
const struct device *const dev = DEVICE_DT_GET(DT_PARENT(MODBUS_NODE));
uint32_t dtr = 0;
if (!device_is_ready(dev) || usb_enable(NULL))
{
return 0;
}
while (!dtr)
{
uart_line_ctrl_get(dev, UART_LINE_CTRL_DTR, &dtr);
k_sleep(K_MSEC(100));
}
LOG_INF("Client connected to server on %s", dev->name);
#endif
modbus_iface = modbus_iface_get_by_name(iface_name);
if (modbus_iface < 0)
{
return modbus_iface;
}
server_param.server.user_cb = &mbs_cbs;
LOG_INF("Starting Modbus server: baudrate=%u, unit_id=%u", saved_baudrate, saved_unit_id);
return modbus_init_server(modbus_iface, server_param);
}
int modbus_reconfigure(uint32_t baudrate, uint8_t unit_id)
{
// Update parameters
server_param.serial.baud = baudrate;
server_param.server.unit_id = unit_id;
// Try to reinitialize - this should work for most cases
int ret = modbus_init_server(modbus_iface, server_param);
if (ret == 0)
{
settings_save_one("modbus/baudrate", &baudrate, sizeof(baudrate));
settings_save_one("modbus/unit_id", &unit_id, sizeof(unit_id));
LOG_INF("Modbus reconfigured: baudrate=%u, unit_id=%u", baudrate, unit_id);
}
else
{
LOG_ERR("Failed to reconfigure Modbus: %d", ret);
LOG_INF("Modbus reconfiguration requires restart to take effect");
// Save settings for next boot
settings_save_one("modbus/baudrate", &baudrate, sizeof(baudrate));
settings_save_one("modbus/unit_id", &unit_id, sizeof(unit_id));
LOG_INF("Settings saved. Type 'reset' to restart the device and apply the change.");
return 0; // Return success since settings are saved
}
return ret;
}
uint32_t modbus_get_baudrate(void) { return server_param.serial.baud; }
uint8_t modbus_get_unit_id(void) { return server_param.server.unit_id; }

View File

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

View File

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

View File

@@ -0,0 +1,119 @@
#include <zephyr/shell/shell.h>
#include <stdlib.h>
#include <lib/modbus_server.h>
#include <lib/valve.h>
static int cmd_modbus_set_baud(const struct shell *sh, size_t argc, char **argv)
{
if (argc != 2) {
shell_error(sh, "Usage: set_baud <baudrate>");
return -EINVAL;
}
uint32_t new_baud = (uint32_t)strtoul(argv[1], NULL, 10);
const uint32_t valid_baud_rates[] = {1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200};
bool is_valid = false;
for (int i = 0; i < ARRAY_SIZE(valid_baud_rates); i++) {
if (new_baud == valid_baud_rates[i]) {
is_valid = true;
break;
}
}
if (!is_valid) {
char error_msg[128];
int offset = snprintf(error_msg, sizeof(error_msg), "Invalid baudrate. Valid rates are: ");
for (int i = 0; i < ARRAY_SIZE(valid_baud_rates); i++) {
offset += snprintf(error_msg + offset, sizeof(error_msg) - offset, "%u ", valid_baud_rates[i]);
}
shell_error(sh, "%s", error_msg);
return -EINVAL;
}
if (modbus_reconfigure(new_baud, modbus_get_unit_id()) != 0) {
shell_error(sh, "Failed to apply new baudrate");
} else {
shell_print(sh, "Modbus baudrate set to: %u (and saved)", new_baud);
}
return 0;
}
static int cmd_modbus_set_id(const struct shell *sh, size_t argc, char **argv)
{
if (argc != 2) {
shell_error(sh, "Usage: set_id <slave_id>");
return -EINVAL;
}
uint32_t new_id_u32 = (uint32_t)strtoul(argv[1], NULL, 10);
if (new_id_u32 == 0 || new_id_u32 > 247) {
shell_error(sh, "Invalid slave ID: %s. Must be between 1 and 247.", argv[1]);
return -EINVAL;
}
uint8_t new_id = (uint8_t)new_id_u32;
if (modbus_reconfigure(modbus_get_baudrate(), new_id) != 0) {
shell_error(sh, "Failed to apply new slave ID");
} else {
shell_print(sh, "Modbus slave ID set to: %u (and saved)", new_id);
}
return 0;
}
static int cmd_valve_set_open_time(const struct shell *sh, size_t argc, char **argv)
{
if (argc != 2) {
shell_error(sh, "Usage: set_open_time <seconds>");
return -EINVAL;
}
uint16_t seconds = (uint16_t)strtoul(argv[1], NULL, 10);
valve_set_max_open_time(seconds);
shell_print(sh, "Max opening time set to: %u seconds (and saved)", seconds);
return 0;
}
static int cmd_valve_set_close_time(const struct shell *sh, size_t argc, char **argv)
{
if (argc != 2) {
shell_error(sh, "Usage: set_close_time <seconds>");
return -EINVAL;
}
uint16_t seconds = (uint16_t)strtoul(argv[1], NULL, 10);
valve_set_max_close_time(seconds);
shell_print(sh, "Max closing time set to: %u seconds (and saved)", seconds);
return 0;
}
static int cmd_config_show(const struct shell *sh, size_t argc, char **argv)
{
shell_print(sh, "Current Modbus Configuration:");
shell_print(sh, " Baudrate: %u", modbus_get_baudrate());
shell_print(sh, " Slave ID: %u", modbus_get_unit_id());
shell_print(sh, "Current Valve Configuration:");
shell_print(sh, " Max Opening Time: %u s", valve_get_max_open_time());
shell_print(sh, " Max Closing Time: %u s", valve_get_max_close_time());
return 0;
}
SHELL_STATIC_SUBCMD_SET_CREATE(sub_modbus_cmds,
SHELL_CMD(set_baud, NULL, "Set Modbus baudrate", cmd_modbus_set_baud),
SHELL_CMD(set_id, NULL, "Set Modbus slave ID", cmd_modbus_set_id),
SHELL_SUBCMD_SET_END
);
SHELL_STATIC_SUBCMD_SET_CREATE(sub_valve_cmds,
SHELL_CMD(set_open_time, NULL, "Set max valve opening time", cmd_valve_set_open_time),
SHELL_CMD(set_close_time, NULL, "Set max valve closing time", cmd_valve_set_close_time),
SHELL_SUBCMD_SET_END
);
SHELL_CMD_REGISTER(modbus, &sub_modbus_cmds, "Modbus configuration", NULL);
SHELL_CMD_REGISTER(valve, &sub_valve_cmds, "Valve configuration", NULL);
SHELL_CMD_REGISTER(show_config, NULL, "Show all configurations", cmd_config_show);

View File

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

View File

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

View File

@@ -0,0 +1,12 @@
#include <zephyr/shell/shell.h>
#include <zephyr/sys/reboot.h>
static int cmd_reset(const struct shell *sh, size_t argc, char **argv)
{
shell_print(sh, "Rebooting system...");
k_sleep(K_MSEC(100)); // Allow the shell to print the message
sys_reboot(SYS_REBOOT_WARM);
return 0;
}
SHELL_CMD_REGISTER(reset, NULL, "Reboot the system", cmd_reset);

View File

@@ -1,163 +0,0 @@
#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;
}

View File

@@ -1,27 +0,0 @@
#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 @@
zephyr_library_sources(valve.c)

View File

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

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

@@ -0,0 +1,216 @@
#include <zephyr/kernel.h>
#include <zephyr/settings/settings.h>
#include <zephyr/logging/log.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/adc.h>
#include <lib/valve.h>
LOG_MODULE_REGISTER(valve, LOG_LEVEL_DBG);
// ADC configuration for MULTISENSE (PA0)
static const struct device *adc_dev = DEVICE_DT_GET(DT_NODELABEL(adc1));
static const struct adc_channel_cfg adc_channel_cfg = {
.gain = ADC_GAIN_1,
.reference = ADC_REF_INTERNAL, // STM32 only supports internal ref (1.2V)
.acquisition_time = ADC_ACQ_TIME_DEFAULT, // Use default acquisition time
.channel_id = 1, // ADC1_IN1 (PA0)
.differential = 0,
};
static const struct valve_gpios valve_gpios = {
.in0 = GPIO_DT_SPEC_GET(DT_NODELABEL(vnd7050aj), in0_gpios),
.in1 = GPIO_DT_SPEC_GET(DT_NODELABEL(vnd7050aj), in1_gpios),
.rst = GPIO_DT_SPEC_GET(DT_NODELABEL(vnd7050aj), rst_gpios),
.sen = GPIO_DT_SPEC_GET(DT_NODELABEL(vnd7050aj), sen_gpios),
.s0 = GPIO_DT_SPEC_GET(DT_NODELABEL(vnd7050aj), s0_gpios),
.s1 = GPIO_DT_SPEC_GET(DT_NODELABEL(vnd7050aj), s1_gpios),
};
static enum valve_state current_state = VALVE_STATE_CLOSED;
static enum valve_movement current_movement = VALVE_MOVEMENT_IDLE;
static uint16_t max_opening_time_s = 60;
static uint16_t max_closing_time_s = 60;
static struct k_work_delayable valve_work;
static void valve_work_handler(struct k_work *work)
{
gpio_pin_set_dt(&valve_gpios.in0, 0);
gpio_pin_set_dt(&valve_gpios.in1, 0);
gpio_pin_set_dt(&valve_gpios.rst, 0);
if (current_movement == VALVE_MOVEMENT_OPENING) {
LOG_INF("Valve finished opening");
} else if (current_movement == VALVE_MOVEMENT_CLOSING) {
current_state = VALVE_STATE_CLOSED;
LOG_INF("Valve finished closing");
}
current_movement = VALVE_MOVEMENT_IDLE;
}
void valve_init(void)
{
k_work_init_delayable(&valve_work, valve_work_handler);
settings_load_one("valve/max_open_time", &max_opening_time_s, sizeof(max_opening_time_s));
settings_load_one("valve/max_close_time", &max_closing_time_s, sizeof(max_closing_time_s));
// Initialize ADC for MULTISENSE
if (!device_is_ready(adc_dev)) {
LOG_ERR("ADC device not ready");
return;
}
int ret = adc_channel_setup(adc_dev, &adc_channel_cfg);
if (ret < 0) {
LOG_ERR("Could not setup ADC channel (%d)", ret);
return;
}
gpio_pin_configure_dt(&valve_gpios.in0, GPIO_OUTPUT_INACTIVE); // IN0 control pin - output, deactivate
gpio_pin_configure_dt(&valve_gpios.in1, GPIO_OUTPUT_INACTIVE); // IN1 control pin - output, deactivate
gpio_pin_configure_dt(&valve_gpios.rst, GPIO_OUTPUT_INACTIVE); // Keep VND7050AJ in reset
gpio_pin_configure_dt(&valve_gpios.sen, GPIO_OUTPUT_INACTIVE); // Sensor enable pin - output, inactive
// S0 and S1 pins are used for selecting the valve state, they are initially inactive
// and will be set to active when the valve is opened or closed.
gpio_pin_configure_dt(&valve_gpios.s0, GPIO_OUTPUT_INACTIVE); // S0 select pin - output
gpio_pin_configure_dt(&valve_gpios.s1, GPIO_OUTPUT_INACTIVE); // S1 select pin - output
LOG_INF("Valve initialized: max_open=%us, max_close=%us", max_opening_time_s, max_closing_time_s);
}
void valve_open(void)
{
if (current_state == VALVE_STATE_CLOSED) {
gpio_pin_set_dt(&valve_gpios.rst, 1);
gpio_pin_set_dt(&valve_gpios.in1, 0);
gpio_pin_set_dt(&valve_gpios.in0, 1);
current_state = VALVE_STATE_OPEN;
current_movement = VALVE_MOVEMENT_OPENING;
k_work_schedule(&valve_work, K_MSEC(max_opening_time_s * 1000 * 0.9));
}
}
void valve_close(void)
{
if (current_state == VALVE_STATE_OPEN) {
gpio_pin_set_dt(&valve_gpios.rst, 1);
gpio_pin_set_dt(&valve_gpios.in0, 0);
gpio_pin_set_dt(&valve_gpios.in1, 1);
current_movement = VALVE_MOVEMENT_CLOSING;
k_work_schedule(&valve_work, K_MSEC(max_closing_time_s * 1000 * 0.9));
}
}
void valve_stop(void)
{
k_work_cancel_delayable(&valve_work);
current_movement = VALVE_MOVEMENT_IDLE;
}
enum valve_state valve_get_state(void) { return current_state; }
enum valve_movement valve_get_movement(void) { return current_movement; }
uint16_t valve_get_motor_current(void) { return (current_movement != VALVE_MOVEMENT_IDLE) ? 150 : 10; }
uint16_t valve_get_supply_voltage(void)
{
LOG_INF("=== ADC TEST MODE - PA0 LAB SUPPLY TEST ===");
LOG_INF("Connect lab supply to PA0. Recommended: 1.0V");
LOG_INF("Expected raw value for 1.0V: ~2007 (using 2.048V VREFBUF)");
LOG_INF("ADC range: 0-2.048V (STM32G431 VREFBUF internal reference)");
LOG_INF("");
// No VND7050AJ configuration - pure ADC test
// Just make sure pins are in safe state
gpio_pin_configure_dt(&valve_gpios.rst, GPIO_OUTPUT);
gpio_pin_configure_dt(&valve_gpios.sen, GPIO_OUTPUT);
gpio_pin_configure_dt(&valve_gpios.s0, GPIO_OUTPUT);
gpio_pin_configure_dt(&valve_gpios.s1, GPIO_OUTPUT);
gpio_pin_configure_dt(&valve_gpios.in0, GPIO_OUTPUT);
gpio_pin_configure_dt(&valve_gpios.in1, GPIO_OUTPUT);
// Set all VND7050AJ pins LOW for safety
gpio_pin_set_dt(&valve_gpios.rst, 0);
gpio_pin_set_dt(&valve_gpios.s0, 0);
gpio_pin_set_dt(&valve_gpios.s1, 0);
gpio_pin_set_dt(&valve_gpios.sen, 0);
gpio_pin_set_dt(&valve_gpios.in0, 0);
gpio_pin_set_dt(&valve_gpios.in1, 0);
LOG_INF("VND7050AJ disabled - all pins LOW");
LOG_INF("PA0 is now isolated for lab supply testing");
k_msleep(100);
// Setup simple ADC sequence
int16_t buf;
struct adc_sequence sequence = {
.buffer = &buf,
.buffer_size = sizeof(buf),
.channels = BIT(adc_channel_cfg.channel_id),
.resolution = 12,
};
LOG_INF("Starting continuous ADC readings every 500ms...");
// Continuous monitoring loop with improved stability
int reading_count = 0;
int32_t samples[10]; // Buffer for averaging
while (1) {
// Take multiple samples and average them for stability
int valid_samples = 0;
int32_t sum = 0;
for (int i = 0; i < 10; i++) {
k_msleep(50); // Longer delay between samples for stability
int adc_ret = adc_read(adc_dev, &sequence);
if (adc_ret == 0 && buf > 100) { // Filter out near-zero readings (floating input)
samples[i] = buf;
sum += buf;
valid_samples++;
} else {
LOG_WRN("Sample %d invalid: raw=%d, ret=%d", i, buf, adc_ret);
samples[i] = 0; // Mark as invalid
}
}
if (valid_samples > 0) {
// Calculate average
int32_t avg_raw = sum / valid_samples;
// Calculate voltage using the correct VREFBUF reference (2.048V)
int32_t pa0_mv = (avg_raw * 2048) / 4096; // Using 2.048V VREFBUF
// Calculate standard deviation to show stability
int32_t variance = 0;
for (int i = 0; i < valid_samples; i++) {
int32_t diff = samples[i] - avg_raw;
variance += diff * diff;
}
int32_t std_dev = (valid_samples > 1) ? variance / (valid_samples - 1) : 0;
// Find min/max for this sample set
int32_t min_raw = samples[0], max_raw = samples[0];
for (int i = 1; i < valid_samples; i++) {
if (samples[i] < min_raw) min_raw = samples[i];
if (samples[i] > max_raw) max_raw = samples[i];
}
LOG_INF("Reading %d: avg_raw=%d (%dmV) | range=%d-%d | std_dev=%d | samples=%d/10",
reading_count, (int)avg_raw, (int)pa0_mv,
(int)min_raw, (int)max_raw, (int)std_dev, valid_samples);
} else {
LOG_ERR("Reading %d: All ADC samples failed", reading_count);
}
reading_count++;
k_msleep(400); // Wait before next reading set
}
return 0; // Never reached
}
void valve_set_max_open_time(uint16_t seconds) { max_opening_time_s = seconds; settings_save_one("valve/max_open_time", &max_opening_time_s, sizeof(max_opening_time_s)); }
void valve_set_max_close_time(uint16_t seconds) { max_closing_time_s = seconds; settings_save_one("valve/max_close_time", &max_closing_time_s, sizeof(max_closing_time_s)); }
uint16_t valve_get_max_open_time(void) { return max_opening_time_s; }
uint16_t valve_get_max_close_time(void) { return max_closing_time_s; }

View File

@@ -1,383 +0,0 @@
#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,
},
};
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

@@ -1,27 +0,0 @@
#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 +0,0 @@
CONFIG_HAS_MODBUS_WATERLEVEL_SENSOR=y
CONFIG_HAS_VALVE=y
CONFIG_LOG=y
CONFIG_LOG_DEFAULT_LEVEL=3
# CONFIG_LOG_CAN_LEVEL=4
# CONFIG_LOG_WATERLEVELSENSOR_LEVEL=4
# CONFIG_LOG_VALVE_LEVEL=4
CONFIG_CBPRINTF_FP_SUPPORT=y
CONFIG_UART_CONSOLE=y # Console on USART1
# CAN loopback mode for testing
#CONFIG_LOOPBACK_MODE=y
CONFIG_SHELL=y
CONFIG_CAN_SHELL=y
CONFIG_GPIO_SHELL=y
CONFIG_REBOOT=y
CONFIG_ADC=y
CONFIG_ADC_STM32=y

View File

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

View File

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

View File

@@ -1,53 +0,0 @@
#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,86 +0,0 @@
#include <zephyr/kernel.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/logging/log.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <stdint.h>
LOG_MODULE_REGISTER(main2, LOG_LEVEL_DBG);
#define MOTOR_ADC_NODE DT_ALIAS(adc_motor_current)
#define VREF_ADC_NODE DT_ALIAS(adc_vref)
static const struct device * const adc_dev = DEVICE_DT_GET(DT_PARENT(MOTOR_ADC_NODE));
static const uint8_t motor_channel_id = DT_REG_ADDR(MOTOR_ADC_NODE);
static const uint8_t vref_channel_id = DT_REG_ADDR(VREF_ADC_NODE);
int main(void)
{
int err;
int16_t adc_raw_value;
LOG_INF("Starting ADC test with direct register setup...");
if (!device_is_ready(adc_dev)) {
LOG_ERR("ADC device is not ready");
return 0;
}
LOG_INF("Manually setting up ADC registers...");
uint32_t adc_base = DT_REG_ADDR(DT_NODELABEL(adc1));
volatile uint32_t *ADC_CR2 = (uint32_t *)(adc_base + 0x08);
volatile uint32_t *ADC_SMPR1 = (uint32_t *)(adc_base + 0x0C);
volatile uint32_t *ADC_SMPR2 = (uint32_t *)(adc_base + 0x10);
// Schritt 1: Internen VREFINT-Kanal einschalten
const uint32_t ADC_CR2_TSVREFE_BIT = 23;
*ADC_CR2 |= (1 << ADC_CR2_TSVREFE_BIT);
LOG_INF("VREFINT channel enabled via CR2 register.");
// Schritt 2: Lange Abtastzeiten für Stabilität setzen
*ADC_SMPR2 |= (0b111 << (3 * 9));
*ADC_SMPR1 |= (0b111 << (3 * (17 - 10)));
LOG_INF("Acquisition times set directly in SMPR registers.");
k_busy_wait(10);
while (1) {
int32_t motor_raw = 0;
int32_t vref_raw = 0;
struct adc_sequence sequence = {
.buffer = &adc_raw_value,
.buffer_size = sizeof(adc_raw_value),
.resolution = 12,
};
// Motor-Kanal lesen
sequence.channels = BIT(motor_channel_id);
if (adc_read(adc_dev, &sequence) == 0) {
motor_raw = adc_raw_value;
}
// VREF-Kanal lesen
sequence.channels = BIT(vref_channel_id);
if (adc_read(adc_dev, &sequence) == 0) {
vref_raw = adc_raw_value;
}
// VDD-Berechnung mit dem generischen, aber für Sie gut funktionierenden 1200mV-Wert
int32_t vdd_mv = (vref_raw > 0) ? (1200 * 4095 / vref_raw) : 0;
int32_t motor_mv = 0;
if (motor_raw > 0 && vdd_mv > 0) {
motor_mv = motor_raw;
err = adc_raw_to_millivolts(vdd_mv, ADC_GAIN_1, 12, &motor_mv);
}
LOG_INF("Motor Raw: %4d, Motor mV: %4d | VDD: %4d mV", motor_raw, motor_mv, vdd_mv);
k_sleep(K_MSEC(2000));
}
return 0;
}

View File

@@ -0,0 +1,103 @@
<img src="../../../docs/img/logo.png" alt="Logo" width="100"/>
# Modbus Tool für Bewässerungssystem-Knoten
Dieses Python-Skript bietet eine interaktive Kommandozeilen-Benutzeroberfläche (TUI) zur Steuerung und Überwachung eines Ventil-Knotens des Bewässerungssystems über Modbus RTU.
## Features
- **Interaktive Benutzeroberfläche:** Eine benutzerfreundliche, auf `curses` basierende Oberfläche, die eine einfache Bedienung ermöglicht.
- **Live-Statusanzeige:** Zeigt tabellarisch und in Echtzeit alle wichtigen Register des Slaves an:
- Ventilstatus (Zustand, Bewegung, Motorstrom)
- Zustand der digitalen Ein- und Ausgänge
- "Clear-on-Read" Taster-Events
- Systemkonfiguration (Öffnungs-/Schließzeiten, Watchdog-Timeout)
- Gerätestatus (Firmware-Version, Uptime)
- **Volle Kontrolle:** Ermöglicht das Senden von Befehlen zum Öffnen, Schließen und Stoppen des Ventils sowie zum Umschalten der digitalen Ausgänge.
- **Konfiguration zur Laufzeit:** Die maximalen Öffnungs-/Schließzeiten und der Watchdog-Timeout können direkt in der Oberfläche geändert werden.
- **Simulierter Firmware-Upload:** Implementiert den vollständigen, in der Dokumentation beschriebenen Firmware-Update-Prozess. Das Tool sendet eine `firmware.bin`-Datei in Chunks an den Slave und folgt dem CRC-Verifizierungs-Protokoll.
## Installation
### Voraussetzungen
- Python 3.x
- `pip` (Python Paket-Installer)
### Installation der Abhängigkeiten
Die benötigten Python-Pakete sind in der Datei `requirements.txt` aufgeführt. Sie können auf zwei Arten installiert werden: global oder in einer virtuellen Umgebung (empfohlen).
#### Option 1: Installation mit virtueller Umgebung (empfohlen)
Eine virtuelle Umgebung isoliert die Projekt-Abhängigkeiten von Ihrem globalen Python-System, was Konflikte vermeidet.
1. **Virtuelle Umgebung erstellen:**
Führen Sie im Verzeichnis `software/tools/modbus_tool` den folgenden Befehl aus, um eine Umgebung im Ordner `.venv` zu erstellen:
```bash
python3 -m venv .venv
```
2. **Umgebung aktivieren:**
- **Linux / macOS:**
```bash
source .venv/bin/activate
```
Ihre Shell-Anzeige sollte sich ändern und `(.venv)` am Anfang zeigen.
- **Windows (cmd.exe):**
```bash
.venv\Scripts\activate.bat
```
- **Windows (PowerShell):**
```powershell
.venv\Scripts\Activate.ps1
```
3. **Abhängigkeiten installieren:**
Wenn die Umgebung aktiv ist, installieren Sie die Pakete:
```bash
pip install -r requirements.txt
```
4. **Umgebung deaktivieren:**
Wenn Sie fertig sind, können Sie die Umgebung mit folgendem Befehl wieder verlassen:
```bash
deactivate
```
#### Option 2: Globale Installation (nicht empfohlen)
Wenn Sie keine virtuelle Umgebung verwenden möchten, können Sie die Pakete direkt in Ihrem globalen Python-System installieren.
```bash
pip install -r requirements.txt
```
## Verwendung
Stellen Sie sicher, dass das Skript ausführbar ist:
```bash
chmod +x modbus_tool.py
```
Starten Sie das Tool, indem Sie den seriellen Port als Argument übergeben:
```bash
./modbus_tool.py /dev/ttyACM0
```
Ersetzen Sie `/dev/ttyACM0` durch den korrekten Port Ihres Geräts.
### Kommandozeilen-Argumente
- `port`: (Erforderlich) Der serielle Port (z.B. `/dev/ttyACM0` oder `COM3`).
- `--baud`: Die Baudrate (Standard: `19200`).
- `--slave-id`: Die Modbus Slave ID des Geräts (Standard: `1`).
- `--interval`: Das Abfrageintervall für den Status in Sekunden (Standard: `1.0`).
### Bedienung der Oberfläche
- **Navigation:** Verwenden Sie die **Pfeiltasten (↑/↓)**, um zwischen den Menüpunkten zu navigieren.
- **Auswählen:** Drücken Sie **Enter**, um den ausgewählten Befehl auszuführen.
- **Werte eingeben:** Bei Aktionen wie "Set Watchdog" werden Sie zur Eingabe eines Wertes aufgefordert. Geben Sie den Wert ein und bestätigen Sie mit **Enter**.
- **Firmware Update:** Diese Funktion startet den Upload der Datei `firmware.bin` aus dem aktuellen Verzeichnis. Während des Updates wird eine Fortschrittsanzeige dargestellt.
- **Beenden:** Wählen Sie den Menüpunkt **"Exit"** und drücken Sie **Enter**.

Binary file not shown.

View File

@@ -0,0 +1,334 @@
#!/usr/bin/env python3
import argparse
import threading
import time
import sys
import curses
import os
from pymodbus.client import ModbusSerialClient
from pymodbus.exceptions import ModbusException
# --- Register Definitions ---
# (omitted for brevity, no changes here)
REG_INPUT_VALVE_STATE_MOVEMENT = 0x0000
REG_INPUT_MOTOR_CURRENT_MA = 0x0001
REG_INPUT_DIGITAL_INPUTS_STATE = 0x0020
REG_INPUT_BUTTON_EVENTS = 0x0021
REG_INPUT_FIRMWARE_VERSION_MAJOR_MINOR = 0x00F0
REG_INPUT_FIRMWARE_VERSION_PATCH = 0x00F1
REG_INPUT_DEVICE_STATUS = 0x00F2
REG_INPUT_UPTIME_SECONDS_LOW = 0x00F3
REG_INPUT_UPTIME_SECONDS_HIGH = 0x00F4
REG_INPUT_SUPPLY_VOLTAGE_MV = 0x00F5
REG_INPUT_FWU_LAST_CHUNK_CRC = 0x0100
REG_HOLDING_VALVE_COMMAND = 0x0000
REG_HOLDING_MAX_OPENING_TIME_S = 0x0001
REG_HOLDING_MAX_CLOSING_TIME_S = 0x0002
REG_HOLDING_DIGITAL_OUTPUTS_STATE = 0x0010
REG_HOLDING_WATCHDOG_TIMEOUT_S = 0x00F0
REG_HOLDING_DEVICE_RESET = 0x00F1
REG_HOLDING_FWU_COMMAND = 0x0100
REG_HOLDING_FWU_CHUNK_OFFSET_LOW = 0x0101
REG_HOLDING_FWU_CHUNK_OFFSET_HIGH = 0x0102
REG_HOLDING_FWU_CHUNK_SIZE = 0x0103
REG_HOLDING_FWU_DATA_BUFFER = 0x0180
# --- Global State ---
stop_event = threading.Event()
client = None
status_data = {}
status_lock = threading.Lock()
update_status = {"running": False, "message": "", "progress": 0.0}
update_lock = threading.Lock()
def format_uptime(seconds):
if not isinstance(seconds, (int, float)) or seconds < 0: return "N/A"
if seconds == 0: return "0s"
days, rem = divmod(seconds, 86400); hours, rem = divmod(rem, 3600); minutes, secs = divmod(rem, 60)
parts = []
if days > 0: parts.append(f"{int(days)}d")
if hours > 0: parts.append(f"{int(hours)}h")
if minutes > 0: parts.append(f"{int(minutes)}m")
if secs > 0 or not parts: parts.append(f"{int(secs)}s")
return " ".join(parts)
def poll_status(slave_id, interval):
global status_data
reconnect_attempts = 0
max_reconnect_attempts = 5
reconnect_delay = 1 # seconds
while not stop_event.is_set():
if update_status["running"]:
time.sleep(interval)
continue
new_data = {}
try:
if not client.is_socket_open():
reconnect_attempts += 1
if reconnect_attempts >= max_reconnect_attempts:
new_data["error"] = f"Failed to reconnect after {max_reconnect_attempts} attempts. Exiting."
stop_event.set()
break
# Attempt to connect
if client.connect():
reconnect_attempts = 0
new_data["error"] = None # Clear error on successful reconnect
else:
new_data["error"] = f"Connection lost. Attempting to reconnect ({reconnect_attempts}/{max_reconnect_attempts})..."
time.sleep(reconnect_delay)
continue
# If connected, try to read data
ir_valve = client.read_input_registers(REG_INPUT_VALVE_STATE_MOVEMENT, count=2, slave=slave_id)
ir_dig = client.read_input_registers(REG_INPUT_DIGITAL_INPUTS_STATE, count=2, slave=slave_id)
ir_sys = client.read_input_registers(REG_INPUT_FIRMWARE_VERSION_MAJOR_MINOR, count=6, slave=slave_id)
hr_valve = client.read_holding_registers(REG_HOLDING_MAX_OPENING_TIME_S, count=2, slave=slave_id)
hr_dig = client.read_holding_registers(REG_HOLDING_DIGITAL_OUTPUTS_STATE, count=1, slave=slave_id)
hr_sys = client.read_holding_registers(REG_HOLDING_WATCHDOG_TIMEOUT_S, count=1, slave=slave_id)
for res in [ir_valve, ir_dig, ir_sys, hr_valve, hr_dig, hr_sys]:
if res.isError():
raise ModbusException(str(res))
valve_state_raw = ir_valve.registers[0]
movement_map = {0: "Idle", 1: "Opening", 2: "Closing", 3: "Error"}
state_map = {0: "Closed", 1: "Open"}
new_data["movement"] = movement_map.get(valve_state_raw >> 8, 'Unknown')
new_data["state"] = state_map.get(valve_state_raw & 0xFF, 'Unknown')
new_data["motor_current"] = f"{ir_valve.registers[1]} mA"
new_data["open_time"] = f"{hr_valve.registers[0]}s"
new_data["close_time"] = f"{hr_valve.registers[1]}s"
new_data["digital_inputs"] = f"0x{ir_dig.registers[0]:04X}"
new_data["button_events"] = f"0x{ir_dig.registers[1]:04X}"
new_data["digital_outputs"] = f"0x{hr_dig.registers[0]:04X}"
fw_major = ir_sys.registers[0] >> 8
fw_minor = ir_sys.registers[0] & 0xFF
fw_patch = ir_sys.registers[1]
uptime_seconds = (ir_sys.registers[4] << 16) | ir_sys.registers[3]
supply_voltage_mv = ir_sys.registers[5]
new_data["firmware"] = f"v{fw_major}.{fw_minor}.{fw_patch}"
new_data["device_status"] = "OK" if ir_sys.registers[2] == 0 else "ERROR"
new_data["uptime"] = format_uptime(uptime_seconds)
new_data["supply_voltage"] = f"{supply_voltage_mv / 1000.0:.2f} V"
new_data["watchdog"] = f"{hr_sys.registers[0]}s"
new_data["error"] = None # Clear any previous error on successful read
reconnect_attempts = 0 # Reset attempts on successful communication
except Exception as e:
new_data["error"] = f"Communication Error: {e}. Closing connection."
client.close() # Close connection to force reconnect attempt in next loop
finally:
with status_lock:
status_data = new_data
time.sleep(interval)
def firmware_update_thread(slave_id, filepath):
global update_status
with update_lock:
update_status = {"running": True, "message": "Starting update...", "progress": 0.0}
try:
with open(filepath, 'rb') as f: firmware = f.read()
file_size = len(firmware)
chunk_size = 248
offset = 0
while offset < file_size:
chunk = firmware[offset:offset + chunk_size]
with update_lock:
update_status["message"] = f"Sending chunk {offset//chunk_size + 1}/{(file_size + chunk_size - 1)//chunk_size}..."
update_status["progress"] = offset / file_size
client.write_register(REG_HOLDING_FWU_CHUNK_OFFSET_LOW, offset & 0xFFFF, slave=slave_id)
client.write_register(REG_HOLDING_FWU_CHUNK_OFFSET_HIGH, (offset >> 16) & 0xFFFF, slave=slave_id)
client.write_register(REG_HOLDING_FWU_CHUNK_SIZE, len(chunk), slave=slave_id)
padded_chunk = chunk + (b'\x00' if len(chunk) % 2 != 0 else b'')
registers = [int.from_bytes(padded_chunk[i:i+2], 'big') for i in range(0, len(padded_chunk), 2)]
burst_size_regs = 16
for i in range(0, len(registers), burst_size_regs):
reg_burst = registers[i:i + burst_size_regs]
start_addr = REG_HOLDING_FWU_DATA_BUFFER + i
client.write_registers(start_addr, reg_burst, slave=slave_id)
time.sleep(0.02)
time.sleep(0.1)
client.read_input_registers(REG_INPUT_FWU_LAST_CHUNK_CRC, count=1, slave=slave_id)
client.write_register(REG_HOLDING_FWU_COMMAND, 1, slave=slave_id)
offset += len(chunk)
with update_lock:
update_status["progress"] = 1.0
update_status["message"] = "Finalizing update..."
client.write_register(REG_HOLDING_FWU_COMMAND, 2, slave=slave_id)
time.sleep(1)
with update_lock: update_status["message"] = "Update complete! Slave is rebooting."
time.sleep(2)
except Exception as e:
with update_lock: update_status["message"] = f"Error: {e}"
time.sleep(3)
finally:
with update_lock: update_status["running"] = False
def draw_button(stdscr, y, x, text, selected=False):
"""Draws a button, handling selection highlight."""
color = curses.color_pair(2) if selected else curses.color_pair(1)
button_width = len(text) + 2
stdscr.addstr(y, x, " " * button_width, color)
stdscr.addstr(y, x + 1, text, color)
def file_browser(stdscr):
"""A simple curses file browser."""
curses.curs_set(1)
path = os.getcwd()
selected_index = 0
while True:
stdscr.clear()
h, w = stdscr.getmaxyx()
stdscr.addstr(0, 0, f"Select Firmware File: {path}".ljust(w-1), curses.color_pair(2))
try:
items = sorted(os.listdir(path))
except OSError as e:
items = [f".. (Error: {e})"]
items.insert(0, "..")
for i, item_name in enumerate(items):
if i >= h - 2: break
display_name = item_name
if os.path.isdir(os.path.join(path, item_name)):
display_name += "/"
if i == selected_index:
stdscr.addstr(i + 1, 0, display_name, curses.color_pair(2))
else:
stdscr.addstr(i + 1, 0, display_name)
key = stdscr.getch()
if key == curses.KEY_UP:
selected_index = max(0, selected_index - 1)
elif key == curses.KEY_DOWN:
selected_index = min(len(items) - 1, selected_index + 1)
elif key == curses.KEY_ENTER or key in [10, 13]:
selected_item_path = os.path.join(path, items[selected_index])
if os.path.isdir(selected_item_path):
path = os.path.abspath(selected_item_path)
selected_index = 0
else:
return selected_item_path
elif key == 27: # ESC key
return None
def main_menu(stdscr, slave_id):
global status_data, update_status
curses.curs_set(0); stdscr.nodelay(1); stdscr.timeout(100)
curses.start_color(); curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE); curses.init_pair(2, curses.COLOR_BLUE, curses.COLOR_WHITE); curses.init_pair(3, curses.COLOR_RED, curses.COLOR_BLUE)
stdscr.bkgd(' ', curses.color_pair(1))
menu = ["Open Valve", "Close Valve", "Stop Valve", "Toggle Output 1", "Toggle Output 2", "Set Watchdog", "Reset Node", "Firmware Update", "Exit"]
current_row_idx = 0
message, message_time = "", 0
input_mode, input_prompt, input_str, input_target_reg = False, "", "", 0
while not stop_event.is_set():
h, w = stdscr.getmaxyx()
key = stdscr.getch()
with update_lock: is_updating = update_status["running"]
if is_updating:
pass
elif input_mode:
if key in [10, 13]:
try:
value = int(input_str)
client.write_register(input_target_reg, value, slave=slave_id)
message = f"-> Set register 0x{input_target_reg:04X} to {value}"
except Exception as e: message = f"-> Error: {e}"
message_time, input_mode, input_str = time.time(), False, ""
elif key == curses.KEY_BACKSPACE or key == 127: input_str = input_str[:-1]
elif key != -1 and chr(key).isprintable(): input_str += chr(key)
else:
if key == curses.KEY_UP: current_row_idx = (current_row_idx - 1) % len(menu)
elif key == curses.KEY_DOWN: current_row_idx = (current_row_idx + 1) % len(menu)
elif key == curses.KEY_ENTER or key in [10, 13]:
selected_option = menu[current_row_idx]
message_time = time.time()
if selected_option == "Exit": stop_event.set(); continue
elif selected_option == "Open Valve": client.write_register(REG_HOLDING_VALVE_COMMAND, 1, slave=slave_id); message = "-> Sent OPEN command"
elif selected_option == "Close Valve": client.write_register(REG_HOLDING_VALVE_COMMAND, 2, slave=slave_id); message = "-> Sent CLOSE command"
elif selected_option == "Stop Valve": client.write_register(REG_HOLDING_VALVE_COMMAND, 0, slave=slave_id); message = "-> Sent STOP command"
elif "Toggle Output" in selected_option:
bit = 0 if "1" in selected_option else 1
try:
current_val = client.read_holding_registers(REG_HOLDING_DIGITAL_OUTPUTS_STATE, count=1, slave=slave_id).registers[0]
client.write_register(REG_HOLDING_DIGITAL_OUTPUTS_STATE, current_val ^ (1 << bit), slave=slave_id)
message = f"-> Toggled Output {bit+1}"
except Exception as e: message = f"-> Error: {e}"
elif selected_option == "Set Watchdog":
input_mode, input_prompt, input_target_reg = True, "Enter Watchdog Timeout (s): ", REG_HOLDING_WATCHDOG_TIMEOUT_S
elif selected_option == "Reset Node":
try:
client.write_register(REG_HOLDING_DEVICE_RESET, 1, slave=slave_id)
message = "-> Sent RESET command. Node should reboot."
except Exception as e:
message = f"-> Error sending reset: {e}"
elif selected_option == "Firmware Update":
filepath = file_browser(stdscr)
if filepath:
threading.Thread(target=firmware_update_thread, args=(slave_id, filepath), daemon=True).start()
else:
message = "-> Firmware update cancelled."
stdscr.clear()
if is_updating:
with update_lock: prog, msg = update_status["progress"], update_status["message"]
stdscr.addstr(h // 2 - 1, w // 2 - 25, "FIRMWARE UPDATE IN PROGRESS", curses.A_BOLD | curses.color_pair(2))
stdscr.addstr(h // 2, w // 2 - 25, f"[{'#' * int(prog * 50):<50}] {prog:.0%}")
stdscr.addstr(h // 2 + 1, w // 2 - 25, msg.ljust(50))
else:
with status_lock: current_data = status_data.copy()
bold, normal = curses.color_pair(1) | curses.A_BOLD, curses.color_pair(1)
if current_data.get("error"): stdscr.addstr(0, 0, current_data["error"], curses.color_pair(3) | curses.A_BOLD)
else:
col1, col2, col3, col4 = 2, 30, 58, 88
stdscr.addstr(1, col1, "State:", bold); stdscr.addstr(1, col1 + 18, str(current_data.get('state', 'N/A')), normal)
stdscr.addstr(2, col1, "Movement:", bold); stdscr.addstr(2, col1 + 18, str(current_data.get('movement', 'N/A')), normal)
stdscr.addstr(3, col1, "Motor Current:", bold); stdscr.addstr(3, col1 + 18, str(current_data.get('motor_current', 'N/A')), normal)
stdscr.addstr(1, col2, "Digital Inputs:", bold); stdscr.addstr(1, col2 + 18, str(current_data.get('digital_inputs', 'N/A')), normal)
stdscr.addstr(2, col2, "Digital Outputs:", bold); stdscr.addstr(2, col2 + 18, str(current_data.get('digital_outputs', 'N/A')), normal)
stdscr.addstr(3, col2, "Button Events:", bold); stdscr.addstr(3, col2 + 18, str(current_data.get('button_events', 'N/A')), normal)
stdscr.addstr(1, col3, "Max Open Time:", bold); stdscr.addstr(1, col3 + 16, str(current_data.get('open_time', 'N/A')), normal)
stdscr.addstr(2, col3, "Max Close Time:", bold); stdscr.addstr(2, col3 + 16, str(current_data.get('close_time', 'N/A')), normal)
stdscr.addstr(3, col3, "Watchdog:", bold); stdscr.addstr(3, col3 + 16, str(current_data.get('watchdog', 'N/A')), normal)
stdscr.addstr(1, col4, "Firmware:", bold); stdscr.addstr(1, col4 + 14, str(current_data.get('firmware', 'N/A')), normal)
stdscr.addstr(2, col4, "Uptime:", bold); stdscr.addstr(2, col4 + 14, str(current_data.get('uptime', 'N/A')), normal)
stdscr.addstr(3, col4, "Dev. Status:", bold); stdscr.addstr(3, col4 + 14, str(current_data.get('device_status', 'N/A')), normal)
stdscr.addstr(4, col4, "Supply V:", bold); stdscr.addstr(4, col4 + 14, str(current_data.get('supply_voltage', 'N/A')), normal)
stdscr.addstr(5, 0, "" * (w - 1), normal)
for idx, row in enumerate(menu):
draw_button(stdscr, h // 2 - len(menu) + (idx * 2), w // 2 - len(row) // 2, row, idx == current_row_idx)
if time.time() - message_time < 2.0: stdscr.addstr(h - 2, 0, message.ljust(w - 1), curses.color_pair(1) | curses.A_BOLD)
if input_mode:
curses.curs_set(1); stdscr.addstr(h - 2, 0, (input_prompt + input_str).ljust(w-1), curses.color_pair(2)); stdscr.move(h - 2, len(input_prompt) + len(input_str))
else: curses.curs_set(0)
stdscr.refresh()
def main():
global client
parser = argparse.ArgumentParser(description="Modbus tool for irrigation system nodes.")
parser.add_argument("port", help="Serial port"); parser.add_argument("--baud", type=int, default=19200); parser.add_argument("--slave-id", type=int, default=1); parser.add_argument("--interval", type=float, default=1.0)
args = parser.parse_args()
client = ModbusSerialClient(port=args.port, baudrate=args.baud, stopbits=1, bytesize=8, parity="N", timeout=1)
if not client.connect(): print(f"Error: Failed to connect to serial port {args.port}"); sys.exit(1)
print("Successfully connected. Starting UI..."); time.sleep(0.5)
threading.Thread(target=poll_status, args=(args.slave_id, args.interval), daemon=True).start()
try: curses.wrapper(main_menu, args.slave_id)
finally:
stop_event.set()
print("\nExiting...")
if client.is_socket_open(): client.close()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,2 @@
pymodbus>=3.6.0
pyserial>=3.5

View File

@@ -1,132 +0,0 @@
{
"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"
]
}
}