use core::future::Future;
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, pipe::Pipe};
use embassy_time::{Duration, with_timeout};
use esp_hal::gpio::AnyPin;
use esp_hal::peripherals::TWAI0;
use esp_hal::twai::{self, EspTwaiFrame, ExtendedId, StandardId, TwaiMode};
use log::warn;
use portable_atomic::{AtomicBool, AtomicUsize, Ordering};
use ssh_stamp::can::{CanAction, CanId, CanParser, ENCODED_FRAME_MAX, encode_frame};
use static_cell::StaticCell;
const INWARD_BUF_SZ: usize = 512;
const OUTWARD_BUF_SZ: usize = 256;
const CAN_BITRATE: u32 = 500_000;
#[cfg(feature = "can-no-ack")]
const TWAI_MODE: TwaiMode = TwaiMode::SelfTest;
#[cfg(not(feature = "can-no-ack"))]
const TWAI_MODE: TwaiMode = TwaiMode::Normal;
#[cfg(feature = "can-no-ack")]
const TX_TIMEOUT: Duration = Duration::from_millis(1);
#[cfg(not(feature = "can-no-ack"))]
const TX_TIMEOUT: Duration = Duration::from_millis(10);
pub struct BufferedCan {
outward: Pipe<CriticalSectionRawMutex, OUTWARD_BUF_SZ>,
inward: Pipe<CriticalSectionRawMutex, INWARD_BUF_SZ>,
dropped_rx_frames: AtomicUsize,
binary_mode: AtomicBool,
proto_reset: AtomicBool,
}
impl BufferedCan {
#[must_use]
pub fn new() -> Self {
BufferedCan {
outward: Pipe::new(),
inward: Pipe::new(),
dropped_rx_frames: AtomicUsize::from(0),
binary_mode: AtomicBool::new(false),
proto_reset: AtomicBool::new(false),
}
}
pub async fn run(&self, twai: twai::Twai<'static, esp_hal::Async>) {
let (mut twai_rx, mut twai_tx) = twai.split();
loop {
use embassy_futures::select::select;
let rd_from = async {
let mut frame_buf = [0u8; ENCODED_FRAME_MAX];
loop {
let frame = match twai_rx.receive_async().await {
Ok(frame) => frame,
Err(e) => {
warn!("TWAI RX error: {e:?}");
continue;
}
};
let binary = self.binary_mode.load(Ordering::Relaxed);
let n = encode_frame(&frame, binary, &mut frame_buf);
self.send_to_ssh(&frame_buf[..n]).await;
}
};
let rd_to = async {
let mut parser = CanParser::new(CAN_BITRATE);
let mut chunk = [0u8; 64];
loop {
let n = self.outward.read(&mut chunk).await;
if self.proto_reset.swap(false, Ordering::Relaxed) {
parser.reset();
}
for &byte in &chunk[..n] {
match parser.feed(byte) {
None => {}
Some(CanAction::EnableBinary) => {
self.binary_mode.store(true, Ordering::Relaxed);
}
Some(CanAction::Reply(bytes)) => {
self.send_to_ssh(&bytes).await;
}
Some(CanAction::Transmit(frame)) => {
let id: Option<twai::Id> = match frame.id {
CanId::Standard(id) => StandardId::new(id).map(twai::Id::from),
CanId::Extended(id) => ExtendedId::new(id).map(twai::Id::from),
};
let Some(esp_frame) =
id.and_then(|id| EspTwaiFrame::new(id, &frame.data))
else {
continue;
};
match with_timeout(TX_TIMEOUT, twai_tx.transmit_async(&esp_frame))
.await
{
Ok(Ok(())) => {}
Ok(Err(e)) => warn!("TWAI TX error: {e:?}"),
Err(_) => warn!(
"TWAI TX stuck (bus fault or missing ACK), aborting retransmission"
),
}
}
}
}
}
};
select(rd_from, rd_to).await;
}
}
async fn send_to_ssh(&self, msg: &[u8]) {
if self.inward.free_capacity() < msg.len() {
let _ =
self.dropped_rx_frames
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |d| {
Some(d.saturating_add(1))
});
} else {
self.inward.write_all(msg).await;
}
}
pub async fn read(&self, buf: &mut [u8]) -> usize {
self.inward.read(buf).await
}
pub async fn write(&self, buf: &[u8]) {
self.outward.write_all(buf).await;
}
pub fn check_dropped_frames(&self) -> usize {
self.dropped_rx_frames.swap(0, Ordering::Relaxed)
}
pub fn reset_protocol(&self) {
self.binary_mode.store(false, Ordering::Relaxed);
self.proto_reset.store(true, Ordering::Relaxed);
let mut sink = [0u8; 32];
while self.inward.try_read(&mut sink).is_ok() {}
}
}
impl Default for BufferedCan {
fn default() -> Self {
Self::new()
}
}
impl ssh_stamp::can::BufferedCan for BufferedCan {
fn read(&self, buf: &mut [u8]) -> impl Future<Output = usize> {
BufferedCan::read(self, buf)
}
fn write(&self, buf: &[u8]) -> impl Future<Output = ()> {
BufferedCan::write(self, buf)
}
fn check_dropped_frames(&self) -> usize {
BufferedCan::check_dropped_frames(self)
}
fn reset_protocol(&self) {
BufferedCan::reset_protocol(self);
}
}
pub struct EspCanPins<'a> {
pub tx: AnyPin<'a>,
pub rx: AnyPin<'a>,
}
pub static CAN_BUF: StaticCell<BufferedCan> = StaticCell::new();
#[embassy_executor::task]
pub async fn can_task(
can_buf: &'static BufferedCan,
twai0: TWAI0<'static>,
pins: EspCanPins<'static>,
) {
let twai_config =
twai::TwaiConfiguration::new(twai0, pins.rx, pins.tx, twai::BaudRate::B500K, TWAI_MODE);
let twai = twai_config.into_async().start();
can_buf.run(twai).await;
}