Compare commits

...
52 Commits
Author SHA1 Message Date
eneuber 5b0f9013aa modbus polling Intervall vergrößert
Build Docker Image (Podman) / build (push) Has been cancelled
2026-05-26 22:46:35 +02:00
eneuber 9d33984c97 CONFIG_PATH für yaml Datei 2026-03-23 22:01:44 +01:00
eneuber 89f02422e7 VERISON in Dockerfile abhandeln 2026-03-23 22:01:10 +01:00
eneuber 58f5522442 Speichern von Holding und Coils ging nicht mehr 2026-03-23 21:13:04 +01:00
eneuber e6c60d0707 Ausführen /Debuggen in vscode nun mit Version 2026-03-23 20:48:56 +01:00
eneuber e96af3ac7e Pipeline triggern
Build Docker Image (Podman) / build (push) Failing after 33m16s
2026-03-20 10:55:02 +01:00
eneuber 87343e354b Pipeline triggern
Build Docker Image (Podman) / build (push) Failing after 32m55s
2026-03-19 22:01:25 +01:00
eneuber ca43d6b9a5 neueres rust verwenden
Build Docker Image (Podman) / build (push) Failing after 14m29s
2026-03-18 17:57:26 +01:00
eneuber 68140a0c45 Daten für InfluxDB ohne type 2026-03-18 17:28:21 +01:00
eneuber 3ed80f4b68 Schreibe MQTT.Set Werte, wenn das Leitsystem aktiv ist
Build Docker Image (Podman) / build (push) Failing after 15m52s
2026-03-13 22:56:06 +01:00
eneuber 497a0e48e3 write-Eigenschaft nun auch an holding-Register 2026-03-11 21:26:41 +01:00
eneuber 825547889c Anpassungen an der paramod.yaml 2026-03-11 17:59:05 +01:00
eneuber 923a35cad1 Lokal debuggen ermöglichen 2026-03-11 17:40:13 +01:00
eneuber 0c10ff4051 Seitenbezeichnung geändert 2026-03-07 21:24:12 +01:00
eneuber 451008b895 Pipeline triggern
Build Docker Image (Podman) / build (push) Failing after 20m44s
2026-02-22 20:42:31 +01:00
eneuber 2ca79a7b9f Pipeline triggern
Build Docker Image (Podman) / build (push) Failing after 5m52s
2026-02-22 20:29:27 +01:00
eneuber a78d49ef7e Pipeline triggern
Build Docker Image (Podman) / build (push) Failing after 1m8s
2026-02-22 20:21:32 +01:00
eneuber 56982cc26a Pipeline triggern
Build Docker Image (Podman) / build (push) Failing after 38s
2026-02-22 19:44:29 +01:00
eneuber 3168d21c5f Pipeline triggern
Build Docker Image (Podman) / build (push) Failing after 5m1s
2026-02-22 19:26:57 +01:00
eneuber 5b7b4fa57e Pipeline triggern
Build Docker Image (Podman) / build (push) Failing after 13m7s
2026-02-22 19:03:06 +01:00
eneuber ee2c69b4c2 Pipeline triggern
Build Docker Image (Podman) / build (push) Failing after 3m31s
2026-02-22 18:48:41 +01:00
eneuber cb7e8d19e4 Pipeline triggern
Build Docker Image (Podman) / build (push) Failing after 55s
2026-02-22 18:43:20 +01:00
eneuber 59de54e80a neue Checkout-Version beim Job
Build Docker Image (Podman) / build (push) Failing after 56s
2026-02-22 18:08:32 +01:00
eneuber 10bdc8fb5a Pipeline triggern
Build Docker Image (Podman) / build (push) Failing after 48s
2026-02-22 17:47:48 +01:00
eneuber c134a20da8 doch nicht privileged
Build Docker Image (Podman) / build (push) Failing after 49s
2026-02-21 22:34:19 +01:00
eneuber 0d696a44e4 Pipeline triggern
Build Docker Image (Podman) / build (push) Failing after 1m14s
2026-02-21 22:19:32 +01:00
eneuber 839fd24b4e Build-Job ist privileged
Build Docker Image (Podman) / build (push) Failing after 47s
2026-02-21 16:58:32 +01:00
eneuber 1c8dd77946 Pipeline tringgern
Build Docker Image (Podman) / build (push) Failing after 58s
2026-02-21 16:54:23 +01:00
eneuber 60823714a6 Pipeline triggern
Build Docker Image (Podman) / build (push) Failing after 1m18s
2026-02-20 00:13:12 +01:00
eneuber 4dd58ecaaa Pipeline triggern
Build Docker Image (Podman) / build (push) Failing after 26s
2026-02-20 00:06:05 +01:00
eneuber e15484c460 Benutze Podman im runner
Build Docker Image (Podman) / build (push) Failing after 55s
2026-02-19 23:57:23 +01:00
eneuber 4673ff3ff1 Pipeline triggern
Build Docker Image (Podman) / build (push) Failing after 2m0s
2026-02-19 23:21:18 +01:00
eneuber e15b1566f0 Pipeline triggern
Build Docker Image (Podman) / build (push) Failing after 47s
2026-02-18 23:20:05 +01:00
eneuber e75764da43 Pipeline triggern
Build Docker Image (Podman) / build (push) Failing after 48s
2026-02-18 22:18:20 +01:00
eneuber 41b88d9e3e Pipeline triggern
Build Docker Image (Podman) / build (push) Failing after 49s
2026-02-18 20:34:54 +01:00
eneuber 241a9ec240 Pipeline Anpassung
Build Docker Image (Podman) / build (push) Failing after 45s
2026-02-18 18:59:12 +01:00
eneuber 55e7260ee5 Änderungen am Trigger der Pipeline
Build Docker Image (Podman) / build (push) Failing after 2m19s
2026-02-18 18:46:07 +01:00
eneuber f47cd3a97b Pipeline umbenannt 2026-02-18 18:23:22 +01:00
eneuber 67b0e4ebd7 Anpassung Pipeline und Dockerfile 2026-02-17 23:20:46 +01:00
eneuber adac95129d Werte in die InfluxDB übermitteln 2026-02-17 21:34:47 +01:00
eneuber 58bacd2f6d Werte in den MQTT Broker übermitteln 2026-02-17 21:01:10 +01:00
eneuber 483d27750b RegisterConfig aufgetrennt 2026-02-17 19:56:33 +01:00
eneuber 698be221c0 Mqtt-Pfad wird jetzt mit angegeben 2026-02-16 23:08:42 +01:00
eneuber 79cbde6b4a Nachkommastellen einschränken 2026-02-16 22:55:52 +01:00
eneuber 7fb98b95f8 Nicht vorhandene Sensoren beachten 2026-02-16 22:50:00 +01:00
eneuber 459b97d95c Modbus Werte werden nun ausgelesen 2026-02-16 22:33:27 +01:00
eneuber c499f1b97c Warnung entfernt 2026-02-13 22:26:24 +01:00
eneuber e8d77ec18e endlich funktionierender Darkmodus 2026-02-13 22:21:29 +01:00
eneuber 4bc399b44f yaml ohne Quotes 2026-02-13 21:25:03 +01:00
eneuber b9bc0e52d3 Modbus-Table-Konfigurationen ausgelagert 2026-02-13 21:16:25 +01:00
eneuber 84eb4b2365 Tabellen erhalten, wenn Settings gespeichert werden 2026-01-18 19:27:05 +01:00
eneuber 49bd17d52b get_config realisiert, damti der darkmode aus der yaml geladen wird 2026-01-18 19:17:48 +01:00
26 changed files with 1761 additions and 759 deletions

No files matched your search

+28
View File
@@ -0,0 +1,28 @@
name: Build Docker Image (Podman)
on:
push:
branches: [ master ]
pull_request:
# workflow_call:
jobs:
build:
runs-on: podman-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Build image with Podman
env:
BUILDAH_ISOLATION: chroot
run: |
VERSION=$(cat VERSION)
podman build --network=host -t paramod:latest -t git.skaville.rocks/eneuber/paramod:$VERSION .
- name: Login to Gitea Registry
run: echo ${{ secrets.REGISTRY_TOKEN }} | podman login -u ${{ secrets.USER }} --password-stdin git.skaville.rocks
- name: Push image with version
run: |
VERSION=$(cat VERSION)
podman push git.skaville.rocks/eneuber/paramod:$VERSION
- name: Push image latest
run: podman push git.skaville.rocks/eneuber/paramod:latest
+6
View File
@@ -0,0 +1,6 @@
#!/bin/bash
set -e
# Patch Cargo.toml version from VERSION file before build
VERSION=$(cat VERSION)
sed -i 's/^version = ".*"/version = "'"$VERSION"'"/' Cargo.toml
exec cargo build
+3
View File
@@ -0,0 +1,3 @@
#!/bin/bash
set -e
sed -i 's/^version = ".*"/version = "__VERSION__"/' Cargo.toml
+12
View File
@@ -0,0 +1,12 @@
#!/bin/bash
set -e
reset_version() {
sed -i 's/^version = ".*"/version = "__VERSION__"/' Cargo.toml
}
trap reset_version EXIT
VERSION=$(cat VERSION)
sed -i 's/^version = ".*"/version = "'"$VERSION"'"/' Cargo.toml
cargo run
+3 -2
View File
@@ -5,11 +5,12 @@
"name": "Debug main.rs",
"type": "lldb",
"request": "launch",
"program": "${workspaceFolder}/target/debug/table-server",
"program": "${workspaceFolder}/target/debug/paramod",
"args": [],
"cwd": "${workspaceFolder}",
"sourceLanguages": ["rust"],
"preLaunchTask": "cargo build"
"preLaunchTask": "cargo build",
"postDebugTask": "cargo reset version placeholder"
}
]
}
+14 -2
View File
@@ -4,12 +4,24 @@
{
"label": "cargo build",
"type": "shell",
"command": "cargo build",
"command": ".vscode/cargo-build-version.sh",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": ["$rustc"]
"problemMatcher": ["$codelldb-rustc"]
},
{
"label": "cargo reset version placeholder",
"type": "shell",
"command": "bash .vscode/cargo-reset-version.sh",
"problemMatcher": []
},
{
"label": "cargo run",
"type": "shell",
"command": "bash .vscode/cargo-run-version.sh",
"problemMatcher": ["$codelldb-rustc"]
}
]
}
Generated
+531 -18
View File
@@ -8,7 +8,7 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a"
dependencies = [
"bitflags",
"bitflags 2.10.0",
"bytes",
"futures-core",
"futures-sink",
@@ -29,7 +29,7 @@ dependencies = [
"actix-service",
"actix-utils",
"actix-web",
"bitflags",
"bitflags 2.10.0",
"bytes",
"derive_more",
"futures-core",
@@ -53,7 +53,7 @@ dependencies = [
"actix-service",
"actix-utils",
"base64 0.22.1",
"bitflags",
"bitflags 2.10.0",
"brotli",
"bytes",
"bytestring",
@@ -300,6 +300,12 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.10.0"
@@ -355,6 +361,12 @@ version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.11.0"
@@ -395,7 +407,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
dependencies = [
"iana-time-zone",
"js-sys",
"num-traits",
"serde",
"wasm-bindgen",
"windows-link",
]
@@ -480,6 +495,16 @@ dependencies = [
"version_check",
]
[[package]]
name = "core-foundation"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
@@ -642,6 +667,17 @@ dependencies = [
"miniz_oxide",
]
[[package]]
name = "flume"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
dependencies = [
"futures-core",
"futures-sink",
"spin",
]
[[package]]
name = "fnv"
version = "1.0.7"
@@ -663,12 +699,32 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "futures-channel"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
dependencies = [
"futures-core",
]
[[package]]
name = "futures-core"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-macro"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-sink"
version = "0.3.31"
@@ -688,9 +744,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
"futures-core",
"futures-macro",
"futures-sink",
"futures-task",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
@@ -745,7 +804,7 @@ version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757"
dependencies = [
"bitflags",
"bitflags 2.10.0",
"ignore",
"walkdir",
]
@@ -805,6 +864,17 @@ dependencies = [
"itoa",
]
[[package]]
name = "http-body"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
dependencies = [
"bytes",
"http",
"pin-project-lite",
]
[[package]]
name = "http-range"
version = "0.1.5"
@@ -832,6 +902,44 @@ dependencies = [
"libm",
]
[[package]]
name = "hyper"
version = "0.14.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7"
dependencies = [
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"h2",
"http",
"http-body",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"socket2 0.5.10",
"tokio",
"tower-service",
"tracing",
"want",
]
[[package]]
name = "hyper-rustls"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
dependencies = [
"futures-util",
"http",
"hyper",
"rustls",
"tokio",
"tokio-rustls",
]
[[package]]
name = "iana-time-zone"
version = "0.1.64"
@@ -990,6 +1098,29 @@ dependencies = [
"hashbrown 0.16.0",
]
[[package]]
name = "influxdb"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "601aa12a5876c044ea2a94a9443d0f086e6fc1f7bb4264bd7120e63c1462d1c8"
dependencies = [
"chrono",
"futures-util",
"http",
"lazy_static",
"regex",
"reqwest",
"serde",
"serde_json",
"thiserror",
]
[[package]]
name = "ipnet"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
[[package]]
name = "itoa"
version = "1.0.15"
@@ -1170,6 +1301,12 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "openssl-probe"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]]
name = "ordered-multimap"
version = "0.7.3"
@@ -1180,6 +1317,26 @@ dependencies = [
"hashbrown 0.14.5",
]
[[package]]
name = "paramod"
version = "0.3.0"
dependencies = [
"actix-files",
"actix-web",
"chrono",
"config",
"influxdb",
"rumqttc",
"serde",
"serde_derive",
"serde_json",
"serde_yaml",
"tera",
"tokio",
"tokio-modbus",
"toml",
]
[[package]]
name = "parking_lot"
version = "0.12.5"
@@ -1436,7 +1593,7 @@ version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags",
"bitflags 2.10.0",
]
[[package]]
@@ -1474,6 +1631,61 @@ version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "reqwest"
version = "0.11.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
dependencies = [
"base64 0.21.7",
"bytes",
"encoding_rs",
"futures-core",
"futures-util",
"h2",
"http",
"http-body",
"hyper",
"hyper-rustls",
"ipnet",
"js-sys",
"log",
"mime",
"once_cell",
"percent-encoding",
"pin-project-lite",
"rustls",
"rustls-pemfile",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper",
"system-configuration",
"tokio",
"tokio-rustls",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"webpki-roots",
"winreg",
]
[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
"getrandom 0.2.16",
"libc",
"untrusted",
"windows-sys 0.52.0",
]
[[package]]
name = "ron"
version = "0.8.1"
@@ -1481,11 +1693,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94"
dependencies = [
"base64 0.21.7",
"bitflags",
"bitflags 2.10.0",
"serde",
"serde_derive",
]
[[package]]
name = "rumqttc"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d8941c6791801b667d52bfe9ff4fc7c968d4f3f9ae8ae7abdaaa1c966feafc8"
dependencies = [
"bytes",
"flume",
"futures-util",
"log",
"rustls-native-certs",
"rustls-pemfile",
"rustls-webpki",
"thiserror",
"tokio",
"tokio-rustls",
]
[[package]]
name = "rust-ini"
version = "0.20.0"
@@ -1496,6 +1726,49 @@ dependencies = [
"ordered-multimap",
]
[[package]]
name = "rustls"
version = "0.21.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
dependencies = [
"log",
"ring",
"rustls-webpki",
"sct",
]
[[package]]
name = "rustls-native-certs"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00"
dependencies = [
"openssl-probe",
"rustls-pemfile",
"schannel",
"security-framework",
]
[[package]]
name = "rustls-pemfile"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
dependencies = [
"base64 0.21.7",
]
[[package]]
name = "rustls-webpki"
version = "0.101.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "rustversion"
version = "1.0.22"
@@ -1517,12 +1790,54 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "schannel"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sct"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "security-framework"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
"bitflags 2.10.0",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "321c8673b092a9a42605034a9879d73cb79101ed5fd117bc9a597b89b4e9e61a"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "serde"
version = "1.0.228"
@@ -1691,6 +2006,15 @@ dependencies = [
"windows-sys 0.60.2",
]
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
dependencies = [
"lock_api",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.1"
@@ -1708,6 +2032,12 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "sync_wrapper"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
[[package]]
name = "synstructure"
version = "0.13.2"
@@ -1720,19 +2050,24 @@ dependencies = [
]
[[package]]
name = "table-server"
version = "0.1.0"
name = "system-configuration"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
dependencies = [
"actix-files",
"actix-web",
"config",
"serde",
"serde_derive",
"serde_json",
"serde_yaml",
"tera",
"tokio",
"toml",
"bitflags 1.3.2",
"core-foundation",
"system-configuration-sys",
]
[[package]]
name = "system-configuration-sys"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
@@ -1757,6 +2092,26 @@ dependencies = [
"unicode-segmentation",
]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "time"
version = "0.3.44"
@@ -1835,6 +2190,32 @@ dependencies = [
"syn",
]
[[package]]
name = "tokio-modbus"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e82dfb1a3265ca939b7a76103c89ad770f8cc0ea8de76c83d352e7c1fc5b2c8b"
dependencies = [
"async-trait",
"byteorder",
"bytes",
"futures-util",
"log",
"smallvec",
"tokio",
"tokio-util",
]
[[package]]
name = "tokio-rustls"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
dependencies = [
"rustls",
"tokio",
]
[[package]]
name = "tokio-util"
version = "0.7.17"
@@ -1889,6 +2270,12 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
[[package]]
name = "tower-service"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
[[package]]
name = "tracing"
version = "0.1.41"
@@ -1921,6 +2308,12 @@ dependencies = [
"once_cell",
]
[[package]]
name = "try-lock"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "typenum"
version = "1.19.0"
@@ -1963,6 +2356,12 @@ version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "url"
version = "2.5.7"
@@ -2003,6 +2402,15 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "want"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
dependencies = [
"try-lock",
]
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
@@ -2031,6 +2439,19 @@ dependencies = [
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0"
dependencies = [
"cfg-if",
"js-sys",
"once_cell",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.105"
@@ -2063,6 +2484,22 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "web-sys"
version = "0.3.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "webpki-roots"
version = "0.25.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
[[package]]
name = "winapi-util"
version = "0.1.11"
@@ -2131,6 +2568,15 @@ dependencies = [
"windows-link",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
@@ -2158,6 +2604,21 @@ dependencies = [
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
@@ -2191,6 +2652,12 @@ dependencies = [
"windows_x86_64_msvc 0.53.1",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
@@ -2203,6 +2670,12 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
@@ -2215,6 +2688,12 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
@@ -2239,6 +2718,12 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
@@ -2251,6 +2736,12 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
@@ -2263,6 +2754,12 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
@@ -2275,6 +2772,12 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
@@ -2296,6 +2799,16 @@ dependencies = [
"memchr",
]
[[package]]
name = "winreg"
version = "0.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
dependencies = [
"cfg-if",
"windows-sys 0.48.0",
]
[[package]]
name = "wit-bindgen"
version = "0.46.0"
+8 -3
View File
@@ -1,7 +1,7 @@
[package]
name = "table-server"
version = "0.1.0"
edition = "2021"
name = "paramod"
version = "__VERSION__"
edition = "2024"
[dependencies]
serde_yaml = "0.9"
@@ -16,3 +16,8 @@ tera = "1.19"
config = "0.14"
toml = "0.8"
serde_derive = "1.0"
tokio-modbus = "0.7"
rumqttc = "0.23"
influxdb = "0.7"
chrono = { version = "0.4", features = ["serde"] }
+11 -19
View File
@@ -1,44 +1,36 @@
# Build Stage
FROM rust:1.75 as builder
FROM rust:1.85 as builder
WORKDIR /usr/src/app
# Copy manifest files
COPY Cargo.toml ./
# Copy source code and templates
COPY Cargo.lock ./
COPY VERSION ./
COPY src ./src
COPY templates ./templates
COPY static ./static
# Build the application
RUN cargo build --release
RUN VERSION=$(cat VERSION) \
&& sed -i 's/^version = "__VERSION__"/version = "'"$VERSION"'"/' Cargo.toml \
&& cargo build --release
# Runtime Stage
FROM debian:bookworm-slim
# Install required dependencies
RUN apt-get update && apt-get install -y \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Copy the binary from builder
COPY --from=builder /usr/src/app/target/release/table-server /app/table-server
# Copy templates and static files
COPY --from=builder /usr/src/app/target/release/paramod /app/paramod
COPY --from=builder /usr/src/app/templates /app/templates
COPY --from=builder /usr/src/app/static /app/static
COPY paramod.yaml /app/paramod.yaml
# Create directory for config file
RUN mkdir -p /app/data
# Expose port
EXPOSE 8080
# Set environment to use the data directory
ENV CONFIG_PATH=/app/data/table_config.json
ENV CONFIG_PATH=/app/paramod.yaml
# Run the binary
CMD ["/app/table-server"]
CMD ["/app/paramod"]
+51 -160
View File
@@ -1,212 +1,103 @@
# Tabellen Webserver
# Paramod
Ein einfacher Rust-Webserver, der eine editierbare 3x3-Tabelle bereitstellt und in einer JSON-Konfigurationsdatei persistiert.
## Projektstruktur
```
table-server/
├── src/
│ └── main.rs
├── templates/
│ ├── index.html
│ └── settings.html
├── static/
│ ├── style.css
│ ├── script.js
│ └── settings.js
├── Cargo.toml
├── Dockerfile
├── .gitignore
└── README.md
```
Rust-Webserver zur Anzeige und Bearbeitung von Modbus-Tabellen mit Persistierung in `paramod.yaml`.
## Funktionen
- **3 separate Tabellen** für verschiedene Sensor-Gruppen
- **Navigation** mit aktivem Status-Indikator
- **Header mit Logo** für professionelles Erscheinungsbild
- **Zeilen hinzufügen/löschen** dynamisch zur Laufzeit
- **Einstellungsseite** für MQTT und InfluxDB Konfiguration
- Editierbare Textfelder (Bezeichnung, Adresse, Type, Faktor)
- Toggle-Schalter für Boolean-Werte (MQTT, InfluxDB)
- **Zentrale JSON-Persistierung** für alle Tabellen und Einstellungen
- REST-API für Daten-Management
- Docker-Unterstützung
- Responsive Design
- Tabellen für `modbus_input_register`, `modbus_holding_register` und `modbus_coils`
- Web-UI zum Bearbeiten und Speichern der Tabellen
- Einstellungsseite für `default`, `modbus`, `mqtt` und `influxdb`
- MQTT/Influx-Integration gemäß Konfiguration
- Speicherung aller Änderungen in `paramod.yaml`
## Lokale Entwicklung
### Voraussetzungen
- Rust (Version 1.75 oder höher)
- Cargo
- Rust + Cargo
### Installation und Start
### Start
```bash
# Projekt erstellen
cargo new table-server
cd table-server
# Dependencies installieren und starten
cargo run
```
Der Server läuft dann auf `http://localhost:8080`
Server: `http://localhost:8080`
## Docker
## Docker (Podman)
### Container bauen
### Image bauen
```bash
docker build -t table-server .
podman build -t paramod:latest .
```
### Container starten
```bash
docker run -p 8080:8080 -v $(pwd)/data:/app/data table-server
podman run --rm -p 8080:8080 -v "$(pwd)/paramod.yaml:/app/paramod.yaml" paramod:latest
```
Mit Volume-Mount bleibt die Konfigurationsdatei auch nach Container-Neustarts erhalten.
### Docker Compose (optional)
Erstelle eine `docker-compose.yml`:
```yaml
version: '3.8'
services:
table-server:
build: .
ports:
- "8080:8080"
volumes:
- ./data:/app/data
restart: unless-stopped
```
Starten mit:
```bash
docker-compose up -d
```
## Verwendung
1. Öffne `http://localhost:8080` im Browser
2. Navigiere zwischen den Tabellen über das Menü:
- **Tabelle 1, 2, 3**: Verschiedene Sensor-Gruppen
- **⚙️ Einstellungen**: MQTT und InfluxDB Konfiguration
3. In den Tabellen:
- **➕ Zeile hinzufügen**: Neue Sensor-Einträge erstellen
- **🗑️ Löschen**: Einzelne Zeilen entfernen
- **Felder bearbeiten**:
- Bezeichnung: Name des Sensors
- Adresse: IP-Adresse oder Identifier
- Type: Sensor-Typ (z.B. Temperatur, Luftfeuchtigkeit)
- Faktor: Numerischer Korrekturfaktor
- MQTT: Toggle-Schalter für MQTT-Aktivierung
- InfluxDB: Toggle-Schalter für InfluxDB-Aktivierung
4. **💾 Speichern**: Änderungen persistieren
5. Alle Daten werden zentral in `table_config.json` gespeichert
## API Endpoints
- `GET /` - Zeigt Tabelle 1
- `GET /table/table2` - Zeigt Tabelle 2
- `GET /table/table3` - Zeigt Tabelle 3
- `GET /settings` - Zeigt Einstellungsseite
- `POST /api/save` - Speichert eine Tabelle
- `POST /api/save-settings` - Speichert die Einstellungen
- `GET /static/*` - Statische Dateien (CSS, JS)
- `GET /`
- `GET /table/modbus_input_register`
- `GET /table/modbus_holding_register`
- `GET /table/modbus_coils`
- `GET /settings`
- `GET /api/config`
- `POST /api/save`
- `POST /api/save-settings`
### Beispiel API-Request (Tabelle speichern)
## API-Beispiel: Tabelle speichern
Beispiel für Holding-Register:
```bash
curl -X POST http://localhost:8080/api/save \
-H "Content-Type: application/json" \
-d '{
"table_id": "table1",
"table_id": "modbus_holding_register",
"rows": [
{
"bezeichnung": "Sensor 1",
"adresse": "192.168.1.100",
"type": "Temperatur",
"faktor": "1.0",
"mqtt": true,
"influxdb": false
"TVsoll": {
"addr": 2,
"type": "INT16",
"factor": 0.1,
"write": true,
"mqtt": true,
"influxdb": true,
"comment": "Vorlauf Soll"
}
}
]
}'
```
### Beispiel API-Request (Einstellungen speichern)
Beispiel für Coils:
```bash
curl -X POST http://localhost:8080/api/save-settings \
curl -X POST http://localhost:8080/api/save \
-H "Content-Type: application/json" \
-d '{
"mqtt_broker": "localhost",
"mqtt_port": "1883",
"influxdb_url": "http://localhost:8086",
"influxdb_token": "your-token-here"
"table_id": "modbus_coils",
"rows": [
{
"MgtSystem": {
"addr": 0,
"write": true,
"mqtt": true,
"influxdb": false,
"comment": "Leitsystem aktiv"
}
}
]
}'
```
## Konfigurationsdatei
## Konfiguration
Die komplette Anwendungskonfiguration wird in `table_config.json` gespeichert:
```json
{
"table1": [
{
"bezeichnung": "Temp Sensor 1",
"adresse": "192.168.1.100",
"type": "Temperatur",
"faktor": "1.0",
"mqtt": true,
"influxdb": false
},
{
"bezeichnung": "Temp Sensor 2",
"adresse": "192.168.1.101",
"type": "Temperatur",
"faktor": "1.0",
"mqtt": false,
"influxdb": true
}
],
"table2": [
{
"bezeichnung": "Humidity Sensor 1",
"adresse": "192.168.1.200",
"type": "Luftfeuchtigkeit",
"faktor": "0.5",
"mqtt": true,
"influxdb": true
}
],
"table3": [
{
"bezeichnung": "Pressure Sensor 1",
"adresse": "192.168.1.300",
"type": "Druck",
"faktor": "2.0",
"mqtt": true,
"influxdb": false
}
],
"settings": {
"mqtt_broker": "localhost",
"mqtt_port": "1883",
"influxdb_url": "http://localhost:8086",
"influxdb_token": ""
}
}
```
Die komplette Konfiguration liegt in `paramod.yaml`.
## Lizenz
+1
View File
@@ -0,0 +1 @@
0.3.0
+4 -1
View File
@@ -6,6 +6,9 @@ Zusätzlich soll noch die Möglichkeit gegeben werden, über MQTT Werte zu setze
## Technische Details
### Konfiguration
Die Konfiguration soll sowohl über Webservice als auch über eine Konfigurationsdatei möglich sein. Änderungen an der Weboberfläche sollen direkten Einfluss haben und in der Konfigurationsdatei persistiert werden. Die Konfigurationsdatei soll beim Start genutzt werden.
Die Konfiguration soll sowohl über Webservice als auch über eine Konfigurationsdatei möglich sein. Änderungen an der Weboberfläche sollen direkten Einfluss haben und in der Konfigurationsdatei persistiert werden. Die Konfigurationsdatei soll beim Start genutzt werden. Die Konfiguration soll im yaml-Format vorliegen.
An der Weboberfläche soll es drei Tabellen geben, um die Modbus-Einstellungen für coils, input_register und holding_register vorzunehmen. Weiterhin soll es ein Einstellungsmenü geben, um den MQTT-Broker, den Modbus-Server, den InfluxDB-Server und allgemeine Einstellungen vorzunehmen.
In den Tabellen soll es jeweils eine Spalte mit dem aktuellen Wert geben. Dieser wird aus den Modbus-Werten auf Rust-Seite ausgelesen und wird je nach Type aus einer bzw. zwei Adressen berechnet und mit dem Faktor multipliziert. Das trifft für input_register und holding_register zu. Bei coils handelt es sich um 1-Bit Datenpunkte und damit Boolean.
-140
View File
@@ -1,140 +0,0 @@
[default]
loglevel = "DEBUG"
[mqtt]
broker = "192.168.178.2"
button_circulation = "zigbee2mqtt/WirelessButton"
password = "97sm3pHNSMZ4M5qUj0x8"
port = 1883
user = "admin"
[influxdb]
bucket = "Paradigma"
location = "Radebeul"
measurement = "ParadigmaModbus"
org = "skaville"
token = "i-sXFQbEkSC1XVzqFEaFwXwzasbsEIciVlK4SaAUOEvk0VjQPkD3fr8d7_3SPeyseTZkqj7ZMZU78b3n2F6_SQ=="
url = "192.168.178.2:8086"
[modbus]
host = "192.168.178.10"
max_coils_addr = 8
max_holding_addr = 61
max_input_addr = 45
port = 502
[[modbus_coils]]
MgtSystem = {addr = 0, write = false, mqtt = false, influxdb = false, comment = "Leitsystem aktiv"}
HK1pres = {addr = 1, write = false, mqtt = false, influxdb = false, comment = "HK1 vorhanden"}
HK2pres = {addr = 2, write = false, mqtt = false, influxdb = false, comment = "HK2 vorhanden"}
HK3pres = {addr = 3, write = false, mqtt = false, influxdb = false, comment = "HK3 vorhanden"}
TWrelease = {addr = 4, write = true, mqtt = false, influxdb = false, comment = "Trinkwassererwärmung freigegeben"}
TWlock = {addr = 5, write = true, mqtt = false, influxdb = false, comment = "Trinkwassererwärmung gesprerrt"}
Zrelease = {addr = 6, write = true, mqtt = false, influxdb = false, comment = "Zirkulation freigegeben"}
Zlock = {addr = 7, write = true, mqtt = false, influxdb = false, comment = "Zirkulation gesperrt"}
SHKpres = {addr = 8, write = false, mqtt = false, influxdb = false, comment = "Schwimmbadheizkrei vorhanden"}
[[modbus_input_register]]
TA = {addr = 0, type = "INT16", factor = 0.1, mqtt = true, influxdb = true}
TV = {addr = 1, type = "INT16", factor = 0.1, mqtt = false, influxdb = true}
TR = {addr = 2, type = "INT16", factor = 0.1, mqtt = false, influxdb = true}
TWO = {addr = 3, type = "INT16", factor = 0.1, mqtt = false, influxdb = true}
TPO = {addr = 4, type = "INT16", factor = 0.1, mqtt = false, influxdb = true}
TPU = {addr = 5, type = "INT16", factor = 0.1, mqtt = false, influxdb = true}
TZR = {addr = 6, type = "INT16", factor = 0.1, mqtt = false, influxdb = false}
TV2 = {addr = 7, type = "INT16", factor = 0.1, mqtt = false, influxdb = false}
TR2 = {addr = 8, type = "INT16", factor = 0.1, mqtt = false, influxdb = false}
RI1 = {addr = 9, type = "INT16", factor = 0.1, mqtt = false, influxdb = true}
RI2 = {addr = 10, type = "INT16", factor = 0.1, mqtt = false, influxdb = false}
TSA = {addr = 11, type = "INT16", factor = 0.1, mqtt = false, influxdb = true}
FATV = {addr = 12, type = "INT16", factor = 0.1, mqtt = false, influxdb = true}
FATR = {addr = 13, type = "INT16", factor = 0.1, mqtt = false, influxdb = true}
TVKH = {addr = 14, type = "INT16", factor = 0.1, mqtt = false, influxdb = false}
TRKH = {addr = 15, type = "INT16", factor = 0.1, mqtt = false, influxdb = false}
TPOKH = {addr = 16, type = "INT16", factor = 0.1, mqtt = false, influxdb = false}
TV3 = {addr = 17, type = "INT16", factor = 0.1, mqtt = false, influxdb = false}
TR3 = {addr = 18, type = "INT16", factor = 0.1, mqtt = false, influxdb = false}
TSB = {addr = 19, type = "INT16", factor = 0.1, mqtt = false, influxdb = false}
TVSB = {addr = 20, type = "INT16", factor = 0.1, mqtt = false, influxdb = false}
TRSB = {addr = 21, type = "INT16", factor = 0.1, mqtt = false, influxdb = false}
TWE = {addr = 22, type = "INT16", factor = 0.1, mqtt = false, influxdb = false}
TWA = {addr = 23, type = "INT16", factor = 0.1, mqtt = false, influxdb = false}
TWS = {addr = 24, type = "INT16", factor = 0.1, mqtt = false, influxdb = false}
TVSI = {addr = 25, type = "INT16", factor = 0.1, mqtt = false, influxdb = false}
TK = {addr = 26, type = "INT16", factor = 0.1, mqtt = false, influxdb = false}
FATV1 = {addr = 27, type = "INT16", factor = 0.1, mqtt = false, influxdb = false}
FATV2 = {addr = 28, type = "INT16", factor = 0.1, mqtt = false, influxdb = false}
FATV3 = {addr = 29, type = "INT16", factor = 0.1, mqtt = false, influxdb = false}
FATV4 = {addr = 30, type = "INT16", factor = 0.1, mqtt = false, influxdb = false}
TSE = {addr = 31, type = "INT16", factor = 0.1, mqtt = false, influxdb = true}
TW = {addr = 32, type = "INT16", factor = 0.1, mqtt = false, influxdb = false}
TSV = {addr = 33, type = "INT16", factor = 0.1, mqtt = false, influxdb = true}
TW2 = {addr = 34, type = "INT16", factor = 0.1, mqtt = false, influxdb = false}
S = {addr = 35, type = "INT16", factor = 0.1, mqtt = false, influxdb = true}
TAM = {addr = 36, type = "INT16", factor = 0.1, mqtt = false, influxdb = true}
TAM2 = {addr = 37, type = "INT16", factor = 0.1, mqtt = false, influxdb = false}
TSA1 = {addr = 38, type = "INT16", factor = 0.1, mqtt = false, influxdb = true}
TSA2 = {addr = 39, type = "INT16", factor = 0.1, mqtt = false, influxdb = false}
TSP = {addr = 40, type = "INT16", factor = 0.1, mqtt = false, influxdb = true}
TWW = {addr = 41, type = "INT16", factor = 0.1, mqtt = false, influxdb = true}
TKW = {addr = 42, type = "INT16", factor = 0.1, mqtt = false, influxdb = true}
VKW = {addr = 43, type = "INT16", factor = 0.1, mqtt = false, influxdb = true}
VSPm = {addr = 44, type = "INT16", factor = 0.1, mqtt = false, influxdb = true}
[[modbus_holding_register]]
nothing = {addr = 0, type = "UINT16", factor = 1, mqtt = false, influxdb = false}
ErrLS = {addr = 1, type = "UINT16", factor = 1, mqtt = false, influxdb = true}
TVsoll = {addr = 2, type = "INT16", factor = 0.1, mqtt = false, influxdb = true}
TV2soll = {addr = 3, type = "INT16", factor = 0.1, mqtt = false, influxdb = false}
TV3soll = {addr = 4, type = "INT16", factor = 0.1, mqtt = false, influxdb = false}
HK1soll = {addr = 5, type = "UINT16", factor = 1, mqtt = false, influxdb = true}
HK2soll = {addr = 6, type = "UINT16", factor = 1, mqtt = false, influxdb = false}
HK3soll = {addr = 7, type = "UINT16", factor = 1, mqtt = false, influxdb = false}
TWWsoll = {addr = 8, type = "INT16", factor = 0.1, mqtt = false, influxdb = true}
TV1max = {addr = 9, type = "INT16", factor = 0.1, mqtt = false, influxdb = true}
TV2max = {addr = 10, type = "INT16", factor = 0.1, mqtt = false, influxdb = false}
TV3max = {addr = 11, type = "INT16", factor = 0.1, mqtt = false, influxdb = false}
ErrHR = {addr = 12, type = "UINT16", factor = 1, mqtt = false, influxdb = true}
ErrSR = {addr = 13, type = "UINT16", factor = 1, mqtt = false, influxdb = true}
ErrWE1_1 = {addr = 14, type = "UINT16", factor = 1, mqtt = false, influxdb = true}
ErrWE1_2 = {addr = 15, type = "UINT16", factor = 1, mqtt = false, influxdb = false}
ErrWE1_3 = {addr = 16, type = "UINT16", factor = 1, mqtt = false, influxdb = false}
ErrWE1_4 = {addr = 17, type = "UINT16", factor = 1, mqtt = false, influxdb = false}
ErrWE1_5 = {addr = 18, type = "UINT16", factor = 1, mqtt = false, influxdb = false}
KollLei = {addr = 19, type = "UINT16", factor = 0.1, mqtt = false, influxdb = true}
TagesS = {addr = 20, type = "UINT16", factor = 0.1, mqtt = false, influxdb = true}
GesS = {addr = 21, type = "UINT32", factor = 0.1, mqtt = false, influxdb = true}
GesWW = {addr = 23, type = "UINT32", factor = 0.1, mqtt = false, influxdb = true}
GesZ = {addr = 25, type = "UINT32", factor = 0.1, mqtt = false, influxdb = true}
HGesK1 = {addr = 27, type = "UINT32", factor = 1, mqtt = false, influxdb = true}
StartK1 = {addr = 29, type = "UINT32", factor = 1, mqtt = false, influxdb = true}
HGesPel = {addr = 31, type = "UINT32", factor = 1, mqtt = false, influxdb = true}
VGesPel = {addr = 33, type = "UINT16", factor = 0.1, mqtt = false, influxdb = true}
StatWW = {addr = 34, type = "UINT16", factor = 1, mqtt = false, influxdb = true}
StatZ = {addr = 35, type = "UINT16", factor = 1, mqtt = false, influxdb = true}
StatHK1 = {addr = 36, type = "UINT16", factor = 1, mqtt = false, influxdb = true}
StatHK2 = {addr = 37, type = "UINT16", factor = 1, mqtt = false, influxdb = false}
StatHK3 = {addr = 38, type = "UINT16", factor = 1, mqtt = false, influxdb = false}
StatS = {addr = 39, type = "UINT16", factor = 1, mqtt = false, influxdb = true}
StatSB = {addr = 40, type = "UINT16", factor = 1, mqtt = false, influxdb = false}
StatK1 = {addr = 41, type = "UINT16", factor = 1, mqtt = false, influxdb = true}
StatPel = {addr = 42, type = "UINT16", factor = 1, mqtt = false, influxdb = true}
StatKH = {addr = 43, type = "UINT16", factor = 1, mqtt = false, influxdb = false}
TPOsoll = {addr = 44, type = "UINT16", factor = 0.1, mqtt = false, influxdb = true}
FATVsoll = {addr = 45, type = "UINT16", factor = 0.1, mqtt = false, influxdb = true}
TSBsollHK = {addr = 46, type = "INT16", factor = 0.1, mqtt = false, influxdb = false}
TSBsollS = {addr = 47, type = "INT16", factor = 0.1, mqtt = false, influxdb = false}
BetrHK1 = {addr = 48, type = "UINT16", factor = 1, mqtt = false, influxdb = true}
BetrHK2 = {addr = 49, type = "UINT16", factor = 1, mqtt = false, influxdb = false}
BetrHK3 = {addr = 50, type = "UINT16", factor = 1, mqtt = false, influxdb = false}
BetrSB = {addr = 51, type = "UINT16", factor = 1, mqtt = false, influxdb = false}
GesKKsoll = {addr = 52, type = "INT16", factor = 0.1, mqtt = false, influxdb = false}
KKsollWE1 = {addr = 53, type = "UINT16", factor = 0.1, mqtt = false, influxdb = false}
KKsollWE2 = {addr = 54, type = "UINT16", factor = 0.1, mqtt = false, influxdb = false}
KKsollWE3 = {addr = 55, type = "UINT16", factor = 0.1, mqtt = false, influxdb = false}
KKsollWE4 = {addr = 56, type = "UINT16", factor = 0.1, mqtt = false, influxdb = false}
ErrWE1 = {addr = 57, type = "UINT16", factor = 1, mqtt = false, influxdb = false}
ErrWE2 = {addr = 58, type = "UINT16", factor = 1, mqtt = false, influxdb = false}
ErrWE3 = {addr = 59, type = "UINT16", factor = 1, mqtt = false, influxdb = false}
ErrWE4 = {addr = 60, type = "UINT16", factor = 1, mqtt = false, influxdb = false}
+127 -130
View File
@@ -1,139 +1,136 @@
default:
loglevel: "DEBUG"
loglevel: DEBUG
darkmode: true
mqtt:
broker: "192.168.178.2"
button_circulation: "zigbee2mqtt/WirelessButton"
password: "97sm3pHNSMZ4M5qUj0x8"
broker: 192.168.178.2
port: 1883
user: "admin"
user: admin
password: 97sm3pHNSMZ4M5qUj0x8
path: heizung/paradigma
leitsystem_path: heizung/leitsystem2
set_write_interval_ms: 2000
influxdb:
bucket: "Paradigma"
location: "Radebeul"
measurement: "ParadigmaModbus"
org: "skaville"
token: "i-sXFQbEkSC1XVzqFEaFwXwzasbsEIciVlK4SaAUOEvk0VjQPkD3fr8d7_3SPeyseTZkqj7ZMZU78b3n2F6_SQ=="
url: "192.168.178.2:8086"
bucket: Paradigma
org: skaville
token: i-sXFQbEkSC1XVzqFEaFwXwzasbsEIciVlK4SaAUOEvk0VjQPkD3fr8d7_3SPeyseTZkqj7ZMZU78b3n2F6_SQ==
url: http://192.168.178.2:8086
location: Radebeul
measurement: ParadigmaModbus
modbus:
host: "192.168.178.10"
max_coils_addr: 8
max_holding_addr: 61
max_input_addr: 45
host: 192.168.178.10
port: 502
max_coils_addr: 8
max_input_addr: 45
max_holding_addr: 61
modbus_coils:
- MgtSystem: {addr: 0, write: false, mqtt: false, influxdb: false, comment: "Leitsystem aktiv"}
- HK1pres: {addr: 1, write: false, mqtt: false, influxdb: false, comment: "HK1 vorhanden"}
- HK2pres: {addr: 2, write: false, mqtt: false, influxdb: false, comment: "HK2 vorhanden"}
- HK3pres: {addr: 3, write: false, mqtt: false, influxdb: false, comment: "HK3 vorhanden"}
- TWrelease: {addr: 4, write: true, mqtt: false, influxdb: false, comment: "Trinkwassererwärmung freigegeben"}
- TWlock: {addr: 5, write: true, mqtt: false, influxdb: false, comment: "Trinkwassererwärmung gesperrt"}
- Zrelease: {addr: 6, write: true, mqtt: false, influxdb: false, comment: "Zirkulation freigegeben"}
- Zlock: {addr: 7, write: true, mqtt: false, influxdb: false, comment: "Zirkulation gesperrt"}
- SHKpres: {addr: 8, write: false, mqtt: false, influxdb: false, comment: "Schwimmbadheizkrei vorhanden"}
- MgtSystem: {addr: 0, write: false, mqtt: true, influxdb: false, comment: Leitsystem aktiv}
- HK1pres: {addr: 1, write: false, mqtt: false, influxdb: false, comment: HK1 vorhanden}
- HK2pres: {addr: 2, write: false, mqtt: false, influxdb: false, comment: HK2 vorhanden}
- HK3pres: {addr: 3, write: false, mqtt: false, influxdb: false, comment: HK3 vorhanden}
- TWrelease: {addr: 4, write: true, mqtt: false, influxdb: false, comment: Trinkwassererwärmung freigegeben}
- TWlock: {addr: 5, write: true, mqtt: false, influxdb: false, comment: Trinkwassererwärmung gesperrt}
- Zrelease: {addr: 6, write: true, mqtt: false, influxdb: false, comment: Zirkulation freigegeben}
- Zlock: {addr: 7, write: true, mqtt: false, influxdb: false, comment: Zirkulation gesperrt}
- SHKpres: {addr: 8, write: false, mqtt: false, influxdb: false, comment: Schwimmbadheizkrei vorhanden}
modbus_input_register:
- TA: {addr: 0, type: "INT16", factor: 0.1, mqtt: true, influxdb: true}
- TV: {addr: 1, type: "INT16", factor: 0.1, mqtt: false, influxdb: true}
- TR: {addr: 2, type: "INT16", factor: 0.1, mqtt: false, influxdb: true}
- TWO: {addr: 3, type: "INT16", factor: 0.1, mqtt: false, influxdb: true}
- TPO: {addr: 4, type: "INT16", factor: 0.1, mqtt: false, influxdb: true}
- TPU: {addr: 5, type: "INT16", factor: 0.1, mqtt: false, influxdb: true}
- TZR: {addr: 6, type: "INT16", factor: 0.1, mqtt: false, influxdb: false}
- TV2: {addr: 7, type: "INT16", factor: 0.1, mqtt: false, influxdb: false}
- TR2: {addr: 8, type: "INT16", factor: 0.1, mqtt: false, influxdb: false}
- RI1: {addr: 9, type: "INT16", factor: 0.1, mqtt: false, influxdb: true}
- RI2: {addr: 10, type: "INT16", factor: 0.1, mqtt: false, influxdb: false}
- TSA: {addr: 11, type: "INT16", factor: 0.1, mqtt: false, influxdb: true}
- FATV: {addr: 12, type: "INT16", factor: 0.1, mqtt: false, influxdb: true}
- FATR: {addr: 13, type: "INT16", factor: 0.1, mqtt: false, influxdb: true}
- TVKH: {addr: 14, type: "INT16", factor: 0.1, mqtt: false, influxdb: false}
- TRKH: {addr: 15, type: "INT16", factor: 0.1, mqtt: false, influxdb: false}
- TPOKH: {addr: 16, type: "INT16", factor: 0.1, mqtt: false, influxdb: false}
- TV3: {addr: 17, type: "INT16", factor: 0.1, mqtt: false, influxdb: false}
- TR3: {addr: 18, type: "INT16", factor: 0.1, mqtt: false, influxdb: false}
- TSB: {addr: 19, type: "INT16", factor: 0.1, mqtt: false, influxdb: false}
- TVSB: {addr: 20, type: "INT16", factor: 0.1, mqtt: false, influxdb: false}
- TRSB: {addr: 21, type: "INT16", factor: 0.1, mqtt: false, influxdb: false}
- TWE: {addr: 22, type: "INT16", factor: 0.1, mqtt: false, influxdb: false}
- TWA: {addr: 23, type: "INT16", factor: 0.1, mqtt: false, influxdb: false}
- TWS: {addr: 24, type: "INT16", factor: 0.1, mqtt: false, influxdb: false}
- TVSI: {addr: 25, type: "INT16", factor: 0.1, mqtt: false, influxdb: false}
- TK: {addr: 26, type: "INT16", factor: 0.1, mqtt: false, influxdb: false}
- FATV1: {addr: 27, type: "INT16", factor: 0.1, mqtt: false, influxdb: false}
- FATV2: {addr: 28, type: "INT16", factor: 0.1, mqtt: false, influxdb: false}
- FATV3: {addr: 29, type: "INT16", factor: 0.1, mqtt: false, influxdb: false}
- FATV4: {addr: 30, type: "INT16", factor: 0.1, mqtt: false, influxdb: false}
- TSE: {addr: 31, type: "INT16", factor: 0.1, mqtt: false, influxdb: true}
- TW: {addr: 32, type: "INT16", factor: 0.1, mqtt: false, influxdb: false}
- TSV: {addr: 33, type: "INT16", factor: 0.1, mqtt: false, influxdb: true}
- TW2: {addr: 34, type: "INT16", factor: 0.1, mqtt: false, influxdb: false}
- S: {addr: 35, type: "INT16", factor: 0.1, mqtt: false, influxdb: true}
- TAM: {addr: 36, type: "INT16", factor: 0.1, mqtt: false, influxdb: true}
- TAM2: {addr: 37, type: "INT16", factor: 0.1, mqtt: false, influxdb: false}
- TSA1: {addr: 38, type: "INT16", factor: 0.1, mqtt: false, influxdb: true}
- TSA2: {addr: 39, type: "INT16", factor: 0.1, mqtt: false, influxdb: false}
- TSP: {addr: 40, type: "INT16", factor: 0.1, mqtt: false, influxdb: true}
- TWW: {addr: 41, type: "INT16", factor: 0.1, mqtt: false, influxdb: true}
- TKW: {addr: 42, type: "INT16", factor: 0.1, mqtt: false, influxdb: true}
- VKW: {addr: 43, type: "INT16", factor: 0.1, mqtt: false, influxdb: true}
- VSPm: {addr: 44, type: "INT16", factor: 0.1, mqtt: false, influxdb: true}
- TA: {addr: 0, type: INT16, factor: 0.1, mqtt: true, influxdb: true}
- TV: {addr: 1, type: INT16, factor: 0.1, mqtt: true, influxdb: true}
- TR: {addr: 2, type: INT16, factor: 0.1, mqtt: true, influxdb: true}
- TWO: {addr: 3, type: INT16, factor: 0.1, mqtt: false, influxdb: true}
- TPO: {addr: 4, type: INT16, factor: 0.1, mqtt: false, influxdb: true}
- TPU: {addr: 5, type: INT16, factor: 0.1, mqtt: false, influxdb: true}
- TZR: {addr: 6, type: INT16, factor: 0.1, mqtt: false, influxdb: false}
- TV2: {addr: 7, type: INT16, factor: 0.1, mqtt: false, influxdb: false}
- TR2: {addr: 8, type: INT16, factor: 0.1, mqtt: false, influxdb: false}
- RI1: {addr: 9, type: INT16, factor: 0.1, mqtt: false, influxdb: true}
- RI2: {addr: 10, type: INT16, factor: 0.1, mqtt: false, influxdb: false}
- TSA: {addr: 11, type: INT16, factor: 0.1, mqtt: false, influxdb: true}
- FATV: {addr: 12, type: INT16, factor: 0.1, mqtt: false, influxdb: true}
- FATR: {addr: 13, type: INT16, factor: 0.1, mqtt: false, influxdb: true}
- TVKH: {addr: 14, type: INT16, factor: 0.1, mqtt: false, influxdb: false}
- TRKH: {addr: 15, type: INT16, factor: 0.1, mqtt: false, influxdb: false}
- TPOKH: {addr: 16, type: INT16, factor: 0.1, mqtt: false, influxdb: false}
- TV3: {addr: 17, type: INT16, factor: 0.1, mqtt: false, influxdb: false}
- TR3: {addr: 18, type: INT16, factor: 0.1, mqtt: false, influxdb: false}
- TSB: {addr: 19, type: INT16, factor: 0.1, mqtt: false, influxdb: false}
- TVSB: {addr: 20, type: INT16, factor: 0.1, mqtt: false, influxdb: false}
- TRSB: {addr: 21, type: INT16, factor: 0.1, mqtt: false, influxdb: false}
- TWE: {addr: 22, type: INT16, factor: 0.1, mqtt: false, influxdb: false}
- TWA: {addr: 23, type: INT16, factor: 0.1, mqtt: false, influxdb: false}
- TWS: {addr: 24, type: INT16, factor: 0.1, mqtt: false, influxdb: false}
- TVSI: {addr: 25, type: INT16, factor: 0.1, mqtt: false, influxdb: false}
- TK: {addr: 26, type: INT16, factor: 0.1, mqtt: false, influxdb: false}
- FATV1: {addr: 27, type: INT16, factor: 0.1, mqtt: false, influxdb: false}
- FATV2: {addr: 28, type: INT16, factor: 0.1, mqtt: false, influxdb: false}
- FATV3: {addr: 29, type: INT16, factor: 0.1, mqtt: false, influxdb: false}
- FATV4: {addr: 30, type: INT16, factor: 0.1, mqtt: false, influxdb: false}
- TSE: {addr: 31, type: INT16, factor: 0.1, mqtt: false, influxdb: true}
- TW: {addr: 32, type: INT16, factor: 0.1, mqtt: false, influxdb: false}
- TSV: {addr: 33, type: INT16, factor: 0.1, mqtt: false, influxdb: true}
- TW2: {addr: 34, type: INT16, factor: 0.1, mqtt: false, influxdb: false}
- S: {addr: 35, type: INT16, factor: 0.1, mqtt: false, influxdb: true}
- TAM: {addr: 36, type: INT16, factor: 0.1, mqtt: false, influxdb: true}
- TAM2: {addr: 37, type: INT16, factor: 0.1, mqtt: false, influxdb: false}
- TSA1: {addr: 38, type: INT16, factor: 0.1, mqtt: false, influxdb: true}
- TSA2: {addr: 39, type: INT16, factor: 0.1, mqtt: false, influxdb: false}
- TSP: {addr: 40, type: INT16, factor: 0.1, mqtt: false, influxdb: true}
- TWW: {addr: 41, type: INT16, factor: 0.1, mqtt: false, influxdb: true}
- TKW: {addr: 42, type: INT16, factor: 0.1, mqtt: false, influxdb: true}
- VKW: {addr: 43, type: INT16, factor: 0.1, mqtt: false, influxdb: true}
- VSPm: {addr: 44, type: INT16, factor: 0.1, mqtt: false, influxdb: true}
modbus_holding_register:
- nothing: {addr: 0, type: "UINT16", factor: 1, mqtt: false, influxdb: false}
- ErrLS: {addr: 1, type: "UINT16", factor: 1, mqtt: false, influxdb: true}
- TVsoll: {addr: 2, type: "INT16", factor: 0.1, mqtt: false, influxdb: true}
- TV2soll: {addr: 3, type: "INT16", factor: 0.1, mqtt: false, influxdb: false}
- TV3soll: {addr: 4, type: "INT16", factor: 0.1, mqtt: false, influxdb: false}
- HK1soll: {addr: 5, type: "UINT16", factor: 1, mqtt: false, influxdb: true}
- HK2soll: {addr: 6, type: "UINT16", factor: 1, mqtt: false, influxdb: false}
- HK3soll: {addr: 7, type: "UINT16", factor: 1, mqtt: false, influxdb: false}
- TWWsoll: {addr: 8, type: "INT16", factor: 0.1, mqtt: false, influxdb: true}
- TV1max: {addr: 9, type: "INT16", factor: 0.1, mqtt: false, influxdb: true}
- TV2max: {addr: 10, type: "INT16", factor: 0.1, mqtt: false, influxdb: false}
- TV3max: {addr: 11, type: "INT16", factor: 0.1, mqtt: false, influxdb: false}
- ErrHR: {addr: 12, type: "UINT16", factor: 1, mqtt: false, influxdb: true}
- ErrSR: {addr: 13, type: "UINT16", factor: 1, mqtt: false, influxdb: true}
- ErrWE1_1: {addr: 14, type: "UINT16", factor: 1, mqtt: false, influxdb: true}
- ErrWE1_2: {addr: 15, type: "UINT16", factor: 1, mqtt: false, influxdb: false}
- ErrWE1_3: {addr: 16, type: "UINT16", factor: 1, mqtt: false, influxdb: false}
- ErrWE1_4: {addr: 17, type: "UINT16", factor: 1, mqtt: false, influxdb: false}
- ErrWE1_5: {addr: 18, type: "UINT16", factor: 1, mqtt: false, influxdb: false}
- KollLei: {addr: 19, type: "UINT16", factor: 0.1, mqtt: false, influxdb: true}
- TagesS: {addr: 20, type: "UINT16", factor: 0.1, mqtt: false, influxdb: true}
- GesS: {addr: 21, type: "UINT32", factor: 0.1, mqtt: false, influxdb: true}
- GesWW: {addr: 23, type: "UINT32", factor: 0.1, mqtt: false, influxdb: true}
- GesZ: {addr: 25, type: "UINT32", factor: 0.1, mqtt: false, influxdb: true}
- HGesK1: {addr: 27, type: "UINT32", factor: 1, mqtt: false, influxdb: true}
- StartK1: {addr: 29, type: "UINT32", factor: 1, mqtt: false, influxdb: true}
- HGesPel: {addr: 31, type: "UINT32", factor: 1, mqtt: false, influxdb: true}
- VGesPel: {addr: 33, type: "UINT16", factor: 0.1, mqtt: false, influxdb: true}
- StatWW: {addr: 34, type: "UINT16", factor: 1, mqtt: false, influxdb: true}
- StatZ: {addr: 35, type: "UINT16", factor: 1, mqtt: false, influxdb: true}
- StatHK1: {addr: 36, type: "UINT16", factor: 1, mqtt: false, influxdb: true}
- StatHK2: {addr: 37, type: "UINT16", factor: 1, mqtt: false, influxdb: false}
- StatHK3: {addr: 38, type: "UINT16", factor: 1, mqtt: false, influxdb: false}
- StatS: {addr: 39, type: "UINT16", factor: 1, mqtt: false, influxdb: true}
- StatSB: {addr: 40, type: "UINT16", factor: 1, mqtt: false, influxdb: false}
- StatK1: {addr: 41, type: "UINT16", factor: 1, mqtt: false, influxdb: true}
- StatPel: {addr: 42, type: "UINT16", factor: 1, mqtt: false, influxdb: true}
- StatKH: {addr: 43, type: "UINT16", factor: 1, mqtt: false, influxdb: false}
- TPOsoll: {addr: 44, type: "UINT16", factor: 0.1, mqtt: false, influxdb: true}
- FATVsoll: {addr: 45, type: "UINT16", factor: 0.1, mqtt: false, influxdb: true}
- TSBsollHK: {addr: 46, type: "INT16", factor: 0.1, mqtt: false, influxdb: false}
- TSBsollS: {addr: 47, type: "INT16", factor: 0.1, mqtt: false, influxdb: false}
- BetrHK1: {addr: 48, type: "UINT16", factor: 1, mqtt: false, influxdb: true}
- BetrHK2: {addr: 49, type: "UINT16", factor: 1, mqtt: false, influxdb: false}
- BetrHK3: {addr: 50, type: "UINT16", factor: 1, mqtt: false, influxdb: false}
- BetrSB: {addr: 51, type: "UINT16", factor: 1, mqtt: false, influxdb: false}
- GesKKsoll: {addr: 52, type: "INT16", factor: 0.1, mqtt: false, influxdb: false}
- KKsollWE1: {addr: 53, type: "UINT16", factor: 0.1, mqtt: false, influxdb: false}
- KKsollWE2: {addr: 54, type: "UINT16", factor: 0.1, mqtt: false, influxdb: false}
- KKsollWE3: {addr: 55, type: "UINT16", factor: 0.1, mqtt: false, influxdb: false}
- KKsollWE4: {addr: 56, type: "UINT16", factor: 0.1, mqtt: false, influxdb: false}
- ErrWE1: {addr: 57, type: "UINT16", factor: 1, mqtt: false, influxdb: false}
- ErrWE2: {addr: 58, type: "UINT16", factor: 1, mqtt: false, influxdb: false}
- ErrWE3: {addr: 59, type: "UINT16", factor: 1, mqtt: false, influxdb: false}
- ErrWE4: {addr: 60, type: "UINT16", factor: 1, mqtt: false, influxdb: false}
- nothing: {addr: 0, type: UINT16, factor: 1, write: false, mqtt: false, influxdb: false}
- ErrLS: {addr: 1, type: UINT16, factor: 1, write: false, mqtt: false, influxdb: true}
- TVsoll: {addr: 2, type: INT16, factor: 0.1, write: true, mqtt: true, influxdb: true}
- TV2soll: {addr: 3, type: INT16, factor: 0.1, write: false, mqtt: false, influxdb: false}
- TV3soll: {addr: 4, type: INT16, factor: 0.1, write: false, mqtt: false, influxdb: false}
- HK1soll: {addr: 5, type: UINT16, factor: 1, write: false, mqtt: false, influxdb: true}
- HK2soll: {addr: 6, type: UINT16, factor: 1, write: false, mqtt: false, influxdb: false}
- HK3soll: {addr: 7, type: UINT16, factor: 1, write: false, mqtt: false, influxdb: false}
- TWWsoll: {addr: 8, type: INT16, factor: 0.1, write: false, mqtt: false, influxdb: true}
- TV1max: {addr: 9, type: INT16, factor: 0.1, write: false, mqtt: false, influxdb: true}
- TV2max: {addr: 10, type: INT16, factor: 0.1, write: false, mqtt: false, influxdb: false}
- TV3max: {addr: 11, type: INT16, factor: 0.1, write: false, mqtt: false, influxdb: false}
- ErrHR: {addr: 12, type: UINT16, factor: 1, write: false, mqtt: false, influxdb: true}
- ErrSR: {addr: 13, type: UINT16, factor: 1, write: false, mqtt: false, influxdb: true}
- ErrWE1_1: {addr: 14, type: UINT16, factor: 1, write: false, mqtt: false, influxdb: true}
- ErrWE1_2: {addr: 15, type: UINT16, factor: 1, write: false, mqtt: false, influxdb: false}
- ErrWE1_3: {addr: 16, type: UINT16, factor: 1, write: false, mqtt: false, influxdb: false}
- ErrWE1_4: {addr: 17, type: UINT16, factor: 1, write: false, mqtt: false, influxdb: false}
- ErrWE1_5: {addr: 18, type: UINT16, factor: 1, write: false, mqtt: false, influxdb: false}
- KollLei: {addr: 19, type: UINT16, factor: 0.1, write: false, mqtt: false, influxdb: true}
- TagesS: {addr: 20, type: UINT16, factor: 0.1, write: false, mqtt: true, influxdb: true}
- GesS: {addr: 21, type: UINT32, factor: 0.1, write: false, mqtt: false, influxdb: true}
- GesWW: {addr: 23, type: UINT32, factor: 0.1, write: false, mqtt: false, influxdb: true}
- GesZ: {addr: 25, type: UINT32, factor: 0.1, write: false, mqtt: false, influxdb: true}
- HGesK1: {addr: 27, type: UINT32, factor: 1, write: false, mqtt: false, influxdb: true}
- StartK1: {addr: 29, type: UINT32, factor: 1, write: false, mqtt: false, influxdb: true}
- HGesPel: {addr: 31, type: UINT32, factor: 1, write: false, mqtt: false, influxdb: true}
- VGesPel: {addr: 33, type: UINT16, factor: 0.1, write: false, mqtt: false, influxdb: true}
- StatWW: {addr: 34, type: UINT16, factor: 1, write: false, mqtt: false, influxdb: true}
- StatZ: {addr: 35, type: UINT16, factor: 1, write: false, mqtt: false, influxdb: true}
- StatHK1: {addr: 36, type: UINT16, factor: 1, write: false, mqtt: false, influxdb: true}
- StatHK2: {addr: 37, type: UINT16, factor: 1, write: false, mqtt: false, influxdb: false}
- StatHK3: {addr: 38, type: UINT16, factor: 1, write: false, mqtt: false, influxdb: false}
- StatS: {addr: 39, type: UINT16, factor: 1, write: false, mqtt: false, influxdb: true}
- StatSB: {addr: 40, type: UINT16, factor: 1, write: false, mqtt: false, influxdb: false}
- StatK1: {addr: 41, type: UINT16, factor: 1, write: false, mqtt: false, influxdb: true}
- StatPel: {addr: 42, type: UINT16, factor: 1, write: false, mqtt: false, influxdb: true}
- StatKH: {addr: 43, type: UINT16, factor: 1, write: false, mqtt: false, influxdb: false}
- TPOsoll: {addr: 44, type: UINT16, factor: 0.1, write: false, mqtt: false, influxdb: true}
- FATVsoll: {addr: 45, type: UINT16, factor: 0.1, write: false, mqtt: false, influxdb: true}
- TSBsollHK: {addr: 46, type: INT16, factor: 0.1, write: false, mqtt: false, influxdb: false}
- TSBsollS: {addr: 47, type: INT16, factor: 0.1, write: false, mqtt: false, influxdb: false}
- BetrHK1: {addr: 48, type: UINT16, factor: 1, write: false, mqtt: false, influxdb: true}
- BetrHK2: {addr: 49, type: UINT16, factor: 1, write: false, mqtt: false, influxdb: false}
- BetrHK3: {addr: 50, type: UINT16, factor: 1, write: false, mqtt: false, influxdb: false}
- BetrSB: {addr: 51, type: UINT16, factor: 1, write: false, mqtt: false, influxdb: false}
- GesKKsoll: {addr: 52, type: INT16, factor: 0.1, write: false, mqtt: false, influxdb: false}
- KKsollWE1: {addr: 53, type: UINT16, factor: 0.1, write: false, mqtt: false, influxdb: false}
- KKsollWE2: {addr: 54, type: UINT16, factor: 0.1, write: false, mqtt: false, influxdb: false}
- KKsollWE3: {addr: 55, type: UINT16, factor: 0.1, write: false, mqtt: false, influxdb: false}
- KKsollWE4: {addr: 56, type: UINT16, factor: 0.1, write: false, mqtt: false, influxdb: false}
- ErrWE1: {addr: 57, type: UINT16, factor: 1, write: false, mqtt: false, influxdb: false}
- ErrWE2: {addr: 58, type: UINT16, factor: 1, write: false, mqtt: false, influxdb: false}
- ErrWE3: {addr: 59, type: UINT16, factor: 1, write: false, mqtt: false, influxdb: false}
- ErrWE4: {addr: 60, type: UINT16, factor: 1, write: false, mqtt: false, influxdb: false}
+33
View File
@@ -0,0 +1,33 @@
use std::sync::{Mutex, Arc};
use tera::Tera;
use crate::config::{AppConfig, ModbusValueMaps};
pub struct AppState {
pub config: Arc<Mutex<AppConfig>>,
pub value_maps: Arc<Mutex<ModbusValueMaps>>,
pub templates: Tera,
}
impl AppState {
pub fn load_from_conf(conf_path: &str) -> Self {
let conf_str = std::fs::read_to_string(conf_path).expect("Config-Datei konnte nicht gelesen werden");
let config: AppConfig = serde_yaml::from_str(&conf_str).expect("Config-Deserialisierung fehlgeschlagen");
let config = Arc::new(Mutex::new(config));
let value_maps = Arc::new(Mutex::new(ModbusValueMaps::from_config(&config.lock().unwrap())));
let tera = match Tera::new("templates/**/*") {
Ok(t) => t,
Err(e) => {
println!("Template parsing error: {}", e);
std::process::exit(1);
}
};
AppState {
config,
value_maps,
templates: tera,
}
}
}
+33 -37
View File
@@ -1,33 +1,11 @@
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::modbus_types::{ModbusCoilsConfig, ModbusInputRegisterConfig, ModbusHoldingRegisterConfig};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct MqttConfig {
pub broker: String,
pub port: u16,
pub user: Option<String>,
pub password: Option<String>,
pub button_circulation: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct InfluxConfig {
pub bucket: String,
pub org: String,
pub token: String,
pub url: String,
pub location: Option<String>,
pub measurement: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ModbusRegisterConfig {
pub addr: u16,
pub r#type: Option<String>,
pub factor: Option<f64>,
pub mqtt: Option<bool>,
pub influxdb: Option<bool>,
pub comment: Option<String>,
pub struct DefaultConfig {
pub loglevel: Option<String>,
pub darkmode: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
@@ -40,26 +18,44 @@ pub struct ModbusConfig {
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct DefaultConfig {
pub loglevel: Option<String>,
pub struct MqttConfig {
pub broker: String,
pub port: u16,
pub user: Option<String>,
pub password: Option<String>,
pub path: Option<String>,
pub leitsystem_path: Option<String>,
pub set_write_interval_ms: Option<u64>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct InfluxConfig {
pub bucket: String,
pub org: String,
pub token: String,
pub url: String,
pub location: Option<String>,
pub measurement: Option<String>,
}
// ModbusRegisterConfig und ModbusCoilsConfig werden jetzt aus modbus_types.rs importiert
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct AppConfig {
pub default: DefaultConfig,
pub mqtt: MqttConfig,
pub influxdb: InfluxConfig,
pub modbus: ModbusConfig,
pub modbus_coils: Option<Vec<HashMap<String, ModbusRegisterConfig>>>,
pub modbus_input_register: Option<Vec<HashMap<String, ModbusRegisterConfig>>>,
pub modbus_holding_register: Option<Vec<HashMap<String, ModbusRegisterConfig>>>,
pub modbus_coils: Option<Vec<HashMap<String, ModbusCoilsConfig>>>,
pub modbus_input_register: Option<Vec<HashMap<String, ModbusInputRegisterConfig>>>,
pub modbus_holding_register: Option<Vec<HashMap<String, ModbusHoldingRegisterConfig>>>,
}
#[derive(Debug, Default, Clone)]
pub struct ModbusValueMaps {
pub modbus_coils_values: HashMap<String, f64>,
pub modbus_input_register_values: HashMap<String, f64>,
pub modbus_holding_register_values: HashMap<String, f64>,
pub modbus_coils_values: HashMap<String, Option<f64>>,
pub modbus_input_register_values: HashMap<String, Option<f64>>,
pub modbus_holding_register_values: HashMap<String, Option<f64>>,
}
impl ModbusValueMaps {
@@ -68,7 +64,7 @@ impl ModbusValueMaps {
if let Some(ref vec) = config.modbus_coils {
for map in vec {
for key in map.keys() {
coils.entry(key.clone()).or_insert(0.0);
coils.entry(key.clone()).or_insert(None);
}
}
}
@@ -76,7 +72,7 @@ impl ModbusValueMaps {
if let Some(ref vec) = config.modbus_input_register {
for map in vec {
for key in map.keys() {
input.entry(key.clone()).or_insert(0.0);
input.entry(key.clone()).or_insert(None);
}
}
}
@@ -84,7 +80,7 @@ impl ModbusValueMaps {
if let Some(ref vec) = config.modbus_holding_register {
for map in vec {
for key in map.keys() {
holding.entry(key.clone()).or_insert(0.0);
holding.entry(key.clone()).or_insert(None);
}
}
}
+112
View File
@@ -0,0 +1,112 @@
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
use crate::config::{AppConfig, ModbusValueMaps};
use influxdb::{Client, WriteQuery, Timestamp};
use chrono::Utc;
use tokio::runtime::Runtime;
pub fn start_influx_thread(config: Arc<Mutex<AppConfig>>, values: Arc<Mutex<ModbusValueMaps>>) {
let influx_config = {
let cfg = config.lock().unwrap();
cfg.influxdb.clone()
};
let url = influx_config.url.clone();
let bucket = influx_config.bucket.clone();
let _org = influx_config.org.clone();
let token = influx_config.token.clone();
let measurement = influx_config.measurement.clone().unwrap_or_else(|| "modbus".to_string());
let location = influx_config.location.clone().unwrap_or_else(|| "default".to_string());
thread::spawn(move || {
let client = Client::new(url, bucket).with_token(token);
let rt = Runtime::new().unwrap();
loop {
let mut points = Vec::new();
{
let values = values.lock().unwrap();
// Input Register
for (name, val) in &values.modbus_input_register_values {
if let Some(v) = val {
if should_write(&config, name, "input_register") {
let point = WriteQuery::new(Timestamp::from(Utc::now()), measurement.clone())
.add_field(name, *v)
// .add_tag("type", "input_register")
.add_tag("location", location.as_str())
.to_owned();
points.push(point);
}
}
}
// Holding Register
for (name, val) in &values.modbus_holding_register_values {
if let Some(v) = val {
if should_write(&config, name, "holding_register") {
let point = WriteQuery::new(Timestamp::from(Utc::now()), measurement.clone())
.add_field(name, *v)
// .add_tag("type", "holding_register")
.add_tag("location", location.as_str())
.to_owned();
points.push(point);
}
}
}
// Coils
for (name, val) in &values.modbus_coils_values {
if let Some(v) = val {
if should_write(&config, name, "coils") {
let point = WriteQuery::new(Timestamp::from(Utc::now()), measurement.clone())
.add_field(name, *v)
// .add_tag("type", "coils")
.add_tag("location", location.as_str())
.to_owned();
points.push(point);
}
}
}
}
if !points.is_empty() {
let res = rt.block_on(client.query(points));
if let Err(e) = res {
eprintln!("Influx write error: {}", e);
}
}
thread::sleep(Duration::from_secs(10));
}
});
}
fn should_write(config: &Arc<Mutex<AppConfig>>, name: &str, reg_type: &str) -> bool {
let cfg = config.lock().unwrap();
match reg_type {
"input_register" => {
if let Some(regs) = &cfg.modbus_input_register {
for map in regs {
if let Some(reg) = map.get(name) {
return reg.influxdb.unwrap_or(false);
}
}
}
}
"holding_register" => {
if let Some(regs) = &cfg.modbus_holding_register {
for map in regs {
if let Some(reg) = map.get(name) {
return reg.influxdb.unwrap_or(false);
}
}
}
}
"coils" => {
if let Some(coils) = &cfg.modbus_coils {
for map in coils {
if let Some(coil) = map.get(name) {
return coil.influxdb.unwrap_or(false);
}
}
}
}
_ => {}
}
false
}
+147 -92
View File
@@ -1,52 +1,25 @@
use actix_web::{web, App, HttpResponse, HttpServer, Result};
use actix_files as actix_fs;
use std::sync::{Mutex, Arc};
use tera::{Context, Tera};
use std::sync::{Arc};
use tera::Context;
use std::fs;
use serde_yaml;
use std::collections::HashMap;
use serde::{Serialize, Deserialize};
mod config;
mod modbus;
use crate::config::{AppConfig, ModbusRegisterConfig, ModbusValueMaps};
mod app_state;
mod mqtt;
mod influx;
pub mod modbus_types;
use crate::config::{AppConfig, ModbusValueMaps};
use crate::modbus_types::{ModbusInputRegisterConfig, ModbusHoldingRegisterConfig, ModbusCoilsConfig};
use crate::app_state::AppState;
// ...existing code...
struct AppState {
config: Mutex<AppConfig>,
value_maps: Arc<Mutex<ModbusValueMaps>>,
templates: Tera,
fn get_config_path() -> String {
std::env::var("CONFIG_PATH").unwrap_or_else(|_| "paramod.yaml".to_string())
}
impl AppState {
fn load_from_conf(conf_path: &str) -> Self {
let conf_str = std::fs::read_to_string(conf_path).expect("Config-Datei konnte nicht gelesen werden");
let config: AppConfig = serde_yaml::from_str(&conf_str).expect("Config-Deserialisierung fehlgeschlagen");
let value_maps = Arc::new(Mutex::new(ModbusValueMaps::from_config(&config)));
let tera = match Tera::new("templates/**/*") {
Ok(t) => t,
Err(e) => {
println!("Template parsing error: {}", e);
std::process::exit(1);
}
};
AppState {
config: Mutex::new(config),
value_maps,
templates: tera,
}
}
}
// ...existing code...
async fn index(data: web::Data<AppState>) -> Result<HttpResponse> {
let config = data.config.lock().unwrap();
let value_maps = data.value_maps.lock().unwrap();
@@ -74,37 +47,63 @@ async fn table_page(data: web::Data<AppState>, path: web::Path<String>) -> Resul
let config = data.config.lock().unwrap();
let value_maps = data.value_maps.lock().unwrap();
let mut context = Context::new();
let (rows, value_map) = match table_id.as_str() {
"modbus_input_register" => (
config.modbus_input_register.clone().unwrap_or_default(),
&value_maps.modbus_input_register_values
),
"modbus_holding_register" => (
config.modbus_holding_register.clone().unwrap_or_default(),
&value_maps.modbus_holding_register_values
),
"modbus_coils" => (
config.modbus_coils.clone().unwrap_or_default(),
&value_maps.modbus_coils_values
),
_ => return Ok(HttpResponse::NotFound().body("Table not found")),
};
let mut sorted_rows = rows;
sorted_rows.sort_by(|a, b| {
let addr_a = a.values().next().map(|v| v.addr).unwrap_or(0);
let addr_b = b.values().next().map(|v| v.addr).unwrap_or(0);
addr_a.cmp(&addr_b)
});
context.insert("rows", &sorted_rows);
context.insert("table_id", &table_id);
context.insert("active_page", &table_id);
context.insert("value_map", value_map);
let html = data.templates.render("index.html", &context)
.map_err(|e| {
eprintln!("Template error: {}", e);
actix_web::error::ErrorInternalServerError("Template error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(html))
match table_id.as_str() {
"modbus_input_register" => {
let mut rows = config.modbus_input_register.clone().unwrap_or_default();
rows.sort_by(|a, b| {
let addr_a = a.values().next().map(|v| v.addr).unwrap_or(0);
let addr_b = b.values().next().map(|v| v.addr).unwrap_or(0);
addr_a.cmp(&addr_b)
});
context.insert("rows", &rows);
context.insert("table_id", &table_id);
context.insert("active_page", &table_id);
context.insert("value_map", &value_maps.modbus_input_register_values);
let html = data.templates.render("index.html", &context)
.map_err(|e| {
eprintln!("Template error: {}", e);
actix_web::error::ErrorInternalServerError("Template error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(html))
}
"modbus_holding_register" => {
let mut rows = config.modbus_holding_register.clone().unwrap_or_default();
rows.sort_by(|a, b| {
let addr_a = a.values().next().map(|v| v.addr).unwrap_or(0);
let addr_b = b.values().next().map(|v| v.addr).unwrap_or(0);
addr_a.cmp(&addr_b)
});
context.insert("rows", &rows);
context.insert("table_id", &table_id);
context.insert("active_page", &table_id);
context.insert("value_map", &value_maps.modbus_holding_register_values);
let html = data.templates.render("index.html", &context)
.map_err(|e| {
eprintln!("Template error: {}", e);
actix_web::error::ErrorInternalServerError("Template error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(html))
}
"modbus_coils" => {
let mut rows = config.modbus_coils.clone().unwrap_or_default();
rows.sort_by(|a, b| {
let addr_a = a.values().next().map(|v| v.addr).unwrap_or(0);
let addr_b = b.values().next().map(|v| v.addr).unwrap_or(0);
addr_a.cmp(&addr_b)
});
context.insert("rows", &rows);
context.insert("table_id", &table_id);
context.insert("active_page", &table_id);
context.insert("value_map", &value_maps.modbus_coils_values);
let html = data.templates.render("index.html", &context)
.map_err(|e| {
eprintln!("Template error: {}", e);
actix_web::error::ErrorInternalServerError("Template error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(html))
}
_ => Ok(HttpResponse::NotFound().body("Table not found")),
}
}
async fn settings_page(data: web::Data<AppState>) -> Result<HttpResponse> {
@@ -124,7 +123,10 @@ async fn settings_page(data: web::Data<AppState>) -> Result<HttpResponse> {
eprintln!("Template error: {}", e);
actix_web::error::ErrorInternalServerError("Template error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(html))
Ok(HttpResponse::Ok()
.content_type("text/html")
.insert_header(("Cache-Control", "no-store, must-revalidate"))
.body(html))
}
@@ -132,7 +134,7 @@ async fn settings_page(data: web::Data<AppState>) -> Result<HttpResponse> {
#[derive(Serialize, Deserialize)]
struct SaveTableRequest {
table_id: String,
rows: Vec<HashMap<String, ModbusRegisterConfig>>,
rows: serde_json::Value,
}
async fn save_table(
@@ -140,24 +142,39 @@ async fn save_table(
req: web::Json<SaveTableRequest>,
) -> Result<HttpResponse> {
let mut config = data.config.lock().unwrap();
let conf_path = "paramod.yaml";
let key = match req.table_id.as_str() {
"modbus_input_register" => "modbus_input_register",
"modbus_holding_register" => "modbus_holding_register",
"modbus_coils" => "modbus_coils",
let conf_path = get_config_path();
match req.table_id.as_str() {
"modbus_input_register" => {
let rows: Vec<HashMap<String, ModbusInputRegisterConfig>> =
match serde_json::from_value(req.rows.clone()) {
Ok(v) => v,
Err(e) => return Ok(HttpResponse::BadRequest().body(format!("Falscher Typ für input_register: {}", e))),
};
config.modbus_input_register = Some(rows);
}
"modbus_holding_register" => {
let rows: Vec<HashMap<String, ModbusHoldingRegisterConfig>> =
match serde_json::from_value(req.rows.clone()) {
Ok(v) => v,
Err(e) => return Ok(HttpResponse::BadRequest().body(format!("Falscher Typ für holding_register: {}", e))),
};
config.modbus_holding_register = Some(rows);
}
"modbus_coils" => {
let rows: Vec<HashMap<String, ModbusCoilsConfig>> =
match serde_json::from_value(req.rows.clone()) {
Ok(v) => v,
Err(e) => return Ok(HttpResponse::BadRequest().body(format!("Falscher Typ für coils: {}", e))),
};
config.modbus_coils = Some(rows);
}
_ => return Ok(HttpResponse::BadRequest().body("Invalid table_id")),
};
match key {
"modbus_input_register" => config.modbus_input_register = Some(req.rows.clone()),
"modbus_holding_register" => config.modbus_holding_register = Some(req.rows.clone()),
"modbus_coils" => config.modbus_coils = Some(req.rows.clone()),
_ => {}
}
let yaml_str = match serde_yaml::to_string(&*config) {
Ok(s) => s,
Err(e) => return Ok(HttpResponse::InternalServerError().body(format!("Serialisierungsfehler: {}", e))),
};
if let Err(e) = fs::write(conf_path, yaml_str) {
if let Err(e) = fs::write(&conf_path, yaml_str) {
return Ok(HttpResponse::InternalServerError().body(format!("Fehler beim Schreiben: {}", e)));
}
Ok(HttpResponse::Ok().body("success"))
@@ -168,28 +185,53 @@ async fn save_settings(
settings: web::Json<AppConfig>,
) -> Result<HttpResponse> {
let mut config = data.config.lock().unwrap();
let new_config = settings.into_inner();
let mut new_config = settings.into_inner();
// Tabellenwerte erhalten, falls sie im Request null sind
if new_config.modbus_coils.is_none() {
new_config.modbus_coils = config.modbus_coils.clone();
}
if new_config.modbus_input_register.is_none() {
new_config.modbus_input_register = config.modbus_input_register.clone();
}
if new_config.modbus_holding_register.is_none() {
new_config.modbus_holding_register = config.modbus_holding_register.clone();
}
// Value-Maps neu initialisieren
let mut value_maps = data.value_maps.lock().unwrap();
*value_maps = ModbusValueMaps::from_config(&new_config);
*config = new_config;
let conf_path = "paramod.yaml";
let yaml_str = match serde_yaml::to_string(&*config) {
let conf_path = get_config_path();
let yaml_str = match serde_yaml::to_string(&new_config) {
Ok(s) => s,
Err(e) => return Ok(HttpResponse::InternalServerError().body(format!("Serialisierungsfehler: {}", e))),
};
if let Err(e) = fs::write(conf_path, yaml_str) {
if let Err(e) = fs::write(&conf_path, yaml_str) {
return Ok(HttpResponse::InternalServerError().body(format!("Fehler beim Schreiben: {}", e)));
}
// Reload config from file
let conf_str = match std::fs::read_to_string(&conf_path) {
Ok(s) => s,
Err(e) => return Ok(HttpResponse::InternalServerError().body(format!("Fehler beim Lesen: {}", e))),
};
let updated_config: AppConfig = match serde_yaml::from_str(&conf_str) {
Ok(cfg) => cfg,
Err(e) => return Ok(HttpResponse::InternalServerError().body(format!("Deserialisierungsfehler: {}", e))),
};
// darkmode-Konvertierung: String zu bool
// (YAML kann bool als string interpretieren)
*config = updated_config;
Ok(HttpResponse::Ok().body("success"))
}
async fn get_config(data: web::Data<AppState>) -> HttpResponse {
let config = data.config.lock().unwrap();
HttpResponse::Ok().json(&*config)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let config_path = "paramod.yaml";
let app_state = web::Data::new(AppState::load_from_conf(config_path));
let config_path = get_config_path();
let app_state = web::Data::new(AppState::load_from_conf(&config_path));
// Starte Modbus-Polling-Thread
{
@@ -201,10 +243,22 @@ async fn main() -> std::io::Result<()> {
&config.modbus_holding_register,
&config.modbus_coils,
value_maps,
std::time::Duration::from_secs(2), // Poll-Intervall
std::time::Duration::from_secs(10), // Poll-Intervall
);
}
// Starte MQTT-Thread
{
let value_maps = Arc::clone(&app_state.value_maps);
mqtt::start_mqtt_thread(Arc::clone(&app_state.config), value_maps);
}
// Starte InfluxDB-Thread
{
let value_maps = Arc::clone(&app_state.value_maps);
influx::start_influx_thread(Arc::clone(&app_state.config), value_maps);
}
println!("Server läuft auf http://0.0.0.0:8080");
HttpServer::new(move || {
@@ -215,6 +269,7 @@ async fn main() -> std::io::Result<()> {
.route("/settings", web::get().to(settings_page))
.route("/api/save", web::post().to(save_table))
.route("/api/save-settings", web::post().to(save_settings))
.route("/api/config", web::get().to(get_config))
.service(actix_fs::Files::new("/static", "./static"))
})
.bind("0.0.0.0:8080")?
+200 -42
View File
@@ -1,66 +1,224 @@
use crate::config::{ModbusValueMaps, ModbusRegisterConfig, ModbusConfig};
use crate::config::{ModbusValueMaps, ModbusConfig};
use crate::modbus_types::{ModbusInputRegisterConfig, ModbusHoldingRegisterConfig, ModbusCoilsConfig};
use crate::config::AppConfig;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
use tokio_modbus::prelude::*;
use tokio_modbus::client::tcp;
use std::net::SocketAddr;
pub fn write_value_by_name(config: &AppConfig, name: &str, payload: &str) -> Result<(), String> {
let addr: SocketAddr = format!("{}:{}", config.modbus.host, config.modbus.port)
.parse()
.map_err(|e| format!("Ungültige Modbus-Adresse: {}", e))?;
if let Some(ref coils) = config.modbus_coils {
for map in coils {
if let Some(coil) = map.get(name) {
if !coil.write.unwrap_or(false) {
return Err(format!("Schreiben für '{}' nicht erlaubt", name));
}
let bool_value = parse_bool_payload(payload)?;
let rt = tokio::runtime::Runtime::new()
.map_err(|e| format!("Tokio Runtime Fehler: {}", e))?;
rt.block_on(async {
let mut client = tcp::connect_slave(addr, 1u8.into())
.await
.map_err(|e| format!("Modbus Verbindung fehlgeschlagen: {}", e))?;
client
.write_single_coil(coil.addr, bool_value)
.await
.map_err(|e| format!("Coil schreiben fehlgeschlagen: {}", e))
})?;
return Ok(());
}
}
}
if let Some(ref holding_registers) = config.modbus_holding_register {
for map in holding_registers {
if let Some(reg) = map.get(name) {
if !reg.write.unwrap_or(false) {
return Err(format!("Schreiben für '{}' nicht erlaubt", name));
}
let numeric_value = payload
.trim()
.parse::<f64>()
.map_err(|e| format!("Ungültiger Zahlenwert '{}': {}", payload, e))?;
let factor = reg.factor.unwrap_or(1.0);
let raw = if factor == 0.0 {
numeric_value
} else {
numeric_value / factor
};
let rt = tokio::runtime::Runtime::new()
.map_err(|e| format!("Tokio Runtime Fehler: {}", e))?;
return rt.block_on(async {
let mut client = tcp::connect_slave(addr, 1u8.into())
.await
.map_err(|e| format!("Modbus Verbindung fehlgeschlagen: {}", e))?;
match reg.r#type.as_deref().unwrap_or("UINT16") {
"INT16" => {
let int_value = raw.round();
if int_value < i16::MIN as f64 || int_value > i16::MAX as f64 {
return Err(format!("Wert außerhalb INT16-Bereich für '{}': {}", name, numeric_value));
}
let register = int_value as i16 as u16;
client
.write_multiple_registers(reg.addr, &[register])
.await
.map_err(|e| format!("Holding Register schreiben fehlgeschlagen: {}", e))?;
}
"UINT32" => {
let int_value = raw.round();
if int_value < 0.0 || int_value > u32::MAX as f64 {
return Err(format!("Wert außerhalb UINT32-Bereich für '{}': {}", name, numeric_value));
}
let val = int_value as u32;
let high = ((val >> 16) & 0xFFFF) as u16;
let low = (val & 0xFFFF) as u16;
client
.write_multiple_registers(reg.addr, &[high, low])
.await
.map_err(|e| format!("Holding Register (UINT32) schreiben fehlgeschlagen: {}", e))?;
}
_ => {
let int_value = raw.round();
if int_value < 0.0 || int_value > u16::MAX as f64 {
return Err(format!("Wert außerhalb UINT16-Bereich für '{}': {}", name, numeric_value));
}
client
.write_multiple_registers(reg.addr, &[int_value as u16])
.await
.map_err(|e| format!("Holding Register schreiben fehlgeschlagen: {}", e))?;
}
}
Ok(())
});
}
}
}
Err(format!("Kein schreibbares Modbus-Mapping für '{}' gefunden", name))
}
fn parse_bool_payload(payload: &str) -> Result<bool, String> {
match payload.trim().to_ascii_lowercase().as_str() {
"1" | "true" | "on" => Ok(true),
"0" | "false" | "off" => Ok(false),
_ => Err(format!("Ungültiger bool-Wert '{}', erwartet 0/1 oder true/false", payload)),
}
}
/// Startet einen Thread, der zyklisch Modbus-Register abfragt und ModbusValueMaps aktualisiert
pub fn start_modbus_polling_thread(
_modbus_config: &ModbusConfig,
input_registers: &Option<Vec<HashMap<String, ModbusRegisterConfig>>>,
holding_registers: &Option<Vec<HashMap<String, ModbusRegisterConfig>>>,
coils: &Option<Vec<HashMap<String, ModbusRegisterConfig>>>,
modbus_config: &ModbusConfig,
input_registers: &Option<Vec<HashMap<String, ModbusInputRegisterConfig>>>,
holding_registers: &Option<Vec<HashMap<String, ModbusHoldingRegisterConfig>>>,
coils: &Option<Vec<HashMap<String, ModbusCoilsConfig>>>,
value_maps: Arc<Mutex<ModbusValueMaps>>,
poll_interval: Duration,
) {
let host = modbus_config.host.clone();
let port = modbus_config.port;
let input_registers = input_registers.clone();
let holding_registers = holding_registers.clone();
let coils = coils.clone();
thread::spawn(move || {
let addr: SocketAddr = format!("{}:{}", host, port).parse().unwrap();
let rt = tokio::runtime::Runtime::new().unwrap();
loop {
// Input Register
if let Some(ref vec) = input_registers {
for map in vec {
for (key, reg) in map {
let typ = reg.r#type.as_deref().unwrap_or("");
let value = match typ {
"INT16" => { let raw: i16 = 0; raw as f64 },
"UINT16" => { let raw: u16 = 0; raw as f64 },
"UINT32" => { let raw1: u16 = 0; let raw2: u16 = 0; let raw32: u32 = ((raw1 as u32) << 16) | (raw2 as u32); raw32 as f64 },
_ => 0.0,
};
if let Ok(mut maps) = value_maps.lock() {
maps.modbus_input_register_values.insert(key.clone(), value);
// Verbindungsaufbau
let connect_result = rt.block_on(async {
tcp::connect_slave(addr, 1u8.into()).await
});
match connect_result {
Ok(mut client) => {
// Werte abfragen
// Input Register
if let Some(ref vec) = input_registers {
for map in vec {
for (key, reg) in map {
let addr = reg.addr;
let typ = reg.r#type.as_deref().unwrap_or("");
let factor = reg.factor.unwrap_or(1.0);
let value: Option<f64> = rt.block_on(async {
match typ {
"INT16" => client.read_input_registers(addr, 1).await.ok().map(|v| {
let raw = v[0] as i16;
if raw == -32768 || raw == 32767 { None } else { Some(((raw as f64 * factor) * 10.0).round() / 10.0) }
}).flatten(),
"UINT16" => client.read_input_registers(addr, 1).await.ok().map(|v| {
if v[0] == 0xFFFF { None } else { Some(((v[0] as f64 * factor) * 10.0).round() / 10.0) }
}).flatten(),
"UINT32" => client.read_input_registers(addr, 2).await.ok().map(|v| {
let raw = (v[0] as u32) << 16 | (v[1] as u32);
if raw == 0xFFFFFFFF { None } else { Some(((raw as f64 * factor) * 10.0).round() / 10.0) }
}).flatten(),
_ => Some(0.0),
}
});
if let Ok(mut maps) = value_maps.lock() {
maps.modbus_input_register_values.insert(key.clone(), value);
}
}
}
}
}
}
// Holding Register
if let Some(ref vec) = holding_registers {
for map in vec {
for (key, reg) in map {
let typ = reg.r#type.as_deref().unwrap_or("");
let value = match typ {
"INT16" => { let raw: i16 = 0; raw as f64 },
"UINT16" => { let raw: u16 = 0; raw as f64 },
"UINT32" => { let raw1: u16 = 0; let raw2: u16 = 0; let raw32: u32 = ((raw1 as u32) << 16) | (raw2 as u32); raw32 as f64 },
_ => 0.0,
};
if let Ok(mut maps) = value_maps.lock() {
maps.modbus_holding_register_values.insert(key.clone(), value);
// Holding Register
if let Some(ref vec) = holding_registers {
for map in vec {
for (key, reg) in map {
let addr = reg.addr;
let typ = reg.r#type.as_deref().unwrap_or("");
let factor = reg.factor.unwrap_or(1.0);
let value: Option<f64> = rt.block_on(async {
match typ {
"INT16" => client.read_holding_registers(addr, 1).await.ok().map(|v| {
let raw = v[0] as i16;
if raw == -32768 || raw == 32767 { None } else { Some(((raw as f64 * factor) * 10.0).round() / 10.0) }
}).flatten(),
"UINT16" => client.read_holding_registers(addr, 1).await.ok().map(|v| {
if v[0] == 0xFFFF { None } else { Some(((v[0] as f64 * factor) * 10.0).round() / 10.0) }
}).flatten(),
"UINT32" => client.read_holding_registers(addr, 2).await.ok().map(|v| {
let raw = (v[0] as u32) << 16 | (v[1] as u32);
if raw == 0xFFFFFFFF { None } else { Some(((raw as f64 * factor) * 10.0).round() / 10.0) }
}).flatten(),
_ => Some(0.0),
}
});
if let Ok(mut maps) = value_maps.lock() {
maps.modbus_holding_register_values.insert(key.clone(), value);
}
}
}
}
}
}
// Coils
if let Some(ref vec) = coils {
for map in vec {
for (key, _reg) in map {
let value = 0.0; // Hier Coil-Wert abfragen
if let Ok(mut maps) = value_maps.lock() {
maps.modbus_coils_values.insert(key.clone(), value);
// Coils
if let Some(ref vec) = coils {
for map in vec {
for (key, reg) in map {
let addr = reg.addr;
let value = rt.block_on(async {
client.read_coils(addr, 1).await.ok().map(|v| if v[0] { 1.0 } else { 0.0 })
}).unwrap_or(0.0);
if let Ok(mut maps) = value_maps.lock() {
maps.modbus_coils_values.insert(key.clone(), Some(value));
}
}
}
}
},
Err(_) => {
eprintln!("Modbus: Verbindung fehlgeschlagen, retry in {:?}", poll_interval);
thread::sleep(poll_interval);
continue;
}
}
thread::sleep(poll_interval);
+31
View File
@@ -0,0 +1,31 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ModbusCoilsConfig {
pub addr: u16,
pub write: Option<bool>,
pub mqtt: Option<bool>,
pub influxdb: Option<bool>,
pub comment: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ModbusInputRegisterConfig {
pub addr: u16,
pub r#type: Option<String>,
pub factor: Option<f64>,
pub mqtt: Option<bool>,
pub influxdb: Option<bool>,
pub comment: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ModbusHoldingRegisterConfig {
pub addr: u16,
pub r#type: Option<String>,
pub factor: Option<f64>,
pub write: Option<bool>,
pub mqtt: Option<bool>,
pub influxdb: Option<bool>,
pub comment: Option<String>,
}
+227
View File
@@ -0,0 +1,227 @@
use std::sync::{Arc, Mutex};
use std::process;
use std::collections::HashMap;
use std::thread;
use std::time::{Duration, Instant};
use crate::config::{AppConfig, ModbusValueMaps};
use crate::modbus;
use rumqttc::{Event, MqttOptions, Packet, Client, QoS, RecvTimeoutError};
pub fn start_mqtt_thread(config: Arc<Mutex<AppConfig>>, values: Arc<Mutex<ModbusValueMaps>>) {
let mqtt_config = {
let cfg = config.lock().unwrap();
cfg.mqtt.clone()
};
let broker = if mqtt_config.broker.is_empty() { "localhost".to_string() } else { mqtt_config.broker.clone() };
let port = if mqtt_config.port == 0 { 1883 } else { mqtt_config.port };
let path = if let Some(ref p) = mqtt_config.path { p.clone() } else { "paramod/values".to_string() };
let leitsystem_path = mqtt_config
.leitsystem_path
.clone()
.unwrap_or_else(|| format!("{}/leitsystem", path));
let set_write_interval = Duration::from_millis(mqtt_config.set_write_interval_ms.unwrap_or(2000).max(100));
let user = mqtt_config.user.clone().unwrap_or_default();
let password = mqtt_config.password.clone().unwrap_or_default();
let user_is_empty = user.is_empty();
thread::spawn(move || {
let client_id = format!("paramod-client-{}", process::id());
let mut mqttoptions = MqttOptions::new(client_id, broker, port);
mqttoptions.set_keep_alive(Duration::from_secs(5));
if !user_is_empty {
mqttoptions.set_credentials(user, password);
}
let (mut client, mut connection) = Client::new(mqttoptions, 10);
let set_topic = format!("{}/+/set", path);
if let Err(e) = client.subscribe(set_topic, QoS::AtLeastOnce) {
eprintln!("MQTT Subscribe fehlgeschlagen: {}", e);
}
let leitsystem_state_topic = format!("{}/active", leitsystem_path.trim_end_matches('/'));
if let Err(e) = client.subscribe(leitsystem_state_topic.clone(), QoS::AtLeastOnce) {
eprintln!("MQTT Subscribe Leitsystem fehlgeschlagen: {}", e);
}
let publish_interval = Duration::from_secs(5);
let mut last_publish = Instant::now() - publish_interval;
let mut last_set_write = Instant::now() - set_write_interval;
let mut pending_set_values: HashMap<String, String> = HashMap::new();
let mut leitsystem_enabled = false;
loop {
if last_publish.elapsed() >= publish_interval {
{
let values = values.lock().unwrap();
// Input Register
for (name, val) in &values.modbus_input_register_values {
if let Some(v) = val {
if should_publish(&config, name, "input_register") {
let topic = format!("{}/{}/state", path, name);
let payload = format!("{}", v);
let _ = client.publish(topic, QoS::AtLeastOnce, false, payload);
}
}
}
// Holding Register
for (name, val) in &values.modbus_holding_register_values {
if let Some(v) = val {
if should_publish(&config, name, "holding_register") {
let topic = format!("{}/{}/state", path, name);
let payload = format!("{}", v);
let _ = client.publish(topic, QoS::AtLeastOnce, false, payload);
}
}
}
// Coils
for (name, val) in &values.modbus_coils_values {
if let Some(v) = val {
if should_publish(&config, name, "coils") {
let topic = format!("{}/{}/state", path, name);
let payload = format!("{}", v);
let _ = client.publish(topic, QoS::AtLeastOnce, false, payload);
}
}
}
}
let state_payload = if leitsystem_enabled { "ON" } else { "OFF" };
let _ = client.publish(
leitsystem_state_topic.clone(),
QoS::AtLeastOnce,
true,
state_payload,
);
last_publish = Instant::now();
}
if last_set_write.elapsed() >= set_write_interval {
let cfg = {
let lock = config.lock().unwrap();
lock.clone()
};
if leitsystem_enabled {
for (name, payload) in &pending_set_values {
if let Err(e) = modbus::write_value_by_name(&cfg, name, payload) {
eprintln!("MQTT set -> Modbus Fehler ({}): {}", name, e);
} else {
mirror_set_value(&values, &cfg, name, payload);
}
}
}
last_set_write = Instant::now();
}
match connection.recv_timeout(Duration::from_millis(200)) {
Ok(Ok(Event::Incoming(Packet::Publish(publish)))) => {
if publish.topic == leitsystem_state_topic {
let payload = String::from_utf8_lossy(&publish.payload).trim().to_string();
match parse_boolish(&payload) {
Some(v) => leitsystem_enabled = v,
None => eprintln!("Ungültiger Leitsystem state '{}', erwartet ON/OFF", payload),
}
continue;
}
if let Ok(name) = extract_name_from_set_topic(&path, &publish.topic) {
let payload = String::from_utf8_lossy(&publish.payload).trim().to_string();
pending_set_values.insert(name.clone(), payload.clone());
}
}
Ok(Ok(_)) => {}
Ok(Err(e)) => {
eprintln!("MQTT Connection Fehler: {}", e);
eprintln!("Hinweis: Häufige Ursache ist eine zweite MQTT-Session mit derselben Client-ID.");
}
Err(RecvTimeoutError::Timeout) => {}
Err(RecvTimeoutError::Disconnected) => {
eprintln!("MQTT Verbindung getrennt (request channel geschlossen)");
thread::sleep(Duration::from_millis(500));
}
}
}
});
}
fn parse_boolish(payload: &str) -> Option<bool> {
match payload.trim().to_ascii_lowercase().as_str() {
"1" | "true" | "on" => Some(true),
"0" | "false" | "off" => Some(false),
_ => None,
}
}
fn mirror_set_value(values: &Arc<Mutex<ModbusValueMaps>>, cfg: &AppConfig, name: &str, payload: &str) {
if let Ok(mut maps) = values.lock() {
if let Some(ref coils) = cfg.modbus_coils {
for map in coils {
if map.contains_key(name) {
if let Some(v) = parse_boolish(payload) {
maps.modbus_coils_values.insert(name.to_string(), Some(if v { 1.0 } else { 0.0 }));
}
return;
}
}
}
if let Some(ref holding) = cfg.modbus_holding_register {
for map in holding {
if map.contains_key(name) {
if let Ok(v) = payload.parse::<f64>() {
maps.modbus_holding_register_values.insert(name.to_string(), Some(v));
}
return;
}
}
}
}
}
fn extract_name_from_set_topic(base_path: &str, topic: &str) -> Result<String, String> {
let prefix = format!("{}/", base_path.trim_end_matches('/'));
if !topic.starts_with(&prefix) || !topic.ends_with("/set") {
return Err("Topic passt nicht zum /set-Schema".to_string());
}
let without_prefix = &topic[prefix.len()..];
let name = without_prefix.strip_suffix("/set").unwrap_or_default();
if name.is_empty() || name.contains('/') {
return Err("Ungültiger Variablenname im Topic".to_string());
}
Ok(name.to_string())
}
fn should_publish(config: &Arc<Mutex<AppConfig>>, name: &str, reg_type: &str) -> bool {
let cfg = config.lock().unwrap();
match reg_type {
"input_register" => {
if let Some(regs) = &cfg.modbus_input_register {
for map in regs {
if let Some(reg) = map.get(name) {
return reg.mqtt.unwrap_or(false);
}
}
}
}
"holding_register" => {
if let Some(regs) = &cfg.modbus_holding_register {
for map in regs {
if let Some(reg) = map.get(name) {
return reg.mqtt.unwrap_or(false);
}
}
}
}
"coils" => {
if let Some(coils) = &cfg.modbus_coils {
for map in coils {
if let Some(coil) = map.get(name) {
return coil.mqtt.unwrap_or(false);
}
}
}
}
_ => {}
}
false
}
+57 -10
View File
@@ -1,11 +1,15 @@
// Sortiere die Tabelle nach Adresse (addr) beim Laden der Seite
document.addEventListener('DOMContentLoaded', function() {
// Darkmode-Status aus localStorage übernehmen
if (localStorage.getItem('darkmode') === 'true') {
document.body.classList.add('darkmode');
} else {
document.body.classList.remove('darkmode');
}
// Darkmode-Status aus Konfiguration übernehmen
fetch('/api/config')
.then(response => response.json())
.then(config => {
if (config && config.default && config.default.darkmode === true) {
document.body.classList.add('darkmode');
} else {
document.body.classList.remove('darkmode');
}
});
const tableBody = document.getElementById('tableBody');
if (tableBody) {
// Extrahiere alle Zeilen und deren addr
@@ -52,6 +56,34 @@ function addRow() {
<button class="delete-btn" onclick="deleteRow(this)">🗑️</button>
</td>
`;
} else if (typeof tableId !== 'undefined' && tableId === 'modbus_holding_register') {
newRow.innerHTML = `
<td><input type='text' class='text-input' data-field='bezeichnung' value='' /></td>
<td><input type='text' class='text-input' data-field='adresse' value='' /></td>
<td><input type='text' class='text-input' data-field='type' value='' /></td>
<td><input type='text' class='text-input' data-field='faktor' value='1.0' /></td>
<td>
<label class='switch'>
<input type='checkbox' class='bool-input' data-field='write' />
<span class='slider'></span>
</label>
</td>
<td>
<label class='switch'>
<input type='checkbox' class='bool-input' data-field='mqtt' />
<span class='slider'></span>
</label>
</td>
<td>
<label class='switch'>
<input type='checkbox' class='bool-input' data-field='influxdb' />
<span class='slider'></span>
</label>
</td>
<td>
<button class="delete-btn" onclick="deleteRow(this)">🗑️</button>
</td>
`;
} else {
newRow.innerHTML = `
<td><input type='text' class='text-input' data-field='bezeichnung' value='' /></td>
@@ -115,13 +147,27 @@ async function saveTable() {
influxdb: influxdb,
comment: comment
};
} else {
const rtype = row.querySelector("input[data-field='type']")?.value || null;
} else if (typeof tableId !== 'undefined' && tableId === 'modbus_holding_register') {
const write = row.querySelector("input[data-field='write']")?.checked || false;
const type = row.querySelector("input[data-field='type']")?.value || null;
const factor = parseFloat(row.querySelector("input[data-field='factor']")?.value || row.querySelector("input[data-field='faktor']")?.value || '1.0');
const comment = row.querySelector("input[data-field='comment']")?.value || null;
value = {
addr: addr,
rtype: rtype,
type: type,
factor: factor,
write: write,
mqtt: mqtt,
influxdb: influxdb,
comment: comment
};
} else {
const type = row.querySelector("input[data-field='type']")?.value || null;
const factor = parseFloat(row.querySelector("input[data-field='factor']")?.value || row.querySelector("input[data-field='faktor']")?.value || '1.0');
const comment = row.querySelector("input[data-field='comment']")?.value || null;
value = {
addr: addr,
type: type,
factor: factor,
mqtt: mqtt,
influxdb: influxdb,
@@ -150,8 +196,9 @@ async function saveTable() {
messageDiv.className = 'message success';
messageDiv.textContent = '✓ Erfolgreich gespeichert!';
} else {
const errorText = (await response.text()) || 'Unbekannter Fehler';
messageDiv.className = 'message error';
messageDiv.textContent = '✗ Fehler beim Speichern!';
messageDiv.textContent = `✗ Fehler beim Speichern: ${errorText}`;
}
setTimeout(() => {
+60 -37
View File
@@ -1,11 +1,62 @@
// Setze den Wert des Darkmode-Schalters beim Laden der Seite, sobald das Element existiert
document.addEventListener('DOMContentLoaded', function() {
fetch('/api/config')
.then(response => response.json())
.then(config => {
function setSwitch() {
const el = document.getElementById('darkmode_switch');
if (el) {
if (config && config.default && (config.default.darkmode === true || config.default.darkmode === 'true')) {
el.checked = true;
document.body.classList.add('darkmode');
} else {
el.checked = false;
document.body.classList.remove('darkmode');
}
// Event Listener für Umschalten
el.addEventListener('change', function() {
if (el.checked) {
document.body.classList.add('darkmode');
} else {
document.body.classList.remove('darkmode');
}
});
} else {
setTimeout(setSwitch, 50);
}
}
setSwitch();
});
});
async function saveSettings() {
// Default (für [default])
const darkmode = document.getElementById('darkmode_switch')?.checked ?? null;
const defaultConfig = {
loglevel: document.getElementById('loglevel')?.value || null,
darkmode: darkmode
};
// Modbus
const modbus = {
host: document.getElementById('modbus_host').value,
port: parseInt(document.getElementById('modbus_port').value, 10),
max_coils_addr: parseInt(document.getElementById('modbus_max_coils_addr').value, 10) || null,
max_input_addr: parseInt(document.getElementById('modbus_max_input_addr').value, 10) || null,
max_holding_addr: parseInt(document.getElementById('modbus_max_holding_addr').value, 10) || null,
modbus_coils: null,
modbus_input_register: null,
modbus_holding_register: null,
};
// MQTT
const mqtt = {
broker: document.getElementById('mqtt_broker').value,
port: parseInt(document.getElementById('mqtt_port').value, 10),
user: document.getElementById('mqtt_user').value || null,
password: document.getElementById('mqtt_password').value || null,
button_circulation: document.getElementById('mqtt_button_circulation').value || null
path: document.getElementById('mqtt_path')?.value || null,
leitsystem_path: document.getElementById('mqtt_leitsystem_path')?.value || null,
set_write_interval_ms: parseInt(document.getElementById('mqtt_set_write_interval_ms')?.value, 10) || 2000
};
// InfluxDB
@@ -18,28 +69,11 @@ async function saveSettings() {
measurement: document.getElementById('influxdb_measurement').value || null
};
// Modbus
const modbus = {
host: document.getElementById('modbus_host').value,
port: parseInt(document.getElementById('modbus_port').value, 10),
max_coils_addr: parseInt(document.getElementById('modbus_max_coils_addr').value, 10) || null,
max_input_addr: parseInt(document.getElementById('modbus_max_input_addr').value, 10) || null,
max_holding_addr: parseInt(document.getElementById('modbus_max_holding_addr').value, 10) || null,
modbus_coils: null,
modbus_input_register: null,
modbus_holding_register: null
};
// Default (für [default])
const defaultConfig = {
loglevel: document.getElementById('loglevel')?.value || null
};
const settings = {
default: defaultConfig,
modbus,
mqtt,
influxdb,
modbus
influxdb
};
try {
@@ -55,14 +89,16 @@ async function saveSettings() {
if (response.ok) {
messageDiv.className = 'message success';
messageDiv.textContent = '✓ Einstellungen erfolgreich gespeichert!';
setTimeout(() => {
window.location.reload();
}, 1000);
} else {
messageDiv.className = 'message error';
messageDiv.textContent = '✗ Fehler beim Speichern der Einstellungen!';
setTimeout(() => {
messageDiv.style.display = 'none';
}, 3000);
}
setTimeout(() => {
messageDiv.style.display = 'none';
}, 3000);
} catch (error) {
const messageDiv = document.getElementById('message');
messageDiv.className = 'message error';
@@ -70,16 +106,3 @@ async function saveSettings() {
}
}
// Darkmode Toggle
document.addEventListener('DOMContentLoaded', function() {
const btn = document.getElementById('darkmode-toggle');
if (!btn) return;
// Initialer Zustand aus LocalStorage
if (localStorage.getItem('darkmode') === 'true') {
document.body.classList.add('darkmode');
}
btn.addEventListener('click', function() {
document.body.classList.toggle('darkmode');
localStorage.setItem('darkmode', document.body.classList.contains('darkmode'));
});
});
+3 -32
View File
@@ -32,7 +32,7 @@
--error-border: #f5c6cb;
}
body.darkmode, body.darkmode :root {
body.darkmode {
--background: #181a1b;
--background-alt: #23272b;
--container-bg: #23272b;
@@ -60,35 +60,6 @@ body.darkmode, body.darkmode :root {
--error-border: #b71c1c;
}
@media (prefers-color-scheme: dark) {
:root {
--background: #181a1b;
--background-alt: #23272b;
--container-bg: #23272b;
--header-bg: #23272b;
--primary: #3399ff;
--primary-dark: #1a2a3a;
--text: #eee;
--text-light: #bbb;
--nav-link: #bbb;
--nav-link-hover: #222;
--table-header: #222e3a;
--table-header-border: #3399ff;
--table-row-hover: #23272b;
--input-border: #444;
--input-focus: #3399ff;
--button-gradient: linear-gradient(135deg, #3399ff 0%, #1a2a3a 100%);
--button-gradient-alt: linear-gradient(135deg, #388e3c 0%, #1b5e20 100%);
--delete-btn: #b71c1c;
--delete-btn-hover: #7f0000;
--success-bg: #234d2c;
--success-text: #b9f6ca;
--success-border: #388e3c;
--error-bg: #4a2323;
--error-text: #ff8a80;
--error-border: #b71c1c;
}
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
@@ -125,8 +96,8 @@ body {
}
.logo-text {
font-size: 20px;
font-weight: 700;
font-size: 40px;
font-weight: 800;
color: var(--text-light);
}
+16 -3
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sensor Konfiguration</title>
<title>Konfiguration</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
@@ -14,7 +14,7 @@
<rect width="40" height="40" rx="8" fill="#667eea"/>
<path d="M12 20L18 26L28 14" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span class="logo-text">Sensor Manager</span>
<span class="logo-text">Paramod</span>
</div>
<nav class="nav">
<a href="/table/modbus_input_register" class="nav-link {% if active_page == 'modbus_input_register' %}active{% endif %}">Input Register</a>
@@ -26,7 +26,7 @@
</header>
<div class="container">
<h1>🔧 Sensor Konfiguration - {{ table_id | upper }}</h1>
<h1>🔧 Konfiguration - {{ table_id | upper }}</h1>
<div id="message" class="message"></div>
<div class="table-wrapper">
<table id="sensorTable">
@@ -37,6 +37,10 @@
<th>Adresse</th>
{% if table_id == "modbus_coils" %}
<th>Write</th>
{% elif table_id == "modbus_holding_registers" or table_id == "modbus_holding_register" %}
<th>Type</th>
<th>Faktor</th>
<th>Write</th>
{% else %}
<th>Type</th>
<th>Faktor</th>
@@ -63,6 +67,15 @@
<span class='slider'></span>
</label>
</td>
{% elif table_id == "modbus_holding_registers" or table_id == "modbus_holding_register" %}
<td><input type='text' class='text-input' data-field='type' value='{{ row.type | default(value="") }}' /></td>
<td><input type='text' class='text-input' data-field='factor' value='{{ row.factor | default(value="") }}' /></td>
<td>
<label class='switch'>
<input type='checkbox' class='bool-input' data-field='write' {% if row.write %}checked{% endif %} />
<span class='slider'></span>
</label>
</td>
{% else %}
<td><input type='text' class='text-input' data-field='type' value='{{ row.type | default(value="") }}' /></td>
<td><input type='text' class='text-input' data-field='factor' value='{{ row.factor | default(value="") }}' /></td>
+43 -31
View File
@@ -6,7 +6,7 @@
<title>Einstellungen</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<body{% if default.darkmode == true or default.darkmode == "true" %} class="darkmode"{% endif %}>
<header class="header">
<div class="header-content">
<div class="logo">
@@ -14,7 +14,7 @@
<rect width="40" height="40" rx="8" fill="#667eea"/>
<path d="M12 20L18 26L28 14" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span class="logo-text">Sensor Manager</span>
<span class="logo-text">Paramod</span>
</div>
<nav class="nav">
<a href="/table/modbus_input_register" class="nav-link {% if active_page == 'modbus_input_register' %}active{% endif %}">Input Register</a>
@@ -41,9 +41,37 @@
<option value="WARN">WARN</option>
<option value="ERROR">ERROR</option>
</select>
</div>
<div class="form-group" style="text-align:right;">
<button id="darkmode-toggle" class="add-btn" type="button">🌙 Darkmode umschalten</button>
</div>
<div class="form-group" style="display: flex; align-items: center; gap: 12px;">
<label for="darkmode_switch">Darkmode:</label>
<label class="switch">
<input type="checkbox" id="darkmode_switch">
<span class="slider"></span>
</label>
</div>
</div>
<div class="settings-section">
<h2>Modbus Konfiguration</h2>
<div class="form-group">
<label for="modbus_host">Host:</label>
<input type="text" id="modbus_host" class="text-input" value="{{ modbus.host }}" />
</div>
<div class="form-group">
<label for="modbus_port">Port:</label>
<input type="text" id="modbus_port" class="text-input" value="{{ modbus.port }}" />
</div>
<div class="form-group">
<label for="modbus_max_coils_addr">Max Coils Addr:</label>
<input type="text" id="modbus_max_coils_addr" class="text-input" value="{{ modbus.max_coils_addr | default(value="") }}" />
</div>
<div class="form-group">
<label for="modbus_max_input_addr">Max Input Addr:</label>
<input type="text" id="modbus_max_input_addr" class="text-input" value="{{ modbus.max_input_addr | default(value="") }}" />
</div>
<div class="form-group">
<label for="modbus_max_holding_addr">Max Holding Addr:</label>
<input type="text" id="modbus_max_holding_addr" class="text-input" value="{{ modbus.max_holding_addr | default(value="") }}" />
</div>
</div>
@@ -66,8 +94,16 @@
<input type="password" id="mqtt_password" class="text-input" value="{{ mqtt.password | default(value="") }}" />
</div>
<div class="form-group">
<label for="mqtt_button_circulation">Button Circulation:</label>
<input type="text" id="mqtt_button_circulation" class="text-input" value="{{ mqtt.button_circulation | default(value="") }}" />
<label for="mqtt_path">MQTT Pfad:</label>
<input type="text" id="mqtt_path" class="text-input" value="{{ mqtt.path | default(value="") }}" />
</div>
<div class="form-group">
<label for="mqtt_leitsystem_path">Leitsystem Pfad:</label>
<input type="text" id="mqtt_leitsystem_path" class="text-input" value="{{ mqtt.leitsystem_path | default(value="") }}" />
</div>
<div class="form-group">
<label for="mqtt_set_write_interval_ms">Set-Sendeintervall (ms):</label>
<input type="number" min="100" id="mqtt_set_write_interval_ms" class="text-input" value="{{ mqtt.set_write_interval_ms | default(value="2000") }}" />
</div>
</div>
@@ -99,30 +135,6 @@
</div>
</div>
<div class="settings-section">
<h2>Modbus Konfiguration</h2>
<div class="form-group">
<label for="modbus_host">Host:</label>
<input type="text" id="modbus_host" class="text-input" value="{{ modbus.host }}" />
</div>
<div class="form-group">
<label for="modbus_port">Port:</label>
<input type="text" id="modbus_port" class="text-input" value="{{ modbus.port }}" />
</div>
<div class="form-group">
<label for="modbus_max_coils_addr">Max Coils Addr:</label>
<input type="text" id="modbus_max_coils_addr" class="text-input" value="{{ modbus.max_coils_addr | default(value="") }}" />
</div>
<div class="form-group">
<label for="modbus_max_input_addr">Max Input Addr:</label>
<input type="text" id="modbus_max_input_addr" class="text-input" value="{{ modbus.max_input_addr | default(value="") }}" />
</div>
<div class="form-group">
<label for="modbus_max_holding_addr">Max Holding Addr:</label>
<input type="text" id="modbus_max_holding_addr" class="text-input" value="{{ modbus.max_holding_addr | default(value="") }}" />
</div>
</div>
<button class="save-btn" onclick="saveSettings()">💾 Einstellungen speichern</button>
</div>