Compare commits

..

No commits in common. "e74689c70fbcdfc4a85e14467323a4698d1bd2a4" and "220161a70aeaf09c72eea3aa5ac1c991d18b6a0d" have entirely different histories.

12 changed files with 197 additions and 919 deletions

15
.vscode/launch.json vendored
View File

@ -1,15 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug main.rs",
"type": "lldb",
"request": "launch",
"program": "${workspaceFolder}/target/debug/table-server",
"args": [],
"cwd": "${workspaceFolder}",
"sourceLanguages": ["rust"],
"preLaunchTask": "cargo build"
}
]
}

15
.vscode/tasks.json vendored
View File

@ -1,15 +0,0 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "cargo build",
"type": "shell",
"command": "cargo build",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": ["$rustc"]
}
]
}

278
Cargo.lock generated
View File

@ -52,7 +52,7 @@ dependencies = [
"actix-rt",
"actix-service",
"actix-utils",
"base64 0.22.1",
"base64",
"bitflags",
"brotli",
"bytes",
@ -214,18 +214,6 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "ahash"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "aho-corasick"
version = "1.1.4"
@ -250,12 +238,6 @@ dependencies = [
"alloc-no-stdlib",
]
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "android_system_properties"
version = "0.1.5"
@ -265,35 +247,12 @@ dependencies = [
"libc",
]
[[package]]
name = "arraydeque"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236"
[[package]]
name = "async-trait"
version = "0.1.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "base64"
version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]]
name = "base64"
version = "0.22.1"
@ -305,9 +264,6 @@ name = "bitflags"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
dependencies = [
"serde_core",
]
[[package]]
name = "block-buffer"
@ -421,54 +377,6 @@ dependencies = [
"phf_codegen",
]
[[package]]
name = "config"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68578f196d2a33ff61b27fae256c3164f65e36382648e30666dde05b8cc9dfdf"
dependencies = [
"async-trait",
"convert_case",
"json5",
"nom",
"pathdiff",
"ron",
"rust-ini",
"serde",
"serde_json",
"toml",
"yaml-rust2",
]
[[package]]
name = "const-random"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
dependencies = [
"const-random-macro",
]
[[package]]
name = "const-random-macro"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
dependencies = [
"getrandom 0.2.16",
"once_cell",
"tiny-keccak",
]
[[package]]
name = "convert_case"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "cookie"
version = "0.16.2"
@ -529,12 +437,6 @@ version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crunchy"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]]
name = "crypto-common"
version = "0.1.7"
@ -602,15 +504,6 @@ dependencies = [
"syn",
]
[[package]]
name = "dlv-list"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f"
dependencies = [
"const-random",
]
[[package]]
name = "encoding_rs"
version = "0.8.35"
@ -769,31 +662,12 @@ dependencies = [
"tracing",
]
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
"ahash",
"allocator-api2",
]
[[package]]
name = "hashbrown"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
[[package]]
name = "hashlink"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
dependencies = [
"hashbrown 0.14.5",
]
[[package]]
name = "http"
version = "0.2.12"
@ -987,7 +861,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
dependencies = [
"equivalent",
"hashbrown 0.16.0",
"hashbrown",
]
[[package]]
@ -1016,17 +890,6 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "json5"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1"
dependencies = [
"pest",
"pest_derive",
"serde",
]
[[package]]
name = "language-tags"
version = "0.3.2"
@ -1111,12 +974,6 @@ dependencies = [
"unicase",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.8.9"
@ -1139,16 +996,6 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "num-conv"
version = "0.1.0"
@ -1170,16 +1017,6 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "ordered-multimap"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79"
dependencies = [
"dlv-list",
"hashbrown 0.14.5",
]
[[package]]
name = "parking_lot"
version = "0.12.5"
@ -1212,12 +1049,6 @@ dependencies = [
"regex",
]
[[package]]
name = "pathdiff"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
[[package]]
name = "percent-encoding"
version = "2.3.2"
@ -1474,28 +1305,6 @@ version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "ron"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94"
dependencies = [
"base64 0.21.7",
"bitflags",
"serde",
"serde_derive",
]
[[package]]
name = "rust-ini"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e0698206bcb8882bf2a9ecb4c1e7785db57ff052297085a6efd4fe42302068a"
dependencies = [
"cfg-if",
"ordered-multimap",
]
[[package]]
name = "rustversion"
version = "1.0.22"
@ -1566,15 +1375,6 @@ dependencies = [
"serde_core",
]
[[package]]
name = "serde_spanned"
version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
dependencies = [
"serde",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
@ -1712,14 +1512,10 @@ version = "0.1.0"
dependencies = [
"actix-files",
"actix-web",
"config",
"serde",
"serde_derive",
"serde_json",
"tera",
"tokio",
"toml",
"toml_edit",
]
[[package]]
@ -1775,15 +1571,6 @@ dependencies = [
"time-core",
]
[[package]]
name = "tiny-keccak"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
dependencies = [
"crunchy",
]
[[package]]
name = "tinystr"
version = "0.8.2"
@ -1835,47 +1622,6 @@ dependencies = [
"tokio",
]
[[package]]
name = "toml"
version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"toml_write",
"winnow",
]
[[package]]
name = "toml_write"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
[[package]]
name = "tracing"
version = "0.1.41"
@ -2268,15 +2014,6 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
[[package]]
name = "winnow"
version = "0.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
dependencies = [
"memchr",
]
[[package]]
name = "wit-bindgen"
version = "0.46.0"
@ -2289,17 +2026,6 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
[[package]]
name = "yaml-rust2"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8902160c4e6f2fb145dbe9d6760a75e3c9522d8bf796ed7047c85919ac7115f8"
dependencies = [
"arraydeque",
"encoding_rs",
"hashlink",
]
[[package]]
name = "yoke"
version = "0.8.1"

View File

@ -4,15 +4,9 @@ version = "0.1.0"
edition = "2021"
[dependencies]
toml_edit = "0.22"
actix-web = "4.4.0"
actix-files = "0.6.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1", features = ["full"] }
tera = "1.19"
# Für TOML/INI-Konfiguration
config = "0.14"
toml = "0.8"
serde_derive = "1.0"

View File

@ -1,11 +0,0 @@
# Heizungsteuerung einer Paradigma-Anlage über Modbus
Man kann an einer Paradigma Anlage über Modbus-TCP viele Daten abfragen und und setzen. Ziel dieses Projekts ist es, die Daten sowohl als MQTT-Daten bereitzustellen, als auch in eine InfluxDB einzupflegen.
Zusätzlich soll noch die Möglichkeit gegeben werden, über MQTT Werte zu setzen und damit die Steuerung zu übernehmen.
## 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.
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.

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, comment = "Leitsystem aktiv"}
HK1pres = {addr = 1, comment = "HK1 vorhanden"}
HK2pres = {addr = 2, comment = "HK2 vorhanden"}
HK3pres = {addr = 3, comment = "HK3 vorhanden"}
TWrelease = {addr = 4, comment = "Trinkwassererwärmung freigegeben"}
TWlock = {addr = 5, comment = "Trinkwassererwärmung gesprerrt"}
Zrelease = {addr = 6, comment = "Zirkulation freigegeben"}
Zlock = {addr = 7, comment = "Zirkulation gesperrt"}
SHKpres = {addr = 8, 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}

View File

@ -1,78 +1,109 @@
use actix_web::{web, App, HttpResponse, HttpServer, Result};
use actix_files as actix_fs;
use actix_files as fs;
use serde::{Deserialize, Serialize};
use std::fs as std_fs;
use std::sync::Mutex;
use tera::{Context, Tera};
// use config::{Config, File};
use std::fs;
use toml_edit::{Document, value, Item};
use std::collections::HashMap;
#[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>,
struct TableRow {
bezeichnung: String,
adresse: String,
r#type: String,
faktor: String,
mqtt: bool,
influxdb: bool,
}
#[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>,
struct Settings {
mqtt_broker: String,
mqtt_port: String,
influxdb_url: String,
influxdb_token: String,
}
impl Default for Settings {
fn default() -> Self {
Settings {
mqtt_broker: "localhost".to_string(),
mqtt_port: "1883".to_string(),
influxdb_url: "http://localhost:8086".to_string(),
influxdb_token: "".to_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>,
struct AppConfig {
table1: Vec<TableRow>,
table2: Vec<TableRow>,
table3: Vec<TableRow>,
settings: Settings,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ModbusConfig {
pub host: String,
pub port: u16,
pub max_coils_addr: Option<u16>,
pub max_input_addr: Option<u16>,
pub max_holding_addr: Option<u16>,
impl Default for AppConfig {
fn default() -> Self {
AppConfig {
table1: vec![
TableRow {
bezeichnung: "Temp Sensor 1".to_string(),
adresse: "192.168.1.100".to_string(),
r#type: "Temperatur".to_string(),
faktor: "1.0".to_string(),
mqtt: true,
influxdb: false,
},
TableRow {
bezeichnung: "Temp Sensor 2".to_string(),
adresse: "192.168.1.101".to_string(),
r#type: "Temperatur".to_string(),
faktor: "1.0".to_string(),
mqtt: false,
influxdb: true,
},
],
table2: vec![
TableRow {
bezeichnung: "Humidity Sensor 1".to_string(),
adresse: "192.168.1.200".to_string(),
r#type: "Luftfeuchtigkeit".to_string(),
faktor: "0.5".to_string(),
mqtt: true,
influxdb: true,
},
],
table3: vec![
TableRow {
bezeichnung: "Pressure Sensor 1".to_string(),
adresse: "192.168.1.300".to_string(),
r#type: "Druck".to_string(),
faktor: "2.0".to_string(),
mqtt: true,
influxdb: false,
},
],
settings: Settings::default(),
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct DefaultConfig {
pub loglevel: Option<String>,
}
#[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>>>,
}
struct AppState {
config: Mutex<AppConfig>,
config_path: String,
templates: Tera,
}
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 = toml::from_str(&conf_str).expect("Config-Deserialisierung fehlgeschlagen");
fn load_or_create(config_path: &str) -> Self {
let config = match std_fs::read_to_string(config_path) {
Ok(content) => serde_json::from_str(&content).unwrap_or_default(),
Err(_) => {
let default = AppConfig::default();
let _ = std_fs::write(config_path, serde_json::to_string_pretty(&default).unwrap());
default
}
};
let tera = match Tera::new("templates/**/*") {
Ok(t) => t,
@ -84,29 +115,32 @@ impl AppState {
AppState {
config: Mutex::new(config),
config_path: config_path.to_string(),
templates: tera,
}
}
fn save(&self) -> Result<(), std::io::Error> {
let config = self.config.lock().unwrap();
let json = serde_json::to_string_pretty(&*config)?;
std_fs::write(&self.config_path, json)
}
}
async fn index(data: web::Data<AppState>) -> Result<HttpResponse> {
let config = data.config.lock().unwrap();
let mut context = Context::new();
// modbus_input_register als rows, sortiert nach addr
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", "modbus_input_register");
context.insert("active_page", "modbus_input_register");
context.insert("rows", &config.table1);
context.insert("table_id", "table1");
context.insert("active_page", "table1");
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))
}
@ -114,26 +148,24 @@ async fn table_page(data: web::Data<AppState>, path: web::Path<String>) -> Resul
let table_id = path.into_inner();
let config = data.config.lock().unwrap();
let mut context = Context::new();
let mut rows = match table_id.as_str() {
"modbus_input_register" => config.modbus_input_register.clone().unwrap_or_default(),
"modbus_holding_register" => config.modbus_holding_register.clone().unwrap_or_default(),
"modbus_coils" => config.modbus_coils.clone().unwrap_or_default(),
let rows = match table_id.as_str() {
"table1" => &config.table1,
"table2" => &config.table2,
"table3" => &config.table3,
_ => return Ok(HttpResponse::NotFound().body("Table not found")),
};
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);
let mut context = Context::new();
context.insert("rows", rows);
context.insert("table_id", &table_id);
context.insert("active_page", &table_id);
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))
}
@ -141,28 +173,22 @@ async fn settings_page(data: web::Data<AppState>) -> Result<HttpResponse> {
let config = data.config.lock().unwrap();
let mut context = Context::new();
context.insert("default", &config.default);
context.insert("mqtt", &config.mqtt);
context.insert("influxdb", &config.influxdb);
context.insert("modbus", &config.modbus);
context.insert("modbus_coils", &config.modbus_coils);
context.insert("modbus_input_register", &config.modbus_input_register);
context.insert("modbus_holding_register", &config.modbus_holding_register);
context.insert("settings", &config.settings);
context.insert("active_page", "settings");
let html = data.templates.render("settings.html", &context)
.map_err(|e| {
eprintln!("Template error: {}", e);
actix_web::error::ErrorInternalServerError("Template error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(html))
}
#[derive(Serialize, Deserialize)]
#[derive(Deserialize)]
struct SaveTableRequest {
table_id: String,
rows: Vec<HashMap<String, ModbusRegisterConfig>>,
rows: Vec<TableRow>,
}
async fn save_table(
@ -170,110 +196,40 @@ async fn save_table(
req: web::Json<SaveTableRequest>,
) -> Result<HttpResponse> {
let mut config = data.config.lock().unwrap();
// Lade bestehende Datei mit toml_edit
let conf_path = "old/paramod.conf";
let conf_str = match fs::read_to_string(conf_path) {
Ok(s) => s,
Err(e) => return Ok(HttpResponse::InternalServerError().body(format!("Fehler beim Lesen: {}", e))),
};
let mut doc = match conf_str.parse::<Document>() {
Ok(d) => d,
Err(e) => return Ok(HttpResponse::InternalServerError().body(format!("TOML-Parse-Fehler: {}", e))),
};
let key = match req.table_id.as_str() {
"modbus_input_register" => "modbus_input_register",
"modbus_holding_register" => "modbus_holding_register",
"modbus_coils" => "modbus_coils",
_ => return Ok(HttpResponse::BadRequest().body("Invalid table_id")),
};
// Serialisiere nur das relevante Feld als TOML
let value_toml = match toml::to_string(&req.rows) {
Ok(s) => s,
Err(e) => return Ok(HttpResponse::InternalServerError().body(format!("Serialisierungsfehler: {}", e))),
};
// value_toml ist z.B. "[[...]]", wir brauchen nur den Wert
let value_item = value_toml.parse::<Item>().unwrap_or(Item::None);
doc[key] = value_item;
// Schreibe zurück
if let Err(e) = fs::write(conf_path, doc.to_string()) {
return Ok(HttpResponse::InternalServerError().body(format!("Fehler beim Schreiben: {}", e)));
match req.table_id.as_str() {
"table1" => config.table1 = req.rows.clone(),
"table2" => config.table2 = req.rows.clone(),
"table3" => config.table3 = req.rows.clone(),
_ => return Ok(HttpResponse::BadRequest().json(serde_json::json!({"status": "error", "message": "Invalid table_id"}))),
}
drop(config);
match data.save() {
Ok(_) => Ok(HttpResponse::Ok().json(serde_json::json!({"status": "success"}))),
Err(_) => Ok(HttpResponse::InternalServerError().json(serde_json::json!({"status": "error"}))),
}
Ok(HttpResponse::Ok().body("success"))
}
async fn save_settings(
data: web::Data<AppState>,
settings: web::Json<AppConfig>,
settings: web::Json<Settings>,
) -> Result<HttpResponse> {
let mut config = data.config.lock().unwrap();
*config = settings.into_inner();
let conf_path = "old/paramod.conf";
let conf_str = match fs::read_to_string(conf_path) {
Ok(s) => s,
Err(e) => return Ok(HttpResponse::InternalServerError().body(format!("Fehler beim Lesen: {}", e))),
};
let mut doc = match conf_str.parse::<Document>() {
Ok(d) => d,
Err(e) => return Ok(HttpResponse::InternalServerError().body(format!("TOML-Parse-Fehler: {}", e))),
};
// mqtt
let mqtt_value = toml::Value::try_from(&config.mqtt).map_err(|e| actix_web::error::ErrorInternalServerError(format!("Serialisierungsfehler: {}", e)))?;
if let toml::Value::Table(table) = mqtt_value {
let mut item = Item::Table(Default::default());
if let Item::Table(ref mut t) = item {
for (k, v) in table {
t[&k] = v.to_string().parse::<Item>().unwrap_or(Item::None);
config.settings = settings.into_inner();
drop(config);
match data.save() {
Ok(_) => Ok(HttpResponse::Ok().json(serde_json::json!({"status": "success"}))),
Err(_) => Ok(HttpResponse::InternalServerError().json(serde_json::json!({"status": "error"}))),
}
}
doc["mqtt"] = item;
}
// influxdb
let influxdb_value = toml::Value::try_from(&config.influxdb).map_err(|e| actix_web::error::ErrorInternalServerError(format!("Serialisierungsfehler: {}", e)))?;
if let toml::Value::Table(table) = influxdb_value {
let mut item = Item::Table(Default::default());
if let Item::Table(ref mut t) = item {
for (k, v) in table {
t[&k] = v.to_string().parse::<Item>().unwrap_or(Item::None);
}
}
doc["influxdb"] = item;
}
// modbus
let modbus_value = toml::Value::try_from(&config.modbus).map_err(|e| actix_web::error::ErrorInternalServerError(format!("Serialisierungsfehler: {}", e)))?;
if let toml::Value::Table(table) = modbus_value {
let mut item = Item::Table(Default::default());
if let Item::Table(ref mut t) = item {
for (k, v) in table {
t[&k] = v.to_string().parse::<Item>().unwrap_or(Item::None);
}
}
doc["modbus"] = item;
}
// [default] Abschnitt
let default_value = toml::Value::try_from(&config.default).map_err(|e| actix_web::error::ErrorInternalServerError(format!("Serialisierungsfehler: {}", e)))?;
if let toml::Value::Table(table) = default_value {
let mut item = Item::Table(Default::default());
if let Item::Table(ref mut t) = item {
for (k, v) in table {
t[&k] = v.to_string().parse::<Item>().unwrap_or(Item::None);
}
}
doc["default"] = item;
}
// Schreibe zurück
if let Err(e) = fs::write(conf_path, doc.to_string()) {
return Ok(HttpResponse::InternalServerError().body(format!("Fehler beim Schreiben: {}", e)));
}
Ok(HttpResponse::Ok().body("success"))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let config_path = "old/paramod.conf";
let app_state = web::Data::new(AppState::load_from_conf(config_path));
let config_path = "table_config.json";
let app_state = web::Data::new(AppState::load_or_create(config_path));
println!("Server läuft auf http://0.0.0.0:8080");
@ -285,7 +241,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))
.service(actix_fs::Files::new("/static", "./static"))
.service(fs::Files::new("/static", "./static"))
})
.bind("0.0.0.0:8080")?
.run()

View File

@ -1,100 +0,0 @@
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tokio::sync::Notify;
use tokio::task::JoinHandle;
use tokio_modbus::client::tcp;
use tokio_modbus::prelude::*;
use log::{info, error, debug};
// Platzhalter für Konfiguration
#[derive(Clone)]
pub struct ModbusConfig {
pub modbus_host: String,
pub modbus_port: u16,
pub modbus_max_holding_addr: u16,
pub modbus_max_input_addr: u16,
// Register-Konfigurationen als Map: key -> (addr, type, factor, mqtt, influxdb)
pub modbus_input_register: HashMap<String, RegisterConfig>,
pub modbus_holding_register: HashMap<String, RegisterConfig>,
}
#[derive(Clone)]
pub struct RegisterConfig {
pub addr: u16,
pub reg_type: String, // z.B. "INT16", "UINT16"
pub factor: f64,
pub mqtt: bool,
pub influxdb: bool,
}
const NONSET_INT16: i16 = -32768;
const NONSET_UINT16: u16 = 65535;
pub struct ModbusThread {
config: ModbusConfig,
timeout: Duration,
polling_interval: Duration,
min_write_interval: Duration,
running: Arc<Notify>,
stop_notify: Arc<Notify>,
// Register-Cache
input_register: Arc<Mutex<Vec<u16>>>,
holding_register: Arc<Mutex<Vec<u16>>>,
// Schreib-Queue
write_holding_register: Arc<Mutex<HashMap<u16, u16>>>,
// Async-Task-Handle
handle: Option<JoinHandle<()>>,
}
impl ModbusThread {
pub fn new(config: ModbusConfig, timeout: f64, polling_interval: f64, min_write_interval: f64) -> Self {
Self {
config,
timeout: Duration::from_secs_f64(timeout),
polling_interval: Duration::from_secs_f64(polling_interval),
min_write_interval: Duration::from_secs_f64(min_write_interval),
running: Arc::new(Notify::new()),
stop_notify: Arc::new(Notify::new()),
input_register: Arc::new(Mutex::new(Vec::new())),
holding_register: Arc::new(Mutex::new(Vec::new())),
write_holding_register: Arc::new(Mutex::new(HashMap::new())),
handle: None,
}
}
pub fn start(&mut self) {
let config = self.config.clone();
let timeout = self.timeout;
let polling_interval = self.polling_interval;
let min_write_interval = self.min_write_interval;
let running = self.running.clone();
let stop_notify = self.stop_notify.clone();
let input_register = self.input_register.clone();
let holding_register = self.holding_register.clone();
let write_holding_register = self.write_holding_register.clone();
self.handle = Some(tokio::spawn(async move {
let mut write_countdown = min_write_interval;
let socket_addr = format!("{}:{}", config.modbus_host, config.modbus_port);
loop {
// Stop-Check
if stop_notify.is_notified() {
break;
}
// Connect
let mut ctx = match tcp::connect(socket_addr.clone()).await {
Ok(c) => {
info!("Modbus: Verbindung hergestellt");
c
}
Err(e) => {
error!("Modbus: Verbindungsfehler: {:?}", e);
tokio::time::sleep(Duration::from_secs(2)).await;
continue;
}
};
// Polling-Loop
loop {
if stop_notify.is_notified() {
let _ =

View File

@ -1,18 +1,3 @@
// Sortiere die Tabelle nach Adresse (addr) beim Laden der Seite
document.addEventListener('DOMContentLoaded', function() {
const tableBody = document.getElementById('tableBody');
if (tableBody) {
// Extrahiere alle Zeilen und deren addr
const rows = Array.from(tableBody.querySelectorAll('tr'));
rows.sort((a, b) => {
const addrA = parseInt(a.querySelector("input[data-field='addr']")?.value || '0', 10);
const addrB = parseInt(b.querySelector("input[data-field='addr']")?.value || '0', 10);
return addrA - addrB;
});
// Füge sortierte Zeilen wieder ein
rows.forEach(row => tableBody.appendChild(row));
}
});
function addRow() {
const tableBody = document.getElementById('tableBody');
const rowCount = tableBody.querySelectorAll('tr').length;
@ -62,26 +47,23 @@ function updateRowIndices() {
async function saveTable() {
const rows = [];
const tableRows = document.querySelectorAll('#tableBody tr');
tableRows.forEach((row) => {
const key = row.querySelector("input[data-field='bezeichnung']").value.trim();
if (!key) return; // leere Zeilen überspringen
const addr = parseInt(row.querySelector("input[data-field='addr']")?.value || row.querySelector("input[data-field='adresse']")?.value || '0', 10);
const rtype = 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 mqtt = row.querySelector("input[data-field='mqtt']")?.checked || false;
const influxdb = row.querySelector("input[data-field='influxdb']")?.checked || false;
const comment = row.querySelector("input[data-field='comment']")?.value || null;
const value = {
addr: addr,
rtype: rtype,
factor: factor,
mqtt: mqtt,
influxdb: influxdb,
comment: comment
};
const obj = {};
obj[key] = value;
rows.push(obj);
const bezeichnung = row.querySelector("input[data-field='bezeichnung']").value;
const adresse = row.querySelector("input[data-field='adresse']").value;
const type = row.querySelector("input[data-field='type']").value;
const faktor = row.querySelector("input[data-field='faktor']").value;
const mqtt = row.querySelector("input[data-field='mqtt']").checked;
const influxdb = row.querySelector("input[data-field='influxdb']").checked;
rows.push({
bezeichnung,
adresse,
type,
faktor,
mqtt,
influxdb
});
});
try {

View File

@ -1,45 +1,14 @@
async function saveSettings() {
// 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
};
// InfluxDB
const influxdb = {
url: document.getElementById('influxdb_url').value,
token: document.getElementById('influxdb_token').value,
bucket: document.getElementById('influxdb_bucket').value,
org: document.getElementById('influxdb_org').value,
location: document.getElementById('influxdb_location').value || null,
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 mqtt_broker = document.getElementById('mqtt_broker').value;
const mqtt_port = document.getElementById('mqtt_port').value;
const influxdb_url = document.getElementById('influxdb_url').value;
const influxdb_token = document.getElementById('influxdb_token').value;
const settings = {
default: defaultConfig,
mqtt,
influxdb,
modbus
mqtt_broker,
mqtt_port,
influxdb_url,
influxdb_token
};
try {

View File

@ -17,9 +17,9 @@
<span class="logo-text">Sensor Manager</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>
<a href="/table/modbus_holding_register" class="nav-link {% if active_page == 'modbus_holding_register' %}active{% endif %}">Holding Register</a>
<a href="/table/modbus_coils" class="nav-link {% if active_page == 'modbus_coils' %}active{% endif %}">Coils</a>
<a href="/" class="nav-link {% if active_page == 'table1' %}active{% endif %}">Tabelle 1</a>
<a href="/table/table2" class="nav-link {% if active_page == 'table2' %}active{% endif %}">Tabelle 2</a>
<a href="/table/table3" class="nav-link {% if active_page == 'table3' %}active{% endif %}">Tabelle 3</a>
<a href="/settings" class="nav-link {% if active_page == 'settings' %}active{% endif %}">⚙️ Einstellungen</a>
</nav>
</div>
@ -42,13 +42,12 @@
</tr>
</thead>
<tbody id="tableBody">
{% for entry in rows %}
{% for key, row in entry %}
{% for row in rows %}
<tr data-row="{{ loop.index0 }}">
<td><input type='text' class='text-input' data-field='key' value='{{ key }}' /></td>
<td><input type='number' class='text-input' data-field='addr' value='{{ row.addr }}' /></td>
<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><input type='text' class='text-input' data-field='bezeichnung' value='{{ row.bezeichnung }}' /></td>
<td><input type='text' class='text-input' data-field='adresse' value='{{ row.adresse }}' /></td>
<td><input type='text' class='text-input' data-field='type' value='{{ row.type }}' /></td>
<td><input type='text' class='text-input' data-field='faktor' value='{{ row.faktor }}' /></td>
<td>
<label class='switch'>
<input type='checkbox' class='bool-input' data-field='mqtt' {% if row.mqtt %}checked{% endif %} />
@ -66,7 +65,6 @@
</td>
</tr>
{% endfor %}
{% endfor %}
</tbody>
</table>
</div>

View File

@ -17,53 +17,27 @@
<span class="logo-text">Sensor Manager</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>
<a href="/table/modbus_holding_register" class="nav-link {% if active_page == 'modbus_holding_register' %}active{% endif %}">Holding Register</a>
<a href="/table/modbus_coils" class="nav-link {% if active_page == 'modbus_coils' %}active{% endif %}">Coils</a>
<a href="/" class="nav-link {% if active_page == 'table1' %}active{% endif %}">Tabelle 1</a>
<a href="/table/table2" class="nav-link {% if active_page == 'table2' %}active{% endif %}">Tabelle 2</a>
<a href="/table/table3" class="nav-link {% if active_page == 'table3' %}active{% endif %}">Tabelle 3</a>
<a href="/settings" class="nav-link {% if active_page == 'settings' %}active{% endif %}">⚙️ Einstellungen</a>
</nav>
</div>
</header>
<div class="container">
<h1>⚙️ Einstellungen</h1>
<div id="message" class="message"></div>
<div class="settings-section">
<h2>Allgemein</h2>
<div class="form-group">
<label for="loglevel">Loglevel:</label>
<select id="loglevel" class="text-input">
<option value="DEBUG">DEBUG</option>
<option value="INFO">INFO</option>
<option value="WARN">WARN</option>
<option value="ERROR">ERROR</option>
</select>
</div>
</div>
<div class="settings-section">
<h2>MQTT Konfiguration</h2>
<div class="form-group">
<label for="mqtt_broker">MQTT Broker:</label>
<input type="text" id="mqtt_broker" class="text-input" value="{{ mqtt.broker }}" />
<input type="text" id="mqtt_broker" class="text-input" value="{{ settings.mqtt_broker }}" />
</div>
<div class="form-group">
<label for="mqtt_port">MQTT Port:</label>
<input type="text" id="mqtt_port" class="text-input" value="{{ mqtt.port }}" />
</div>
<div class="form-group">
<label for="mqtt_user">MQTT User:</label>
<input type="text" id="mqtt_user" class="text-input" value="{{ mqtt.user | default(value="") }}" />
</div>
<div class="form-group">
<label for="mqtt_password">MQTT Passwort:</label>
<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="") }}" />
<input type="text" id="mqtt_port" class="text-input" value="{{ settings.mqtt_port }}" />
</div>
</div>
@ -71,51 +45,11 @@
<h2>InfluxDB Konfiguration</h2>
<div class="form-group">
<label for="influxdb_url">InfluxDB URL:</label>
<input type="text" id="influxdb_url" class="text-input" value="{{ influxdb.url }}" />
<input type="text" id="influxdb_url" class="text-input" value="{{ settings.influxdb_url }}" />
</div>
<div class="form-group">
<label for="influxdb_token">InfluxDB Token:</label>
<input type="password" id="influxdb_token" class="text-input" value="{{ influxdb.token }}" />
</div>
<div class="form-group">
<label for="influxdb_bucket">Bucket:</label>
<input type="text" id="influxdb_bucket" class="text-input" value="{{ influxdb.bucket }}" />
</div>
<div class="form-group">
<label for="influxdb_org">Org:</label>
<input type="text" id="influxdb_org" class="text-input" value="{{ influxdb.org }}" />
</div>
<div class="form-group">
<label for="influxdb_location">Location:</label>
<input type="text" id="influxdb_location" class="text-input" value="{{ influxdb.location | default(value="") }}" />
</div>
<div class="form-group">
<label for="influxdb_measurement">Measurement:</label>
<input type="text" id="influxdb_measurement" class="text-input" value="{{ influxdb.measurement | default(value="") }}" />
</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="") }}" />
<input type="password" id="influxdb_token" class="text-input" value="{{ settings.influxdb_token }}" />
</div>
</div>