use core::fmt::Write as _;
use core::future::Future;
use embassy_futures::select::select;
use embassy_time::Instant;
use embedded_can::{ExtendedId, Frame, Id, StandardId};
use embedded_io_async::{Read, Write};
use log::{debug, warn};
const SLCAN_LINE_SZ: usize = 32;
pub const ENCODED_FRAME_MAX: usize = 32;
pub trait CanEncoder {
fn encode(&self, frame: &impl Frame, buf: &mut [u8]) -> usize;
}
pub trait CanDecoder {
fn decode(&self, buf: &[u8]) -> Option<CanFrame>;
}
pub trait BufferedCan: Sync {
fn read(&self, buf: &mut [u8]) -> impl Future<Output = usize>;
fn write(&self, buf: &[u8]) -> impl Future<Output = ()>;
fn check_dropped_frames(&self) -> usize;
fn reset_protocol(&self);
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CanFrame {
pub id: CanId,
pub data: heapless::Vec<u8, 8>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CanId {
Standard(u16),
Extended(u32),
}
impl From<Id> for CanId {
fn from(id: Id) -> Self {
match id {
Id::Standard(s) => CanId::Standard(s.as_raw()),
Id::Extended(e) => CanId::Extended(e.as_raw()),
}
}
}
impl From<CanId> for Id {
fn from(id: CanId) -> Self {
match id {
CanId::Standard(v) => Id::Standard(StandardId::new(v).unwrap_or(StandardId::ZERO)),
CanId::Extended(v) => Id::Extended(ExtendedId::new(v).unwrap_or(ExtendedId::ZERO)),
}
}
}
impl Frame for CanFrame {
fn new(id: impl Into<Id>, data: &[u8]) -> Option<Self> {
Some(CanFrame {
id: id.into().into(),
data: heapless::Vec::from_slice(data).ok()?,
})
}
fn new_remote(_id: impl Into<Id>, _dlc: usize) -> Option<Self> {
None
}
fn is_extended(&self) -> bool {
matches!(self.id, CanId::Extended(_))
}
fn is_remote_frame(&self) -> bool {
false
}
fn id(&self) -> Id {
self.id.into()
}
fn dlc(&self) -> usize {
self.data.len()
}
fn data(&self) -> &[u8] {
&self.data
}
}
pub struct Slcan;
impl CanEncoder for Slcan {
fn encode(&self, frame: &impl Frame, buf: &mut [u8]) -> usize {
let mut s = heapless::String::<64>::new();
match frame.id() {
Id::Standard(id) => {
let _ = write!(s, "t{:03X}{:1X}", id.as_raw(), frame.dlc());
}
Id::Extended(id) => {
let _ = write!(s, "T{:08X}{:1X}", id.as_raw(), frame.dlc());
}
}
let data = frame.data();
for &b in data {
let _ = write!(s, "{b:02X}");
}
let _ = s.push('\r');
let len = s.len().min(buf.len());
buf[..len].copy_from_slice(s.as_bytes());
len
}
}
impl CanDecoder for Slcan {
fn decode(&self, buf: &[u8]) -> Option<CanFrame> {
let s = core::str::from_utf8(buf).ok()?;
if !s.is_ascii() {
return None;
}
let s = s.trim_end_matches('\r');
let bytes = s.as_bytes();
match bytes.first()? {
b't' => {
if s.len() < 5 {
return None;
}
let id = u16::from_str_radix(&s[1..4], 16).ok()?;
let dlc = (bytes[4] as char).to_digit(16)? as usize;
if dlc > 8 {
return None;
}
let mut data = heapless::Vec::new();
let hex_data = &s[5..];
if hex_data.len() < dlc * 2 {
return None;
}
for i in 0..dlc {
let b = u8::from_str_radix(&hex_data[i * 2..i * 2 + 2], 16).ok()?;
let _ = data.push(b);
}
Some(CanFrame {
id: CanId::Standard(id),
data,
})
}
b'T' => {
if s.len() < 10 {
return None;
}
let id = u32::from_str_radix(&s[1..9], 16).ok()?;
let dlc = (bytes[9] as char).to_digit(16)? as usize;
if dlc > 8 {
return None;
}
let mut data = heapless::Vec::new();
let hex_data = &s[10..];
if hex_data.len() < dlc * 2 {
return None;
}
for i in 0..dlc {
let b = u8::from_str_radix(&hex_data[i * 2..i * 2 + 2], 16).ok()?;
let _ = data.push(b);
}
Some(CanFrame {
id: CanId::Extended(id),
data,
})
}
_ => None,
}
}
}
pub struct Gvret;
impl CanEncoder for Gvret {
fn encode(&self, frame: &impl Frame, buf: &mut [u8]) -> usize {
let data = frame.data();
let n = 12 + data.len();
let Ok(dlc) = u8::try_from(data.len()) else {
return 0;
};
if dlc > 8 || buf.len() < n {
return 0;
}
let raw_id = match frame.id() {
Id::Standard(id) => u32::from(id.as_raw()),
Id::Extended(id) => id.as_raw() | 0x8000_0000,
};
buf[0] = 0xF1;
buf[1] = gvret::BUILD_CAN_FRAME;
buf[2..6].copy_from_slice(×tamp_us());
buf[6..10].copy_from_slice(&raw_id.to_le_bytes());
buf[10] = dlc; buf[11..11 + data.len()].copy_from_slice(data);
buf[11 + data.len()] = 0; n
}
}
fn timestamp_us() -> [u8; 4] {
#[allow(clippy::cast_possible_truncation)]
let t = Instant::now().as_micros() as u32;
t.to_le_bytes()
}
pub fn encode_frame(frame: &impl Frame, binary: bool, buf: &mut [u8]) -> usize {
if binary {
Gvret.encode(frame, buf)
} else {
Slcan.encode(frame, buf)
}
}
mod gvret {
pub const BUILD_CAN_FRAME: u8 = 0x00;
pub const TIME_SYNC: u8 = 0x01;
pub const GET_DIG_INPUTS: u8 = 0x02;
pub const GET_ANALOG_INPUTS: u8 = 0x03;
pub const SET_DIG_OUT: u8 = 0x04;
pub const SETUP_CANBUS: u8 = 0x05;
pub const GET_CANBUS_PARAMS: u8 = 0x06;
pub const GET_DEVICE_INFO: u8 = 0x07;
pub const SET_SINGLEWIRE_MODE: u8 = 0x08;
pub const KEEPALIVE: u8 = 0x09;
pub const SET_SYSTYPE: u8 = 0x0A;
pub const ECHO_CAN_FRAME: u8 = 0x0B;
pub const GET_NUM_BUSES: u8 = 0x0C;
pub const GET_EXT_BUSES: u8 = 0x0D;
pub const SET_EXT_BUSES: u8 = 0x0E;
}
pub enum CanAction {
Transmit(CanFrame),
Reply(heapless::Vec<u8, ENCODED_FRAME_MAX>),
EnableBinary,
}
#[derive(Clone, Copy)]
enum GvretState {
Command,
Frame {
echo: bool,
buf: [u8; 15],
got: usize,
},
Consume(usize),
}
pub struct CanParser {
bitrate: u32,
line: heapless::Vec<u8, SLCAN_LINE_SZ>,
gvret: Option<GvretState>,
}
impl CanParser {
#[must_use]
pub fn new(bitrate: u32) -> Self {
CanParser {
bitrate,
line: heapless::Vec::new(),
gvret: None,
}
}
pub fn reset(&mut self) {
self.line.clear();
self.gvret = None;
}
pub fn feed(&mut self, byte: u8) -> Option<CanAction> {
if let Some(state) = self.gvret.take() {
return self.feed_gvret(state, byte);
}
match byte {
0xF1 => {
self.line.clear();
self.gvret = Some(GvretState::Command);
None
}
0xE7 => {
self.line.clear();
Some(CanAction::EnableBinary)
}
b'\r' | b'\n' => {
let frame = Slcan.decode(&self.line);
self.line.clear();
frame.map(CanAction::Transmit)
}
b => {
if self.line.push(b).is_err() {
self.line.clear();
}
None
}
}
}
fn feed_gvret(&mut self, state: GvretState, byte: u8) -> Option<CanAction> {
match state {
GvretState::Command => self.gvret_command(byte),
GvretState::Consume(n) => {
if n > 1 {
self.gvret = Some(GvretState::Consume(n - 1));
}
None
}
GvretState::Frame { echo, mut buf, got } => {
buf[got] = byte;
let got = got + 1;
if got >= 6 {
let dlc = usize::from(buf[5] & 0x0F).min(8);
if got == 6 + dlc + 1 {
return Self::finish_frame(echo, &buf, dlc);
}
}
self.gvret = Some(GvretState::Frame { echo, buf, got });
None
}
}
}
fn gvret_command(&mut self, cmd: u8) -> Option<CanAction> {
match cmd {
gvret::BUILD_CAN_FRAME | gvret::ECHO_CAN_FRAME => {
self.gvret = Some(GvretState::Frame {
echo: cmd == gvret::ECHO_CAN_FRAME,
buf: [0u8; 15],
got: 0,
});
None
}
gvret::TIME_SYNC => {
let mut b = [0u8; 6];
b[0] = 0xF1;
b[1] = gvret::TIME_SYNC;
b[2..6].copy_from_slice(×tamp_us());
reply(&b)
}
gvret::GET_DIG_INPUTS => reply(&[0xF1, gvret::GET_DIG_INPUTS, 0, 0]),
gvret::GET_ANALOG_INPUTS => {
reply(&[0xF1, gvret::GET_ANALOG_INPUTS, 0, 0, 0, 0, 0, 0, 0, 0, 0])
}
gvret::GET_CANBUS_PARAMS => {
let mut b = [0u8; 12];
b[0] = 0xF1;
b[1] = gvret::GET_CANBUS_PARAMS;
b[2] = 1;
b[3..7].copy_from_slice(&self.bitrate.to_le_bytes());
b[8..12].copy_from_slice(&self.bitrate.to_le_bytes());
reply(&b)
}
gvret::GET_DEVICE_INFO => {
reply(&[0xF1, gvret::GET_DEVICE_INFO, 0x6A, 0x02, 0, 0, 0, 0])
}
gvret::KEEPALIVE => reply(&[0xF1, gvret::KEEPALIVE, 0xDE, 0xAD]),
gvret::GET_NUM_BUSES => reply(&[0xF1, gvret::GET_NUM_BUSES, 1]),
gvret::GET_EXT_BUSES => {
let mut b = [0u8; 17];
b[0] = 0xF1;
b[1] = gvret::GET_EXT_BUSES;
reply(&b)
}
gvret::SET_DIG_OUT | gvret::SET_SINGLEWIRE_MODE | gvret::SET_SYSTYPE => {
self.gvret = Some(GvretState::Consume(1));
None
}
gvret::SETUP_CANBUS => {
self.gvret = Some(GvretState::Consume(8));
None
}
gvret::SET_EXT_BUSES => {
self.gvret = Some(GvretState::Consume(10));
None
}
_ => None,
}
}
fn finish_frame(echo: bool, buf: &[u8; 15], dlc: usize) -> Option<CanAction> {
let raw_id = u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]);
let id = if raw_id & 0x8000_0000 != 0 {
CanId::Extended(raw_id & 0x1FFF_FFFF)
} else {
CanId::Standard((raw_id & 0x7FF) as u16)
};
let frame = CanFrame {
id,
data: heapless::Vec::from_slice(&buf[6..6 + dlc]).ok()?,
};
if echo {
let mut b = [0u8; ENCODED_FRAME_MAX];
let n = Gvret.encode(&frame, &mut b);
reply(&b[..n])
} else {
Some(CanAction::Transmit(frame))
}
}
}
fn reply(bytes: &[u8]) -> Option<CanAction> {
heapless::Vec::from_slice(bytes).ok().map(CanAction::Reply)
}
pub async fn can_bridge<C: BufferedCan + ?Sized>(
chan_read: impl Read<Error = sunset::Error>,
chan_write: impl Write<Error = sunset::Error>,
can: &C,
) -> Result<(), sunset::Error> {
debug!("Starting CAN <--> SSH bridge");
can.reset_protocol();
select(can_to_ssh(can, chan_write), ssh_to_can(chan_read, can)).await;
debug!("Stopping CAN <--> SSH bridge");
Ok(())
}
async fn can_to_ssh<C: BufferedCan + ?Sized>(
can_buf: &C,
mut chan_write: impl Write<Error = sunset::Error>,
) -> Result<(), sunset::Error> {
let mut ssh_tx_buf = [0u8; 128];
loop {
let dropped = can_buf.check_dropped_frames();
if dropped > 0 {
warn!("CAN RX dropped {dropped} frames");
}
let n = can_buf.read(&mut ssh_tx_buf).await;
chan_write.write_all(&ssh_tx_buf[..n]).await?;
}
}
async fn ssh_to_can<C: BufferedCan + ?Sized>(
mut chan_read: impl Read<Error = sunset::Error>,
can_buf: &C,
) -> Result<(), sunset::Error> {
let mut can_tx_buf = [0u8; 64];
loop {
let n = chan_read.read(&mut can_tx_buf).await?;
if n == 0 {
return Err(sunset::Error::ChannelEOF);
}
can_buf.write(&can_tx_buf[..n]).await;
}
}