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::() .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 { 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>>, holding_registers: &Option>>, coils: &Option>>, value_maps: Arc>, 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 { // 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 = 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 addr = reg.addr; let typ = reg.r#type.as_deref().unwrap_or(""); let factor = reg.factor.unwrap_or(1.0); let value: Option = 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 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); } }); }