use heapless::Vec;
pub const ABORT_TOGGLE_BIT: u32 = 0x0503_0000;
pub const ABORT_TIMEOUT: u32 = 0x0504_0000;
pub const ABORT_INVALID_CS: u32 = 0x0504_0001;
pub const ABORT_OBJ_NOT_EXIST: u32 = 0x0602_0000;
pub const ABORT_SUBIDX_NOT_EXIST: u32 = 0x0609_0011;
pub const ABORT_VALUE_RANGE: u32 = 0x0609_0030;
pub const ABORT_GENERAL: u32 = 0x0800_0000;
pub const ABORT_DATA_TRANSFER: u32 = 0x0800_0020;
pub mod sdo_cs {
pub const DOWNLOAD_INIT_REQ: u8 = 0x21;
pub const DOWNLOAD_INIT_RESP: u8 = 0x60;
pub const DOWNLOAD_SEG_REQ_BASE: u8 = 0x00;
pub const DOWNLOAD_SEG_RESP_BASE: u8 = 0x20;
pub const UPLOAD_INIT_REQ: u8 = 0x40;
pub const UPLOAD_INIT_RESP: u8 = 0x41;
pub const UPLOAD_SEG_REQ_BASE: u8 = 0x60;
pub const UPLOAD_SEG_RESP_BASE: u8 = 0x00;
pub const ABORT: u8 = 0x80;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TransferDir {
Download,
Upload,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SdoSegState {
Idle,
Initiated,
InProgress,
Complete,
Aborted,
}
pub struct SdoSegmentTransfer<const BUF: usize> {
state: SdoSegState,
direction: TransferDir,
index: u16,
sub_index: u8,
data: Vec<u8, BUF>,
total_len: usize,
toggle: bool,
segment_count: u32,
abort_code: u32,
}
impl<const BUF: usize> SdoSegmentTransfer<BUF> {
pub fn new() -> Self {
Self {
state: SdoSegState::Idle,
direction: TransferDir::Download,
index: 0,
sub_index: 0,
data: Vec::new(),
total_len: 0,
toggle: false,
segment_count: 0,
abort_code: 0,
}
}
pub fn state(&self) -> SdoSegState {
self.state
}
pub fn segment_count(&self) -> u32 {
self.segment_count
}
pub fn data(&self) -> &[u8] {
&self.data
}
pub fn abort_code(&self) -> u32 {
self.abort_code
}
pub fn download_initiate(&mut self, frame: &[u8; 8]) -> Result<[u8; 8], u32> {
let cs = frame[0];
if cs & 0xE0 != 0x20 {
return Err(ABORT_INVALID_CS);
}
self.index = u16::from_le_bytes([frame[1], frame[2]]);
self.sub_index = frame[3];
if cs & 0x01 != 0 {
self.total_len = u32::from_le_bytes([frame[4], frame[5], frame[6], frame[7]]) as usize;
} else {
self.total_len = 0;
}
self.data.clear();
self.toggle = false;
self.segment_count = 0;
self.direction = TransferDir::Download;
self.state = SdoSegState::Initiated;
let idx = self.index.to_le_bytes();
Ok([0x60, idx[0], idx[1], self.sub_index, 0, 0, 0, 0])
}
pub fn download_segment(&mut self, frame: &[u8; 8]) -> Result<[u8; 8], u32> {
if self.state != SdoSegState::Initiated && self.state != SdoSegState::InProgress {
return Err(ABORT_GENERAL);
}
let cs = frame[0];
let frame_toggle = (cs & 0x10) != 0;
if self.segment_count > 0 && frame_toggle == self.toggle {
self.state = SdoSegState::Aborted;
self.abort_code = ABORT_TOGGLE_BIT;
return Err(ABORT_TOGGLE_BIT);
}
self.toggle = frame_toggle;
let n = ((cs >> 1) & 0x07) as usize;
let data_len = 7usize.saturating_sub(n);
let last = (cs & 0x01) != 0;
for i in 0..data_len {
if self.data.push(frame[1 + i]).is_err() {
self.state = SdoSegState::Aborted;
self.abort_code = ABORT_DATA_TRANSFER;
return Err(ABORT_DATA_TRANSFER);
}
}
self.segment_count += 1;
self.state = if last {
SdoSegState::Complete
} else {
SdoSegState::InProgress
};
let resp_cs = 0x20u8 | (if frame_toggle { 0x10 } else { 0x00 });
Ok([resp_cs, 0, 0, 0, 0, 0, 0, 0])
}
pub fn upload_initiate(
&mut self,
index: u16,
sub_index: u8,
data: &[u8],
) -> Result<[u8; 8], u32> {
self.index = index;
self.sub_index = sub_index;
self.data.clear();
for &b in data {
if self.data.push(b).is_err() {
return Err(ABORT_DATA_TRANSFER);
}
}
self.total_len = data.len();
self.toggle = false;
self.segment_count = 0;
self.direction = TransferDir::Upload;
self.state = SdoSegState::Initiated;
let idx = index.to_le_bytes();
let sz = (data.len() as u32).to_le_bytes();
Ok([0x41, idx[0], idx[1], sub_index, sz[0], sz[1], sz[2], sz[3]])
}
pub fn upload_segment(&mut self, frame: &[u8; 8]) -> Result<[u8; 8], u32> {
if self.state != SdoSegState::Initiated && self.state != SdoSegState::InProgress {
return Err(ABORT_GENERAL);
}
let cs = frame[0];
let req_toggle = (cs & 0x10) != 0;
if self.segment_count > 0 && req_toggle == self.toggle {
self.state = SdoSegState::Aborted;
self.abort_code = ABORT_TOGGLE_BIT;
return Err(ABORT_TOGGLE_BIT);
}
self.toggle = req_toggle;
let offset = self.segment_count as usize * 7;
let remaining = if offset < self.data.len() {
self.data.len() - offset
} else {
0
};
let chunk_len = remaining.min(7);
let last = remaining <= 7;
let n = 7usize.saturating_sub(chunk_len);
let mut resp = [0u8; 8];
resp[0] = (if req_toggle { 0x10u8 } else { 0x00u8 })
| ((n as u8) << 1)
| (if last { 0x01 } else { 0x00 });
for i in 0..chunk_len {
resp[1 + i] = self.data[offset + i];
}
self.segment_count += 1;
self.state = if last {
SdoSegState::Complete
} else {
SdoSegState::InProgress
};
Ok(resp)
}
pub fn build_abort(&self, abort_code: u32) -> [u8; 8] {
let idx = self.index.to_le_bytes();
let ac = abort_code.to_le_bytes();
[
0x80,
idx[0],
idx[1],
self.sub_index,
ac[0],
ac[1],
ac[2],
ac[3],
]
}
pub fn on_abort(&mut self, frame: &[u8; 8]) {
if frame[0] == 0x80 {
self.abort_code = u32::from_le_bytes([frame[4], frame[5], frame[6], frame[7]]);
self.state = SdoSegState::Aborted;
}
}
pub fn reset(&mut self) {
self.state = SdoSegState::Idle;
self.data.clear();
self.toggle = false;
self.segment_count = 0;
self.abort_code = 0;
}
pub fn total_len(&self) -> usize {
self.total_len
}
pub fn index(&self) -> u16 {
self.index
}
pub fn sub_index(&self) -> u8 {
self.sub_index
}
}
impl<const BUF: usize> Default for SdoSegmentTransfer<BUF> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_download_init(index: u16, sub: u8, size: u32) -> [u8; 8] {
let idx = index.to_le_bytes();
let sz = size.to_le_bytes();
[0x21, idx[0], idx[1], sub, sz[0], sz[1], sz[2], sz[3]]
}
fn make_download_seg(toggle: bool, data: &[u8], last: bool) -> [u8; 8] {
let mut frame = [0u8; 8];
let n = 7usize.saturating_sub(data.len());
frame[0] = (if toggle { 0x10 } else { 0x00 })
| ((n as u8) << 1)
| (if last { 0x01 } else { 0x00 });
for (i, &b) in data.iter().enumerate() {
frame[1 + i] = b;
}
frame
}
#[test]
fn test_download_single_segment() {
let mut sdo = SdoSegmentTransfer::<64>::new();
let init = make_download_init(0x2001, 0, 5);
let resp = sdo.download_initiate(&init).unwrap();
assert_eq!(resp[0], 0x60);
assert_eq!(sdo.state(), SdoSegState::Initiated);
let seg = make_download_seg(false, &[1, 2, 3, 4, 5], true);
let sresp = sdo.download_segment(&seg).unwrap();
assert_eq!(sresp[0] & 0xE0, 0x20);
assert_eq!(sdo.state(), SdoSegState::Complete);
assert_eq!(sdo.data(), &[1, 2, 3, 4, 5]);
}
#[test]
fn test_download_multi_segment_toggle() {
let mut sdo = SdoSegmentTransfer::<64>::new();
let init = make_download_init(0x2002, 0, 14);
sdo.download_initiate(&init).unwrap();
let s1 = make_download_seg(false, &[10, 11, 12, 13, 14, 15, 16], false);
sdo.download_segment(&s1).unwrap();
assert_eq!(sdo.state(), SdoSegState::InProgress);
let s2 = make_download_seg(true, &[20, 21, 22, 23, 24, 25, 26], true);
sdo.download_segment(&s2).unwrap();
assert_eq!(sdo.state(), SdoSegState::Complete);
assert_eq!(sdo.data().len(), 14);
}
#[test]
fn test_toggle_bit_error() {
let mut sdo = SdoSegmentTransfer::<64>::new();
let init = make_download_init(0x2003, 0, 14);
sdo.download_initiate(&init).unwrap();
let s1 = make_download_seg(false, &[1, 2, 3, 4, 5, 6, 7], false);
sdo.download_segment(&s1).unwrap();
let s2 = make_download_seg(false, &[8, 9, 10, 11, 12, 13, 14], true);
let result = sdo.download_segment(&s2);
assert_eq!(result, Err(ABORT_TOGGLE_BIT));
assert_eq!(sdo.state(), SdoSegState::Aborted);
}
#[test]
fn test_upload_transfer() {
let mut sdo = SdoSegmentTransfer::<64>::new();
let payload = [1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let resp = sdo.upload_initiate(0x2010, 0, &payload).unwrap();
assert_eq!(resp[0], 0x41);
assert_eq!(sdo.state(), SdoSegState::Initiated);
let req1 = [0x60u8, 0, 0, 0, 0, 0, 0, 0]; let seg1 = sdo.upload_segment(&req1).unwrap();
assert_eq!(seg1[1..8], payload[..7]);
assert_eq!(sdo.state(), SdoSegState::InProgress);
let req2 = [0x70u8, 0, 0, 0, 0, 0, 0, 0]; let seg2 = sdo.upload_segment(&req2).unwrap();
assert_eq!(seg2[1], 8); assert_eq!(seg2[2], 9); assert_eq!(seg2[3], 10); assert_eq!(sdo.state(), SdoSegState::Complete);
}
#[test]
fn test_abort_handling() {
let mut sdo = SdoSegmentTransfer::<64>::new();
sdo.upload_initiate(0x2000, 0, &[1, 2, 3]).unwrap();
let abort_frame = [0x80u8, 0x00, 0x20, 0x00, 0x00, 0x00, 0x02, 0x06];
sdo.on_abort(&abort_frame);
assert_eq!(sdo.state(), SdoSegState::Aborted);
assert_eq!(sdo.abort_code(), 0x0602_0000);
}
#[test]
fn test_reset() {
let mut sdo = SdoSegmentTransfer::<64>::new();
let init = make_download_init(0x2001, 0, 3);
sdo.download_initiate(&init).unwrap();
sdo.reset();
assert_eq!(sdo.state(), SdoSegState::Idle);
assert_eq!(sdo.data().len(), 0);
assert_eq!(sdo.segment_count(), 0);
}
}