use anyhow::Result;
use mbus_async::server::AsyncTcpServer;
use mbus_core::errors::MbusError;
use mbus_core::transport::UnitIdOrSlaveAddr;
use mbus_macros::async_modbus_app;
use mbus_server::{CoilsModel, HoldingRegistersModel};
use std::sync::Arc;
use tokio::sync::Mutex;
#[derive(Default, CoilsModel)]
struct HvacCoils {
#[coil(addr = 0)]
compressor_online: bool,
#[coil(addr = 1)]
alarm_active: bool,
#[coil(addr = 2)]
maintenance_required: bool,
}
#[derive(Default, HoldingRegistersModel)]
struct HvacHolding {
#[reg(addr = 0, scale = 0.1, unit = "C")]
current_temp: u16,
#[reg(addr = 1, scale = 0.1, unit = "C")]
setpoint_temp: u16,
#[reg(addr = 2)]
runtime_hours: u16,
}
#[derive(Default)]
#[async_modbus_app(holding_registers(holding, on_write_1 = on_setpoint_temp_write), coils(coils))]
struct HvacApp {
holding: HvacHolding,
coils: HvacCoils,
}
impl HvacApp {
async fn on_setpoint_temp_write(
&mut self,
addr: u16,
_old: u16,
new: u16,
) -> Result<(), MbusError> {
println!(" [hook] register {addr:#06x} updated → {new:#06x}");
Ok(())
}
}
#[cfg(feature = "traffic")]
impl mbus_async::server::AsyncServerTrafficNotifier for HvacApp {}
fn unit_id(v: u8) -> UnitIdOrSlaveAddr {
UnitIdOrSlaveAddr::try_from(v).expect("valid unit id")
}
#[tokio::main]
async fn main() -> Result<()> {
run_level1().await
}
async fn run_level1() -> Result<()> {
println!("Starting Level 1 async Modbus TCP server on 127.0.0.1:5502 …");
println!("Press Ctrl-C to stop.\n");
let mut app = HvacApp::default();
app.holding.set_current_temp(215); app.holding.set_setpoint_temp(220); app.holding.set_runtime_hours(42);
use mbus_server::CoilMap as _;
app.coils.write_single(0, true).ok();
let shared = Arc::new(Mutex::new(app));
let _: std::convert::Infallible =
AsyncTcpServer::serve_shared("127.0.0.1:5502", shared, unit_id(1))
.await
.map_err(|e| anyhow::anyhow!("{e}"))?;
unreachable!()
}