use core::ffi::c_void;
use super::error::MbusStatusCode;
pub type MbusTransportConnectCb = unsafe extern "C" fn(userdata: *mut c_void) -> MbusStatusCode;
pub type MbusTransportDisconnectCb = unsafe extern "C" fn(userdata: *mut c_void) -> MbusStatusCode;
pub type MbusTransportSendCb =
unsafe extern "C" fn(data: *const u8, len: u16, userdata: *mut c_void) -> MbusStatusCode;
pub type MbusTransportRecvCb = unsafe extern "C" fn(
buffer: *mut u8,
buffer_cap: u16,
out_len: *mut u16,
userdata: *mut c_void,
) -> MbusStatusCode;
pub type MbusTransportIsConnectedCb = unsafe extern "C" fn(userdata: *mut c_void) -> u8;
#[repr(C)]
pub struct MbusTransportCallbacks {
pub userdata: *mut c_void,
pub on_connect: Option<unsafe extern "C" fn(userdata: *mut c_void) -> MbusStatusCode>,
pub on_disconnect: Option<unsafe extern "C" fn(userdata: *mut c_void) -> MbusStatusCode>,
pub on_send: Option<
unsafe extern "C" fn(data: *const u8, len: u16, userdata: *mut c_void) -> MbusStatusCode,
>,
pub on_recv: Option<
unsafe extern "C" fn(
buffer: *mut u8,
buffer_cap: u16,
out_len: *mut u16,
userdata: *mut c_void,
) -> MbusStatusCode,
>,
pub on_is_connected: Option<unsafe extern "C" fn(userdata: *mut c_void) -> u8>,
}
#[cfg(all(
any(feature = "c-client", feature = "c-server", feature = "c-gateway"),
any(
feature = "network-tcp",
feature = "serial-rtu",
feature = "serial-ascii"
)
))]
pub(crate) use c_impl::validate_transport_callbacks;
#[cfg(feature = "serial-rtu")]
pub(crate) use c_impl::CRtuTransport;
#[cfg(feature = "serial-ascii")]
pub(crate) use c_impl::CAsciiTransport;
#[cfg(feature = "network-tcp")]
pub(crate) use c_impl::CTcpTransport;
#[cfg(any(feature = "c-client", feature = "c-server", feature = "c-gateway"))]
mod c_impl {
#[cfg(any(
feature = "network-tcp",
feature = "serial-rtu",
feature = "serial-ascii"
))]
use heapless::Vec;
#[cfg(any(
feature = "network-tcp",
feature = "serial-rtu",
feature = "serial-ascii"
))]
use mbus_core::data_unit::common::MAX_ADU_FRAME_LEN;
#[cfg(any(
feature = "network-tcp",
feature = "serial-rtu",
feature = "serial-ascii"
))]
use mbus_core::errors::MbusError;
#[cfg(any(feature = "serial-rtu", feature = "serial-ascii"))]
use mbus_core::transport::SerialMode;
#[cfg(any(
feature = "network-tcp",
feature = "serial-rtu",
feature = "serial-ascii"
))]
use mbus_core::transport::{ModbusConfig, Transport, TransportType};
#[cfg(any(
feature = "network-tcp",
feature = "serial-rtu",
feature = "serial-ascii"
))]
use super::MbusTransportCallbacks;
#[cfg(any(
feature = "network-tcp",
feature = "serial-rtu",
feature = "serial-ascii"
))]
use crate::c::error::MbusStatusCode;
#[cfg(feature = "network-tcp")]
pub struct CTcpTransport {
pub(crate) callbacks: MbusTransportCallbacks,
}
#[cfg(feature = "network-tcp")]
impl CTcpTransport {
pub fn new(callbacks: MbusTransportCallbacks) -> Self {
Self { callbacks }
}
}
#[cfg(any(feature = "serial-rtu", feature = "serial-ascii"))]
pub struct CSerialTransport<const ASCII: bool = false> {
pub(crate) callbacks: MbusTransportCallbacks,
}
#[cfg(any(feature = "serial-rtu", feature = "serial-ascii"))]
impl<const ASCII: bool> CSerialTransport<ASCII> {
pub const MODE: SerialMode = if ASCII {
SerialMode::Ascii
} else {
SerialMode::Rtu
};
pub fn new(callbacks: MbusTransportCallbacks) -> Self {
Self { callbacks }
}
}
#[cfg(feature = "serial-rtu")]
pub type CRtuTransport = CSerialTransport<false>;
#[cfg(feature = "serial-ascii")]
pub type CAsciiTransport = CSerialTransport<true>;
#[cfg(any(
feature = "network-tcp",
feature = "serial-rtu",
feature = "serial-ascii"
))]
pub fn validate_transport_callbacks(callbacks: &MbusTransportCallbacks) -> bool {
callbacks.on_connect.is_some()
&& callbacks.on_disconnect.is_some()
&& callbacks.on_send.is_some()
&& callbacks.on_recv.is_some()
&& callbacks.on_is_connected.is_some()
}
#[cfg(any(
feature = "network-tcp",
feature = "serial-rtu",
feature = "serial-ascii"
))]
fn c_connect(callbacks: &MbusTransportCallbacks) -> Result<(), MbusError> {
let cb = callbacks
.on_connect
.ok_or(MbusError::InvalidConfiguration)?;
let status = unsafe { cb(callbacks.userdata) };
status_to_result(status)
}
#[cfg(any(
feature = "network-tcp",
feature = "serial-rtu",
feature = "serial-ascii"
))]
fn c_disconnect(callbacks: &MbusTransportCallbacks) -> Result<(), MbusError> {
let cb = callbacks
.on_disconnect
.ok_or(MbusError::InvalidConfiguration)?;
let status = unsafe { cb(callbacks.userdata) };
status_to_result(status)
}
#[cfg(any(
feature = "network-tcp",
feature = "serial-rtu",
feature = "serial-ascii"
))]
fn c_send(callbacks: &MbusTransportCallbacks, adu: &[u8]) -> Result<(), MbusError> {
let cb = callbacks.on_send.ok_or(MbusError::InvalidConfiguration)?;
let len = u16::try_from(adu.len()).map_err(|_| MbusError::BufferTooSmall)?;
let status = unsafe { cb(adu.as_ptr(), len, callbacks.userdata) };
status_to_result(status)
}
#[cfg(any(
feature = "network-tcp",
feature = "serial-rtu",
feature = "serial-ascii"
))]
fn c_recv(callbacks: &MbusTransportCallbacks) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, MbusError> {
let cb = callbacks.on_recv.ok_or(MbusError::InvalidConfiguration)?;
let mut buf = [0u8; MAX_ADU_FRAME_LEN];
let mut out_len: u16 = 0;
let cap = u16::try_from(MAX_ADU_FRAME_LEN).map_err(|_| MbusError::BufferTooSmall)?;
let status = unsafe {
cb(
buf.as_mut_ptr(),
cap,
&mut out_len as *mut u16,
callbacks.userdata,
)
};
if status != MbusStatusCode::MbusOk {
return Err(status_to_error(status));
}
let out_len_usize = out_len as usize;
if out_len_usize == 0 {
return Err(MbusError::Timeout);
}
if out_len_usize > MAX_ADU_FRAME_LEN {
return Err(MbusError::BufferTooSmall);
}
let mut out: Vec<u8, MAX_ADU_FRAME_LEN> = Vec::new();
out.extend_from_slice(&buf[..out_len_usize])
.map_err(|_| MbusError::BufferTooSmall)?;
Ok(out)
}
#[cfg(any(
feature = "network-tcp",
feature = "serial-rtu",
feature = "serial-ascii"
))]
fn c_is_connected(callbacks: &MbusTransportCallbacks) -> bool {
match callbacks.on_is_connected {
Some(cb) => unsafe { cb(callbacks.userdata) != 0 },
None => false,
}
}
#[cfg(feature = "network-tcp")]
impl Transport for CTcpTransport {
type Error = MbusError;
const TRANSPORT_TYPE: TransportType = TransportType::CustomTcp;
fn connect(&mut self, _config: &ModbusConfig) -> Result<(), Self::Error> {
c_connect(&self.callbacks)
}
fn disconnect(&mut self) -> Result<(), Self::Error> {
c_disconnect(&self.callbacks)
}
fn send(&mut self, adu: &[u8]) -> Result<(), Self::Error> {
c_send(&self.callbacks, adu)
}
fn recv(&mut self) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, Self::Error> {
c_recv(&self.callbacks)
}
fn is_connected(&self) -> bool {
c_is_connected(&self.callbacks)
}
}
#[cfg(any(feature = "serial-rtu", feature = "serial-ascii"))]
impl<const ASCII: bool> Transport for CSerialTransport<ASCII> {
type Error = MbusError;
const SUPPORTS_BROADCAST_WRITES: bool = true;
const TRANSPORT_TYPE: TransportType = TransportType::CustomSerial(Self::MODE);
fn connect(&mut self, _config: &ModbusConfig) -> Result<(), Self::Error> {
c_connect(&self.callbacks)
}
fn disconnect(&mut self) -> Result<(), Self::Error> {
c_disconnect(&self.callbacks)
}
fn send(&mut self, adu: &[u8]) -> Result<(), Self::Error> {
c_send(&self.callbacks, adu)
}
fn recv(&mut self) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, Self::Error> {
c_recv(&self.callbacks)
}
fn is_connected(&self) -> bool {
c_is_connected(&self.callbacks)
}
}
#[cfg(any(
feature = "network-tcp",
feature = "serial-rtu",
feature = "serial-ascii"
))]
fn status_to_result(status: MbusStatusCode) -> Result<(), MbusError> {
if status == MbusStatusCode::MbusOk {
Ok(())
} else {
Err(status_to_error(status))
}
}
#[cfg(any(
feature = "network-tcp",
feature = "serial-rtu",
feature = "serial-ascii"
))]
fn status_to_error(status: MbusStatusCode) -> MbusError {
match status {
MbusStatusCode::MbusErrParseError => MbusError::ParseError,
MbusStatusCode::MbusErrBasicParseError => MbusError::BasicParseError,
MbusStatusCode::MbusErrTimeout => MbusError::Timeout,
MbusStatusCode::MbusErrIoError => MbusError::IoError,
MbusStatusCode::MbusErrConnectionFailed => MbusError::ConnectionFailed,
MbusStatusCode::MbusErrConnectionClosed => MbusError::ConnectionClosed,
MbusStatusCode::MbusErrConnectionLost => MbusError::ConnectionLost,
MbusStatusCode::MbusErrBufferTooSmall => MbusError::BufferTooSmall,
MbusStatusCode::MbusErrSendFailed => MbusError::SendFailed,
MbusStatusCode::MbusErrInvalidConfiguration => MbusError::InvalidConfiguration,
MbusStatusCode::MbusErrInvalidTransport => MbusError::InvalidTransport,
_ => MbusError::Unexpected,
}
}
}