use super::register::ModbusError;
pub fn crc16(data: &[u8]) -> u16 {
let mut crc: u16 = 0xFFFF;
for &byte in data {
crc ^= byte as u16;
for _ in 0..8 {
if crc & 0x0001 != 0 {
crc = (crc >> 1) ^ 0xA001;
} else {
crc >>= 1;
}
}
}
crc
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum FunctionCode {
ReadCoils = 0x01,
ReadDiscreteInputs = 0x02,
ReadHoldingRegisters = 0x03,
ReadInputRegisters = 0x04,
WriteSingleCoil = 0x05,
WriteSingleRegister = 0x06,
WriteMultipleCoils = 0x0F,
WriteMultipleRegisters = 0x10,
ReportSlaveId = 0x11,
}
#[derive(Debug, Clone)]
pub struct RtuFrame {
pub device_address: u8,
pub function_code: u8,
pub data: heapless::Vec<u8, 256>,
}
impl RtuFrame {
pub fn read_holding_registers(addr: u8, start: u16, count: u16) -> Self {
let mut data = heapless::Vec::new();
let _ = data.extend_from_slice(&start.to_be_bytes());
let _ = data.extend_from_slice(&count.to_be_bytes());
Self {
device_address: addr,
function_code: 0x03,
data,
}
}
pub fn write_single_register(addr: u8, reg: u16, val: u16) -> Self {
let mut data = heapless::Vec::new();
let _ = data.extend_from_slice(®.to_be_bytes());
let _ = data.extend_from_slice(&val.to_be_bytes());
Self {
device_address: addr,
function_code: 0x06,
data,
}
}
pub fn to_bytes(&self) -> heapless::Vec<u8, 260> {
let mut buf: heapless::Vec<u8, 260> = heapless::Vec::new();
let _ = buf.push(self.device_address);
let _ = buf.push(self.function_code);
let _ = buf.extend_from_slice(&self.data);
let crc = crc16(&buf);
let _ = buf.extend_from_slice(&crc.to_le_bytes()); buf
}
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.len() < 4 {
return None;
}
let payload_len = bytes.len() - 2;
let crc_calc = crc16(&bytes[..payload_len]);
let crc_recv = u16::from_le_bytes([bytes[payload_len], bytes[payload_len + 1]]);
if crc_calc != crc_recv {
return None;
}
let mut data = heapless::Vec::new();
let _ = data.extend_from_slice(&bytes[2..payload_len]);
Some(Self {
device_address: bytes[0],
function_code: bytes[1],
data,
})
}
}
pub fn encode_rtu(addr: u8, pdu_buf: &[u8], frame_buf: &mut [u8]) -> Result<usize, ModbusError> {
let needed = pdu_buf.len() + 3; if frame_buf.len() < needed {
return Err(ModbusError::IllegalDataValue);
}
frame_buf[0] = addr;
frame_buf[1..1 + pdu_buf.len()].copy_from_slice(pdu_buf);
let crc = crc16(&frame_buf[..1 + pdu_buf.len()]);
let crc_offset = 1 + pdu_buf.len();
frame_buf[crc_offset] = (crc & 0xFF) as u8;
frame_buf[crc_offset + 1] = (crc >> 8) as u8;
Ok(needed)
}
pub fn decode_rtu(frame: &[u8]) -> Result<(u8, &[u8]), ModbusError> {
if frame.len() < 4 {
return Err(ModbusError::IllegalDataValue);
}
let payload_len = frame.len() - 2;
let crc_calc = crc16(&frame[..payload_len]);
let crc_recv = u16::from_le_bytes([frame[payload_len], frame[payload_len + 1]]);
if crc_calc != crc_recv {
return Err(ModbusError::SlaveDeviceFailure); }
let addr = frame[0];
let pdu = &frame[1..payload_len];
Ok((addr, pdu))
}
pub const fn silent_interval_us(baud_rate: u32, bits_per_char: u32) -> u32 {
if baud_rate >= 19_200 {
1_750
} else {
(3_500_000u64 * bits_per_char as u64 / baud_rate as u64) as u32
}
}
pub trait RtuWriter {
type Error;
fn write(&mut self, bytes: &[u8]) -> Result<(), Self::Error>;
fn flush(&mut self) -> Result<(), Self::Error>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RtuMasterState {
Idle,
WaitingForResponse,
ReceivingResponse,
ResponseReady,
Error,
}
pub struct RtuMaster<W: RtuWriter> {
writer: W,
state: RtuMasterState,
rx_buf: heapless::Vec<u8, 260>,
expected_len: usize,
silence_us: u32,
gap_threshold_us: u32,
ready_len: usize,
}
impl<W: RtuWriter> RtuMaster<W> {
pub fn new(writer: W, baud_rate: u32, bits_per_char: u32) -> Self {
Self {
writer,
state: RtuMasterState::Idle,
rx_buf: heapless::Vec::new(),
expected_len: 0,
silence_us: 0,
gap_threshold_us: silent_interval_us(baud_rate, bits_per_char),
ready_len: 0,
}
}
pub fn writer(&self) -> &W {
&self.writer
}
pub fn writer_mut(&mut self) -> &mut W {
&mut self.writer
}
pub fn state(&self) -> RtuMasterState {
self.state
}
pub fn send_request(&mut self, addr: u8, pdu_buf: &[u8]) -> Result<(), ModbusError> {
if self.state != RtuMasterState::Idle {
return Err(ModbusError::IllegalDataValue);
}
let mut frame_buf = [0u8; 260];
let n = encode_rtu(addr, pdu_buf, &mut frame_buf)?;
self.writer
.write(&frame_buf[..n])
.map_err(|_| ModbusError::SlaveDeviceFailure)?;
self.writer
.flush()
.map_err(|_| ModbusError::SlaveDeviceFailure)?;
self.rx_buf.clear();
self.expected_len = 0;
self.silence_us = 0;
self.ready_len = 0;
self.state = RtuMasterState::WaitingForResponse;
Ok(())
}
pub fn feed_byte(&mut self, byte: u8) {
match self.state {
RtuMasterState::WaitingForResponse | RtuMasterState::ReceivingResponse => {
if self.rx_buf.push(byte).is_err() {
self.state = RtuMasterState::Error;
return;
}
self.silence_us = 0;
self.state = RtuMasterState::ReceivingResponse;
if self.expected_len > 0 && self.rx_buf.len() >= self.expected_len {
self.ready_len = self.rx_buf.len();
self.state = RtuMasterState::ResponseReady;
}
}
_ => {} }
}
pub fn tick(&mut self, elapsed_us: u32) {
if self.state != RtuMasterState::ReceivingResponse {
return;
}
self.silence_us = self.silence_us.saturating_add(elapsed_us);
if self.silence_us >= self.gap_threshold_us && !self.rx_buf.is_empty() {
self.ready_len = self.rx_buf.len();
self.state = RtuMasterState::ResponseReady;
}
}
pub fn set_expected_response_len(&mut self, len: usize) {
self.expected_len = len;
}
pub fn take_response(&mut self) -> Option<heapless::Vec<u8, 260>> {
if self.state != RtuMasterState::ResponseReady {
return None;
}
let frame = self.rx_buf.clone();
self.rx_buf.clear();
self.ready_len = 0;
self.state = RtuMasterState::Idle;
Some(frame)
}
pub fn decode_response(&mut self) -> Result<(u8, heapless::Vec<u8, 256>), ModbusError> {
if self.state != RtuMasterState::ResponseReady {
return Err(ModbusError::IllegalDataValue);
}
let raw = self.rx_buf.clone();
match decode_rtu(&raw) {
Ok((addr, pdu_slice)) => {
let mut pdu: heapless::Vec<u8, 256> = heapless::Vec::new();
pdu.extend_from_slice(pdu_slice)
.map_err(|_| ModbusError::IllegalDataValue)?;
self.rx_buf.clear();
self.ready_len = 0;
self.state = RtuMasterState::Idle;
Ok((addr, pdu))
}
Err(e) => {
self.state = RtuMasterState::Error;
Err(e)
}
}
}
pub fn reset(&mut self) {
self.state = RtuMasterState::Idle;
self.rx_buf.clear();
self.expected_len = 0;
self.silence_us = 0;
self.ready_len = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn crc16_known_value() {
let data = [0x01u8, 0x03, 0x00, 0x00, 0x00, 0x0A];
let crc = crc16(&data);
assert_eq!(crc, 0xCDC5, "CRC=0x{crc:04X}");
}
#[test]
fn crc16_empty() {
assert_eq!(crc16(&[]), 0xFFFF);
}
#[test]
fn rtu_frame_roundtrip() {
let frame = RtuFrame::read_holding_registers(1, 0, 10);
let bytes = frame.to_bytes();
let parsed = RtuFrame::from_bytes(&bytes).expect("parse failed");
assert_eq!(parsed.device_address, 1);
assert_eq!(parsed.function_code, 0x03);
}
#[test]
fn rtu_frame_bad_crc() {
let frame = RtuFrame::write_single_register(1, 100, 0xABCD);
let mut bytes = frame.to_bytes();
let len = bytes.len();
bytes[len - 1] ^= 0xFF;
assert!(RtuFrame::from_bytes(&bytes).is_none());
}
#[test]
fn encode_decode_rtu_roundtrip() {
let pdu = [0x03u8, 0x00, 0x00, 0x00, 0x0A]; let mut frame_buf = [0u8; 260];
let n = encode_rtu(0x01, &pdu, &mut frame_buf).expect("encode failed");
assert_eq!(n, pdu.len() + 3);
let (addr, pdu_back) = decode_rtu(&frame_buf[..n]).expect("decode failed");
assert_eq!(addr, 0x01);
assert_eq!(pdu_back, &pdu);
}
#[test]
fn encode_rtu_buffer_too_small() {
let pdu = [0x03u8; 5];
let mut buf = [0u8; 4]; assert_eq!(
encode_rtu(1, &pdu, &mut buf),
Err(ModbusError::IllegalDataValue)
);
}
#[test]
fn decode_rtu_too_short() {
let frame = [0x01u8, 0x03, 0x00]; assert_eq!(decode_rtu(&frame), Err(ModbusError::IllegalDataValue));
}
#[test]
fn decode_rtu_bad_crc() {
let pdu = [0x03u8, 0x00, 0x00, 0x00, 0x01];
let mut frame_buf = [0u8; 260];
let n = encode_rtu(1, &pdu, &mut frame_buf).expect("encode");
frame_buf[n - 1] ^= 0xFF; assert_eq!(
decode_rtu(&frame_buf[..n]),
Err(ModbusError::SlaveDeviceFailure)
);
}
#[test]
fn silent_interval_high_baud() {
assert_eq!(silent_interval_us(19_200, 11), 1_750);
assert_eq!(silent_interval_us(115_200, 11), 1_750);
}
#[test]
fn silent_interval_low_baud() {
let us = silent_interval_us(9_600, 11);
assert_eq!(us, 4010);
}
struct BufWriter {
pub buf: heapless::Vec<u8, 512>,
}
impl BufWriter {
fn new() -> Self {
Self {
buf: heapless::Vec::new(),
}
}
}
impl RtuWriter for BufWriter {
type Error = ();
fn write(&mut self, bytes: &[u8]) -> Result<(), ()> {
self.buf.extend_from_slice(bytes).map_err(|_| ())
}
fn flush(&mut self) -> Result<(), ()> {
Ok(())
}
}
#[test]
fn master_send_and_receive_response() {
let writer = BufWriter::new();
let mut master = RtuMaster::new(writer, 9600, 11);
let pdu = [0x03u8, 0x00, 0x00, 0x00, 0x01];
master.send_request(1, &pdu).expect("send");
assert_eq!(master.state(), RtuMasterState::WaitingForResponse);
let resp_pdu = [0x03u8, 0x02, 0xAB, 0xCD];
let mut resp_frame = [0u8; 260];
let resp_n = encode_rtu(1, &resp_pdu, &mut resp_frame).expect("resp encode");
master.set_expected_response_len(resp_n);
for &b in &resp_frame[..resp_n] {
master.feed_byte(b);
}
assert_eq!(master.state(), RtuMasterState::ResponseReady);
let (addr, pdu_back) = master.decode_response().expect("decode");
assert_eq!(addr, 1);
assert_eq!(pdu_back.as_slice(), &resp_pdu);
assert_eq!(master.state(), RtuMasterState::Idle);
}
#[test]
fn master_detects_response_via_gap() {
let writer = BufWriter::new();
let mut master = RtuMaster::new(writer, 9600, 11);
let pdu = [0x03u8, 0x00, 0x00, 0x00, 0x01];
master.send_request(1, &pdu).expect("send");
let resp_pdu = [0x03u8, 0x02, 0x00, 0x01];
let mut resp_frame = [0u8; 260];
let resp_n = encode_rtu(1, &resp_pdu, &mut resp_frame).expect("resp encode");
for &b in &resp_frame[..resp_n] {
master.feed_byte(b);
}
assert_eq!(master.state(), RtuMasterState::ReceivingResponse);
master.tick(5_000);
assert_eq!(master.state(), RtuMasterState::ResponseReady);
}
#[test]
fn master_reset_clears_state() {
let writer = BufWriter::new();
let mut master = RtuMaster::new(writer, 9600, 11);
let pdu = [0x03u8, 0x00, 0x00, 0x00, 0x01];
master.send_request(1, &pdu).expect("send");
master.reset();
assert_eq!(master.state(), RtuMasterState::Idle);
}
#[test]
fn master_send_while_not_idle_fails() {
let writer = BufWriter::new();
let mut master = RtuMaster::new(writer, 9600, 11);
let pdu = [0x03u8, 0x00, 0x00, 0x00, 0x01];
master.send_request(1, &pdu).expect("first send");
let result = master.send_request(1, &pdu);
assert_eq!(result, Err(ModbusError::IllegalDataValue));
}
}