1use heapless::Vec;
29
30use super::{
31 decode_abort, decode_data_segment, decode_download_response, decode_download_segment_response,
32 decode_upload_expedited_response, decode_upload_initiate_segmented_response,
33 encode_data_segment, encode_download_expedited, encode_download_initiate_segmented,
34 encode_upload_request, encode_upload_segment_request, request_cob_id, response_cob_id,
35 SdoPayload, SEGMENT_DATA_MAX,
36};
37use crate::datatypes::{DataType, Value};
38use crate::object_dictionary::Address;
39use crate::types::NodeId;
40
41const ABORT_TOGGLE: u32 = 0x0503_0000;
44const ABORT_GENERAL: u32 = 0x0800_0000;
46
47#[derive(Debug, Clone, PartialEq)]
49pub enum SdoEvent {
50 Send(SdoPayload),
52 Complete(Option<Value>),
54 Aborted(u32),
56}
57
58#[derive(Debug)]
59enum State {
60 Idle,
61 DownloadExpedited,
63 DownloadInit {
65 data: [u8; 8],
66 len: usize,
67 },
68 DownloadSeg {
70 data: [u8; 8],
71 len: usize,
72 pos: usize,
73 last_toggle: bool,
74 },
75 UploadInit {
77 data_type: DataType,
78 },
79 UploadSeg {
81 data_type: DataType,
82 buf: Vec<u8, 8>,
83 toggle: bool,
84 },
85}
86
87#[derive(Debug)]
89pub struct SdoClient {
90 node: NodeId,
91 state: State,
92}
93
94impl SdoClient {
95 pub const fn new(node: NodeId) -> Self {
97 Self {
98 node,
99 state: State::Idle,
100 }
101 }
102
103 pub fn request_cob_id(&self) -> u16 {
105 request_cob_id(self.node)
106 }
107
108 pub fn response_cob_id(&self) -> u16 {
110 response_cob_id(self.node)
111 }
112
113 pub fn is_busy(&self) -> bool {
115 !matches!(self.state, State::Idle)
116 }
117
118 pub fn read(&mut self, addr: Address, data_type: DataType) -> SdoPayload {
121 self.state = State::UploadInit { data_type };
122 encode_upload_request(addr)
123 }
124
125 pub fn write(&mut self, addr: Address, value: Value) -> SdoPayload {
128 let size = value.size();
129 if size <= 4 {
130 self.state = State::DownloadExpedited;
131 encode_download_expedited(addr, &value).expect("size <= 4")
132 } else {
133 let mut data = [0u8; 8];
134 value.encode_le(&mut data[..size]).expect("size <= 8");
135 self.state = State::DownloadInit { data, len: size };
136 encode_download_initiate_segmented(addr, size as u32)
137 }
138 }
139
140 pub fn on_response(&mut self, resp: &SdoPayload) -> SdoEvent {
142 if let Ok((_, code)) = decode_abort(resp) {
144 self.state = State::Idle;
145 return SdoEvent::Aborted(code);
146 }
147 match core::mem::replace(&mut self.state, State::Idle) {
148 State::Idle => SdoEvent::Aborted(ABORT_GENERAL),
149 State::DownloadExpedited => self.on_download_expedited(resp),
150 State::DownloadInit { data, len } => self.on_download_init(resp, data, len),
151 State::DownloadSeg {
152 data,
153 len,
154 pos,
155 last_toggle,
156 } => self.on_download_seg(resp, data, len, pos, last_toggle),
157 State::UploadInit { data_type } => self.on_upload_init(resp, data_type),
158 State::UploadSeg {
159 data_type,
160 buf,
161 toggle,
162 } => self.on_upload_seg(resp, data_type, buf, toggle),
163 }
164 }
165
166 fn on_download_expedited(&mut self, resp: &SdoPayload) -> SdoEvent {
167 match decode_download_response(resp) {
168 Ok(_) => SdoEvent::Complete(None),
169 Err(_) => SdoEvent::Aborted(ABORT_GENERAL),
170 }
171 }
172
173 fn on_download_init(&mut self, resp: &SdoPayload, data: [u8; 8], len: usize) -> SdoEvent {
174 if decode_download_response(resp).is_err() {
177 return SdoEvent::Aborted(ABORT_GENERAL);
178 }
179 self.send_download_segment(data, len, 0, false)
180 }
181
182 fn on_download_seg(
183 &mut self,
184 resp: &SdoPayload,
185 data: [u8; 8],
186 len: usize,
187 pos: usize,
188 last_toggle: bool,
189 ) -> SdoEvent {
190 match decode_download_segment_response(resp) {
191 Ok(t) if t == last_toggle => {
192 if pos >= len {
193 SdoEvent::Complete(None)
194 } else {
195 self.send_download_segment(data, len, pos, !last_toggle)
196 }
197 }
198 Ok(_) => SdoEvent::Aborted(ABORT_TOGGLE),
199 Err(_) => SdoEvent::Aborted(ABORT_GENERAL),
200 }
201 }
202
203 fn send_download_segment(
206 &mut self,
207 data: [u8; 8],
208 len: usize,
209 pos: usize,
210 toggle: bool,
211 ) -> SdoEvent {
212 let remaining = len - pos;
213 let n = remaining.min(SEGMENT_DATA_MAX);
214 let last = remaining <= SEGMENT_DATA_MAX;
215 let frame = encode_data_segment(&data[pos..pos + n], toggle, last).expect("1..=7");
216 self.state = State::DownloadSeg {
217 data,
218 len,
219 pos: pos + n,
220 last_toggle: toggle,
221 };
222 SdoEvent::Send(frame)
223 }
224
225 fn on_upload_init(&mut self, resp: &SdoPayload, data_type: DataType) -> SdoEvent {
226 if let Ok((_, value)) = decode_upload_expedited_response(resp, data_type) {
228 return SdoEvent::Complete(Some(value));
229 }
230 if decode_upload_initiate_segmented_response(resp).is_ok() {
231 self.state = State::UploadSeg {
232 data_type,
233 buf: Vec::new(),
234 toggle: false,
235 };
236 return SdoEvent::Send(encode_upload_segment_request(false));
237 }
238 SdoEvent::Aborted(ABORT_GENERAL)
239 }
240
241 fn on_upload_seg(
242 &mut self,
243 resp: &SdoPayload,
244 data_type: DataType,
245 mut buf: Vec<u8, 8>,
246 toggle: bool,
247 ) -> SdoEvent {
248 let seg = match decode_data_segment(resp) {
249 Ok(s) => s,
250 Err(_) => return SdoEvent::Aborted(ABORT_GENERAL),
251 };
252 if seg.toggle != toggle {
253 return SdoEvent::Aborted(ABORT_TOGGLE);
254 }
255 if buf.extend_from_slice(seg.data).is_err() {
256 return SdoEvent::Aborted(ABORT_GENERAL);
257 }
258 if seg.last {
259 match Value::decode_le(data_type, &buf) {
260 Ok(value) => SdoEvent::Complete(Some(value)),
261 Err(_) => SdoEvent::Aborted(ABORT_GENERAL),
262 }
263 } else {
264 let next = !toggle;
265 self.state = State::UploadSeg {
266 data_type,
267 buf,
268 toggle: next,
269 };
270 SdoEvent::Send(encode_upload_segment_request(next))
271 }
272 }
273}
274
275#[cfg(test)]
276mod tests {
277 use super::*;
278
279 fn client() -> SdoClient {
280 SdoClient::new(NodeId::new(0x10).unwrap())
281 }
282
283 #[test]
284 fn cob_ids_track_node() {
285 let c = client();
286 assert_eq!(c.request_cob_id(), 0x610);
287 assert_eq!(c.response_cob_id(), 0x590);
288 }
289
290 #[test]
291 fn read_emits_upload_request_then_completes_on_expedited_response() {
292 let mut c = client();
293 let req = c.read(Address::new(0x1000, 0), DataType::Unsigned32);
294 assert_eq!(req[0], 0x40); assert!(c.is_busy());
296 let resp = super::super::encode_upload_expedited_response(
298 Address::new(0x1000, 0),
299 &Value::Unsigned32(0x192),
300 )
301 .unwrap();
302 assert_eq!(
303 c.on_response(&resp),
304 SdoEvent::Complete(Some(Value::Unsigned32(0x192)))
305 );
306 assert!(!c.is_busy());
307 }
308
309 #[test]
310 fn write_small_value_is_expedited() {
311 let mut c = client();
312 let req = c.write(Address::new(0x1017, 0), Value::Unsigned16(1234));
313 assert_eq!(req[0] & 0xE0, 0x20); assert_ne!(req[0] & 0x02, 0); let resp = super::super::encode_download_response(Address::new(0x1017, 0));
316 assert_eq!(c.on_response(&resp), SdoEvent::Complete(None));
317 }
318
319 #[test]
320 fn abort_response_surfaces_code() {
321 let mut c = client();
322 c.read(Address::new(0x9999, 0), DataType::Unsigned32);
323 let abort = super::super::encode_abort(
324 Address::new(0x9999, 0),
325 super::super::SdoAbortCode::ObjectDoesNotExist,
326 );
327 assert_eq!(c.on_response(&abort), SdoEvent::Aborted(0x0602_0000));
328 assert!(!c.is_busy());
329 }
330}