use mbus_core::transport::UnitIdOrSlaveAddr;
use mbus_server_async::AsyncTcpServer as InnerAsyncTcpServer;
use pyo3::prelude::*;
use pyo3_async_runtimes::tokio::future_into_py;
use std::sync::Arc;
use tokio::sync::Notify;
use super::app::{ModbusApp, PythonAppAdapter};
use crate::python::client::helpers::get_runtime;
use crate::python::errors::async_server_error_to_py;
#[pyclass(name = "AsyncTcpServer")]
pub struct AsyncTcpServer {
bind_addr: String,
unit_id: u8,
app: Py<ModbusApp>,
stop_signal: Arc<Notify>,
}
#[pymethods]
impl AsyncTcpServer {
#[new]
#[pyo3(signature = (host, app, port=502, unit_id=1))]
fn new(host: &str, app: Py<ModbusApp>, port: u16, unit_id: u8) -> PyResult<Self> {
let _ = UnitIdOrSlaveAddr::new(unit_id).map_err(crate::python::errors::mbus_error_to_py)?;
let bind_addr = format!("{}:{}", host, port);
Ok(Self {
bind_addr,
unit_id,
app,
stop_signal: Arc::new(Notify::new()),
})
}
fn bind_address(&self) -> String {
self.bind_addr.clone()
}
fn serve_forever<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
let addr = self.bind_addr.clone();
let unit = UnitIdOrSlaveAddr::new(self.unit_id)
.map_err(crate::python::errors::mbus_error_to_py)?;
let asyncio = py.import("asyncio")?;
let loop_obj = asyncio.call_method0("get_running_loop")?;
let adapter = PythonAppAdapter::new(self.app.clone_ref(py), Some(loop_obj.unbind()));
let stop_signal = self.stop_signal.clone();
future_into_py(py, async move {
InnerAsyncTcpServer::serve_with_shutdown(
addr.as_str(),
adapter,
unit,
stop_signal.notified(),
)
.await
.map_err(async_server_error_to_py)
})
}
fn stop<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
let stop_signal = self.stop_signal.clone();
future_into_py(py, async move {
stop_signal.notify_one();
Ok::<(), PyErr>(())
})
}
fn __aenter__<'py>(slf: PyRef<'py, Self>, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
let _ =
UnitIdOrSlaveAddr::new(slf.unit_id).map_err(crate::python::errors::mbus_error_to_py)?;
let this = slf.into_pyobject(py)?.into_any().unbind();
future_into_py(py, async move { Ok::<Py<PyAny>, PyErr>(this) })
}
fn __aexit__<'py>(
&self,
py: Python<'py>,
_exc_type: Option<Bound<'py, PyAny>>,
_exc_val: Option<Bound<'py, PyAny>>,
_exc_tb: Option<Bound<'py, PyAny>>,
) -> PyResult<Bound<'py, PyAny>> {
let stop_signal = self.stop_signal.clone();
future_into_py(py, async move {
stop_signal.notify_one();
Ok::<bool, PyErr>(false)
})
}
}
#[pyclass(name = "TcpServer")]
pub struct TcpServer {
bind_addr: String,
unit_id: u8,
app: Py<ModbusApp>,
stop_signal: Arc<Notify>,
}
#[pymethods]
impl TcpServer {
#[new]
#[pyo3(signature = (host, app, port=502, unit_id=1))]
fn new(host: &str, app: Py<ModbusApp>, port: u16, unit_id: u8) -> PyResult<Self> {
let _ = UnitIdOrSlaveAddr::new(unit_id).map_err(crate::python::errors::mbus_error_to_py)?;
let bind_addr = format!("{}:{}", host, port);
Ok(Self {
bind_addr,
unit_id,
app,
stop_signal: Arc::new(Notify::new()),
})
}
fn bind_address(&self) -> String {
self.bind_addr.clone()
}
fn serve_forever(&self, py: Python<'_>) -> PyResult<()> {
let rt = get_runtime();
let addr = self.bind_addr.clone();
let unit = UnitIdOrSlaveAddr::new(self.unit_id)
.map_err(crate::python::errors::mbus_error_to_py)?;
let adapter = PythonAppAdapter::new(self.app.clone_ref(py), None);
let stop_signal = self.stop_signal.clone();
py.detach(|| {
rt.block_on(InnerAsyncTcpServer::serve_with_shutdown(
addr.as_str(),
adapter,
unit,
stop_signal.notified(),
))
.map(|_| ())
.map_err(async_server_error_to_py)
})
}
fn stop(&self) {
self.stop_signal.notify_one();
}
fn __enter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
slf
}
fn __exit__(
&self,
_py: Python<'_>,
_exc_type: Option<Bound<'_, PyAny>>,
_exc_val: Option<Bound<'_, PyAny>>,
_exc_tb: Option<Bound<'_, PyAny>>,
) -> bool {
self.stop_signal.notify_one();
false
}
}