use heapless::Vec;
use super::{
decode_abort, decode_data_segment, decode_download_response, decode_download_segment_response,
decode_upload_expedited_response, decode_upload_initiate_segmented_response,
encode_data_segment, encode_download_expedited, encode_download_initiate_segmented,
encode_upload_request, encode_upload_segment_request, request_cob_id, response_cob_id,
SdoPayload, SEGMENT_DATA_MAX,
};
use crate::datatypes::{DataType, Value};
use crate::object_dictionary::Address;
use crate::types::NodeId;
const ABORT_TOGGLE: u32 = 0x0503_0000;
const ABORT_GENERAL: u32 = 0x0800_0000;
#[derive(Debug, Clone, PartialEq)]
pub enum SdoEvent {
Send(SdoPayload),
Complete(Option<Value>),
Aborted(u32),
}
#[derive(Debug)]
enum State {
Idle,
DownloadExpedited,
DownloadInit {
data: [u8; 8],
len: usize,
},
DownloadSeg {
data: [u8; 8],
len: usize,
pos: usize,
last_toggle: bool,
},
UploadInit {
data_type: DataType,
},
UploadSeg {
data_type: DataType,
buf: Vec<u8, 8>,
toggle: bool,
},
}
#[derive(Debug)]
pub struct SdoClient {
node: NodeId,
state: State,
}
impl SdoClient {
pub const fn new(node: NodeId) -> Self {
Self {
node,
state: State::Idle,
}
}
pub fn request_cob_id(&self) -> u16 {
request_cob_id(self.node)
}
pub fn response_cob_id(&self) -> u16 {
response_cob_id(self.node)
}
pub fn is_busy(&self) -> bool {
!matches!(self.state, State::Idle)
}
pub fn read(&mut self, addr: Address, data_type: DataType) -> SdoPayload {
self.state = State::UploadInit { data_type };
encode_upload_request(addr)
}
pub fn write(&mut self, addr: Address, value: Value) -> SdoPayload {
let size = value.size();
if size <= 4 {
self.state = State::DownloadExpedited;
encode_download_expedited(addr, &value).expect("size <= 4")
} else {
let mut data = [0u8; 8];
value.encode_le(&mut data[..size]).expect("size <= 8");
self.state = State::DownloadInit { data, len: size };
encode_download_initiate_segmented(addr, size as u32)
}
}
pub fn on_response(&mut self, resp: &SdoPayload) -> SdoEvent {
if let Ok((_, code)) = decode_abort(resp) {
self.state = State::Idle;
return SdoEvent::Aborted(code);
}
match core::mem::replace(&mut self.state, State::Idle) {
State::Idle => SdoEvent::Aborted(ABORT_GENERAL),
State::DownloadExpedited => self.on_download_expedited(resp),
State::DownloadInit { data, len } => self.on_download_init(resp, data, len),
State::DownloadSeg {
data,
len,
pos,
last_toggle,
} => self.on_download_seg(resp, data, len, pos, last_toggle),
State::UploadInit { data_type } => self.on_upload_init(resp, data_type),
State::UploadSeg {
data_type,
buf,
toggle,
} => self.on_upload_seg(resp, data_type, buf, toggle),
}
}
fn on_download_expedited(&mut self, resp: &SdoPayload) -> SdoEvent {
match decode_download_response(resp) {
Ok(_) => SdoEvent::Complete(None),
Err(_) => SdoEvent::Aborted(ABORT_GENERAL),
}
}
fn on_download_init(&mut self, resp: &SdoPayload, data: [u8; 8], len: usize) -> SdoEvent {
if decode_download_response(resp).is_err() {
return SdoEvent::Aborted(ABORT_GENERAL);
}
self.send_download_segment(data, len, 0, false)
}
fn on_download_seg(
&mut self,
resp: &SdoPayload,
data: [u8; 8],
len: usize,
pos: usize,
last_toggle: bool,
) -> SdoEvent {
match decode_download_segment_response(resp) {
Ok(t) if t == last_toggle => {
if pos >= len {
SdoEvent::Complete(None)
} else {
self.send_download_segment(data, len, pos, !last_toggle)
}
}
Ok(_) => SdoEvent::Aborted(ABORT_TOGGLE),
Err(_) => SdoEvent::Aborted(ABORT_GENERAL),
}
}
fn send_download_segment(
&mut self,
data: [u8; 8],
len: usize,
pos: usize,
toggle: bool,
) -> SdoEvent {
let remaining = len - pos;
let n = remaining.min(SEGMENT_DATA_MAX);
let last = remaining <= SEGMENT_DATA_MAX;
let frame = encode_data_segment(&data[pos..pos + n], toggle, last).expect("1..=7");
self.state = State::DownloadSeg {
data,
len,
pos: pos + n,
last_toggle: toggle,
};
SdoEvent::Send(frame)
}
fn on_upload_init(&mut self, resp: &SdoPayload, data_type: DataType) -> SdoEvent {
if let Ok((_, value)) = decode_upload_expedited_response(resp, data_type) {
return SdoEvent::Complete(Some(value));
}
if decode_upload_initiate_segmented_response(resp).is_ok() {
self.state = State::UploadSeg {
data_type,
buf: Vec::new(),
toggle: false,
};
return SdoEvent::Send(encode_upload_segment_request(false));
}
SdoEvent::Aborted(ABORT_GENERAL)
}
fn on_upload_seg(
&mut self,
resp: &SdoPayload,
data_type: DataType,
mut buf: Vec<u8, 8>,
toggle: bool,
) -> SdoEvent {
let seg = match decode_data_segment(resp) {
Ok(s) => s,
Err(_) => return SdoEvent::Aborted(ABORT_GENERAL),
};
if seg.toggle != toggle {
return SdoEvent::Aborted(ABORT_TOGGLE);
}
if buf.extend_from_slice(seg.data).is_err() {
return SdoEvent::Aborted(ABORT_GENERAL);
}
if seg.last {
match Value::decode_le(data_type, &buf) {
Ok(value) => SdoEvent::Complete(Some(value)),
Err(_) => SdoEvent::Aborted(ABORT_GENERAL),
}
} else {
let next = !toggle;
self.state = State::UploadSeg {
data_type,
buf,
toggle: next,
};
SdoEvent::Send(encode_upload_segment_request(next))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn client() -> SdoClient {
SdoClient::new(NodeId::new(0x10).unwrap())
}
#[test]
fn cob_ids_track_node() {
let c = client();
assert_eq!(c.request_cob_id(), 0x610);
assert_eq!(c.response_cob_id(), 0x590);
}
#[test]
fn read_emits_upload_request_then_completes_on_expedited_response() {
let mut c = client();
let req = c.read(Address::new(0x1000, 0), DataType::Unsigned32);
assert_eq!(req[0], 0x40); assert!(c.is_busy());
let resp = super::super::encode_upload_expedited_response(
Address::new(0x1000, 0),
&Value::Unsigned32(0x192),
)
.unwrap();
assert_eq!(
c.on_response(&resp),
SdoEvent::Complete(Some(Value::Unsigned32(0x192)))
);
assert!(!c.is_busy());
}
#[test]
fn write_small_value_is_expedited() {
let mut c = client();
let req = c.write(Address::new(0x1017, 0), Value::Unsigned16(1234));
assert_eq!(req[0] & 0xE0, 0x20); assert_ne!(req[0] & 0x02, 0); let resp = super::super::encode_download_response(Address::new(0x1017, 0));
assert_eq!(c.on_response(&resp), SdoEvent::Complete(None));
}
#[test]
fn abort_response_surfaces_code() {
let mut c = client();
c.read(Address::new(0x9999, 0), DataType::Unsigned32);
let abort = super::super::encode_abort(
Address::new(0x9999, 0),
super::super::SdoAbortCode::ObjectDoesNotExist,
);
assert_eq!(c.on_response(&abort), SdoEvent::Aborted(0x0602_0000));
assert!(!c.is_busy());
}
}