mod config;
mod framing;
pub use config::{Hc12Channel, Hc12Config, Hc12Mode, Hc12Power};
pub use framing::{FrameDecoder, FrameEncoder, FRAME_OVERHEAD};
use crate::error::{Error, Result};
use crate::rtps::Locator;
use crate::transport::Transport;
pub const HC12_MAX_PACKET: usize = 58;
pub trait Uart {
fn write(&mut self, data: &[u8]) -> Result<usize>;
fn read(&mut self, buf: &mut [u8], timeout_ms: u32) -> Result<usize>;
fn try_read(&mut self, buf: &mut [u8]) -> Result<usize>;
fn flush(&mut self) -> Result<()>;
fn available(&self) -> usize;
}
pub trait SetPin {
fn set_high(&mut self);
fn set_low(&mut self);
}
pub struct Hc12Transport<U: Uart, S: SetPin> {
uart: U,
set_pin: S,
config: Hc12Config,
encoder: FrameEncoder,
decoder: FrameDecoder,
node_id: u8,
rx_timeout_ms: u32,
}
impl<U: Uart, S: SetPin> Hc12Transport<U, S> {
pub fn new(uart: U, set_pin: S, node_id: u8) -> Self {
Self {
uart,
set_pin,
config: Hc12Config::default(),
encoder: FrameEncoder::new(),
decoder: FrameDecoder::new(),
node_id,
rx_timeout_ms: 1000,
}
}
pub fn with_config(uart: U, set_pin: S, node_id: u8, config: Hc12Config) -> Self {
let rx_timeout = config.rx_timeout_ms;
Self {
uart,
set_pin,
config,
encoder: FrameEncoder::new(),
decoder: FrameDecoder::new(),
node_id,
rx_timeout_ms: rx_timeout,
}
}
fn enter_at_mode(&mut self) -> Result<()> {
self.set_pin.set_low();
Ok(())
}
fn exit_at_mode(&mut self) {
self.set_pin.set_high();
}
fn send_at_command(&mut self, cmd: &[u8], response: &mut [u8]) -> Result<usize> {
self.uart.write(cmd)?;
self.uart.flush()?;
self.uart.read(response, 200)
}
pub fn configure(&mut self) -> Result<()> {
self.enter_at_mode()?;
let mut response = [0u8; 32];
let len = self.send_at_command(b"AT\r\n", &mut response)?;
if len < 2 || &response[..2] != b"OK" {
self.exit_at_mode();
return Err(Error::TransportError);
}
let channel_cmd = self.config.channel.at_command();
self.send_at_command(&channel_cmd, &mut response)?;
let mode_cmd = self.config.mode.at_command();
self.send_at_command(&mode_cmd, &mut response)?;
let power_cmd = self.config.power.at_command();
self.send_at_command(&power_cmd, &mut response)?;
self.exit_at_mode();
Ok(())
}
pub fn get_version(&mut self) -> Result<[u8; 32]> {
self.enter_at_mode()?;
let mut response = [0u8; 32];
let len = self.send_at_command(b"AT+V\r\n", &mut response)?;
self.exit_at_mode();
if len == 0 {
return Err(Error::TransportError);
}
Ok(response)
}
pub const fn config(&self) -> &Hc12Config {
&self.config
}
pub const fn node_id(&self) -> u8 {
self.node_id
}
pub fn set_rx_timeout(&mut self, timeout_ms: u32) {
self.rx_timeout_ms = timeout_ms;
}
}
impl<U: Uart, S: SetPin> Transport for Hc12Transport<U, S> {
fn init(&mut self) -> Result<()> {
self.exit_at_mode();
Ok(())
}
fn send(&mut self, data: &[u8], _dest: &Locator) -> Result<usize> {
let mut frame_buf = [0u8; HC12_MAX_PACKET + FRAME_OVERHEAD];
let frame_len = self.encoder.encode(self.node_id, data, &mut frame_buf)?;
let sent = self.uart.write(&frame_buf[..frame_len])?;
self.uart.flush()?;
if sent != frame_len {
return Err(Error::TransportError);
}
Ok(data.len())
}
fn recv(&mut self, buf: &mut [u8]) -> Result<(usize, Locator)> {
let mut rx_buf = [0u8; HC12_MAX_PACKET + FRAME_OVERHEAD];
loop {
let len = self.uart.read(&mut rx_buf, self.rx_timeout_ms)?;
if len == 0 {
return Err(Error::Timeout);
}
for &byte in &rx_buf[..len] {
if let Some((src_node, payload)) = self.decoder.feed(byte)? {
if payload.len() > buf.len() {
return Err(Error::BufferTooSmall);
}
buf[..payload.len()].copy_from_slice(payload);
let locator = Locator::udpv4([0, 0, 0, src_node], 0);
return Ok((payload.len(), locator));
}
}
}
}
fn try_recv(&mut self, buf: &mut [u8]) -> Result<(usize, Locator)> {
if self.uart.available() == 0 {
return Err(Error::ResourceExhausted);
}
let mut rx_buf = [0u8; HC12_MAX_PACKET + FRAME_OVERHEAD];
let len = self.uart.try_read(&mut rx_buf)?;
if len == 0 {
return Err(Error::ResourceExhausted);
}
for &byte in &rx_buf[..len] {
if let Some((src_node, payload)) = self.decoder.feed(byte)? {
if payload.len() > buf.len() {
return Err(Error::BufferTooSmall);
}
buf[..payload.len()].copy_from_slice(payload);
let locator = Locator::udpv4([0, 0, 0, src_node], 0);
return Ok((payload.len(), locator));
}
}
Err(Error::ResourceExhausted)
}
fn local_locator(&self) -> Locator {
Locator::udpv4([0, 0, 0, self.node_id], 0)
}
fn mtu(&self) -> usize {
HC12_MAX_PACKET - FRAME_OVERHEAD
}
fn shutdown(&mut self) -> Result<()> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
struct MockUart {
tx_buf: [u8; 256],
tx_len: usize,
rx_buf: [u8; 256],
rx_len: usize,
rx_pos: usize,
}
impl MockUart {
fn new() -> Self {
Self {
tx_buf: [0u8; 256],
tx_len: 0,
rx_buf: [0u8; 256],
rx_len: 0,
rx_pos: 0,
}
}
}
impl Uart for MockUart {
fn write(&mut self, data: &[u8]) -> Result<usize> {
let len = data.len().min(256 - self.tx_len);
self.tx_buf[self.tx_len..self.tx_len + len].copy_from_slice(&data[..len]);
self.tx_len += len;
Ok(len)
}
fn read(&mut self, buf: &mut [u8], _timeout_ms: u32) -> Result<usize> {
let available = self.rx_len - self.rx_pos;
let len = buf.len().min(available);
buf[..len].copy_from_slice(&self.rx_buf[self.rx_pos..self.rx_pos + len]);
self.rx_pos += len;
Ok(len)
}
fn try_read(&mut self, buf: &mut [u8]) -> Result<usize> {
self.read(buf, 0)
}
fn flush(&mut self) -> Result<()> {
Ok(())
}
fn available(&self) -> usize {
self.rx_len - self.rx_pos
}
}
struct MockSetPin {
is_high: bool,
}
impl MockSetPin {
fn new() -> Self {
Self { is_high: true }
}
}
impl SetPin for MockSetPin {
fn set_high(&mut self) {
self.is_high = true;
}
fn set_low(&mut self) {
self.is_high = false;
}
}
#[test]
fn test_hc12_creation() {
let uart = MockUart::new();
let set_pin = MockSetPin::new();
let transport = Hc12Transport::new(uart, set_pin, 42);
assert_eq!(transport.node_id(), 42);
}
#[test]
fn test_hc12_local_locator() {
let uart = MockUart::new();
let set_pin = MockSetPin::new();
let transport = Hc12Transport::new(uart, set_pin, 123);
let locator = transport.local_locator();
assert_eq!(locator.address[15], 123);
}
#[test]
fn test_hc12_mtu() {
let uart = MockUart::new();
let set_pin = MockSetPin::new();
let transport = Hc12Transport::new(uart, set_pin, 1);
assert!(transport.mtu() > 0);
assert!(transport.mtu() < HC12_MAX_PACKET);
}
#[test]
fn test_hc12_send() {
let uart = MockUart::new();
let set_pin = MockSetPin::new();
let mut transport = Hc12Transport::new(uart, set_pin, 1);
let data = b"Hello";
let dest = Locator::udpv4([0, 0, 0, 2], 0);
let result = transport.send(data, &dest);
assert!(result.is_ok());
assert_eq!(result.unwrap(), data.len());
}
#[test]
fn test_hc12_try_recv_no_data() {
let uart = MockUart::new();
let set_pin = MockSetPin::new();
let mut transport = Hc12Transport::new(uart, set_pin, 1);
let mut buf = [0u8; 64];
let result = transport.try_recv(&mut buf);
assert_eq!(result, Err(Error::ResourceExhausted));
}
}