use std::sync::Mutex;
use napi::bindgen_prelude::*;
use napi_derive::napi;
use mbus_core::transport::UnitIdOrSlaveAddr;
use mbus_gateway::{AsyncTcpGatewayServer, UnitRouteTable};
use mbus_network::TokioTcpTransport;
use tokio::sync::{Mutex as TokioMutex, Notify};
use tokio::task::JoinHandle;
use crate::nodejs::errors::{ERR_MODBUS_INTERNAL, ERR_MODBUS_INVALID_ARGUMENT, to_napi_err};
use crate::nodejs::runtime;
#[napi(object)]
#[derive(Debug, Clone)]
pub struct GatewayBindOptions {
pub host: String,
pub port: u16,
}
#[napi(object)]
#[derive(Debug, Clone)]
pub struct DownstreamConfig {
pub host: String,
pub port: u16,
}
#[napi(object)]
#[derive(Debug, Clone)]
pub struct RouteEntry {
pub unit_id: u8,
pub channel: u32,
}
#[napi(object)]
#[derive(Debug, Clone)]
pub struct GatewayConfig {
pub downstreams: Vec<DownstreamConfig>,
pub routes: Vec<RouteEntry>,
}
#[napi]
pub struct AsyncTcpGateway {
stop_signal: std::sync::Arc<Notify>,
join_handle: Mutex<Option<JoinHandle<()>>>,
}
#[napi]
impl AsyncTcpGateway {
#[napi(factory)]
pub async fn bind(opts: GatewayBindOptions, config: GatewayConfig) -> Result<AsyncTcpGateway> {
let bind_addr = format!("{}:{}", opts.host, opts.port);
let stop_signal = std::sync::Arc::new(Notify::new());
let stop_signal_clone = stop_signal.clone();
let mut route_table: UnitRouteTable<16> = UnitRouteTable::new();
for entry in &config.routes {
let unit = UnitIdOrSlaveAddr::new(entry.unit_id)
.map_err(|e| to_napi_err(ERR_MODBUS_INVALID_ARGUMENT, e))?;
route_table
.add(unit, entry.channel as usize)
.map_err(|e| to_napi_err(ERR_MODBUS_INVALID_ARGUMENT, e))?;
}
let mut downstream_transports = Vec::with_capacity(config.downstreams.len());
for ds in &config.downstreams {
let addr = format!("{}:{}", ds.host, ds.port);
let transport = TokioTcpTransport::connect(&addr)
.await
.map_err(|e| to_napi_err(ERR_MODBUS_INTERNAL, e))?;
downstream_transports.push(std::sync::Arc::new(TokioMutex::new(transport)));
}
let rt = runtime::get();
let join_handle = rt.spawn(async move {
let handler = std::sync::Arc::new(TokioMutex::new(mbus_gateway::NoopEventHandler));
let response_timeout = std::time::Duration::from_secs(1);
let _ = AsyncTcpGatewayServer::serve_with_shutdown(
&bind_addr,
route_table,
downstream_transports,
handler,
response_timeout,
stop_signal_clone.notified(),
)
.await;
});
Ok(AsyncTcpGateway {
stop_signal,
join_handle: Mutex::new(Some(join_handle)),
})
}
#[napi]
pub async fn shutdown(&self) -> Result<()> {
self.stop_signal.notify_one();
let handle = {
let mut guard = self
.join_handle
.lock()
.map_err(|_| napi::Error::new(Status::GenericFailure, "Failed to acquire lock"))?;
guard.take()
};
if let Some(h) = handle {
let _ = h.await;
}
Ok(())
}
}