Compare commits
42 Commits
v0.0.1
...
54e991294b
| Author | SHA1 | Date | |
|---|---|---|---|
| 54e991294b | |||
| 0227e54198 | |||
| c3c23efc95 | |||
| 4466b677a6 | |||
| dcbd02ad7a | |||
| 8467b3e347 | |||
| fc0add8583 | |||
| 66cdc3ae27 | |||
| 32bb77926f | |||
| 4df0181d7f | |||
| d6fb501594 | |||
|
|
76d0d0647c | ||
| 3de42a46c2 | |||
| ddaaa8988d | |||
| b937c52bcc | |||
| 3c2235733b | |||
| a3e8d5c168 | |||
| 5fd904de9e | |||
| 92bb171e85 | |||
| bd8a7a766c | |||
| 8f89713866 | |||
| bf29061db6 | |||
| c1622bb01c | |||
| 222ffea568 | |||
| a9a0626913 | |||
| b11f844415 | |||
| 2e8a86bc54 | |||
| 224adccf6b | |||
| 9b7159d5a4 | |||
| bc327acc41 | |||
| c9b0f38576 | |||
| edf0fb2563 | |||
| 537d76ef5d | |||
| 45d011952f | |||
| bb25134b6c | |||
| 9f96384aa5 | |||
| b543579393 | |||
| 69cf7e9511 | |||
| 8df7aef51b | |||
| f6ee0a5122 | |||
| 6c1ff0c4df | |||
| 3f0d5a76c6 |
7
.gemini_commit_message.txt
Normal file
7
.gemini_commit_message.txt
Normal 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
5
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"files.associations": {
|
||||||
|
"fwu.h": "c"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,7 +39,7 @@ Die Slave-Nodes sind die Arbeitseinheiten im Feld. Um bei der Fertigung kleiner
|
|||||||
|
|
||||||
* **Mikrocontroller:** Ein `STM32G431PB`. Dieser ist zwar leistungsstark, bietet aber alle nötigen Peripherien (mehrere UARTs, ADCs, CAN) und ermöglicht ein einheitliches Hardware- und Software-Design.
|
* **Mikrocontroller:** Ein `STM32G431PB`. Dieser ist zwar leistungsstark, bietet aber alle nötigen Peripherien (mehrere UARTs, ADCs, CAN) und ermöglicht ein einheitliches Hardware- und Software-Design.
|
||||||
* **Peripherie pro Node:**
|
* **Peripherie pro Node:**
|
||||||
* **Zwei High-Side Ausgänge (+12V):** Realisiert über einen `VND7050AJ`. Perfekt zur Ansteuerung der 12V-Motorventile (`Öffnen`/`Schliessen`). Die `Sense`-Leitung des Treibers wird über einen AD-Wandler ausgelesen, um durch Messung des Motorstroms eine Endlagen-Erkennung ohne physische Endschalter zu realisieren (Motorstrom im Stillstand ≈ 0).
|
* **Zwei High-Side Ausgänge (+12V):** Realisiert über einen `VND7050AJ`. Perfekt zur Ansteuerung der 12V-Motorventile (`Öffnen`/`Schliessen`). Die `Sense`-Leitung des Treibers wird über einen AD-Wandler ausgelesen, um durch Messung des Motorstroms eine Endlagen-Erkennung ohne physische Endschalter zu realisieren (Motorstrom im Stillstand ≈ 0). Zusätzlich können die Temperatur und die Versorgungsspannung des Treibers ausgelesen werden.
|
||||||
* **Zwei Low-Side Ausgänge (0V):** Über N-Kanal-MOSFETs geschaltete Ausgänge. Nutzbar zur Ansteuerung von 12V-LEDs in Tastern oder zum Schalten des Halbleiter-Relais für die Pumpe.
|
* **Zwei Low-Side Ausgänge (0V):** Über N-Kanal-MOSFETs geschaltete Ausgänge. Nutzbar zur Ansteuerung von 12V-LEDs in Tastern oder zum Schalten des Halbleiter-Relais für die Pumpe.
|
||||||
* **Zwei digitale Eingänge:** Direkte, geschützte Eingänge am Controller zum Anschluss von Tastern oder den kapazitiven NPN-Sensoren.
|
* **Zwei digitale Eingänge:** Direkte, geschützte Eingänge am Controller zum Anschluss von Tastern oder den kapazitiven NPN-Sensoren.
|
||||||
|
|
||||||
|
|||||||
@@ -29,32 +29,38 @@ Alle Register sind in einer einzigen, durchgehenden Liste pro Register-Typ (`Inp
|
|||||||
|
|
||||||
| Adresse (hex) | Name | Zugehörigkeit | Beschreibung |
|
| Adresse (hex) | Name | Zugehörigkeit | Beschreibung |
|
||||||
| :------------ | :----------------------------- | :---------------- | :---------------------------------------------------------------------------------------------------------------------------------------- |
|
| :------------ | :----------------------------- | :---------------- | :---------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| **0x0000** | `VENTIL_ZUSTAND_BEWEGUNG` | Ventil | Kombiniertes Status-Register. **High-Byte**: Bewegung (`0`=Idle, `1`=Öffnet, `2`=Schliesst, `3`=Fehler). **Low-Byte**: Zustand (`0`=Geschlossen, `1`=Geöffnet). |
|
| **0x0000** | `VALVE_STATE_MOVEMENT` | Ventil | Kombiniertes Status-Register. **High-Byte**: Bewegung (`0`=Idle, `1`=Öffnet, `2`=Schliesst, `3`=Fehler). **Low-Byte**: Zustand (`0`=Geschlossen, `1`=Geöffnet). |
|
||||||
| **0x0001** | `MOTORSTROM_MA` | Ventil | Aktueller Motorstrom in Milliampere (mA). |
|
| **0x0001** | `REG_INPUT_MOTOR_OPEN_CURRENT_MA` | Ventil | Motorstrom beim Öffnen in Milliampere (mA). |
|
||||||
| **0x0020** | `DIGITAL_EINGAENGE_ZUSTAND` | Eingänge | Bitmaske der digitalen Eingänge. Bit 0: Eingang 1, Bit 1: Eingang 2. `1`=Aktiv. |
|
| **0x0002** | `REG_INPUT_MOTOR_CLOSE_CURRENT_MA` | Ventil | Motorstrom beim Schließen in Milliampere (mA). |
|
||||||
| **0x0021** | `TASTER_EVENTS` | Eingänge | Event-Flags für Taster (Clear-on-Read). Bit 0: Taster 1 gedrückt. Bit 1: Taster 2 gedrückt. |
|
| **0x0020** | `REG_INPUT_DIGITAL_INPUTS_STATE` | Eingänge | Bitmaske der digitalen Eingänge. Bit 0: Eingang 1, Bit 1: Eingang 2. `1`=Aktiv. |
|
||||||
| **0x00F0** | `FIRMWARE_VERSION_MAJOR_MINOR` | System | z.B. `0x0102` für v1.2. |
|
| **0x0021** | `REG_INPUT_BUTTON_EVENTS` | Eingänge | Event-Flags für Taster (Clear-on-Read). Bit 0: Taster 1 gedrückt. Bit 1: Taster 2 gedrückt. |
|
||||||
| **0x00F1** | `FIRMWARE_VERSION_PATCH` | System | z.B. `3` für v1.2.3. |
|
| **0x00F0** | `REG_INPUT_FIRMWARE_VERSION_MAJOR_MINOR` | System | z.B. `0x0102` für v1.2. |
|
||||||
| **0x00F2** | `DEVICE_STATUS` | System | `0`=OK, `1`=Allgemeiner Fehler. |
|
| **0x00F1** | `REG_INPUT_FIRMWARE_VERSION_PATCH` | System | z.B. `3` für v1.2.3. |
|
||||||
| **0x00F3** | `UPTIME_SECONDS_LOW` | System | Untere 16 Bit der Uptime in Sekunden. |
|
| **0x00F2** | `REG_INPUT_DEVICE_STATUS` | System | `0`=OK, `1`=Allgemeiner Fehler. |
|
||||||
| **0x00F4** | `UPTIME_SECONDS_HIGH` | System | Obere 16 Bit der Uptime. |
|
| **0x00F3** | `REG_INPUT_UPTIME_SECONDS_LOW` | System | Untere 16 Bit der Uptime in Sekunden. |
|
||||||
| **0x0100** | `FWU_LAST_CHUNK_CRC` | Firmware-Update | Enthält den CRC16 des zuletzt im Puffer empfangenen Daten-Chunks. |
|
| **0x00F4** | `REG_INPUT_UPTIME_SECONDS_HIGH` | System | Obere 16 Bit der Uptime. |
|
||||||
|
| **0x00F5** | `REG_INPUT_SUPPLY_VOLTAGE_MV` | System | Aktuelle Versorgungsspannung in Millivolt (mV). |
|
||||||
|
| **0x0100** | `REG_INPUT_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)
|
||||||
|
|
||||||
| Adresse (hex) | Name | Zugehörigkeit | Beschreibung |
|
| Adresse (hex) | Name | Zugehörigkeit | Beschreibung |
|
||||||
| :------------ | :---------------------------- | :---------------- | :---------------------------------------------------------------------------------------------------------------------------------------- |
|
| :------------ | :---------------------------- | :---------------- | :---------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| **0x0000** | `VENTIL_BEFEHL` | Ventil | `1`=Öffnen, `2`=Schliessen, `0`=Bewegung stoppen. |
|
| **0x0000** | `REG_HOLDING_VALVE_COMMAND` | Ventil | `1`=Öffnen, `2`=Schliessen, `0`=Bewegung stoppen. |
|
||||||
| **0x0001** | `MAX_OEFFNUNGSZEIT_S` | Ventil | Sicherheits-Timeout in Sekunden für den Öffnen-Vorgang. |
|
| **0x0001** | `REG_HOLDING_MAX_OPENING_TIME_S` | Ventil | Sicherheits-Timeout in Sekunden für den Öffnen-Vorgang. |
|
||||||
| **0x0002** | `MAX_SCHLIESSZEIT_S` | Ventil | Sicherheits-Timeout in Sekunden für den Schliessen-Vorgang. |
|
| **0x0002** | `REG_HOLDING_MAX_CLOSING_TIME_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. |
|
| **0x0003** | `REG_HOLDING_END_CURRENT_THRESHOLD_OPEN_MA` | Ventil | Minimaler Stromschwellenwert in mA zur Endlagenerkennung beim Öffnen. |
|
||||||
| **0x00F0** | `WATCHDOG_TIMEOUT_S` | System | Timeout des Fail-Safe-Watchdogs in Sekunden. `0`=Deaktiviert. |
|
| **0x0004** | `REG_HOLDING_END_CURRENT_THRESHOLD_CLOSE_MA` | Ventil | Minimaler Stromschwellenwert in mA zur Endlagenerkennung beim Schliessen. |
|
||||||
| **0x00F1** | `DEVICE_RESET` | System | Schreibt `1` um das Gerät neu zu starten. |
|
| **0x0005** | `REG_HOLDING_OBSTACLE_THRESHOLD_OPEN_MA` | Ventil | Stromschwellenwert in mA für die Hinderniserkennung beim Öffnen. |
|
||||||
| **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. |
|
| **0x0006** | `REG_HOLDING_OBSTACLE_THRESHOLD_CLOSE_MA` | Ventil | Stromschwellenwert in mA für die Hinderniserkennung beim Schließen. |
|
||||||
| **0x0101** | `FWU_CHUNK_OFFSET_LOW` | Firmware-Update | Untere 16 Bit des 32-Bit-Offsets, an den der nächste Chunk geschrieben werden soll. |
|
| **0x0010** | `REG_HOLDING_DIGITAL_OUTPUTS_STATE` | Ausgänge | Bitmaske zum Lesen und Schreiben der Ausgänge. Bit 0: Ausgang 1, Bit 1: Ausgang 2. `1`=AN, `0`=AUS. |
|
||||||
| **0x0102** | `FWU_CHUNK_OFFSET_HIGH` | Firmware-Update | Obere 16 Bit des 32-Bit-Offsets. |
|
| **0x00F0** | `REG_HOLDING_WATCHDOG_TIMEOUT_S` | System | Timeout des Fail-Safe-Watchdogs in Sekunden. `0`=Deaktiviert. |
|
||||||
| **0x0103** | `FWU_CHUNK_SIZE` | Firmware-Update | Grösse des nächsten Chunks in Bytes (max. 256). |
|
| **0x00F1** | `REG_HOLDING_DEVICE_RESET` | System | Schreibt `1` um das Gerät neu zu starten. |
|
||||||
| **0x0180** | `FWU_DATA_BUFFER` | Firmware-Update | **Startadresse** eines 128x16-bit Puffers (256 Bytes). Entspricht den Registern `40384` bis `40511`. |
|
| **0x0100** | `REG_HOLDING_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** | `REG_HOLDING_FWU_CHUNK_OFFSET_LOW` | Firmware-Update | Untere 16 Bit des 32-Bit-Offsets, an den der nächste Chunk geschrieben werden soll. |
|
||||||
|
| **0x0102** | `REG_HOLDING_FWU_CHUNK_OFFSET_HIGH` | Firmware-Update | Obere 16 Bit des 32-Bit-Offsets. |
|
||||||
|
| **0x0103** | `REG_HOLDING_FWU_CHUNK_SIZE` | Firmware-Update | Grösse des nächsten Chunks in Bytes (max. 256). |
|
||||||
|
| **0x0180** | `REG_HOLDING_FWU_DATA_BUFFER` | Firmware-Update | **Startadresse** eines 128x16-bit Puffers (256 Bytes). Entspricht den Registern `40384` bis `40511`. |
|
||||||
|
|
||||||
## 4. Detaillierter Firmware-Update-Prozess
|
## 4. Detaillierter Firmware-Update-Prozess
|
||||||
|
|
||||||
@@ -79,10 +85,10 @@ Diese Register gehören zum externen Füllstandsensor und können auf dem Bus eb
|
|||||||
|
|
||||||
| Adresse (hex) | Name | R/W | Beschreibung |
|
| Adresse (hex) | Name | R/W | Beschreibung |
|
||||||
| :------------ | :------------------------- | :-- | :---------------------------------------------------------------------------------------------------------------------------------------- |
|
| :------------ | :------------------------- | :-- | :---------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| **0x0000** | `NODE_ADRESSE` | R/W | Geräteadresse des Sensors (1-255). |
|
| **0x0000** | `NODE_ADDRESS` | R/W | Geräteadresse des Sensors (1-255). |
|
||||||
| **0x0001** | `BAUDRATE` | R/W | `0`=1200, `1`=2400, `2`=4800, `3`=9600, `4`=19200, `5`=38400, `6`=57600, `7`=115200. |
|
| **0x0001** | `BAUDRATE` | R/W | `0`=1200, `1`=2400, `2`=4800, `3`=9600, `4`=19200, `5`=38400, `6`=57600, `7`=115200. |
|
||||||
| **0x0002** | `EINHEIT` | R/W | `0`=Keine, `1`=cm, `2`=mm, `3`=MPa, `4`=Pa, `5`=kPa. |
|
| **0x0002** | `UNIT` | R/W | `0`=Keine, `1`=cm, `2`=mm, `3`=MPa, `4`=Pa, `5`=kPa. |
|
||||||
| **0x0003** | `NACHKOMMASTELLEN` | R/W | Anzahl der Dezimalstellen für den Messwert (0-3). |
|
| **0x0003** | `DECIMAL_PLACES` | R/W | Anzahl der Dezimalstellen für den Messwert (0-3). |
|
||||||
| **0x0004** | `MESSWERT_AKTUELL` | R | Der skalierte Messwert als vorzeichenbehafteter 16-Bit-Integer. |
|
| **0x0004** | `CURRENT_MEASUREMENT` | R | Der skalierte Messwert als vorzeichenbehafteter 16-Bit-Integer. |
|
||||||
| **0x0005** | `MESSBEREICH_NULLPUNKT` | R/W | Rohwert für den Nullpunkt der Skala. |
|
| **0x0005** | `MEASUREMENT_RANGE_ZERO_POINT` | R/W | Rohwert für den Nullpunkt der Skala. |
|
||||||
| **0x0006** | `MESSBEREICH_ENDPUNKT` | R/W | Rohwert für den Endpunkt der Skala. |
|
| **0x0006** | `MEASUREMENT_RANGE_END_POINT` | R/W | Rohwert für den Endpunkt der Skala. |
|
||||||
|
|||||||
@@ -9,11 +9,13 @@
|
|||||||
| ✅ | **Phase 0: Planung & Definition** | | |
|
| ✅ | **Phase 0: Planung & Definition** | | |
|
||||||
| ✅ | Konzept erstellen und finalisieren | 30.06.2025 | Architektur, Komponenten und grundlegende Architektur sind festgelegt. |
|
| ✅ | Konzept erstellen und finalisieren | 30.06.2025 | Architektur, Komponenten und grundlegende Architektur sind festgelegt. |
|
||||||
| ✅ | MODBUS Register Map definieren | 30.06.2025 | Die "API" der Slaves ist definiert und bildet die Grundlage für die Software-Entwicklung. |
|
| ✅ | MODBUS Register Map definieren | 30.06.2025 | Die "API" der Slaves ist definiert und bildet die Grundlage für die Software-Entwicklung. |
|
||||||
|
| ✅ | Header- und deutsche Dokumentation aktualisiert | 10.07.2025 | Doxygen-Kommentare in Headern und deutsche .md-Dateien auf den neuesten Stand gebracht und übersetzt. |
|
||||||
| ☐ | **Phase 1: Slave-Node Prototyp (STM32 Eval-Board)** | | **Ziel:** Ein einzelner Slave wird auf dem Eval-Board zum Leben erweckt. |
|
| ☐ | **Phase 1: Slave-Node Prototyp (STM32 Eval-Board)** | | **Ziel:** Ein einzelner Slave wird auf dem Eval-Board zum Leben erweckt. |
|
||||||
| ✅ | 1.1 Entwicklungsumgebung für STM32/Zephyr einrichten | 30.06.2025 | Toolchain, VS Code, Zephyr-SDK, MCUBoot etc. installieren und ein "Hello World" zum Laufen bringen. |
|
| ✅ | 1.1 Entwicklungsumgebung für STM32/Zephyr einrichten | 30.06.2025 | Toolchain, VS Code, Zephyr-SDK, MCUBoot etc. installieren und ein "Hello World" zum Laufen bringen. |
|
||||||
| ☐ | 1.2 Basis-Firmware für Slave-Node erstellen | | Hardware-Abstraktion (GPIOs, ADC, UART für RS485) implementieren. |
|
| ✅ | 1.2 Hardware-Abstraktion (VND7050AJ, RS485) | 10.07.2025 | Implementierung der Treiber für den VND7050AJ und die RS485-Kommunikation. |
|
||||||
| ☐ | 1.3 MODBUS-RTU Stack auf dem Slave implementieren | | Basierend auf der definierten Register-Map. Zuerst nur lesende Funktionen (Status, Version). |
|
| ✅ | 1.3 Basis-Firmware für Slave-Node erstellen | 10.07.2025 | Hardware-Abstraktion (GPIOs) implementiert. |
|
||||||
| ☐ | 1.4 Kernlogik implementieren (z.B. Ventilsteuerung) | | Umsetzung der `VENTIL_ZUSTAND_BEWEGUNG` Logik, Strommessung für Endlagen etc. |
|
| ✅ | 1.3 MODBUS-RTU Stack auf dem Slave implementieren | 10.07.2025 | Basierend auf der definierten Register-Map. Zuerst nur lesende Funktionen (Status, Version). |
|
||||||
|
| ✅ | 1.4 Kernlogik implementieren (z.B. Ventilsteuerung) | 10.07.2025 | Umsetzung der `VALVE_STATE_MOVEMENT` Logik, Strommessung für Endlagen etc. |
|
||||||
| ☐ | **Phase 2: Verifikation der Slave-Firmware** | | **Ziel:** Nachweisen, dass der Slave sich exakt an die MODBUS-Spezifikation hält. |
|
| ☐ | **Phase 2: Verifikation der Slave-Firmware** | | **Ziel:** Nachweisen, dass der Slave sich exakt an die MODBUS-Spezifikation hält. |
|
||||||
| ☐ | 2.1 Slave-Node mit PC via USB-MODBUS-Adapter testen | | **Kritischer Meilenstein.** Mit Tools wie "QModMaster" oder einem Python-Skript die Register lesen & schreiben. Die Slave-Firmware wird so unabhängig vom Gateway validiert. |
|
| ☐ | 2.1 Slave-Node mit PC via USB-MODBUS-Adapter testen | | **Kritischer Meilenstein.** Mit Tools wie "QModMaster" oder einem Python-Skript die Register lesen & schreiben. Die Slave-Firmware wird so unabhängig vom Gateway validiert. |
|
||||||
| ☐ | 2.2 Firmware-Update Mechanismus testen | | Den kompletten Update-Prozess (Chunking, CRC-Check) mit einem Skript vom PC aus testen. Der Slave schreibt die Firmware dabei vorerst nur in einen ungenutzten RAM-Bereich. |
|
| ☐ | 2.2 Firmware-Update Mechanismus testen | | Den kompletten Update-Prozess (Chunking, CRC-Check) mit einem Skript vom PC aus testen. Der Slave schreibt die Firmware dabei vorerst nur in einen ungenutzten RAM-Bereich. |
|
||||||
|
|||||||
56
setup-format-hook.sh
Executable file
56
setup-format-hook.sh
Executable file
@@ -0,0 +1,56 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# This script sets up a Git pre-commit hook to automatically format C/C++ files
|
||||||
|
# in the 'software/' subdirectory using clang-format.
|
||||||
|
|
||||||
|
# Define the path for the pre-commit hook
|
||||||
|
HOOK_DIR=".git/hooks"
|
||||||
|
HOOK_FILE="$HOOK_DIR/pre-commit"
|
||||||
|
|
||||||
|
# Create the hooks directory if it doesn't exist
|
||||||
|
mkdir -p "$HOOK_DIR"
|
||||||
|
|
||||||
|
# Create the pre-commit hook script using a 'here document'
|
||||||
|
cat > "$HOOK_FILE" << 'EOF'
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# --- Pre-commit hook for clang-format ---
|
||||||
|
#
|
||||||
|
# This hook formats staged C, C++, and Objective-C files in the 'software/'
|
||||||
|
# subdirectory before a commit is made.
|
||||||
|
# It automatically finds the .clang-format file in the software/ directory.
|
||||||
|
#
|
||||||
|
|
||||||
|
# Directory to be formatted
|
||||||
|
TARGET_DIR="software/"
|
||||||
|
|
||||||
|
# Use git diff to find staged files that are Added (A), Copied (C), or Modified (M).
|
||||||
|
# We filter for files only within the TARGET_DIR.
|
||||||
|
# The grep regex matches common C/C++ and Objective-C file extensions.
|
||||||
|
FILES_TO_FORMAT=$(git diff --cached --name-only --diff-filter=ACM "$TARGET_DIR" | grep -E '\.(c|h|cpp|hpp|cxx|hxx|cc|hh|m|mm)$')
|
||||||
|
|
||||||
|
if [ -z "$FILES_TO_FORMAT" ]; then
|
||||||
|
# No relevant files to format, exit successfully.
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "› Running clang-format on staged files in '$TARGET_DIR'..."
|
||||||
|
|
||||||
|
# Run clang-format in-place on the identified files.
|
||||||
|
# clang-format will automatically find the .clang-format file in the software/ directory
|
||||||
|
# or any of its parent directories.
|
||||||
|
echo "$FILES_TO_FORMAT" | xargs clang-format -i
|
||||||
|
|
||||||
|
# Since clang-format may have changed the files, we need to re-stage them.
|
||||||
|
echo "$FILES_TO_FORMAT" | xargs git add
|
||||||
|
|
||||||
|
echo "› Formatting complete."
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Make the hook executable
|
||||||
|
chmod +x "$HOOK_FILE"
|
||||||
|
|
||||||
|
echo "✅ Git pre-commit hook has been set up successfully."
|
||||||
|
echo " It will now automatically format files in the '$PWD/software' directory before each commit."
|
||||||
142
software/.clang-format
Normal file
142
software/.clang-format
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
# Zephyr Project .clang-format configuration
|
||||||
|
# Based on Linux kernel style with Zephyr-specific adaptations
|
||||||
|
|
||||||
|
# Use LLVM as the base style and customize from there
|
||||||
|
BasedOnStyle: LLVM
|
||||||
|
|
||||||
|
# Language settings
|
||||||
|
Language: Cpp
|
||||||
|
|
||||||
|
# Indentation settings
|
||||||
|
IndentWidth: 8
|
||||||
|
TabWidth: 8
|
||||||
|
UseTab: ForIndentation
|
||||||
|
|
||||||
|
# Line length
|
||||||
|
ColumnLimit: 100
|
||||||
|
|
||||||
|
# Brace settings
|
||||||
|
BreakBeforeBraces: Linux
|
||||||
|
BraceWrapping:
|
||||||
|
AfterClass: true
|
||||||
|
AfterControlStatement: false
|
||||||
|
AfterEnum: true
|
||||||
|
AfterFunction: true
|
||||||
|
AfterNamespace: true
|
||||||
|
AfterStruct: true
|
||||||
|
AfterUnion: true
|
||||||
|
BeforeCatch: true
|
||||||
|
BeforeElse: false
|
||||||
|
IndentBraces: false
|
||||||
|
SplitEmptyFunction: true
|
||||||
|
SplitEmptyRecord: true
|
||||||
|
SplitEmptyNamespace: true
|
||||||
|
|
||||||
|
# Always add braces for control statements (Zephyr requirement)
|
||||||
|
RemoveBracesLLVM: false
|
||||||
|
|
||||||
|
# Control statement settings
|
||||||
|
SpaceBeforeParens: ControlStatements
|
||||||
|
SpacesInParentheses: false
|
||||||
|
|
||||||
|
# Function settings
|
||||||
|
AllowShortFunctionsOnASingleLine: None
|
||||||
|
AllowShortBlocksOnASingleLine: Empty
|
||||||
|
AllowShortIfStatementsOnASingleLine: Never
|
||||||
|
AllowShortLoopsOnASingleLine: false
|
||||||
|
AllowShortCaseLabelsOnASingleLine: false
|
||||||
|
|
||||||
|
# Pointer and reference alignment
|
||||||
|
PointerAlignment: Right
|
||||||
|
ReferenceAlignment: Right
|
||||||
|
|
||||||
|
# Spacing settings
|
||||||
|
SpaceAfterCStyleCast: false
|
||||||
|
SpaceAfterLogicalNot: false
|
||||||
|
SpaceBeforeAssignmentOperators: true
|
||||||
|
SpaceBeforeCpp11BracedList: false
|
||||||
|
SpaceBeforeCtorInitializerColon: true
|
||||||
|
SpaceBeforeInheritanceColon: true
|
||||||
|
SpaceBeforeRangeBasedForLoopColon: true
|
||||||
|
SpaceInEmptyParentheses: false
|
||||||
|
SpacesBeforeTrailingComments: 1
|
||||||
|
SpacesInAngles: false
|
||||||
|
SpacesInCStyleCastParentheses: false
|
||||||
|
SpacesInContainerLiterals: false
|
||||||
|
SpacesInSquareBrackets: false
|
||||||
|
|
||||||
|
# Alignment settings
|
||||||
|
AlignAfterOpenBracket: DontAlign
|
||||||
|
AlignConsecutiveAssignments: false
|
||||||
|
AlignConsecutiveDeclarations: false
|
||||||
|
AlignEscapedNewlines: Right
|
||||||
|
AlignOperands: false
|
||||||
|
AlignTrailingComments: false
|
||||||
|
|
||||||
|
# Breaking settings
|
||||||
|
AlwaysBreakAfterDefinitionReturnType: None
|
||||||
|
AlwaysBreakAfterReturnType: None
|
||||||
|
AlwaysBreakBeforeMultilineStrings: false
|
||||||
|
AlwaysBreakTemplateDeclarations: false
|
||||||
|
BinPackArguments: false
|
||||||
|
BinPackParameters: false
|
||||||
|
BreakBeforeBinaryOperators: None
|
||||||
|
BreakBeforeTernaryOperators: true
|
||||||
|
BreakConstructorInitializersBeforeComma: false
|
||||||
|
BreakAfterJavaFieldAnnotations: false
|
||||||
|
BreakStringLiterals: true
|
||||||
|
|
||||||
|
# Penalties (used for line breaking decisions)
|
||||||
|
PenaltyBreakAssignment: 2
|
||||||
|
PenaltyBreakBeforeFirstCallParameter: 19
|
||||||
|
PenaltyBreakComment: 300
|
||||||
|
PenaltyBreakFirstLessLess: 120
|
||||||
|
PenaltyBreakString: 1000
|
||||||
|
PenaltyExcessCharacter: 1000000
|
||||||
|
PenaltyReturnTypeOnItsOwnLine: 60
|
||||||
|
|
||||||
|
# Comment settings
|
||||||
|
ReflowComments: true
|
||||||
|
CommentPragmas: '^ IWYU pragma:'
|
||||||
|
|
||||||
|
# Sorting settings
|
||||||
|
SortIncludes: true
|
||||||
|
SortUsingDeclarations: true
|
||||||
|
|
||||||
|
# Preprocessor settings
|
||||||
|
IndentPPDirectives: None
|
||||||
|
MacroBlockBegin: ''
|
||||||
|
MacroBlockEnd: ''
|
||||||
|
|
||||||
|
# Misc settings
|
||||||
|
CompactNamespaces: false
|
||||||
|
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||||
|
ConstructorInitializerIndentWidth: 4
|
||||||
|
ContinuationIndentWidth: 4
|
||||||
|
Cpp11BracedListStyle: true
|
||||||
|
DerivePointerAlignment: false
|
||||||
|
DisableFormat: false
|
||||||
|
ExperimentalAutoDetectBinPacking: false
|
||||||
|
FixNamespaceComments: true
|
||||||
|
ForEachMacros: ['LISTIFY', 'FOR_EACH', 'FOR_EACH_FIXED_ARG', 'FOR_EACH_IDX', 'FOR_EACH_IDX_FIXED_ARG', 'FOR_EACH_NONEMPTY_TERM', 'Z_FOR_EACH', 'Z_FOR_EACH_FIXED_ARG', 'Z_FOR_EACH_IDX', 'Z_FOR_EACH_IDX_FIXED_ARG']
|
||||||
|
IncludeBlocks: Preserve
|
||||||
|
IncludeCategories:
|
||||||
|
- Regex: '^<zephyr/.*\.h>'
|
||||||
|
Priority: 1
|
||||||
|
- Regex: '^<.*\.h>'
|
||||||
|
Priority: 2
|
||||||
|
- Regex: '^<.*'
|
||||||
|
Priority: 3
|
||||||
|
- Regex: '.*'
|
||||||
|
Priority: 4
|
||||||
|
IndentCaseLabels: false
|
||||||
|
IndentWrappedFunctionNames: false
|
||||||
|
JavaScriptQuotes: Leave
|
||||||
|
JavaScriptWrapImports: true
|
||||||
|
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||||
|
MaxEmptyLinesToKeep: 1
|
||||||
|
NamespaceIndentation: None
|
||||||
|
ObjCBinPackProtocolList: Auto
|
||||||
|
ObjCBlockIndentWidth: 2
|
||||||
|
ObjCSpaceAfterProperty: false
|
||||||
|
ObjCSpaceBeforeProtocolList: true
|
||||||
9
software/.vscode/settings.json
vendored
9
software/.vscode/settings.json
vendored
@@ -1,12 +1,15 @@
|
|||||||
{
|
{
|
||||||
// Hush CMake
|
// Hush CMake
|
||||||
"cmake.configureOnOpen": false,
|
"cmake.configureOnOpen": false,
|
||||||
|
|
||||||
// IntelliSense
|
// IntelliSense
|
||||||
"C_Cpp.default.compilerPath": "${userHome}/zephyr-sdk-0.17.1/arm-zephyr-eabi/bin/arm-zephyr-eabi-gcc.exe",
|
"C_Cpp.default.compilerPath": "${userHome}/zephyr-sdk-0.17.1/arm-zephyr-eabi/bin/arm-zephyr-eabi-gcc.exe",
|
||||||
"C_Cpp.default.compileCommands": "${workspaceFolder}/build/compile_commands.json",
|
"C_Cpp.default.compileCommands": "${workspaceFolder}/build/compile_commands.json",
|
||||||
|
|
||||||
// File Associations
|
// File Associations
|
||||||
"files.associations": {
|
"files.associations": {
|
||||||
}
|
"app_version.h": "c"
|
||||||
|
},
|
||||||
|
"C_Cpp.clang_format_style": "file",
|
||||||
|
"nrf-connect.applications": [
|
||||||
|
"${workspaceFolder}/apps/slave_node"
|
||||||
|
],
|
||||||
}
|
}
|
||||||
28
software/.vscode/tasks.json
vendored
28
software/.vscode/tasks.json
vendored
@@ -2,31 +2,19 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"tasks": [
|
"tasks": [
|
||||||
{
|
{
|
||||||
"label": "West Build",
|
"label": "Format All C/C++ Files",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
|
"command": "find . -name \"*.c\" -o -name \"*.h\" | xargs clang-format -i",
|
||||||
|
"problemMatcher": [],
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
"isDefault": true
|
"isDefault": true
|
||||||
},
|
},
|
||||||
"linux": {
|
"presentation": {
|
||||||
"command": "${userHome}/zephyrproject/.venv/bin/west"
|
"reveal": "silent",
|
||||||
},
|
"clear": true,
|
||||||
"windows": {
|
"panel": "shared"
|
||||||
"command": "${userHome}/zephyrproject/.venv/Scripts/west.exe"
|
}
|
||||||
},
|
|
||||||
"osx": {
|
|
||||||
"command": "${userHome}/zephyrproject/.venv/bin/west"
|
|
||||||
},
|
|
||||||
"args": [
|
|
||||||
"build",
|
|
||||||
"-p",
|
|
||||||
"auto",
|
|
||||||
"-b",
|
|
||||||
"valve_node"
|
|
||||||
],
|
|
||||||
"problemMatcher": [
|
|
||||||
"$gcc"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "West Configurable Build",
|
"label": "West Configurable Build",
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.13.1)
|
|
||||||
|
|
||||||
project(software)
|
|
||||||
|
|
||||||
add_subdirectory(modules/modbus_server)
|
|
||||||
add_subdirectory(modules/valve)
|
|
||||||
add_subdirectory(modules/fwu)
|
|
||||||
add_subdirectory(apps/stm32g431_tests)
|
|
||||||
1
software/Kconfig
Normal file
1
software/Kconfig
Normal file
@@ -0,0 +1 @@
|
|||||||
|
rsource "lib/Kconfig"
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
cmake_minimum_required(VERSION 3.20)
|
cmake_minimum_required(VERSION 3.20.0)
|
||||||
|
|
||||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||||
project(hello_world)
|
project(bl_test)
|
||||||
|
|
||||||
|
# Add application source files
|
||||||
target_sources(app PRIVATE src/main.c)
|
target_sources(app PRIVATE src/main.c)
|
||||||
5
software/apps/bl_test/VERSION
Normal file
5
software/apps/bl_test/VERSION
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
VERSION_MAJOR = 0
|
||||||
|
VERSION_MINOR = 0
|
||||||
|
PATCHLEVEL = 1
|
||||||
|
VERSION_TWEAK = 1
|
||||||
|
EXTRAVERSION = devel
|
||||||
46
software/apps/bl_test/prj.conf
Normal file
46
software/apps/bl_test/prj.conf
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# Enable Console and printk for logging via UART
|
||||||
|
CONFIG_CONSOLE=y
|
||||||
|
CONFIG_LOG=y
|
||||||
|
CONFIG_UART_CONSOLE=y
|
||||||
|
|
||||||
|
# Enable more detailed MCUMGR logging
|
||||||
|
CONFIG_MCUMGR_LOG_LEVEL_DBG=y
|
||||||
|
CONFIG_IMG_MANAGER_LOG_LEVEL_DBG=y
|
||||||
|
CONFIG_STREAM_FLASH_LOG_LEVEL_DBG=y
|
||||||
|
|
||||||
|
# Enable USB for MCUMGR only
|
||||||
|
CONFIG_USB_DEVICE_STACK=y
|
||||||
|
CONFIG_USB_CDC_ACM=y
|
||||||
|
CONFIG_USB_DEVICE_INITIALIZE_AT_BOOT=y
|
||||||
|
|
||||||
|
# USB CDC ACM buffer configuration for better MCUMGR performance
|
||||||
|
CONFIG_USB_CDC_ACM_RINGBUF_SIZE=1024
|
||||||
|
|
||||||
|
# Set log level to info for reasonable size
|
||||||
|
CONFIG_LOG_DEFAULT_LEVEL=3
|
||||||
|
|
||||||
|
# Enable MCUMGR info logging (not debug to save space)
|
||||||
|
CONFIG_MCUMGR_LOG_LEVEL_INF=y
|
||||||
|
|
||||||
|
# Enable USB CDC info logging
|
||||||
|
CONFIG_USB_CDC_ACM_LOG_LEVEL_INF=y
|
||||||
|
|
||||||
|
# STEP 5.2 - Enable mcumgr DFU in application
|
||||||
|
# Enable MCUMGR
|
||||||
|
CONFIG_MCUMGR=y # Enable MCUMGR management for both OS and Images
|
||||||
|
CONFIG_MCUMGR_GRP_OS=y
|
||||||
|
CONFIG_MCUMGR_GRP_IMG=y
|
||||||
|
|
||||||
|
# Configure MCUMGR transport to UART (will use USB-CDC via chosen device)
|
||||||
|
CONFIG_MCUMGR_TRANSPORT_UART=y
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
# Configure dependencies for CONFIG_MCUMGR
|
||||||
|
CONFIG_NET_BUF=y
|
||||||
|
CONFIG_ZCBOR=y
|
||||||
|
CONFIG_CRC=y # Configure dependencies for CONFIG_MCUMGR_GRP_IMG
|
||||||
|
CONFIG_FLASH=y
|
||||||
|
CONFIG_IMG_MANAGER=y # Configure dependencies for CONFIG_IMG_MANAGER
|
||||||
|
CONFIG_STREAM_FLASH=y
|
||||||
|
CONFIG_FLASH_MAP=y # Configure dependencies for CONFIG_MCUMGR_TRANSPORT_USB_CDC
|
||||||
|
CONFIG_BASE64=y
|
||||||
11
software/apps/bl_test/src/main.c
Normal file
11
software/apps/bl_test/src/main.c
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#include <zephyr/kernel.h>
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
#include <app_version.h>
|
||||||
|
|
||||||
|
LOG_MODULE_REGISTER(bl_test_app, LOG_LEVEL_INF);
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
LOG_INF("Hello World from bl_test! This is version %s", APP_VERSION_EXTENDED_STRING);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
1
software/apps/bl_test/sysbuild.conf
Normal file
1
software/apps/bl_test/sysbuild.conf
Normal file
@@ -0,0 +1 @@
|
|||||||
|
SB_CONFIG_BOOTLOADER_MCUBOOT=y
|
||||||
9
software/apps/bl_test/sysbuild/bl_test.overlay
Normal file
9
software/apps/bl_test/sysbuild/bl_test.overlay
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#include "common.dtsi"
|
||||||
|
|
||||||
|
/* Application Configuration - Firmware wird in slot0_partition geschrieben */
|
||||||
|
/ {
|
||||||
|
chosen {
|
||||||
|
zephyr,code-partition = &slot0_partition;
|
||||||
|
zephyr,uart-mcumgr = &cdc_acm_uart0;
|
||||||
|
};
|
||||||
|
};
|
||||||
94
software/apps/bl_test/sysbuild/common.dtsi
Normal file
94
software/apps/bl_test/sysbuild/common.dtsi
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* Common Devicetree Configuration für weact_stm32g431_core
|
||||||
|
* - Konfiguriert einen W25Q128 Flash-Speicher auf SPI2
|
||||||
|
* - Konfiguriert USB-CDC für MCUMGR
|
||||||
|
* - Setzt den Chip Select (CS) Pin auf PA5
|
||||||
|
* - Weist das Label "flash1" zu
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Partitions für internes Flash (STM32G431) */
|
||||||
|
&flash0 {
|
||||||
|
/delete-node/ partitions; /* Entferne die Standard-Partitionen */
|
||||||
|
partitions {
|
||||||
|
compatible = "fixed-partitions";
|
||||||
|
#address-cells = <1>;
|
||||||
|
#size-cells = <1>;
|
||||||
|
|
||||||
|
/* MCUboot bootloader - 48 KB */
|
||||||
|
boot_partition: partition@0 {
|
||||||
|
label = "mcuboot";
|
||||||
|
reg = <0x00000000 0x0000C000>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Slot0 partition für primäres Application Image - 80 KB (20 sectors @ 4KB) */
|
||||||
|
slot0_partition: partition@C000 {
|
||||||
|
label = "image-0";
|
||||||
|
reg = <0x0000C000 0x00014000>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/* USB-CDC Konfiguration für MCUMGR */
|
||||||
|
&usb {
|
||||||
|
status = "okay";
|
||||||
|
cdc_acm_uart0: cdc_acm_uart0 {
|
||||||
|
compatible = "zephyr,cdc-acm-uart";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/ {
|
||||||
|
chosen {
|
||||||
|
zephyr,uart-mcumgr = &cdc_acm_uart0;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
&spi2 {
|
||||||
|
/* Definiere die Pins für SCK, MISO, MOSI auf Port B */
|
||||||
|
pinctrl-0 = <&spi2_sck_pb13 &spi2_miso_pb14 &spi2_mosi_pb15>;
|
||||||
|
pinctrl-names = "default";
|
||||||
|
status = "okay";
|
||||||
|
|
||||||
|
/* === Chip Select (CS) auf PA5 gesetzt === */
|
||||||
|
cs-gpios = <&gpioa 5 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>;
|
||||||
|
|
||||||
|
/* Definiere den Flash-Chip als SPI NOR Gerät */
|
||||||
|
flash1: flash@0 {
|
||||||
|
compatible = "jedec,spi-nor";
|
||||||
|
reg = <0>;
|
||||||
|
label = "flash1";
|
||||||
|
|
||||||
|
/* JEDEC ID für einen Winbond W25Q128 (16 MBytes) */
|
||||||
|
jedec-id = [ef 40 18];
|
||||||
|
|
||||||
|
/* Speichergröße in Bytes (16 MBytes) */
|
||||||
|
size = <DT_SIZE_M(16)>;
|
||||||
|
|
||||||
|
/* Maximale Taktfrequenz - angepasst an STM32G431 Limits */
|
||||||
|
spi-max-frequency = <1000000>;
|
||||||
|
|
||||||
|
/* Partitions für externes Flash */
|
||||||
|
partitions {
|
||||||
|
compatible = "fixed-partitions";
|
||||||
|
#address-cells = <1>;
|
||||||
|
#size-cells = <1>;
|
||||||
|
|
||||||
|
/* Slot1 partition für MCUboot (sekundäres Image) - 80 KB (20 sectors @ 4KB) */
|
||||||
|
slot1_partition: partition@0 {
|
||||||
|
label = "image-1";
|
||||||
|
reg = <0x00000000 0x00014000>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Scratch partition für MCUboot - 80 KB (20 sectors @ 4KB) */
|
||||||
|
scratch_partition: partition@14000 {
|
||||||
|
label = "scratch";
|
||||||
|
reg = <0x00014000 0x00014000>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Speicher partition für LittleFS - ~15.83 MB */
|
||||||
|
storage_partition: partition@28000 {
|
||||||
|
label = "storage";
|
||||||
|
reg = <0x00028000 0x00FD8000>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
23
software/apps/bl_test/sysbuild/mcuboot.conf
Normal file
23
software/apps/bl_test/sysbuild/mcuboot.conf
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
CONFIG_LOG=y
|
||||||
|
CONFIG_MCUBOOT_LOG_LEVEL_INF=y
|
||||||
|
|
||||||
|
# Enable UART console for MCUboot debug output
|
||||||
|
CONFIG_UART_CONSOLE=y
|
||||||
|
CONFIG_CONSOLE=y
|
||||||
|
CONFIG_MCUBOOT_INDICATION_LED=y
|
||||||
|
|
||||||
|
# Enable external SPI flash support
|
||||||
|
CONFIG_SPI=y
|
||||||
|
CONFIG_SPI_NOR=y
|
||||||
|
CONFIG_SPI_NOR_SFDP_DEVICETREE=n
|
||||||
|
CONFIG_FLASH=y
|
||||||
|
CONFIG_FLASH_MAP=y
|
||||||
|
CONFIG_GPIO=y
|
||||||
|
|
||||||
|
# Add SPI NOR specific configurations - use 4KB page size (required by driver)
|
||||||
|
CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE=4096
|
||||||
|
CONFIG_SPI_NOR_INIT_PRIORITY=80
|
||||||
|
|
||||||
|
# Set maximum image sectors manually since auto doesn't work with external flash
|
||||||
|
CONFIG_BOOT_MAX_IMG_SECTORS_AUTO=n
|
||||||
|
CONFIG_BOOT_MAX_IMG_SECTORS=80
|
||||||
8
software/apps/bl_test/sysbuild/mcuboot.overlay
Normal file
8
software/apps/bl_test/sysbuild/mcuboot.overlay
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#include "common.dtsi"
|
||||||
|
|
||||||
|
/* MCUboot Configuration - Bootloader wird in boot_partition geschrieben */
|
||||||
|
/ {
|
||||||
|
chosen {
|
||||||
|
zephyr,code-partition = &boot_partition;
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
|
|
||||||
CONFIG_CONSOLE=y
|
|
||||||
CONFIG_LOG=y
|
|
||||||
CONFIG_UART_CONSOLE=y
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
#include <zephyr/kernel.h>
|
|
||||||
#include <zephyr/sys/printk.h>
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
printk("Hello World! %s\n", CONFIG_BOARD);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
12
software/apps/slave_node/.vscode/c_cpp_properties.json
vendored
Normal file
12
software/apps/slave_node/.vscode/c_cpp_properties.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Linux",
|
||||||
|
"compileCommands": "${workspaceFolder}/build/compile_commands.json",
|
||||||
|
"cStandard": "c99",
|
||||||
|
"cppStandard": "gnu++17",
|
||||||
|
"intelliSenseMode": "linux-gcc-arm"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 4
|
||||||
|
}
|
||||||
@@ -1,27 +1,8 @@
|
|||||||
cmake_minimum_required(VERSION 3.20)
|
cmake_minimum_required(VERSION 3.20)
|
||||||
|
|
||||||
# Point BOARD_ROOT and DTS_ROOT to the 'software' directory, which contains 'boards'.
|
|
||||||
list(APPEND BOARD_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../..)
|
|
||||||
|
|
||||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||||
project(slave_node)
|
|
||||||
|
|
||||||
list(APPEND KCONFIG_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/../../modules/modbus_server/Kconfig)
|
project(slave_node LANGUAGES C)
|
||||||
list(APPEND KCONFIG_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/../../modules/valve/Kconfig)
|
zephyr_include_directories(../../include)
|
||||||
list(APPEND KCONFIG_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/../../modules/fwu/Kconfig)
|
add_subdirectory(../../lib lib)
|
||||||
|
target_sources(app PRIVATE src/main.c)
|
||||||
target_include_directories(app PRIVATE
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../../modules/valve/include
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../../modules/modbus_server/include
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../../modules/fwu/include
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add the source files from the app and the libraries
|
|
||||||
target_sources(app PRIVATE
|
|
||||||
src/main.c
|
|
||||||
src/shell_modbus.c
|
|
||||||
src/shell_system.c
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../../modules/valve/src/valve.c
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../../modules/modbus_server/src/modbus_server.c
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../../modules/fwu/src/fwu.c
|
|
||||||
)
|
|
||||||
|
|||||||
2
software/apps/slave_node/Kconfig
Normal file
2
software/apps/slave_node/Kconfig
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
rsource "../../lib/Kconfig"
|
||||||
|
source "Kconfig.zephyr"
|
||||||
5
software/apps/slave_node/VERSION
Normal file
5
software/apps/slave_node/VERSION
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
VERSION_MAJOR = 0
|
||||||
|
VERSION_MINOR = 0
|
||||||
|
PATCHLEVEL = 1
|
||||||
|
VERSION_TWEAK = 1
|
||||||
|
EXTRAVERSION = devel
|
||||||
@@ -4,3 +4,4 @@ CONFIG_UART_CONSOLE=n
|
|||||||
# Enable RTT console
|
# Enable RTT console
|
||||||
CONFIG_RTT_CONSOLE=y
|
CONFIG_RTT_CONSOLE=y
|
||||||
CONFIG_USE_SEGGER_RTT=y
|
CONFIG_USE_SEGGER_RTT=y
|
||||||
|
CONFIG_SHELL_BACKEND_RTT=y
|
||||||
@@ -1,9 +1,48 @@
|
|||||||
|
/ {
|
||||||
|
aliases {
|
||||||
|
vnd7050aj = &vnd7050aj;
|
||||||
|
};
|
||||||
|
|
||||||
|
vnd7050aj: vnd7050aj {
|
||||||
|
compatible = "st,vnd7050aj";
|
||||||
|
status = "okay";
|
||||||
|
|
||||||
|
input0-gpios = <&gpiob 7 GPIO_ACTIVE_HIGH>;
|
||||||
|
input1-gpios = <&gpiob 9 GPIO_ACTIVE_HIGH>;
|
||||||
|
select0-gpios = <&gpiob 5 GPIO_ACTIVE_HIGH>;
|
||||||
|
select1-gpios = <&gpiob 6 GPIO_ACTIVE_HIGH>;
|
||||||
|
sense-enable-gpios = <&gpiob 4 GPIO_ACTIVE_HIGH>;
|
||||||
|
fault-reset-gpios = <&gpiob 3 GPIO_ACTIVE_LOW>;
|
||||||
|
io-channels = <&adc1 1>;
|
||||||
|
r-sense-ohms = <1500>;
|
||||||
|
k-vcc = <4000>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
&adc1 {
|
||||||
|
status = "okay";
|
||||||
|
pinctrl-0 = <&adc1_in1_pa0>;
|
||||||
|
pinctrl-names = "default";
|
||||||
|
st,adc-clock-source = "SYNC";
|
||||||
|
st,adc-prescaler = <4>;
|
||||||
|
#address-cells = <1>;
|
||||||
|
#size-cells = <0>;
|
||||||
|
|
||||||
|
channel@1 {
|
||||||
|
reg = <1>;
|
||||||
|
zephyr,gain = "ADC_GAIN_1";
|
||||||
|
zephyr,reference = "ADC_REF_INTERNAL";
|
||||||
|
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
|
||||||
|
zephyr,resolution = <12>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
&usart1 {
|
&usart1 {
|
||||||
modbus0 {
|
modbus0 {
|
||||||
compatible = "zephyr,modbus-serial";
|
compatible = "zephyr,modbus-serial";
|
||||||
status = "okay";
|
status = "okay";
|
||||||
};
|
};
|
||||||
status = "okay";
|
status = "okay";
|
||||||
pinctrl-0 = <&usart1_tx_pa9 &usart1_rx_pa10>;
|
pinctrl-0 = <&usart1_tx_pa9 &usart1_rx_pa10>; // PA9=TX, PA10=RX for Modbus communication
|
||||||
pinctrl-names = "default";
|
pinctrl-names = "default";
|
||||||
};
|
};
|
||||||
16
software/apps/slave_node/cdc-acm.overlay
Normal file
16
software/apps/slave_node/cdc-acm.overlay
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#include <zephyr/dt-bindings/gpio/gpio.h>
|
||||||
|
|
||||||
|
&zephyr_udc0 {
|
||||||
|
cdc_acm_uart0: cdc_acm_uart0 {
|
||||||
|
compatible = "zephyr,cdc-acm-uart";
|
||||||
|
|
||||||
|
modbus0 {
|
||||||
|
compatible = "zephyr,modbus-serial";
|
||||||
|
status = "okay";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
&usart1 {
|
||||||
|
/delete-node/ modbus0;
|
||||||
|
};
|
||||||
4
software/apps/slave_node/overlay-cdc-acm.conf
Normal file
4
software/apps/slave_node/overlay-cdc-acm.conf
Normal 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
|
||||||
@@ -4,8 +4,10 @@ CONFIG_LOG=y
|
|||||||
|
|
||||||
# Enable Shell
|
# Enable Shell
|
||||||
CONFIG_SHELL=y
|
CONFIG_SHELL=y
|
||||||
CONFIG_SHELL_BACKEND_RTT=y
|
|
||||||
CONFIG_REBOOT=y
|
CONFIG_REBOOT=y
|
||||||
|
CONFIG_SHELL_MODBUS=y
|
||||||
|
CONFIG_SHELL_VALVE=y
|
||||||
|
CONFIG_SHELL_SYSTEM=y
|
||||||
|
|
||||||
# Enable Settings Subsystem
|
# Enable Settings Subsystem
|
||||||
CONFIG_SETTINGS=y
|
CONFIG_SETTINGS=y
|
||||||
@@ -22,3 +24,5 @@ CONFIG_MODBUS=y
|
|||||||
CONFIG_MODBUS_ROLE_SERVER=y
|
CONFIG_MODBUS_ROLE_SERVER=y
|
||||||
CONFIG_MODBUS_BUFFER_SIZE=256
|
CONFIG_MODBUS_BUFFER_SIZE=256
|
||||||
|
|
||||||
|
# Enable VND7050AJ
|
||||||
|
CONFIG_VND7050AJ=y
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
#include <zephyr/kernel.h>
|
#include <zephyr/kernel.h>
|
||||||
#include <zephyr/settings/settings.h>
|
|
||||||
#include <zephyr/logging/log.h>
|
#include <zephyr/logging/log.h>
|
||||||
#include <modbus_server.h>
|
#include <zephyr/settings/settings.h>
|
||||||
#include <valve.h>
|
#include <lib/fwu.h>
|
||||||
#include <fwu.h>
|
#include <lib/modbus_server.h>
|
||||||
|
#include <lib/valve.h>
|
||||||
|
|
||||||
LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);
|
LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);
|
||||||
|
|
||||||
|
|||||||
@@ -1,119 +0,0 @@
|
|||||||
#include <zephyr/shell/shell.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <modbus_server.h>
|
|
||||||
#include <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);
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
#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);
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.20)
|
|
||||||
|
|
||||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
|
||||||
project(stm32g431_tests)
|
|
||||||
|
|
||||||
target_sources(app PRIVATE src/main.c)
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
|
|
||||||
CONFIG_CONSOLE=y
|
|
||||||
CONFIG_LOG=y
|
|
||||||
CONFIG_UART_CONSOLE=y
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
#include <zephyr/kernel.h>
|
|
||||||
#include <zephyr/sys/printk.h>
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
printk("Hello World! %s\n", CONFIG_BOARD);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
47
software/include/lib/fwu.h
Normal file
47
software/include/lib/fwu.h
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#ifndef FWU_H
|
||||||
|
#define FWU_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file fwu.h
|
||||||
|
* @brief API for the Firmware Update (FWU) library.
|
||||||
|
*
|
||||||
|
* This library provides the core logic for handling the over-the-air firmware
|
||||||
|
* update process via Modbus. It manages the data buffer, processes commands,
|
||||||
|
* and calculates CRC checksums for data verification.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initializes the firmware update module.
|
||||||
|
*
|
||||||
|
* This function currently does nothing but is a placeholder for future
|
||||||
|
* initialization logic.
|
||||||
|
*/
|
||||||
|
void fwu_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Handles incoming Modbus register writes related to firmware updates.
|
||||||
|
*
|
||||||
|
* This function is the main entry point for the FWU process. It parses the
|
||||||
|
* address and value from a Modbus write operation and takes appropriate action,
|
||||||
|
* such as storing metadata (offset, size) or data chunks, and processing
|
||||||
|
* commands (verify, finalize).
|
||||||
|
*
|
||||||
|
* @param addr The Modbus register address being written to.
|
||||||
|
* @param reg The 16-bit value being written to the register.
|
||||||
|
*/
|
||||||
|
void fwu_handler(uint16_t addr, uint16_t reg);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the CRC16-CCITT of the last received firmware chunk.
|
||||||
|
*
|
||||||
|
* After a data chunk is fully received into the buffer, this function can be
|
||||||
|
* called to retrieve the calculated CRC checksum. The master can then compare
|
||||||
|
* this with its own calculated CRC to verify data integrity.
|
||||||
|
*
|
||||||
|
* @return The 16-bit CRC of the last chunk.
|
||||||
|
*/
|
||||||
|
uint16_t fwu_get_last_chunk_crc(void);
|
||||||
|
|
||||||
|
#endif // FWU_H
|
||||||
185
software/include/lib/modbus_server.h
Normal file
185
software/include/lib/modbus_server.h
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
#ifndef MODBUS_SERVER_H
|
||||||
|
#define MODBUS_SERVER_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file modbus_server.h
|
||||||
|
* @brief API for the Modbus server implementation.
|
||||||
|
*
|
||||||
|
* This file defines the Modbus register map and provides functions to
|
||||||
|
* initialize and manage the Modbus server.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Modbus Input Register Addresses (Read-Only).
|
||||||
|
* @see docs/modbus-registers.de.md
|
||||||
|
*/
|
||||||
|
enum {
|
||||||
|
/**
|
||||||
|
* @brief Combined status register for the valve.
|
||||||
|
* High-Byte: Movement (0=Idle, 1=Opening, 2=Closing, 3=Error).
|
||||||
|
* Low-Byte: State (0=Closed, 1=Open).
|
||||||
|
*/
|
||||||
|
REG_INPUT_VALVE_STATE_MOVEMENT = 0x0000,
|
||||||
|
/**
|
||||||
|
* @brief Motor current during opening in milliamperes (mA).
|
||||||
|
*/
|
||||||
|
REG_INPUT_MOTOR_OPEN_CURRENT_MA = 0x0001,
|
||||||
|
/**
|
||||||
|
* @brief Motor current during closing in milliamperes (mA).
|
||||||
|
*/
|
||||||
|
REG_INPUT_MOTOR_CLOSE_CURRENT_MA = 0x0002,
|
||||||
|
/**
|
||||||
|
* @brief Bitmask of digital inputs. Bit 0: Input 1, Bit 1: Input 2.
|
||||||
|
* 1=Active.
|
||||||
|
*/
|
||||||
|
REG_INPUT_DIGITAL_INPUTS_STATE = 0x0020,
|
||||||
|
/**
|
||||||
|
* @brief Event flags for buttons (Clear-on-Read). Bit 0: Button 1 pressed.
|
||||||
|
* Bit 1: Button 2 pressed.
|
||||||
|
*/
|
||||||
|
REG_INPUT_BUTTON_EVENTS = 0x0021,
|
||||||
|
/**
|
||||||
|
* @brief Firmware version, e.g., 0x0102 for v1.2.
|
||||||
|
*/
|
||||||
|
REG_INPUT_FIRMWARE_VERSION_MAJOR_MINOR = 0x00F0,
|
||||||
|
/**
|
||||||
|
* @brief Firmware version patch level, e.g., 3 for v1.2.3.
|
||||||
|
*/
|
||||||
|
REG_INPUT_FIRMWARE_VERSION_PATCH = 0x00F1,
|
||||||
|
/**
|
||||||
|
* @brief Device status (0=OK, 1=General Error).
|
||||||
|
*/
|
||||||
|
REG_INPUT_DEVICE_STATUS = 0x00F2,
|
||||||
|
/**
|
||||||
|
* @brief Lower 16 bits of uptime in seconds.
|
||||||
|
*/
|
||||||
|
REG_INPUT_UPTIME_SECONDS_LOW = 0x00F3,
|
||||||
|
/**
|
||||||
|
* @brief Upper 16 bits of uptime in seconds.
|
||||||
|
*/
|
||||||
|
REG_INPUT_UPTIME_SECONDS_HIGH = 0x00F4,
|
||||||
|
/**
|
||||||
|
* @brief Current supply voltage in millivolts (mV).
|
||||||
|
*/
|
||||||
|
REG_INPUT_SUPPLY_VOLTAGE_MV = 0x00F5,
|
||||||
|
/**
|
||||||
|
* @brief CRC16 of the last received data chunk in the buffer for firmware
|
||||||
|
* update.
|
||||||
|
*/
|
||||||
|
REG_INPUT_FWU_LAST_CHUNK_CRC = 0x0100
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Modbus Holding Register Addresses (Read/Write).
|
||||||
|
* @see docs/modbus-registers.de.md
|
||||||
|
*/
|
||||||
|
enum {
|
||||||
|
/**
|
||||||
|
* @brief Valve control command (1=Open, 2=Close, 0=Stop movement).
|
||||||
|
*/
|
||||||
|
REG_HOLDING_VALVE_COMMAND = 0x0000,
|
||||||
|
/**
|
||||||
|
* @brief Safety timeout in seconds for the opening process.
|
||||||
|
*/
|
||||||
|
REG_HOLDING_MAX_OPENING_TIME_S = 0x0001,
|
||||||
|
/**
|
||||||
|
* @brief Safety timeout in seconds for the closing process.
|
||||||
|
*/
|
||||||
|
REG_HOLDING_MAX_CLOSING_TIME_S = 0x0002,
|
||||||
|
/**
|
||||||
|
* @brief Minimum current threshold in mA for end-position detection.
|
||||||
|
*/
|
||||||
|
REG_HOLDING_END_CURRENT_THRESHOLD_OPEN_MA = 0x0003,
|
||||||
|
/**
|
||||||
|
* @brief Minimum current threshold in mA for end-position detection during
|
||||||
|
* closing.
|
||||||
|
*/
|
||||||
|
REG_HOLDING_END_CURRENT_THRESHOLD_CLOSE_MA = 0x0004,
|
||||||
|
/**
|
||||||
|
* @brief Current threshold in mA for obstacle detection during opening.
|
||||||
|
*/
|
||||||
|
REG_HOLDING_OBSTACLE_THRESHOLD_OPEN_MA = 0x0005,
|
||||||
|
/**
|
||||||
|
* @brief Current threshold in mA for obstacle detection during closing.
|
||||||
|
*/
|
||||||
|
REG_HOLDING_OBSTACLE_THRESHOLD_CLOSE_MA = 0x0006,
|
||||||
|
/**
|
||||||
|
* @brief Bitmask for reading and writing digital outputs. Bit 0: Output 1,
|
||||||
|
* Bit 1: Output 2. 1=ON, 0=OFF.
|
||||||
|
*/
|
||||||
|
REG_HOLDING_DIGITAL_OUTPUTS_STATE = 0x0010,
|
||||||
|
/**
|
||||||
|
* @brief Fail-safe watchdog timeout in seconds. 0=Disabled.
|
||||||
|
*/
|
||||||
|
REG_HOLDING_WATCHDOG_TIMEOUT_S = 0x00F0,
|
||||||
|
/**
|
||||||
|
* @brief Writing 1 restarts the device.
|
||||||
|
*/
|
||||||
|
REG_HOLDING_DEVICE_RESET = 0x00F1,
|
||||||
|
/**
|
||||||
|
* @brief Command for firmware update.
|
||||||
|
* 1: Verify Chunk - Slave writes the last chunk to flash.
|
||||||
|
* 2: Finalize Update - Complete installation and restart.
|
||||||
|
*/
|
||||||
|
REG_HOLDING_FWU_COMMAND = 0x0100,
|
||||||
|
/**
|
||||||
|
* @brief Lower 16 bits of the 32-bit offset for the next firmware update
|
||||||
|
* chunk.
|
||||||
|
*/
|
||||||
|
REG_HOLDING_FWU_CHUNK_OFFSET_LOW = 0x0101,
|
||||||
|
/**
|
||||||
|
* @brief Upper 16 bits of the 32-bit offset for the next firmware update
|
||||||
|
* chunk.
|
||||||
|
*/
|
||||||
|
REG_HOLDING_FWU_CHUNK_OFFSET_HIGH = 0x0102,
|
||||||
|
/**
|
||||||
|
* @brief Size of the next firmware update chunk in bytes (max. 256).
|
||||||
|
*/
|
||||||
|
REG_HOLDING_FWU_CHUNK_SIZE = 0x0103,
|
||||||
|
/**
|
||||||
|
* @brief Start address of the 256-byte buffer for firmware update data.
|
||||||
|
*/
|
||||||
|
REG_HOLDING_FWU_DATA_BUFFER = 0x0180,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initializes the Modbus server.
|
||||||
|
*
|
||||||
|
* This function sets up the Modbus RTU server interface, loads saved settings
|
||||||
|
* (baudrate, unit ID), and starts listening for requests.
|
||||||
|
*
|
||||||
|
* @return 0 on success, or a negative error code on failure.
|
||||||
|
*/
|
||||||
|
int modbus_server_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reconfigures the Modbus server at runtime.
|
||||||
|
*
|
||||||
|
* Updates the baudrate and unit ID of the server. If the reconfiguration
|
||||||
|
* fails, the settings are saved and will be applied after a device reset.
|
||||||
|
*
|
||||||
|
* @param baudrate The new baudrate to set.
|
||||||
|
* @param unit_id The new Modbus unit ID (slave address).
|
||||||
|
* @return 0 on success, or a negative error code if immediate reconfiguration
|
||||||
|
* fails. Returns 0 even on failure if settings could be saved for the next
|
||||||
|
* boot.
|
||||||
|
*/
|
||||||
|
int modbus_reconfigure(uint32_t baudrate, uint8_t unit_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the current baudrate of the Modbus server.
|
||||||
|
*
|
||||||
|
* @return The current baudrate.
|
||||||
|
*/
|
||||||
|
uint32_t modbus_get_baudrate(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the current unit ID of the Modbus server.
|
||||||
|
*
|
||||||
|
* @return The current unit ID.
|
||||||
|
*/
|
||||||
|
uint8_t modbus_get_unit_id(void);
|
||||||
|
|
||||||
|
#endif // MODBUS_SERVER_H
|
||||||
194
software/include/lib/valve.h
Normal file
194
software/include/lib/valve.h
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
#ifndef VALVE_H
|
||||||
|
#define VALVE_H
|
||||||
|
|
||||||
|
#include <zephyr/drivers/gpio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file valve.h
|
||||||
|
* @brief API for controlling the motorized valve.
|
||||||
|
*
|
||||||
|
* This library provides functions to initialize, open, close, and stop the
|
||||||
|
* valve. It also allows getting the valve's state and movement status, and
|
||||||
|
* configuring the maximum opening and closing times.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define VALVE_CHANNEL_OPEN 0
|
||||||
|
#define VALVE_CHANNEL_CLOSE 1
|
||||||
|
#define VALVE_CURRENT_CHECK_INTERVAL K_MSEC(CONFIG_VALVE_INTERVALL_CURRENT_CHECK_MS)
|
||||||
|
#define VALVE_INITIAL_CURRENT_CHECK_INTERVAL K_MSEC(CONFIG_VALVE_INITIAL_INTERVALL_CURRENT_CHECK_MS)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Represents the static state of the valve (open or closed).
|
||||||
|
*/
|
||||||
|
enum valve_state {
|
||||||
|
VALVE_STATE_CLOSED, /**< The valve is fully closed. */
|
||||||
|
VALVE_STATE_OPEN, /**< The valve is fully open. */
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Represents the dynamic movement status of the valve.
|
||||||
|
*/
|
||||||
|
enum valve_movement {
|
||||||
|
VALVE_MOVEMENT_IDLE, /**< The valve is not moving. */
|
||||||
|
VALVE_MOVEMENT_OPENING, /**< The valve is currently opening. */
|
||||||
|
VALVE_MOVEMENT_CLOSING, /**< The valve is currently closing. */
|
||||||
|
VALVE_MOVEMENT_ERROR /**< An error occurred during movement. */
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initializes the valve control system.
|
||||||
|
*
|
||||||
|
* Configures the GPIOs and loads saved settings for timeouts.
|
||||||
|
* This function must be called before any other valve functions.
|
||||||
|
*
|
||||||
|
* @return 0 on success, or a negative error code on failure.
|
||||||
|
*/
|
||||||
|
int valve_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Starts opening the valve.
|
||||||
|
*
|
||||||
|
* The valve will open for the configured maximum opening time.
|
||||||
|
*/
|
||||||
|
void valve_open(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Starts closing the valve.
|
||||||
|
*
|
||||||
|
* The valve will close for the configured maximum closing time.
|
||||||
|
*/
|
||||||
|
void valve_close(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Stops any ongoing valve movement immediately.
|
||||||
|
*/
|
||||||
|
void valve_stop(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the current static state of the valve.
|
||||||
|
*
|
||||||
|
* @return The current valve state (VALVE_STATE_CLOSED or VALVE_STATE_OPEN).
|
||||||
|
*/
|
||||||
|
enum valve_state valve_get_state(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the current movement status of the valve.
|
||||||
|
*
|
||||||
|
* @return The current movement status.
|
||||||
|
*/
|
||||||
|
enum valve_movement valve_get_movement(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the maximum time for the valve to open.
|
||||||
|
*
|
||||||
|
* @param seconds The timeout in seconds.
|
||||||
|
*/
|
||||||
|
void valve_set_max_open_time(uint16_t seconds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the maximum time for the valve to close.
|
||||||
|
*
|
||||||
|
* @param seconds The timeout in seconds.
|
||||||
|
*/
|
||||||
|
void valve_set_max_close_time(uint16_t seconds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the current threshold for end-position detection during opening.
|
||||||
|
*
|
||||||
|
* @param current_ma The current threshold in milliamps.
|
||||||
|
*/
|
||||||
|
void valve_set_end_current_threshold_open(uint16_t current_ma);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the current threshold for end-position detection during closing.
|
||||||
|
*
|
||||||
|
* @param current_ma The current threshold in milliamps.
|
||||||
|
*/
|
||||||
|
void valve_set_end_current_threshold_close(uint16_t current_ma);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the current threshold for end-position detection during opening.
|
||||||
|
*
|
||||||
|
* @return The current threshold in milliamps.
|
||||||
|
*/
|
||||||
|
uint16_t valve_get_end_current_threshold_open(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the current threshold for end-position detection during closing.
|
||||||
|
*
|
||||||
|
* @return The current threshold in milliamps.
|
||||||
|
*/
|
||||||
|
uint16_t valve_get_end_current_threshold_close(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the configured maximum opening time.
|
||||||
|
*
|
||||||
|
* @return The timeout in seconds.
|
||||||
|
*/
|
||||||
|
uint16_t valve_get_max_open_time(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the configured maximum closing time.
|
||||||
|
*
|
||||||
|
* @return The timeout in seconds.
|
||||||
|
*/
|
||||||
|
uint16_t valve_get_max_close_time(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the current drawn by the valve motor during opening.
|
||||||
|
*
|
||||||
|
* @return The motor current in milliamps.
|
||||||
|
*/
|
||||||
|
int32_t valve_get_opening_current(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the current drawn by the valve motor during closing.
|
||||||
|
*
|
||||||
|
* @return The motor current in milliamps.
|
||||||
|
*/
|
||||||
|
int32_t valve_get_closing_current(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the temperature of the valve motor driver.
|
||||||
|
*
|
||||||
|
* @return The temperature in degrees Celsius.
|
||||||
|
*/
|
||||||
|
int32_t valve_get_vnd_temp(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the voltage supplied to the valve motor driver.
|
||||||
|
*
|
||||||
|
* @return The voltage in millivolts.
|
||||||
|
*/
|
||||||
|
int32_t valve_get_vnd_voltage(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the current threshold for obstacle detection during opening.
|
||||||
|
*
|
||||||
|
* @param current_ma The current threshold in milliamps.
|
||||||
|
*/
|
||||||
|
void valve_set_obstacle_threshold_open(uint16_t current_ma);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the current threshold for obstacle detection during closing.
|
||||||
|
*
|
||||||
|
* @param current_ma The current threshold in milliamps.
|
||||||
|
*/
|
||||||
|
void valve_set_obstacle_threshold_close(uint16_t current_ma);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the current threshold for obstacle detection during opening.
|
||||||
|
*
|
||||||
|
* @return The current threshold in milliamps.
|
||||||
|
*/
|
||||||
|
uint16_t valve_get_obstacle_threshold_open(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the current threshold for obstacle detection during closing.
|
||||||
|
*
|
||||||
|
* @return The current threshold in milliamps.
|
||||||
|
*/
|
||||||
|
uint16_t valve_get_obstacle_threshold_close(void);
|
||||||
|
|
||||||
|
#endif // VALVE_H
|
||||||
6
software/lib/CMakeLists.txt
Normal file
6
software/lib/CMakeLists.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
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)
|
||||||
|
add_subdirectory_ifdef(CONFIG_SHELL_VALVE shell_valve)
|
||||||
9
software/lib/Kconfig
Normal file
9
software/lib/Kconfig
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
menu "Irrigation system software libraries"
|
||||||
|
|
||||||
|
rsource "fwu/Kconfig"
|
||||||
|
rsource "modbus_server/Kconfig"
|
||||||
|
rsource "valve/Kconfig"
|
||||||
|
rsource "shell_system/Kconfig"
|
||||||
|
rsource "shell_modbus/Kconfig"
|
||||||
|
rsource "shell_valve/Kconfig"
|
||||||
|
endmenu
|
||||||
1
software/lib/fwu/CMakeLists.txt
Normal file
1
software/lib/fwu/CMakeLists.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
zephyr_library_sources(fwu.c)
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
config FWU
|
config LIB_FWU
|
||||||
bool "Enable Firmware Update Library"
|
bool "Enable Firmware Update Library"
|
||||||
default y
|
default y
|
||||||
help
|
help
|
||||||
Enable the Firmware Update module.
|
Enable the Firmware Update Library.
|
||||||
64
software/lib/fwu/fwu.c
Normal file
64
software/lib/fwu/fwu.c
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
/**
|
||||||
|
* @file fwu.c
|
||||||
|
* @brief Implementation of the Firmware Update (FWU) library.
|
||||||
|
*
|
||||||
|
* This file implements the logic for receiving a new firmware image in chunks
|
||||||
|
* over Modbus. It maintains a buffer for the incoming data, calculates the CRC
|
||||||
|
* of the received chunk, and handles commands to verify the chunk and finalize
|
||||||
|
* the update process. The actual writing to flash is simulated.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
#include <zephyr/sys/byteorder.h>
|
||||||
|
#include <zephyr/sys/crc.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]; // Buffer to store incoming
|
||||||
|
// firmware data chunks
|
||||||
|
static uint32_t fwu_chunk_offset = 0; // Offset for the current firmware chunk in the overall image
|
||||||
|
static uint16_t fwu_chunk_size = 0; // Size of the current firmware chunk
|
||||||
|
static uint16_t fwu_last_chunk_crc = 0; // CRC16 of the last received firmware chunk
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
1
software/lib/modbus_server/CMakeLists.txt
Normal file
1
software/lib/modbus_server/CMakeLists.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
zephyr_library_sources(modbus_server.c)
|
||||||
5
software/lib/modbus_server/Kconfig
Normal file
5
software/lib/modbus_server/Kconfig
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
config LIB_MODBUS_SERVER
|
||||||
|
bool "Enable Modbus Server Library"
|
||||||
|
default y
|
||||||
|
help
|
||||||
|
Enable the Modbus Server Library.
|
||||||
295
software/lib/modbus_server/modbus_server.c
Normal file
295
software/lib/modbus_server/modbus_server.c
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
/**
|
||||||
|
* @file modbus_server.c
|
||||||
|
* @brief Modbus RTU server implementation for the irrigation system slave node.
|
||||||
|
*
|
||||||
|
* This file implements the Modbus server logic, including register callbacks,
|
||||||
|
* watchdog handling, and dynamic reconfiguration. It interfaces with other
|
||||||
|
* libraries like valve control, ADC sensors, and firmware updates.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <zephyr/device.h>
|
||||||
|
#include <zephyr/drivers/misc/vnd7050aj/vnd7050aj.h>
|
||||||
|
#include <zephyr/drivers/uart.h>
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
#include <zephyr/modbus/modbus.h>
|
||||||
|
#include <zephyr/settings/settings.h>
|
||||||
|
#include <zephyr/sys/reboot.h>
|
||||||
|
#include <zephyr/usb/usb_device.h>
|
||||||
|
#include <app_version.h>
|
||||||
|
#include <lib/fwu.h>
|
||||||
|
#include <lib/modbus_server.h>
|
||||||
|
#include <lib/valve.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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Timer handler for the Modbus watchdog.
|
||||||
|
*
|
||||||
|
* This function is called when the watchdog timer expires, indicating a loss
|
||||||
|
* of communication with the Modbus master. It triggers a fail-safe action,
|
||||||
|
* which is to close the valve.
|
||||||
|
*
|
||||||
|
* @param timer_id Pointer to the timer instance.
|
||||||
|
*/
|
||||||
|
static void watchdog_timer_handler(struct k_timer *timer_id)
|
||||||
|
{
|
||||||
|
LOG_WRN("Modbus watchdog expired! Closing valve as a fail-safe.");
|
||||||
|
valve_close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Resets the Modbus watchdog timer.
|
||||||
|
*
|
||||||
|
* This function should be called upon receiving any valid Modbus request
|
||||||
|
* to prevent the watchdog from expiring.
|
||||||
|
*/
|
||||||
|
static inline void reset_watchdog(void)
|
||||||
|
{
|
||||||
|
if (watchdog_timeout_s > 0) {
|
||||||
|
k_timer_start(&watchdog_timer, K_SECONDS(watchdog_timeout_s), K_NO_WAIT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Callback for reading Modbus holding registers.
|
||||||
|
*
|
||||||
|
* @param addr Register address.
|
||||||
|
* @param reg Pointer to store the read value.
|
||||||
|
* @return 0 on success.
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
case REG_HOLDING_END_CURRENT_THRESHOLD_OPEN_MA:
|
||||||
|
*reg = valve_get_end_current_threshold_open();
|
||||||
|
break;
|
||||||
|
case REG_HOLDING_END_CURRENT_THRESHOLD_CLOSE_MA:
|
||||||
|
*reg = valve_get_end_current_threshold_close();
|
||||||
|
break;
|
||||||
|
case REG_HOLDING_OBSTACLE_THRESHOLD_OPEN_MA:
|
||||||
|
*reg = valve_get_obstacle_threshold_open();
|
||||||
|
break;
|
||||||
|
case REG_HOLDING_OBSTACLE_THRESHOLD_CLOSE_MA:
|
||||||
|
*reg = valve_get_obstacle_threshold_close();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
*reg = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Callback for writing Modbus holding registers.
|
||||||
|
*
|
||||||
|
* @param addr Register address.
|
||||||
|
* @param reg Value to write.
|
||||||
|
* @return 0 on success.
|
||||||
|
*/
|
||||||
|
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_END_CURRENT_THRESHOLD_OPEN_MA:
|
||||||
|
valve_set_end_current_threshold_open(reg);
|
||||||
|
break;
|
||||||
|
case REG_HOLDING_END_CURRENT_THRESHOLD_CLOSE_MA:
|
||||||
|
valve_set_end_current_threshold_close(reg);
|
||||||
|
break;
|
||||||
|
case REG_HOLDING_OBSTACLE_THRESHOLD_OPEN_MA:
|
||||||
|
valve_set_obstacle_threshold_open(reg);
|
||||||
|
break;
|
||||||
|
case REG_HOLDING_OBSTACLE_THRESHOLD_CLOSE_MA:
|
||||||
|
valve_set_obstacle_threshold_close(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Callback for reading Modbus input registers.
|
||||||
|
*
|
||||||
|
* @param addr Register address.
|
||||||
|
* @param reg Pointer to store the read value.
|
||||||
|
* @return 0 on success.
|
||||||
|
*/
|
||||||
|
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_OPEN_CURRENT_MA:
|
||||||
|
*reg = (uint16_t)valve_get_opening_current();
|
||||||
|
break;
|
||||||
|
case REG_INPUT_MOTOR_CLOSE_CURRENT_MA:
|
||||||
|
*reg = (uint16_t)valve_get_closing_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 = (uint16_t)valve_get_vnd_voltage();
|
||||||
|
break;
|
||||||
|
case REG_INPUT_FWU_LAST_CHUNK_CRC:
|
||||||
|
*reg = fwu_get_last_chunk_crc();
|
||||||
|
break;
|
||||||
|
case REG_INPUT_FIRMWARE_VERSION_MAJOR_MINOR:
|
||||||
|
*reg = (APP_VERSION_MAJOR << 8) | APP_VERSION_MINOR;
|
||||||
|
break;
|
||||||
|
case REG_INPUT_FIRMWARE_VERSION_PATCH:
|
||||||
|
*reg = APP_PATCHLEVEL;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
*reg = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct modbus_user_callbacks mbs_cbs = {
|
||||||
|
// Modbus server callback functions
|
||||||
|
.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;
|
||||||
|
}
|
||||||
1
software/lib/shell_modbus/CMakeLists.txt
Normal file
1
software/lib/shell_modbus/CMakeLists.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
zephyr_library_sources(shell_modbus.c)
|
||||||
7
software/lib/shell_modbus/Kconfig
Normal file
7
software/lib/shell_modbus/Kconfig
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
config SHELL_MODBUS
|
||||||
|
bool "Enable Shell Modbus"
|
||||||
|
default n
|
||||||
|
depends on SHELL
|
||||||
|
depends on LIB_MODBUS_SERVER
|
||||||
|
help
|
||||||
|
Enable the modbus shell commands.
|
||||||
119
software/lib/shell_modbus/shell_modbus.c
Normal file
119
software/lib/shell_modbus/shell_modbus.c
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
/**
|
||||||
|
* @file shell_modbus.c
|
||||||
|
* @brief Provides shell commands for Modbus and valve configuration.
|
||||||
|
*
|
||||||
|
* This file implements a set of commands for the Zephyr shell to allow
|
||||||
|
* runtime configuration of the Modbus server (baudrate, slave ID) and the
|
||||||
|
* valve (max opening/closing times). The settings are persisted to non-volatile
|
||||||
|
* storage.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <zephyr/shell/shell.h>
|
||||||
|
#include <lib/modbus_server.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Shell command to set the Modbus baudrate.
|
||||||
|
*
|
||||||
|
* @param sh The shell instance.
|
||||||
|
* @param argc Argument count.
|
||||||
|
* @param argv Argument values.
|
||||||
|
* @return 0 on success, -EINVAL on error.
|
||||||
|
*/
|
||||||
|
static int cmd_modbus_setb(const struct shell *sh, size_t argc, char **argv)
|
||||||
|
{
|
||||||
|
if (argc != 2) {
|
||||||
|
shell_error(sh, "Usage: setb <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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Shell command to set the Modbus slave ID.
|
||||||
|
*
|
||||||
|
* @param sh The shell instance.
|
||||||
|
* @param argc Argument count.
|
||||||
|
* @param argv Argument values.
|
||||||
|
* @return 0 on success, -EINVAL on error.
|
||||||
|
*/
|
||||||
|
static int cmd_modbus_setid(const struct shell *sh, size_t argc, char **argv)
|
||||||
|
{
|
||||||
|
if (argc != 2) {
|
||||||
|
shell_error(sh, "Usage: setid <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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Shell command to show the current Modbus configuration.
|
||||||
|
*
|
||||||
|
* @param sh The shell instance.
|
||||||
|
* @param argc Argument count.
|
||||||
|
* @param argv Argument values.
|
||||||
|
* @return 0 on success.
|
||||||
|
*/
|
||||||
|
static int cmd_modbus_show(const struct shell *sh, size_t argc, char **argv)
|
||||||
|
{
|
||||||
|
const int label_width = 15;
|
||||||
|
|
||||||
|
shell_print(sh, "Modbus Settings:");
|
||||||
|
shell_print(sh, "%*s %u", label_width, "Baudrate:", modbus_get_baudrate());
|
||||||
|
shell_print(sh, "%*s %u", label_width, "Slave ID:", modbus_get_unit_id());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
SHELL_STATIC_SUBCMD_SET_CREATE(sub_modbus_cmds,
|
||||||
|
SHELL_CMD(setb, NULL, "Set Modbus baudrate", cmd_modbus_setb),
|
||||||
|
SHELL_CMD(setid, NULL, "Set Modbus slave ID", cmd_modbus_setid),
|
||||||
|
SHELL_CMD(show, NULL, "Show Modbus configuration", cmd_modbus_show),
|
||||||
|
SHELL_SUBCMD_SET_END);
|
||||||
|
|
||||||
|
SHELL_CMD_REGISTER(modbus, &sub_modbus_cmds, "Modbus commands", NULL);
|
||||||
1
software/lib/shell_system/CMakeLists.txt
Normal file
1
software/lib/shell_system/CMakeLists.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
zephyr_library_sources(shell_system.c)
|
||||||
5
software/lib/shell_system/Kconfig
Normal file
5
software/lib/shell_system/Kconfig
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
config SHELL_SYSTEM
|
||||||
|
bool "Enable Shell System"
|
||||||
|
default n
|
||||||
|
help
|
||||||
|
Enable the system commands.
|
||||||
31
software/lib/shell_system/shell_system.c
Normal file
31
software/lib/shell_system/shell_system.c
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* @file shell_system.c
|
||||||
|
* @brief Provides basic system-level shell commands.
|
||||||
|
*
|
||||||
|
* This file implements essential system commands for the Zephyr shell,
|
||||||
|
* such as rebooting the device.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <zephyr/shell/shell.h>
|
||||||
|
#include <zephyr/sys/reboot.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Shell command to reset the system.
|
||||||
|
*
|
||||||
|
* This command performs a warm reboot of the device after a short delay
|
||||||
|
* to ensure the shell message is printed.
|
||||||
|
*
|
||||||
|
* @param sh The shell instance.
|
||||||
|
* @param argc Argument count.
|
||||||
|
* @param argv Argument values.
|
||||||
|
* @return 0 on success.
|
||||||
|
*/
|
||||||
|
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);
|
||||||
1
software/lib/shell_valve/CMakeLists.txt
Normal file
1
software/lib/shell_valve/CMakeLists.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
zephyr_library_sources(shell_valve.c)
|
||||||
7
software/lib/shell_valve/Kconfig
Normal file
7
software/lib/shell_valve/Kconfig
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
config SHELL_VALVE
|
||||||
|
bool "Shell Valve commands"
|
||||||
|
default n
|
||||||
|
depends on SHELL
|
||||||
|
depends on LIB_VALVE
|
||||||
|
help
|
||||||
|
Enable the valve shell commands.
|
||||||
136
software/lib/shell_valve/shell_valve.c
Normal file
136
software/lib/shell_valve/shell_valve.c
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
#include <zephyr/kernel.h>
|
||||||
|
#include <zephyr/shell/shell.h>
|
||||||
|
#include <lib/valve.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
static int cmd_valve_set_open_t(const struct shell *sh, size_t argc, char **argv)
|
||||||
|
{
|
||||||
|
if (argc != 2) {
|
||||||
|
shell_print(sh, "Usage: valve set_open_t <seconds>");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t seconds = (uint16_t)atoi(argv[1]);
|
||||||
|
valve_set_max_open_time(seconds);
|
||||||
|
shell_print(sh, "Max open time set to %u seconds.", seconds);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cmd_valve_set_close_t(const struct shell *sh, size_t argc, char **argv)
|
||||||
|
{
|
||||||
|
if (argc != 2) {
|
||||||
|
shell_print(sh, "Usage: valve set_close_t <seconds>");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t seconds = (uint16_t)atoi(argv[1]);
|
||||||
|
valve_set_max_close_time(seconds);
|
||||||
|
shell_print(sh, "Max close time set to %u seconds.", seconds);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cmd_valve_set_end_curr_open(const struct shell *sh, size_t argc, char **argv)
|
||||||
|
{
|
||||||
|
if (argc != 2) {
|
||||||
|
shell_print(sh, "Usage: valve set_end_curr_open <milliamps>");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t current_ma = (uint16_t)atoi(argv[1]);
|
||||||
|
valve_set_end_current_threshold_open(current_ma);
|
||||||
|
shell_print(sh, "End current threshold (open) set to %u mA.", current_ma);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cmd_valve_set_end_curr_close(const struct shell *sh, size_t argc, char **argv)
|
||||||
|
{
|
||||||
|
if (argc != 2) {
|
||||||
|
shell_print(sh, "Usage: valve set_end_curr_close <milliamps>");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t current_ma = (uint16_t)atoi(argv[1]);
|
||||||
|
valve_set_end_current_threshold_close(current_ma);
|
||||||
|
shell_print(sh, "End current threshold (close) set to %u mA.", current_ma);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cmd_valve_set_obstacle_open(const struct shell *sh, size_t argc, char **argv)
|
||||||
|
{
|
||||||
|
if (argc != 2) {
|
||||||
|
shell_print(sh, "Usage: valve set_obstacle_open <milliamps>");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t current_ma = (uint16_t)atoi(argv[1]);
|
||||||
|
valve_set_obstacle_threshold_open(current_ma);
|
||||||
|
shell_print(sh, "Obstacle threshold (open) set to %u mA.", current_ma);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cmd_valve_set_obstacle_close(const struct shell *sh, size_t argc, char **argv)
|
||||||
|
{
|
||||||
|
if (argc != 2) {
|
||||||
|
shell_print(sh, "Usage: valve set_obstacle_close <milliamps>");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t current_ma = (uint16_t)atoi(argv[1]);
|
||||||
|
valve_set_obstacle_threshold_close(current_ma);
|
||||||
|
shell_print(sh, "Obstacle threshold (close) set to %u mA.", current_ma);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cmd_valve_show(const struct shell *sh, size_t argc, char **argv)
|
||||||
|
{
|
||||||
|
const int label_width = 30;
|
||||||
|
|
||||||
|
shell_print(sh, "Valve Settings:");
|
||||||
|
shell_print(sh, "%*s %u s", label_width, "Max Open Time:", valve_get_max_open_time());
|
||||||
|
shell_print(sh, "%*s %u s", label_width, "Max Close Time:", valve_get_max_close_time());
|
||||||
|
shell_print(sh,
|
||||||
|
"%*s %u mA",
|
||||||
|
label_width,
|
||||||
|
"End Current Threshold (Open):",
|
||||||
|
valve_get_end_current_threshold_open());
|
||||||
|
shell_print(sh,
|
||||||
|
"%*s %u mA",
|
||||||
|
label_width,
|
||||||
|
"End Current Threshold (Close):",
|
||||||
|
valve_get_end_current_threshold_close());
|
||||||
|
shell_print(sh,
|
||||||
|
"%*s %u mA",
|
||||||
|
label_width,
|
||||||
|
"Obstacle Threshold (Open):",
|
||||||
|
valve_get_obstacle_threshold_open());
|
||||||
|
shell_print(sh,
|
||||||
|
"%*s %u mA",
|
||||||
|
label_width,
|
||||||
|
"Obstacle Threshold (Close):",
|
||||||
|
valve_get_obstacle_threshold_close());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
SHELL_STATIC_SUBCMD_SET_CREATE(sub_valve_settings,
|
||||||
|
SHELL_CMD(set_open_t, NULL, "Set max open time (seconds)", cmd_valve_set_open_t),
|
||||||
|
SHELL_CMD(set_close_t, NULL, "Set max close time (seconds)", cmd_valve_set_close_t),
|
||||||
|
SHELL_CMD(set_end_curr_open,
|
||||||
|
NULL,
|
||||||
|
"Set end current threshold for opening (mA)",
|
||||||
|
cmd_valve_set_end_curr_open),
|
||||||
|
SHELL_CMD(set_end_curr_close,
|
||||||
|
NULL,
|
||||||
|
"Set end current threshold for closing (mA)",
|
||||||
|
cmd_valve_set_end_curr_close),
|
||||||
|
SHELL_CMD(set_obstacle_open,
|
||||||
|
NULL,
|
||||||
|
"Set obstacle threshold for opening (mA)",
|
||||||
|
cmd_valve_set_obstacle_open),
|
||||||
|
SHELL_CMD(set_obstacle_close,
|
||||||
|
NULL,
|
||||||
|
"Set obstacle threshold for closing (mA)",
|
||||||
|
cmd_valve_set_obstacle_close),
|
||||||
|
SHELL_CMD(show, NULL, "Show valve configuration", cmd_valve_show),
|
||||||
|
SHELL_SUBCMD_SET_END);
|
||||||
|
|
||||||
|
SHELL_CMD_REGISTER(valve, &sub_valve_settings, "Valve commands", NULL);
|
||||||
1
software/lib/valve/CMakeLists.txt
Normal file
1
software/lib/valve/CMakeLists.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
zephyr_library_sources(valve.c)
|
||||||
45
software/lib/valve/Kconfig
Normal file
45
software/lib/valve/Kconfig
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
config LIB_VALVE
|
||||||
|
bool "Enable Valve Library"
|
||||||
|
default y
|
||||||
|
help
|
||||||
|
Enable the Valve Library.
|
||||||
|
|
||||||
|
if LIB_VALVE
|
||||||
|
config LOG_VALVE_LEVEL
|
||||||
|
int "Valve Log Level"
|
||||||
|
default 3
|
||||||
|
help
|
||||||
|
Set the log level for the Valve Library.
|
||||||
|
0 = None, 1 = Error, 2 = Warning, 3 = Info, 4 = Debug
|
||||||
|
|
||||||
|
config VALVE_INTERVALL_CURRENT_CHECK_MS
|
||||||
|
int "Interval Current Check (ms)"
|
||||||
|
default 100
|
||||||
|
help
|
||||||
|
Set the interval in milliseconds for checking the motor current
|
||||||
|
during valve operation. This is used to detect obstacles.
|
||||||
|
|
||||||
|
config VALVE_INITIAL_INTERVALL_CURRENT_CHECK_MS
|
||||||
|
int "Initial Current Check (ms)"
|
||||||
|
default 200
|
||||||
|
help
|
||||||
|
Set the initial delay in milliseconds before the first current check
|
||||||
|
after starting the valve operation. This allows the motor to stabilize.
|
||||||
|
|
||||||
|
config VALVE_OBSTACLE_THRESHOLD_OPEN_MA
|
||||||
|
int "Obstacle Threshold Open (mA)"
|
||||||
|
default 200
|
||||||
|
help
|
||||||
|
Set the current threshold in milliamps for obstacle detection
|
||||||
|
during valve opening. If the motor current exceeds this value,
|
||||||
|
an obstacle is detected and the valve stops.
|
||||||
|
|
||||||
|
config VALVE_OBSTACLE_THRESHOLD_CLOSE_MA
|
||||||
|
int "Obstacle Threshold Close (mA)"
|
||||||
|
default 200
|
||||||
|
help
|
||||||
|
Set the current threshold in milliamps for obstacle detection
|
||||||
|
during vaslve closing. If the motor current exceeds this value,
|
||||||
|
an obstacle is detected and the valve stops.
|
||||||
|
|
||||||
|
endif # LIB_VALVE
|
||||||
293
software/lib/valve/valve.c
Normal file
293
software/lib/valve/valve.c
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
/**
|
||||||
|
* @file valve.c
|
||||||
|
* @brief Implementation of the motorized valve control library.
|
||||||
|
*
|
||||||
|
* This file contains the logic for controlling a motorized valve using a
|
||||||
|
* VND7050AJ high-side driver. It uses a delayed work item to handle the
|
||||||
|
* safety timeouts for opening and closing operations.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <zephyr/device.h>
|
||||||
|
#include <zephyr/drivers/gpio.h>
|
||||||
|
#include <zephyr/drivers/misc/vnd7050aj/vnd7050aj.h>
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
#include <zephyr/settings/settings.h>
|
||||||
|
#include <lib/valve.h>
|
||||||
|
|
||||||
|
#define VND_NODE DT_ALIAS(vnd7050aj)
|
||||||
|
#if !DT_NODE_HAS_STATUS(VND_NODE, okay)
|
||||||
|
#error VND7050AJ node is not defined or enabled
|
||||||
|
#endif
|
||||||
|
const struct device *vnd7050aj_dev = DEVICE_DT_GET(VND_NODE);
|
||||||
|
|
||||||
|
LOG_MODULE_REGISTER(valve, CONFIG_LOG_VALVE_LEVEL);
|
||||||
|
|
||||||
|
static enum valve_state current_state = VALVE_STATE_OPEN;
|
||||||
|
static enum valve_movement current_movement = VALVE_MOVEMENT_IDLE;
|
||||||
|
static uint16_t max_opening_time_s = 10;
|
||||||
|
static uint16_t max_closing_time_s = 10;
|
||||||
|
static uint16_t end_current_threshold_open_ma = 10;
|
||||||
|
static uint16_t end_current_threshold_close_ma = 10;
|
||||||
|
static uint16_t obstacle_threshold_open_ma = CONFIG_VALVE_OBSTACLE_THRESHOLD_OPEN_MA;
|
||||||
|
static uint16_t obstacle_threshold_close_ma = CONFIG_VALVE_OBSTACLE_THRESHOLD_CLOSE_MA;
|
||||||
|
static struct k_work_delayable valve_work;
|
||||||
|
static struct k_timer movement_timer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Work handler for end position checks of the valve.
|
||||||
|
*
|
||||||
|
* This function is called periodically to check if the valve has reached its
|
||||||
|
* end position. It reads the current load on the motor and determines if the
|
||||||
|
* valve has reached its target position.
|
||||||
|
*
|
||||||
|
* @param work Pointer to the k_work item.
|
||||||
|
*/
|
||||||
|
static void valve_work_handler(struct k_work *work)
|
||||||
|
{
|
||||||
|
int current_ma = 0;
|
||||||
|
|
||||||
|
if (current_movement == VALVE_MOVEMENT_OPENING) {
|
||||||
|
vnd7050aj_read_load_current(vnd7050aj_dev, VALVE_CHANNEL_OPEN, ¤t_ma);
|
||||||
|
LOG_DBG("Current load during opening: %d mA", current_ma);
|
||||||
|
if (current_ma > obstacle_threshold_open_ma) {
|
||||||
|
LOG_ERR(
|
||||||
|
"Obstacle detected during opening (current: %d mA), stopping motor.",
|
||||||
|
current_ma);
|
||||||
|
current_movement = VALVE_MOVEMENT_ERROR;
|
||||||
|
valve_stop();
|
||||||
|
return;
|
||||||
|
} else if (current_ma > end_current_threshold_open_ma) {
|
||||||
|
k_work_schedule(&valve_work, VALVE_CURRENT_CHECK_INTERVAL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LOG_DBG("Valve finished opening");
|
||||||
|
} else if (current_movement == VALVE_MOVEMENT_CLOSING) {
|
||||||
|
vnd7050aj_read_load_current(vnd7050aj_dev, VALVE_CHANNEL_CLOSE, ¤t_ma);
|
||||||
|
LOG_DBG("Current load during closing: %d mA", current_ma);
|
||||||
|
if (current_ma > obstacle_threshold_close_ma) {
|
||||||
|
LOG_ERR(
|
||||||
|
"Obstacle detected during closing (current: %d mA), stopping motor.",
|
||||||
|
current_ma);
|
||||||
|
current_movement = VALVE_MOVEMENT_ERROR;
|
||||||
|
valve_stop();
|
||||||
|
return;
|
||||||
|
} else if (current_ma > end_current_threshold_close_ma) {
|
||||||
|
k_work_schedule(&valve_work, VALVE_CURRENT_CHECK_INTERVAL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
current_state = VALVE_STATE_CLOSED;
|
||||||
|
LOG_DBG("Valve finished closing");
|
||||||
|
}
|
||||||
|
current_movement = VALVE_MOVEMENT_IDLE;
|
||||||
|
|
||||||
|
valve_stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Timer handler for valve movement timeouts.
|
||||||
|
*
|
||||||
|
* This function is called when the maximum allowed time for valve movement
|
||||||
|
* (opening or closing) has been reached. It stops the valve motor, cancels
|
||||||
|
* any pending end-position checks, and sets the movement status to error.
|
||||||
|
*
|
||||||
|
* @param timer Pointer to the k_timer instance that expired.
|
||||||
|
*/
|
||||||
|
void movement_timeout_handler(struct k_timer *timer)
|
||||||
|
{
|
||||||
|
// Stop the end position check if the timer expires
|
||||||
|
k_work_cancel_delayable(&valve_work);
|
||||||
|
if (current_movement == VALVE_MOVEMENT_OPENING) {
|
||||||
|
LOG_WRN("Valve opening timeout reached, stopping motor.");
|
||||||
|
current_movement = VALVE_MOVEMENT_ERROR;
|
||||||
|
} else if (current_movement == VALVE_MOVEMENT_CLOSING) {
|
||||||
|
LOG_WRN("Valve closing timeout reached, stopping motor.");
|
||||||
|
current_movement = VALVE_MOVEMENT_ERROR;
|
||||||
|
}
|
||||||
|
vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_OPEN, false);
|
||||||
|
vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_CLOSE, false);
|
||||||
|
current_state = VALVE_STATE_CLOSED;
|
||||||
|
}
|
||||||
|
|
||||||
|
int valve_init(void)
|
||||||
|
{
|
||||||
|
if (!device_is_ready(vnd7050aj_dev)) {
|
||||||
|
LOG_ERR("VND7050AJ device is not ready");
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
k_work_init_delayable(&valve_work, valve_work_handler);
|
||||||
|
k_timer_init(&movement_timer, movement_timeout_handler, NULL);
|
||||||
|
|
||||||
|
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));
|
||||||
|
settings_load_one("valve/end_current_threshold_open",
|
||||||
|
&end_current_threshold_open_ma,
|
||||||
|
sizeof(end_current_threshold_open_ma));
|
||||||
|
settings_load_one("valve/end_current_threshold_close",
|
||||||
|
&end_current_threshold_close_ma,
|
||||||
|
sizeof(end_current_threshold_close_ma));
|
||||||
|
settings_load_one("valve/obstacle_threshold_open",
|
||||||
|
&obstacle_threshold_open_ma,
|
||||||
|
sizeof(obstacle_threshold_open_ma));
|
||||||
|
settings_load_one("valve/obstacle_threshold_close",
|
||||||
|
&obstacle_threshold_close_ma,
|
||||||
|
sizeof(obstacle_threshold_close_ma));
|
||||||
|
|
||||||
|
LOG_INF("Valve initialized: max_open=%us, max_close=%us, end_curr_open=%umA, "
|
||||||
|
"end_curr_close=%umA, obs_open=%umA, obs_close=%umA",
|
||||||
|
max_opening_time_s,
|
||||||
|
max_closing_time_s,
|
||||||
|
end_current_threshold_open_ma,
|
||||||
|
end_current_threshold_close_ma,
|
||||||
|
obstacle_threshold_open_ma,
|
||||||
|
obstacle_threshold_close_ma);
|
||||||
|
valve_close();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void valve_open(void)
|
||||||
|
{
|
||||||
|
LOG_DBG("Opening valve");
|
||||||
|
vnd7050aj_reset_fault(vnd7050aj_dev);
|
||||||
|
vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_CLOSE, false);
|
||||||
|
vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_OPEN, true);
|
||||||
|
current_state =
|
||||||
|
VALVE_STATE_OPEN; /* Security: assume valve open as soon as it starts opening */
|
||||||
|
current_movement = VALVE_MOVEMENT_OPENING;
|
||||||
|
if (max_opening_time_s > 0) {
|
||||||
|
k_timer_start(&movement_timer, K_SECONDS(max_opening_time_s), K_NO_WAIT);
|
||||||
|
}
|
||||||
|
k_work_schedule(&valve_work, VALVE_INITIAL_CURRENT_CHECK_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void valve_close(void)
|
||||||
|
{
|
||||||
|
LOG_DBG("Closing valve");
|
||||||
|
vnd7050aj_reset_fault(vnd7050aj_dev);
|
||||||
|
vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_OPEN, false);
|
||||||
|
vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_CLOSE, true);
|
||||||
|
if (max_closing_time_s > 0) {
|
||||||
|
k_timer_start(&movement_timer, K_SECONDS(max_closing_time_s), K_NO_WAIT);
|
||||||
|
}
|
||||||
|
current_movement = VALVE_MOVEMENT_CLOSING;
|
||||||
|
k_work_schedule(&valve_work, VALVE_INITIAL_CURRENT_CHECK_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void valve_stop(void)
|
||||||
|
{
|
||||||
|
k_work_cancel_delayable(&valve_work);
|
||||||
|
k_timer_stop(&movement_timer);
|
||||||
|
vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_OPEN, false);
|
||||||
|
vnd7050aj_set_output_state(vnd7050aj_dev, VALVE_CHANNEL_CLOSE, false);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
void valve_set_end_current_threshold_open(uint16_t current_ma)
|
||||||
|
{
|
||||||
|
end_current_threshold_open_ma = current_ma;
|
||||||
|
settings_save_one("valve/end_current_threshold_open",
|
||||||
|
&end_current_threshold_open_ma,
|
||||||
|
sizeof(end_current_threshold_open_ma));
|
||||||
|
}
|
||||||
|
|
||||||
|
void valve_set_end_current_threshold_close(uint16_t current_ma)
|
||||||
|
{
|
||||||
|
end_current_threshold_close_ma = current_ma;
|
||||||
|
settings_save_one("valve/end_current_threshold_close",
|
||||||
|
&end_current_threshold_close_ma,
|
||||||
|
sizeof(end_current_threshold_close_ma));
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t valve_get_end_current_threshold_open(void)
|
||||||
|
{
|
||||||
|
return end_current_threshold_open_ma;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t valve_get_end_current_threshold_close(void)
|
||||||
|
{
|
||||||
|
return end_current_threshold_close_ma;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t valve_get_opening_current(void)
|
||||||
|
{
|
||||||
|
int32_t current;
|
||||||
|
vnd7050aj_read_load_current(vnd7050aj_dev, VALVE_CHANNEL_OPEN, ¤t);
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t valve_get_closing_current(void)
|
||||||
|
{
|
||||||
|
int32_t current;
|
||||||
|
vnd7050aj_read_load_current(vnd7050aj_dev, VALVE_CHANNEL_CLOSE, ¤t);
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t valve_get_vnd_temp(void)
|
||||||
|
{
|
||||||
|
int32_t temp_c;
|
||||||
|
vnd7050aj_read_chip_temp(vnd7050aj_dev, &temp_c);
|
||||||
|
return temp_c;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t valve_get_vnd_voltage(void)
|
||||||
|
{
|
||||||
|
int32_t voltage_mv;
|
||||||
|
vnd7050aj_read_supply_voltage(vnd7050aj_dev, &voltage_mv);
|
||||||
|
return voltage_mv;
|
||||||
|
}
|
||||||
|
|
||||||
|
void valve_set_obstacle_threshold_open(uint16_t current_ma)
|
||||||
|
{
|
||||||
|
obstacle_threshold_open_ma = current_ma;
|
||||||
|
settings_save_one("valve/obstacle_threshold_open",
|
||||||
|
&obstacle_threshold_open_ma,
|
||||||
|
sizeof(obstacle_threshold_open_ma));
|
||||||
|
}
|
||||||
|
|
||||||
|
void valve_set_obstacle_threshold_close(uint16_t current_ma)
|
||||||
|
{
|
||||||
|
obstacle_threshold_close_ma = current_ma;
|
||||||
|
settings_save_one("valve/obstacle_threshold_close",
|
||||||
|
&obstacle_threshold_close_ma,
|
||||||
|
sizeof(obstacle_threshold_close_ma));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t valve_get_obstacle_threshold_open(void)
|
||||||
|
{
|
||||||
|
return obstacle_threshold_open_ma;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t valve_get_obstacle_threshold_close(void)
|
||||||
|
{
|
||||||
|
return obstacle_threshold_close_ma;
|
||||||
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.13.1)
|
|
||||||
include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE)
|
|
||||||
|
|
||||||
project(fwu)
|
|
||||||
|
|
||||||
target_sources(app PRIVATE src/fwu.c)
|
|
||||||
target_include_directories(app PUBLIC include)
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
#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
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
#include "fwu.h"
|
|
||||||
#include <zephyr/kernel.h>
|
|
||||||
#include <zephyr/sys/crc.h>
|
|
||||||
#include <zephyr/sys/byteorder.h>
|
|
||||||
#include <zephyr/logging/log.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;
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.13.1)
|
|
||||||
include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE)
|
|
||||||
|
|
||||||
project(modbus_server)
|
|
||||||
|
|
||||||
target_sources(app PRIVATE src/modbus_server.c)
|
|
||||||
target_include_directories(app PUBLIC include)
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
config MODBUS_SERVER
|
|
||||||
bool "Enable Modbus Server Library"
|
|
||||||
default y
|
|
||||||
help
|
|
||||||
Enable the Modbus Server module.
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
#ifndef MODBUS_SERVER_H
|
|
||||||
#define MODBUS_SERVER_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Modbus Input Register Addresses.
|
|
||||||
*/
|
|
||||||
enum {
|
|
||||||
/* Valve Control & Status */
|
|
||||||
REG_INPUT_VALVE_STATE_MOVEMENT = 0x0000,
|
|
||||||
REG_INPUT_MOTOR_CURRENT_MA = 0x0001,
|
|
||||||
/* Digital Inputs */
|
|
||||||
REG_INPUT_DIGITAL_INPUTS_STATE = 0x0020,
|
|
||||||
REG_INPUT_BUTTON_EVENTS = 0x0021,
|
|
||||||
/* System Config & Status */
|
|
||||||
REG_INPUT_FIRMWARE_VERSION_MAJOR_MINOR = 0x00F0,
|
|
||||||
REG_INPUT_FIRMWARE_VERSION_PATCH = 0x00F1,
|
|
||||||
REG_INPUT_DEVICE_STATUS = 0x00F2,
|
|
||||||
REG_INPUT_UPTIME_SECONDS_LOW = 0x00F3,
|
|
||||||
REG_INPUT_UPTIME_SECONDS_HIGH = 0x00F4,
|
|
||||||
/* Firmware Update */
|
|
||||||
REG_INPUT_FWU_LAST_CHUNK_CRC = 0x0100,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Modbus Holding Register Addresses.
|
|
||||||
*/
|
|
||||||
enum {
|
|
||||||
/* Valve Control */
|
|
||||||
REG_HOLDING_VALVE_COMMAND = 0x0000,
|
|
||||||
REG_HOLDING_MAX_OPENING_TIME_S = 0x0001,
|
|
||||||
REG_HOLDING_MAX_CLOSING_TIME_S = 0x0002,
|
|
||||||
/* Digital Outputs */
|
|
||||||
REG_HOLDING_DIGITAL_OUTPUTS_STATE = 0x0010,
|
|
||||||
/* System Config */
|
|
||||||
REG_HOLDING_WATCHDOG_TIMEOUT_S = 0x00F0,
|
|
||||||
REG_HOLDING_DEVICE_RESET = 0x00F1,
|
|
||||||
/* Firmware Update */
|
|
||||||
REG_HOLDING_FWU_COMMAND = 0x0100,
|
|
||||||
REG_HOLDING_FWU_CHUNK_OFFSET_LOW = 0x0101,
|
|
||||||
REG_HOLDING_FWU_CHUNK_OFFSET_HIGH = 0x0102,
|
|
||||||
REG_HOLDING_FWU_CHUNK_SIZE = 0x0103,
|
|
||||||
REG_HOLDING_FWU_DATA_BUFFER = 0x0180,
|
|
||||||
};
|
|
||||||
|
|
||||||
int modbus_server_init(void);
|
|
||||||
int modbus_reconfigure(uint32_t baudrate, uint8_t unit_id);
|
|
||||||
uint32_t modbus_get_baudrate(void);
|
|
||||||
uint8_t modbus_get_unit_id(void);
|
|
||||||
|
|
||||||
#endif // MODBUS_SERVER_H
|
|
||||||
@@ -1,202 +0,0 @@
|
|||||||
#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 "modbus_server.h"
|
|
||||||
#include "valve.h"
|
|
||||||
#include "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_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 = 1;
|
|
||||||
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);
|
|
||||||
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;
|
|
||||||
return modbus_init_server(modbus_iface, server_param);
|
|
||||||
}
|
|
||||||
|
|
||||||
int modbus_reconfigure(uint32_t baudrate, uint8_t unit_id)
|
|
||||||
{
|
|
||||||
server_param.serial.baud = baudrate;
|
|
||||||
server_param.server.unit_id = unit_id;
|
|
||||||
|
|
||||||
int ret = modbus_init_server(modbus_iface, server_param);
|
|
||||||
|
|
||||||
if (ret == 0)
|
|
||||||
{
|
|
||||||
settings_save_one("modbus/baudrate", &baudrate, sizeof(baudrate));
|
|
||||||
settings_save_one("modbus/unit_id", &unit_id, sizeof(unit_id));
|
|
||||||
}
|
|
||||||
|
|
||||||
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; }
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.13.1)
|
|
||||||
include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE)
|
|
||||||
|
|
||||||
project(valve)
|
|
||||||
|
|
||||||
target_sources(app PRIVATE src/valve.c)
|
|
||||||
target_include_directories(app PUBLIC include)
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
config VALVE
|
|
||||||
bool "Enable Valve Library"
|
|
||||||
default y
|
|
||||||
help
|
|
||||||
Enable the Valve module.
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
#ifndef VALVE_H
|
|
||||||
#define VALVE_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
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
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
#include "valve.h"
|
|
||||||
#include <zephyr/kernel.h>
|
|
||||||
#include <zephyr/settings/settings.h>
|
|
||||||
#include <zephyr/logging/log.h>
|
|
||||||
|
|
||||||
LOG_MODULE_REGISTER(valve, LOG_LEVEL_INF);
|
|
||||||
|
|
||||||
static enum valve_state current_state = VALVE_STATE_CLOSED;
|
|
||||||
static enum valve_movement current_movement = VALVE_MOVEMENT_IDLE;
|
|
||||||
static uint16_t max_opening_time_s = 60;
|
|
||||||
static uint16_t max_closing_time_s = 60;
|
|
||||||
static struct k_work_delayable valve_work;
|
|
||||||
|
|
||||||
static void valve_work_handler(struct k_work *work)
|
|
||||||
{
|
|
||||||
if (current_movement == VALVE_MOVEMENT_OPENING) {
|
|
||||||
LOG_INF("Virtual valve finished opening");
|
|
||||||
} else if (current_movement == VALVE_MOVEMENT_CLOSING) {
|
|
||||||
current_state = VALVE_STATE_CLOSED;
|
|
||||||
LOG_INF("Virtual valve finished closing");
|
|
||||||
}
|
|
||||||
current_movement = VALVE_MOVEMENT_IDLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
void valve_init(void)
|
|
||||||
{
|
|
||||||
k_work_init_delayable(&valve_work, valve_work_handler);
|
|
||||||
settings_load_one("valve/max_open_time", &max_opening_time_s, sizeof(max_opening_time_s));
|
|
||||||
settings_load_one("valve/max_close_time", &max_closing_time_s, sizeof(max_closing_time_s));
|
|
||||||
}
|
|
||||||
|
|
||||||
void valve_open(void)
|
|
||||||
{
|
|
||||||
if (current_state == VALVE_STATE_CLOSED) {
|
|
||||||
current_state = VALVE_STATE_OPEN;
|
|
||||||
current_movement = VALVE_MOVEMENT_OPENING;
|
|
||||||
k_work_schedule(&valve_work, K_SECONDS(max_opening_time_s));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void valve_close(void)
|
|
||||||
{
|
|
||||||
if (current_state == VALVE_STATE_OPEN) {
|
|
||||||
current_movement = VALVE_MOVEMENT_CLOSING;
|
|
||||||
k_work_schedule(&valve_work, K_SECONDS(max_closing_time_s));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void valve_stop(void)
|
|
||||||
{
|
|
||||||
k_work_cancel_delayable(&valve_work);
|
|
||||||
current_movement = VALVE_MOVEMENT_IDLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum valve_state valve_get_state(void) { return current_state; }
|
|
||||||
enum valve_movement valve_get_movement(void) { return current_movement; }
|
|
||||||
uint16_t valve_get_motor_current(void) { return (current_movement != VALVE_MOVEMENT_IDLE) ? 150 : 10; }
|
|
||||||
|
|
||||||
void valve_set_max_open_time(uint16_t seconds) { max_opening_time_s = seconds; settings_save_one("valve/max_open_time", &max_opening_time_s, sizeof(max_opening_time_s)); }
|
|
||||||
void valve_set_max_close_time(uint16_t seconds) { max_closing_time_s = seconds; settings_save_one("valve/max_close_time", &max_closing_time_s, sizeof(max_closing_time_s)); }
|
|
||||||
uint16_t valve_get_max_open_time(void) { return max_opening_time_s; }
|
|
||||||
uint16_t valve_get_max_close_time(void) { return max_closing_time_s; }
|
|
||||||
@@ -9,9 +9,9 @@ from pymodbus.client import ModbusSerialClient
|
|||||||
from pymodbus.exceptions import ModbusException
|
from pymodbus.exceptions import ModbusException
|
||||||
|
|
||||||
# --- Register Definitions ---
|
# --- Register Definitions ---
|
||||||
# (omitted for brevity, no changes here)
|
|
||||||
REG_INPUT_VALVE_STATE_MOVEMENT = 0x0000
|
REG_INPUT_VALVE_STATE_MOVEMENT = 0x0000
|
||||||
REG_INPUT_MOTOR_CURRENT_MA = 0x0001
|
REG_INPUT_MOTOR_OPEN_CURRENT_MA = 0x0001
|
||||||
|
REG_INPUT_MOTOR_CLOSE_CURRENT_MA = 0x0002
|
||||||
REG_INPUT_DIGITAL_INPUTS_STATE = 0x0020
|
REG_INPUT_DIGITAL_INPUTS_STATE = 0x0020
|
||||||
REG_INPUT_BUTTON_EVENTS = 0x0021
|
REG_INPUT_BUTTON_EVENTS = 0x0021
|
||||||
REG_INPUT_FIRMWARE_VERSION_MAJOR_MINOR = 0x00F0
|
REG_INPUT_FIRMWARE_VERSION_MAJOR_MINOR = 0x00F0
|
||||||
@@ -19,10 +19,15 @@ REG_INPUT_FIRMWARE_VERSION_PATCH = 0x00F1
|
|||||||
REG_INPUT_DEVICE_STATUS = 0x00F2
|
REG_INPUT_DEVICE_STATUS = 0x00F2
|
||||||
REG_INPUT_UPTIME_SECONDS_LOW = 0x00F3
|
REG_INPUT_UPTIME_SECONDS_LOW = 0x00F3
|
||||||
REG_INPUT_UPTIME_SECONDS_HIGH = 0x00F4
|
REG_INPUT_UPTIME_SECONDS_HIGH = 0x00F4
|
||||||
|
REG_INPUT_SUPPLY_VOLTAGE_MV = 0x00F5
|
||||||
REG_INPUT_FWU_LAST_CHUNK_CRC = 0x0100
|
REG_INPUT_FWU_LAST_CHUNK_CRC = 0x0100
|
||||||
REG_HOLDING_VALVE_COMMAND = 0x0000
|
REG_HOLDING_VALVE_COMMAND = 0x0000
|
||||||
REG_HOLDING_MAX_OPENING_TIME_S = 0x0001
|
REG_HOLDING_MAX_OPENING_TIME_S = 0x0001
|
||||||
REG_HOLDING_MAX_CLOSING_TIME_S = 0x0002
|
REG_HOLDING_MAX_CLOSING_TIME_S = 0x0002
|
||||||
|
REG_HOLDING_END_CURRENT_THRESHOLD_OPEN_MA = 0x0003
|
||||||
|
REG_HOLDING_END_CURRENT_THRESHOLD_CLOSE_MA = 0x0004
|
||||||
|
REG_HOLDING_OBSTACLE_THRESHOLD_OPEN_MA = 0x0005
|
||||||
|
REG_HOLDING_OBSTACLE_THRESHOLD_CLOSE_MA = 0x0006
|
||||||
REG_HOLDING_DIGITAL_OUTPUTS_STATE = 0x0010
|
REG_HOLDING_DIGITAL_OUTPUTS_STATE = 0x0010
|
||||||
REG_HOLDING_WATCHDOG_TIMEOUT_S = 0x00F0
|
REG_HOLDING_WATCHDOG_TIMEOUT_S = 0x00F0
|
||||||
REG_HOLDING_DEVICE_RESET = 0x00F1
|
REG_HOLDING_DEVICE_RESET = 0x00F1
|
||||||
@@ -75,21 +80,24 @@ def poll_status(slave_id, interval):
|
|||||||
# Attempt to connect
|
# Attempt to connect
|
||||||
if client.connect():
|
if client.connect():
|
||||||
reconnect_attempts = 0
|
reconnect_attempts = 0
|
||||||
new_data["error"] = None # Clear error on successful reconnect
|
with status_lock:
|
||||||
|
status_data["error"] = None # Clear error in status_data immediately
|
||||||
|
time.sleep(0.1) # Allow UI to refresh with cleared error
|
||||||
else:
|
else:
|
||||||
new_data["error"] = f"Connection lost. Attempting to reconnect ({reconnect_attempts}/{max_reconnect_attempts})..."
|
new_data["error"] = f"Connection lost. Attempting to reconnect ({reconnect_attempts}/{max_reconnect_attempts})..."
|
||||||
time.sleep(reconnect_delay)
|
time.sleep(reconnect_delay)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# If connected, try to read data
|
# If connected, try to read data
|
||||||
ir_valve = client.read_input_registers(REG_INPUT_VALVE_STATE_MOVEMENT, count=2, slave=slave_id)
|
ir_valve = client.read_input_registers(REG_INPUT_VALVE_STATE_MOVEMENT, count=1, slave=slave_id)
|
||||||
|
ir_current = client.read_input_registers(REG_INPUT_MOTOR_OPEN_CURRENT_MA, count=2, slave=slave_id)
|
||||||
ir_dig = client.read_input_registers(REG_INPUT_DIGITAL_INPUTS_STATE, count=2, slave=slave_id)
|
ir_dig = client.read_input_registers(REG_INPUT_DIGITAL_INPUTS_STATE, count=2, slave=slave_id)
|
||||||
ir_sys = client.read_input_registers(REG_INPUT_FIRMWARE_VERSION_MAJOR_MINOR, count=5, slave=slave_id)
|
ir_sys = client.read_input_registers(REG_INPUT_FIRMWARE_VERSION_MAJOR_MINOR, count=6, slave=slave_id)
|
||||||
hr_valve = client.read_holding_registers(REG_HOLDING_MAX_OPENING_TIME_S, count=2, slave=slave_id)
|
hr_valve = client.read_holding_registers(REG_HOLDING_MAX_OPENING_TIME_S, count=6, slave=slave_id)
|
||||||
hr_dig = client.read_holding_registers(REG_HOLDING_DIGITAL_OUTPUTS_STATE, count=1, 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)
|
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]:
|
for res in [ir_valve, ir_current, ir_dig, ir_sys, hr_valve, hr_dig, hr_sys]:
|
||||||
if res.isError():
|
if res.isError():
|
||||||
raise ModbusException(str(res))
|
raise ModbusException(str(res))
|
||||||
|
|
||||||
@@ -98,9 +106,14 @@ def poll_status(slave_id, interval):
|
|||||||
state_map = {0: "Closed", 1: "Open"}
|
state_map = {0: "Closed", 1: "Open"}
|
||||||
new_data["movement"] = movement_map.get(valve_state_raw >> 8, 'Unknown')
|
new_data["movement"] = movement_map.get(valve_state_raw >> 8, 'Unknown')
|
||||||
new_data["state"] = state_map.get(valve_state_raw & 0xFF, 'Unknown')
|
new_data["state"] = state_map.get(valve_state_raw & 0xFF, 'Unknown')
|
||||||
new_data["motor_current"] = f"{ir_valve.registers[1]} mA"
|
new_data["motor_current_open"] = f"{ir_current.registers[0]} mA"
|
||||||
|
new_data["motor_current_close"] = f"{ir_current.registers[1]} mA"
|
||||||
new_data["open_time"] = f"{hr_valve.registers[0]}s"
|
new_data["open_time"] = f"{hr_valve.registers[0]}s"
|
||||||
new_data["close_time"] = f"{hr_valve.registers[1]}s"
|
new_data["close_time"] = f"{hr_valve.registers[1]}s"
|
||||||
|
new_data["end_curr_open"] = f"{hr_valve.registers[2]}mA"
|
||||||
|
new_data["end_curr_close"] = f"{hr_valve.registers[3]}mA"
|
||||||
|
new_data["obstacle_open"] = f"{hr_valve.registers[4]}mA"
|
||||||
|
new_data["obstacle_close"] = f"{hr_valve.registers[5]}mA"
|
||||||
new_data["digital_inputs"] = f"0x{ir_dig.registers[0]:04X}"
|
new_data["digital_inputs"] = f"0x{ir_dig.registers[0]:04X}"
|
||||||
new_data["button_events"] = f"0x{ir_dig.registers[1]:04X}"
|
new_data["button_events"] = f"0x{ir_dig.registers[1]:04X}"
|
||||||
new_data["digital_outputs"] = f"0x{hr_dig.registers[0]:04X}"
|
new_data["digital_outputs"] = f"0x{hr_dig.registers[0]:04X}"
|
||||||
@@ -109,9 +122,11 @@ def poll_status(slave_id, interval):
|
|||||||
fw_minor = ir_sys.registers[0] & 0xFF
|
fw_minor = ir_sys.registers[0] & 0xFF
|
||||||
fw_patch = ir_sys.registers[1]
|
fw_patch = ir_sys.registers[1]
|
||||||
uptime_seconds = (ir_sys.registers[4] << 16) | ir_sys.registers[3]
|
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["firmware"] = f"v{fw_major}.{fw_minor}.{fw_patch}"
|
||||||
new_data["device_status"] = "OK" if ir_sys.registers[2] == 0 else "ERROR"
|
new_data["device_status"] = "OK" if ir_sys.registers[2] == 0 else "ERROR"
|
||||||
new_data["uptime"] = format_uptime(uptime_seconds)
|
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["watchdog"] = f"{hr_sys.registers[0]}s"
|
||||||
new_data["error"] = None # Clear any previous error on successful read
|
new_data["error"] = None # Clear any previous error on successful read
|
||||||
reconnect_attempts = 0 # Reset attempts on successful communication
|
reconnect_attempts = 0 # Reset attempts on successful communication
|
||||||
@@ -179,7 +194,7 @@ def file_browser(stdscr):
|
|||||||
selected_index = 0
|
selected_index = 0
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
stdscr.clear()
|
stdscr.erase()
|
||||||
h, w = stdscr.getmaxyx()
|
h, w = stdscr.getmaxyx()
|
||||||
stdscr.addstr(0, 0, f"Select Firmware File: {path}".ljust(w-1), curses.color_pair(2))
|
stdscr.addstr(0, 0, f"Select Firmware File: {path}".ljust(w-1), curses.color_pair(2))
|
||||||
|
|
||||||
@@ -223,7 +238,9 @@ def main_menu(stdscr, slave_id):
|
|||||||
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)
|
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))
|
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"]
|
menu = ["Open Valve", "Close Valve", "Stop Valve", "Settings", "Reset Node", "Firmware Update", "Exit"]
|
||||||
|
settings_menu = ["Set Max Open Time", "Set Max Close Time", "Set End Current Open", "Set End Current Close", "Set Obstacle Current Open", "Set Obstacle Current Close", "Set Watchdog", "Back"]
|
||||||
|
current_menu = menu
|
||||||
current_row_idx = 0
|
current_row_idx = 0
|
||||||
message, message_time = "", 0
|
message, message_time = "", 0
|
||||||
input_mode, input_prompt, input_str, input_target_reg = False, "", "", 0
|
input_mode, input_prompt, input_str, input_target_reg = False, "", "", 0
|
||||||
@@ -247,24 +264,31 @@ def main_menu(stdscr, slave_id):
|
|||||||
elif key == curses.KEY_BACKSPACE or key == 127: input_str = input_str[:-1]
|
elif key == curses.KEY_BACKSPACE or key == 127: input_str = input_str[:-1]
|
||||||
elif key != -1 and chr(key).isprintable(): input_str += chr(key)
|
elif key != -1 and chr(key).isprintable(): input_str += chr(key)
|
||||||
else:
|
else:
|
||||||
if key == curses.KEY_UP: current_row_idx = (current_row_idx - 1) % len(menu)
|
if key == curses.KEY_UP: current_row_idx = (current_row_idx - 1) % len(current_menu)
|
||||||
elif key == curses.KEY_DOWN: current_row_idx = (current_row_idx + 1) % len(menu)
|
elif key == curses.KEY_DOWN: current_row_idx = (current_row_idx + 1) % len(current_menu)
|
||||||
elif key == curses.KEY_ENTER or key in [10, 13]:
|
elif key == curses.KEY_ENTER or key in [10, 13]:
|
||||||
selected_option = menu[current_row_idx]
|
selected_option = current_menu[current_row_idx]
|
||||||
message_time = time.time()
|
message_time = time.time()
|
||||||
if selected_option == "Exit": stop_event.set(); continue
|
if selected_option == "Exit": stop_event.set(); continue
|
||||||
|
elif selected_option == "Back": current_menu = menu; current_row_idx = 0; continue
|
||||||
|
elif selected_option == "Settings": current_menu = settings_menu; current_row_idx = 0; continue
|
||||||
elif selected_option == "Open Valve": client.write_register(REG_HOLDING_VALVE_COMMAND, 1, slave=slave_id); message = "-> Sent OPEN command"
|
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 == "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 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:
|
elif selected_option == "Set Max Open Time":
|
||||||
bit = 0 if "1" in selected_option else 1
|
input_mode, input_prompt, input_target_reg = True, "Enter Max Open Time (s): ", REG_HOLDING_MAX_OPENING_TIME_S
|
||||||
try:
|
elif selected_option == "Set Max Close Time":
|
||||||
current_val = client.read_holding_registers(REG_HOLDING_DIGITAL_OUTPUTS_STATE, count=1, slave=slave_id).registers[0]
|
input_mode, input_prompt, input_target_reg = True, "Enter Max Close Time (s): ", REG_HOLDING_MAX_CLOSING_TIME_S
|
||||||
client.write_register(REG_HOLDING_DIGITAL_OUTPUTS_STATE, current_val ^ (1 << bit), slave=slave_id)
|
elif selected_option == "Set End Current Open":
|
||||||
message = f"-> Toggled Output {bit+1}"
|
input_mode, input_prompt, input_target_reg = True, "Enter End Current Threshold Open (mA): ", REG_HOLDING_END_CURRENT_THRESHOLD_OPEN_MA
|
||||||
except Exception as e: message = f"-> Error: {e}"
|
elif selected_option == "Set End Current Close":
|
||||||
|
input_mode, input_prompt, input_target_reg = True, "Enter End Current Threshold Close (mA): ", REG_HOLDING_END_CURRENT_THRESHOLD_CLOSE_MA
|
||||||
elif selected_option == "Set Watchdog":
|
elif selected_option == "Set Watchdog":
|
||||||
input_mode, input_prompt, input_target_reg = True, "Enter Watchdog Timeout (s): ", REG_HOLDING_WATCHDOG_TIMEOUT_S
|
input_mode, input_prompt, input_target_reg = True, "Enter Watchdog Timeout (s): ", REG_HOLDING_WATCHDOG_TIMEOUT_S
|
||||||
|
elif selected_option == "Set Obstacle Current Open":
|
||||||
|
input_mode, input_prompt, input_target_reg = True, "Enter Obstacle Threshold Open (mA): ", REG_HOLDING_OBSTACLE_THRESHOLD_OPEN_MA
|
||||||
|
elif selected_option == "Set Obstacle Current Close":
|
||||||
|
input_mode, input_prompt, input_target_reg = True, "Enter Obstacle Threshold Close (mA): ", REG_HOLDING_OBSTACLE_THRESHOLD_CLOSE_MA
|
||||||
elif selected_option == "Reset Node":
|
elif selected_option == "Reset Node":
|
||||||
try:
|
try:
|
||||||
client.write_register(REG_HOLDING_DEVICE_RESET, 1, slave=slave_id)
|
client.write_register(REG_HOLDING_DEVICE_RESET, 1, slave=slave_id)
|
||||||
@@ -278,7 +302,7 @@ def main_menu(stdscr, slave_id):
|
|||||||
else:
|
else:
|
||||||
message = "-> Firmware update cancelled."
|
message = "-> Firmware update cancelled."
|
||||||
|
|
||||||
stdscr.clear()
|
stdscr.erase()
|
||||||
if is_updating:
|
if is_updating:
|
||||||
with update_lock: prog, msg = update_status["progress"], update_status["message"]
|
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 - 1, w // 2 - 25, "FIRMWARE UPDATE IN PROGRESS", curses.A_BOLD | curses.color_pair(2))
|
||||||
@@ -292,24 +316,30 @@ def main_menu(stdscr, slave_id):
|
|||||||
col1, col2, col3, col4 = 2, 30, 58, 88
|
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(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(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(3, col1, "Open Current:", bold); stdscr.addstr(3, col1 + 18, str(current_data.get('motor_current_open', 'N/A')), normal)
|
||||||
|
stdscr.addstr(4, col1, "Close Current:", bold); stdscr.addstr(4, col1 + 18, str(current_data.get('motor_current_close', '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(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(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(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(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(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(3, col3, "Watchdog:", bold); stdscr.addstr(3, col3 + 16, str(current_data.get('watchdog', 'N/A')), normal)
|
||||||
|
stdscr.addstr(4, col3, "End Curr Open:", bold); stdscr.addstr(4, col3 + 16, str(current_data.get('end_curr_open', 'N/A')), normal)
|
||||||
|
stdscr.addstr(5, col3, "End Curr Close:", bold); stdscr.addstr(5, col3 + 16, str(current_data.get('end_curr_close', 'N/A')), normal)
|
||||||
|
stdscr.addstr(6, col3, "Obstacle Open:", bold); stdscr.addstr(6, col3 + 16, str(current_data.get('obstacle_open', 'N/A')), normal)
|
||||||
|
stdscr.addstr(7, col3, "Obstacle Close:", bold); stdscr.addstr(7, col3 + 16, str(current_data.get('obstacle_close', 'N/A')), normal)
|
||||||
stdscr.addstr(1, col4, "Firmware:", bold); stdscr.addstr(1, col4 + 14, str(current_data.get('firmware', '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(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(3, col4, "Dev. Status:", bold); stdscr.addstr(3, col4 + 14, str(current_data.get('device_status', 'N/A')), normal)
|
||||||
stdscr.addstr(5, 0, "─" * (w - 1), normal)
|
stdscr.addstr(4, col4, "Supply V:", bold); stdscr.addstr(4, col4 + 14, str(current_data.get('supply_voltage', 'N/A')), normal)
|
||||||
for idx, row in enumerate(menu):
|
stdscr.addstr(8, 0, "─" * (w - 1), normal)
|
||||||
draw_button(stdscr, h // 2 - len(menu) + (idx * 2), w // 2 - len(row) // 2, row, idx == current_row_idx)
|
for idx, row in enumerate(current_menu):
|
||||||
|
draw_button(stdscr, 9 + (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 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:
|
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))
|
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)
|
else: curses.curs_set(0)
|
||||||
stdscr.refresh()
|
curses.doupdate()
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
global client
|
global client
|
||||||
|
|||||||
Reference in New Issue
Block a user