diff --git a/concept.md b/concept.md index 2781324..49d711d 100644 --- a/concept.md +++ b/concept.md @@ -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. + + diff --git a/src/app_state.rs b/src/app_state.rs new file mode 100644 index 0000000..098ae38 --- /dev/null +++ b/src/app_state.rs @@ -0,0 +1,32 @@ +use std::sync::{Mutex, Arc}; +use tera::Tera; +use crate::config::{AppConfig, ModbusValueMaps}; + +pub struct AppState { + pub config: Mutex, + pub value_maps: Arc>, + 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 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, + } + } +} diff --git a/src/config.rs b/src/config.rs index 0e7a9fa..ca1962c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,5 +1,6 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use crate::modbus_types::{ModbusRegisterConfig, ModbusCoilsConfig}; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct DefaultConfig { @@ -35,15 +36,7 @@ pub struct InfluxConfig { pub measurement: Option, } -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct ModbusRegisterConfig { - pub addr: u16, - pub r#type: Option, - pub factor: Option, - pub mqtt: Option, - pub influxdb: Option, - pub comment: Option, -} +// ModbusRegisterConfig und ModbusCoilsConfig werden jetzt aus modbus_types.rs importiert #[derive(Debug, Serialize, Deserialize, Clone)] pub struct AppConfig { @@ -51,7 +44,7 @@ pub struct AppConfig { pub mqtt: MqttConfig, pub influxdb: InfluxConfig, pub modbus: ModbusConfig, - pub modbus_coils: Option>>, + pub modbus_coils: Option>>, pub modbus_input_register: Option>>, pub modbus_holding_register: Option>>, } diff --git a/src/main.rs b/src/main.rs index 3afb3ff..37ad7c8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,44 +1,18 @@ 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}; - - -struct AppState { - config: Mutex, - value_maps: Arc>, - 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 = 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, - } - } -} +mod app_state; +pub mod modbus_types; +use crate::config::{AppConfig, ModbusValueMaps}; +use crate::modbus_types::{ModbusRegisterConfig, ModbusCoilsConfig}; +use crate::app_state::AppState; async fn index(data: web::Data) -> Result { let config = data.config.lock().unwrap(); @@ -67,37 +41,63 @@ async fn table_page(data: web::Data, path: web::Path) -> 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) -> Result { @@ -122,10 +122,17 @@ async fn settings_page(data: web::Data) -> Result { +#[derive(Serialize, Deserialize, Clone)] +#[serde(untagged)] +enum SaveTableRows { + Register(Vec>), + Coils(Vec>), +} + #[derive(Serialize, Deserialize)] struct SaveTableRequest { table_id: String, - rows: Vec>, + rows: SaveTableRows, } async fn save_table( @@ -134,17 +141,29 @@ async fn save_table( ) -> Result { 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", + match req.table_id.as_str() { + "modbus_input_register" => { + if let SaveTableRows::Register(rows) = req.rows.clone() { + config.modbus_input_register = Some(rows); + } else { + return Ok(HttpResponse::BadRequest().body("Falscher Typ für input_register")); + } + } + "modbus_holding_register" => { + if let SaveTableRows::Register(rows) = req.rows.clone() { + config.modbus_holding_register = Some(rows); + } else { + return Ok(HttpResponse::BadRequest().body("Falscher Typ für holding_register")); + } + } + "modbus_coils" => { + if let SaveTableRows::Coils(rows) = req.rows.clone() { + config.modbus_coils = Some(rows); + } else { + return Ok(HttpResponse::BadRequest().body("Falscher Typ für coils")); + } + } _ => 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, diff --git a/src/modbus.rs b/src/modbus.rs index 54d1194..02621d1 100644 --- a/src/modbus.rs +++ b/src/modbus.rs @@ -1,4 +1,5 @@ -use crate::config::{ModbusValueMaps, ModbusRegisterConfig, ModbusConfig}; +use crate::config::{ModbusValueMaps, ModbusConfig}; +use crate::modbus_types::{ModbusRegisterConfig, ModbusCoilsConfig}; use std::collections::HashMap; use std::sync::{Arc, Mutex}; use std::thread; @@ -9,7 +10,7 @@ pub fn start_modbus_polling_thread( _modbus_config: &ModbusConfig, input_registers: &Option>>, holding_registers: &Option>>, - coils: &Option>>, + coils: &Option>>, value_maps: Arc>, poll_interval: Duration, ) { diff --git a/src/modbus_types.rs b/src/modbus_types.rs new file mode 100644 index 0000000..64b9fa9 --- /dev/null +++ b/src/modbus_types.rs @@ -0,0 +1,20 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct ModbusCoilsConfig { + pub addr: u16, + pub write: Option, + pub mqtt: Option, + pub influxdb: Option, + pub comment: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct ModbusRegisterConfig { + pub addr: u16, + pub r#type: Option, + pub factor: Option, + pub mqtt: Option, + pub influxdb: Option, + pub comment: Option, +} diff --git a/templates/settings.html b/templates/settings.html index 168f202..d5b388d 100644 --- a/templates/settings.html +++ b/templates/settings.html @@ -6,7 +6,7 @@ Einstellungen - +