paramod-rust/src/main.rs
2026-01-12 20:35:09 +01:00

224 lines
7.9 KiB
Rust

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::fs;
use serde_yaml;
use std::collections::HashMap;
use serde::{Serialize, Deserialize};
mod config;
mod modbus;
use crate::config::{AppConfig, ModbusRegisterConfig, ModbusValueMaps};
// ...existing code...
struct AppState {
config: Mutex<AppConfig>,
value_maps: Arc<Mutex<ModbusValueMaps>>,
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,
}
}
}
// ...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();
let mut context = Context::new();
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("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))
}
async fn table_page(data: web::Data<AppState>, path: web::Path<String>) -> Result<HttpResponse> {
let table_id = path.into_inner();
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))
}
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("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)]
struct SaveTableRequest {
table_id: String,
rows: Vec<HashMap<String, ModbusRegisterConfig>>,
}
async fn save_table(
data: web::Data<AppState>,
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",
_ => 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) {
return Ok(HttpResponse::InternalServerError().body(format!("Fehler beim Schreiben: {}", e)));
}
Ok(HttpResponse::Ok().body("success"))
}
async fn save_settings(
data: web::Data<AppState>,
settings: web::Json<AppConfig>,
) -> Result<HttpResponse> {
let mut config = data.config.lock().unwrap();
let new_config = settings.into_inner();
// 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) {
Ok(s) => s,
Err(e) => return Ok(HttpResponse::InternalServerError().body(format!("Serialisierungsfehler: {}", e))),
};
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"))
}
#[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));
// Starte Modbus-Polling-Thread
{
let config = app_state.config.lock().unwrap().clone();
let value_maps = Arc::clone(&app_state.value_maps);
modbus::start_modbus_polling_thread(
&config.modbus,
&config.modbus_input_register,
&config.modbus_holding_register,
&config.modbus_coils,
value_maps,
std::time::Duration::from_secs(2), // Poll-Intervall
);
}
println!("Server läuft auf http://0.0.0.0:8080");
HttpServer::new(move || {
App::new()
.app_data(app_state.clone())
.route("/", web::get().to(index))
.route("/table/{id}", web::get().to(table_page))
.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"))
})
.bind("0.0.0.0:8080")?
.run()
.await
}