use embedded_hal::delay::DelayNs;
use embedded_io::Write;
use crate::framing::{
EXCEPTION_BIT, MAX_ADU, MAX_READ_REGS, MAX_WRITE_REGS, build_read_request,
build_write_multiple_request, build_write_single_request, parse_read_response,
parse_write_multiple_response, parse_write_single_response,
};
use crate::transport::{BlockingRead, ModbusTransport, RtuError};
const _: () = assert!(MAX_ADU >= 9 + 2 * MAX_WRITE_REGS);
#[derive(Debug)]
pub struct UartTransport<U, D> {
uart: U,
delay: D,
response_timeout_ms: u32,
inter_frame_ms: u32,
buf: [u8; MAX_ADU],
}
impl<U, D> UartTransport<U, D>
where
U: BlockingRead + Write,
D: DelayNs,
{
pub fn new(uart: U, delay: D) -> Self {
Self {
uart,
delay,
response_timeout_ms: 500,
inter_frame_ms: 50,
buf: [0u8; MAX_ADU],
}
}
pub fn with_timing(mut self, response_timeout_ms: u32, inter_frame_ms: u32) -> Self {
self.response_timeout_ms = response_timeout_ms;
self.inter_frame_ms = inter_frame_ms;
self
}
pub fn release(self) -> (U, D) {
(self.uart, self.delay)
}
fn pre_tx_silence(&mut self) {
self.delay.delay_ms(self.inter_frame_ms);
drain_rx(&mut self.uart);
}
}
fn drain_rx<U: BlockingRead>(uart: &mut U) {
const DRAIN_MAX_BYTES: usize = 4 * MAX_ADU;
let mut scratch = [0u8; 32];
let mut drained = 0;
while drained < DRAIN_MAX_BYTES {
match uart.read(&mut scratch, 0) {
Ok(0) | Err(_) => return,
Ok(n) => drained += n,
}
}
}
fn write_all<U: Write>(uart: &mut U, mut buf: &[u8]) -> Result<(), RtuError> {
while !buf.is_empty() {
match uart.write(buf) {
Ok(0) => return Err(RtuError::Io),
Ok(n) => buf = &buf[n..],
Err(_) => return Err(RtuError::Io),
}
}
uart.flush().map_err(|_| RtuError::Io)?;
Ok(())
}
fn read_exact<U: BlockingRead>(
uart: &mut U,
buf: &mut [u8],
response_timeout_ms: u32,
) -> Result<(), RtuError> {
let mut filled = 0;
while filled < buf.len() {
match uart.read(&mut buf[filled..], response_timeout_ms) {
Ok(0) => return Err(RtuError::Timeout),
Ok(n) => filled += n,
Err(_) => return Err(RtuError::Io),
}
}
Ok(())
}
fn read_response<'b, U: BlockingRead>(
uart: &mut U,
buf: &'b mut [u8],
full_len: usize,
response_timeout_ms: u32,
) -> Result<&'b [u8], RtuError> {
assert!(full_len >= 5 && full_len <= buf.len());
read_exact(uart, &mut buf[..3], response_timeout_ms)?;
if buf[1] & EXCEPTION_BIT != 0 {
read_exact(uart, &mut buf[3..5], response_timeout_ms)?;
return Ok(&buf[..5]);
}
if full_len > 3 {
read_exact(uart, &mut buf[3..full_len], response_timeout_ms)?;
}
Ok(&buf[..full_len])
}
impl<U, D> ModbusTransport for UartTransport<U, D>
where
U: BlockingRead + Write,
D: DelayNs,
{
fn read_holding(&mut self, slave: u8, addr: u16, dst: &mut [u16]) -> Result<(), RtuError> {
assert!(slave != 0, "read does not support broadcast");
assert!(!dst.is_empty() && dst.len() <= MAX_READ_REGS);
let count = dst.len() as u16;
let req = build_read_request(slave, addr, count);
let expected_len = 5 + 2 * dst.len();
self.pre_tx_silence();
write_all(&mut self.uart, &req)?;
let resp = read_response(
&mut self.uart,
&mut self.buf,
expected_len,
self.response_timeout_ms,
)?;
parse_read_response(resp, slave, dst)?;
Ok(())
}
fn write_single_holding(&mut self, slave: u8, addr: u16, value: u16) -> Result<(), RtuError> {
assert!(
slave != 0,
"single-register write does not support broadcast"
);
let req = build_write_single_request(slave, addr, value);
self.pre_tx_silence();
write_all(&mut self.uart, &req)?;
let resp = read_response(&mut self.uart, &mut self.buf, 8, self.response_timeout_ms)?;
parse_write_single_response(resp, &req)?;
Ok(())
}
fn write_multiple_holdings(
&mut self,
slave: u8,
addr: u16,
values: &[u16],
) -> Result<(), RtuError> {
assert!(
slave != 0,
"multi-register write does not support broadcast"
);
assert!(!values.is_empty() && values.len() <= MAX_WRITE_REGS);
let n = build_write_multiple_request(slave, addr, values, &mut self.buf)
.expect("inputs validated above");
self.pre_tx_silence();
write_all(&mut self.uart, &self.buf[..n])?;
let resp = read_response(&mut self.uart, &mut self.buf, 8, self.response_timeout_ms)?;
parse_write_multiple_response(resp, slave, addr, values.len() as u16)?;
Ok(())
}
}
#[cfg(test)]
mod tests;