use crate::commands::{
self, BcuType, L_DATA_CON_SUCCESS_MASK, U_ACK_REQ, U_L_DATA_CONT_REQ, U_L_DATA_END_REQ,
U_L_DATA_INDEX_MASK, U_L_DATA_START_REQ, U_RESET_IND, U_RESET_REQ, U_STATE_REQ,
};
use crate::frame::{
APDU_LEN_MASK, CRC_LEN, HEADER_EXT, HEADER_STD, MAX_FRAME_LEN, TpFrame, is_extended_ctrl,
};
pub trait UartInterface {
fn write_byte(&mut self, byte: u8);
fn read_byte(&mut self) -> Option<u8>;
fn available(&self) -> bool;
}
#[derive(Debug)]
pub enum TpIndication {
Reset,
State(u8),
Frame(TpFrame),
TransmitConfirm(bool),
Overrun,
}
pub struct TpUartProtocol {
bcu_type: BcuType,
rx_buf: [u8; MAX_FRAME_LEN],
rx_pos: usize,
rx_expected: usize,
}
impl TpUartProtocol {
pub const fn new(bcu_type: BcuType) -> Self {
Self {
bcu_type,
rx_buf: [0u8; MAX_FRAME_LEN],
rx_pos: 0,
rx_expected: 0,
}
}
pub const fn bcu_type(&self) -> BcuType {
self.bcu_type
}
pub fn reset(uart: &mut impl UartInterface) {
uart.write_byte(U_RESET_REQ);
}
pub fn request_state(uart: &mut impl UartInterface) {
uart.write_byte(U_STATE_REQ);
}
pub fn set_address(&self, uart: &mut impl UartInterface, address: u16) {
let cmd = match self.bcu_type {
BcuType::Ncn5120 => commands::U_NCN5120_SET_ADDRESS_REQ,
BcuType::TpUart2 => commands::U_TPUART2_SET_ADDRESS_REQ,
};
uart.write_byte(cmd);
uart.write_byte((address >> 8) as u8);
uart.write_byte((address & 0xFF) as u8);
}
pub fn send_ack(uart: &mut impl UartInterface, addressed: bool, busy: bool, nack: bool) {
let mut ack = U_ACK_REQ;
if addressed {
ack |= commands::ACK_ADDRESSED;
}
if busy {
ack |= commands::ACK_BUSY;
}
if nack {
ack |= commands::ACK_NACK;
}
uart.write_byte(ack);
}
pub fn transmit(uart: &mut impl UartInterface, frame: &TpFrame) {
let data = frame.as_bytes();
let last = data.len() - 1;
for (i, &byte) in data.iter().enumerate() {
#[expect(clippy::cast_possible_truncation)]
let idx = i as u8 & U_L_DATA_INDEX_MASK;
let cmd = if i == 0 {
U_L_DATA_START_REQ | idx
} else if i == last {
U_L_DATA_END_REQ | idx
} else {
U_L_DATA_CONT_REQ | idx
};
uart.write_byte(cmd);
uart.write_byte(byte);
}
}
pub fn process(&mut self, uart: &mut impl UartInterface) -> Option<TpIndication> {
let byte = uart.read_byte()?;
if self.rx_pos == 0 {
if byte == U_RESET_IND {
return Some(TpIndication::Reset);
}
if byte & commands::U_STATE_MASK == commands::U_STATE_IND {
return Some(TpIndication::State(byte));
}
if byte & commands::U_FRAME_STATE_MASK == commands::U_FRAME_STATE_IND {
return Some(TpIndication::TransmitConfirm(
byte & L_DATA_CON_SUCCESS_MASK == 0,
));
}
if is_frame_start(byte) {
self.rx_buf[0] = byte;
self.rx_pos = 1;
self.rx_expected = 0;
return None;
}
return None;
}
if self.rx_pos < self.rx_buf.len() {
self.rx_buf[self.rx_pos] = byte;
self.rx_pos += 1;
}
if self.rx_expected == 0 && self.rx_pos >= HEADER_STD + CRC_LEN {
let is_ext = is_extended_ctrl(self.rx_buf[0]);
let apdu_len = if is_ext {
self.rx_buf[6] as usize
} else {
(self.rx_buf[5] & APDU_LEN_MASK) as usize
};
let header = if is_ext { HEADER_EXT } else { HEADER_STD };
let expected = header + apdu_len + 1 + CRC_LEN;
if expected > self.rx_buf.len() {
self.rx_pos = 0;
self.rx_expected = 0;
return Some(TpIndication::Overrun);
}
self.rx_expected = expected;
}
if self.rx_expected > 0 && self.rx_pos >= self.rx_expected {
let frame = TpFrame::from_bytes(&self.rx_buf[..self.rx_pos]);
self.rx_pos = 0;
self.rx_expected = 0;
return frame.map(TpIndication::Frame);
}
None
}
}
const fn is_frame_start(byte: u8) -> bool {
let masked = byte & commands::L_DATA_MASK;
masked == commands::L_DATA_STANDARD_IND || masked == commands::L_DATA_EXTENDED_IND
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
extern crate alloc;
use alloc::vec::Vec;
struct MockUart {
tx: Vec<u8>,
rx: Vec<u8>,
rx_pos: usize,
}
impl MockUart {
fn new(rx_data: &[u8]) -> Self {
Self {
tx: Vec::new(),
rx: rx_data.to_vec(),
rx_pos: 0,
}
}
}
impl UartInterface for MockUart {
fn write_byte(&mut self, byte: u8) {
self.tx.push(byte);
}
fn read_byte(&mut self) -> Option<u8> {
if self.rx_pos < self.rx.len() {
let b = self.rx[self.rx_pos];
self.rx_pos += 1;
Some(b)
} else {
None
}
}
fn available(&self) -> bool {
self.rx_pos < self.rx.len()
}
}
#[test]
fn reset_sends_correct_byte() {
let mut uart = MockUart::new(&[]);
TpUartProtocol::reset(&mut uart);
assert_eq!(uart.tx, &[U_RESET_REQ]);
}
#[test]
fn process_reset_indication() {
let mut proto = TpUartProtocol::new(BcuType::Ncn5120);
let mut uart = MockUart::new(&[U_RESET_IND]);
assert!(matches!(
proto.process(&mut uart),
Some(TpIndication::Reset)
));
}
#[test]
fn process_state_indication() {
let mut proto = TpUartProtocol::new(BcuType::Ncn5120);
let mut uart = MockUart::new(&[0x07]);
assert!(matches!(
proto.process(&mut uart),
Some(TpIndication::State(0x07))
));
}
#[test]
fn process_transmit_confirm() {
let mut proto = TpUartProtocol::new(BcuType::Ncn5120);
let mut uart = MockUart::new(&[0x13]);
assert!(matches!(
proto.process(&mut uart),
Some(TpIndication::TransmitConfirm(true))
));
}
#[test]
fn set_address_ncn5120() {
let proto = TpUartProtocol::new(BcuType::Ncn5120);
let mut uart = MockUart::new(&[]);
proto.set_address(&mut uart, 0x1101);
assert_eq!(uart.tx, &[commands::U_NCN5120_SET_ADDRESS_REQ, 0x11, 0x01]);
}
#[test]
fn set_address_tpuart2() {
let proto = TpUartProtocol::new(BcuType::TpUart2);
let mut uart = MockUart::new(&[]);
proto.set_address(&mut uart, 0x1101);
assert_eq!(uart.tx, &[commands::U_TPUART2_SET_ADDRESS_REQ, 0x11, 0x01]);
}
#[test]
fn oversized_extended_length_does_not_wedge() {
let mut proto = TpUartProtocol::new(BcuType::Ncn5120);
let bytes = [0x10u8, 0x00, 0x11, 0x01, 0x08, 0x01, 0xFF];
let mut uart = MockUart::new(&bytes);
let mut last = None;
for _ in 0..bytes.len() {
if let Some(ind) = proto.process(&mut uart) {
last = Some(ind);
}
}
assert!(matches!(last, Some(TpIndication::Overrun)));
let mut data = [0xBC, 0x11, 0x01, 0x08, 0x01, 0xE1, 0x00, 0x81, 0x00];
data[8] = knx_rs_core::cemi::CemiFrame::calc_crc_tp(&data[..8]);
let mut uart2 = MockUart::new(&data);
let mut frame = None;
for _ in 0..data.len() {
if let Some(ind) = proto.process(&mut uart2) {
frame = Some(ind);
}
}
assert!(matches!(frame, Some(TpIndication::Frame(_))));
}
#[test]
fn transmit_frame() {
let mut uart = MockUart::new(&[]);
let data = [0xBC, 0x11, 0x01, 0x08, 0x01, 0xE1, 0x00, 0x81];
let crc = knx_rs_core::cemi::CemiFrame::calc_crc_tp(&data);
let mut frame_data = [0u8; 9];
frame_data[..8].copy_from_slice(&data);
frame_data[8] = crc;
let frame = TpFrame::from_bytes(&frame_data).unwrap();
TpUartProtocol::transmit(&mut uart, &frame);
assert_eq!(uart.tx.len(), 18);
assert_eq!(uart.tx[0], U_L_DATA_START_REQ);
assert_eq!(uart.tx[1], 0xBC);
assert_eq!(uart.tx[16], U_L_DATA_END_REQ | 8);
assert_eq!(uart.tx[17], crc);
}
}