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, MAX_STRING_LEN};
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; MAX_STRING_LEN],
66 len: usize,
67 },
68 DownloadSeg {
70 data: [u8; MAX_STRING_LEN],
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, MAX_STRING_LEN>,
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; MAX_STRING_LEN];
134 value
135 .encode_le(&mut data[..size])
136 .expect("size <= MAX_STRING_LEN");
137 self.state = State::DownloadInit { data, len: size };
138 encode_download_initiate_segmented(addr, size as u32)
139 }
140 }
141
142 pub fn on_response(&mut self, resp: &SdoPayload) -> SdoEvent {
144 if let Ok((_, code)) = decode_abort(resp) {
146 self.state = State::Idle;
147 return SdoEvent::Aborted(code);
148 }
149 match core::mem::replace(&mut self.state, State::Idle) {
150 State::Idle => SdoEvent::Aborted(ABORT_GENERAL),
151 State::DownloadExpedited => self.on_download_expedited(resp),
152 State::DownloadInit { data, len } => self.on_download_init(resp, data, len),
153 State::DownloadSeg {
154 data,
155 len,
156 pos,
157 last_toggle,
158 } => self.on_download_seg(resp, data, len, pos, last_toggle),
159 State::UploadInit { data_type } => self.on_upload_init(resp, data_type),
160 State::UploadSeg {
161 data_type,
162 buf,
163 toggle,
164 } => self.on_upload_seg(resp, data_type, buf, toggle),
165 }
166 }
167
168 fn on_download_expedited(&mut self, resp: &SdoPayload) -> SdoEvent {
169 match decode_download_response(resp) {
170 Ok(_) => SdoEvent::Complete(None),
171 Err(_) => SdoEvent::Aborted(ABORT_GENERAL),
172 }
173 }
174
175 fn on_download_init(
176 &mut self,
177 resp: &SdoPayload,
178 data: [u8; MAX_STRING_LEN],
179 len: usize,
180 ) -> SdoEvent {
181 if decode_download_response(resp).is_err() {
184 return SdoEvent::Aborted(ABORT_GENERAL);
185 }
186 self.send_download_segment(data, len, 0, false)
187 }
188
189 fn on_download_seg(
190 &mut self,
191 resp: &SdoPayload,
192 data: [u8; MAX_STRING_LEN],
193 len: usize,
194 pos: usize,
195 last_toggle: bool,
196 ) -> SdoEvent {
197 match decode_download_segment_response(resp) {
198 Ok(t) if t == last_toggle => {
199 if pos >= len {
200 SdoEvent::Complete(None)
201 } else {
202 self.send_download_segment(data, len, pos, !last_toggle)
203 }
204 }
205 Ok(_) => SdoEvent::Aborted(ABORT_TOGGLE),
206 Err(_) => SdoEvent::Aborted(ABORT_GENERAL),
207 }
208 }
209
210 fn send_download_segment(
213 &mut self,
214 data: [u8; MAX_STRING_LEN],
215 len: usize,
216 pos: usize,
217 toggle: bool,
218 ) -> SdoEvent {
219 let remaining = len - pos;
220 let n = remaining.min(SEGMENT_DATA_MAX);
221 let last = remaining <= SEGMENT_DATA_MAX;
222 let frame = encode_data_segment(&data[pos..pos + n], toggle, last).expect("1..=7");
223 self.state = State::DownloadSeg {
224 data,
225 len,
226 pos: pos + n,
227 last_toggle: toggle,
228 };
229 SdoEvent::Send(frame)
230 }
231
232 fn on_upload_init(&mut self, resp: &SdoPayload, data_type: DataType) -> SdoEvent {
233 if let Ok((_, value)) = decode_upload_expedited_response(resp, data_type) {
235 return SdoEvent::Complete(Some(value));
236 }
237 if decode_upload_initiate_segmented_response(resp).is_ok() {
238 self.state = State::UploadSeg {
239 data_type,
240 buf: Vec::new(),
241 toggle: false,
242 };
243 return SdoEvent::Send(encode_upload_segment_request(false));
244 }
245 SdoEvent::Aborted(ABORT_GENERAL)
246 }
247
248 fn on_upload_seg(
249 &mut self,
250 resp: &SdoPayload,
251 data_type: DataType,
252 mut buf: Vec<u8, MAX_STRING_LEN>,
253 toggle: bool,
254 ) -> SdoEvent {
255 let seg = match decode_data_segment(resp) {
256 Ok(s) => s,
257 Err(_) => return SdoEvent::Aborted(ABORT_GENERAL),
258 };
259 if seg.toggle != toggle {
260 return SdoEvent::Aborted(ABORT_TOGGLE);
261 }
262 if buf.extend_from_slice(seg.data).is_err() {
263 return SdoEvent::Aborted(ABORT_GENERAL);
264 }
265 if seg.last {
266 match Value::decode_le(data_type, &buf) {
267 Ok(value) => SdoEvent::Complete(Some(value)),
268 Err(_) => SdoEvent::Aborted(ABORT_GENERAL),
269 }
270 } else {
271 let next = !toggle;
272 self.state = State::UploadSeg {
273 data_type,
274 buf,
275 toggle: next,
276 };
277 SdoEvent::Send(encode_upload_segment_request(next))
278 }
279 }
280}
281
282#[cfg(test)]
283mod tests {
284 use super::*;
285
286 fn client() -> SdoClient {
287 SdoClient::new(NodeId::new(0x10).unwrap())
288 }
289
290 #[test]
291 fn cob_ids_track_node() {
292 let c = client();
293 assert_eq!(c.request_cob_id(), 0x610);
294 assert_eq!(c.response_cob_id(), 0x590);
295 }
296
297 #[test]
298 fn read_emits_upload_request_then_completes_on_expedited_response() {
299 let mut c = client();
300 let req = c.read(Address::new(0x1000, 0), DataType::Unsigned32);
301 assert_eq!(req[0], 0x40); assert!(c.is_busy());
303 let resp = super::super::encode_upload_expedited_response(
305 Address::new(0x1000, 0),
306 &Value::Unsigned32(0x192),
307 )
308 .unwrap();
309 assert_eq!(
310 c.on_response(&resp),
311 SdoEvent::Complete(Some(Value::Unsigned32(0x192)))
312 );
313 assert!(!c.is_busy());
314 }
315
316 #[test]
317 fn write_small_value_is_expedited() {
318 let mut c = client();
319 let req = c.write(Address::new(0x1017, 0), Value::Unsigned16(1234));
320 assert_eq!(req[0] & 0xE0, 0x20); assert_ne!(req[0] & 0x02, 0); let resp = super::super::encode_download_response(Address::new(0x1017, 0));
323 assert_eq!(c.on_response(&resp), SdoEvent::Complete(None));
324 }
325
326 #[test]
327 fn abort_response_surfaces_code() {
328 let mut c = client();
329 c.read(Address::new(0x9999, 0), DataType::Unsigned32);
330 let abort = super::super::encode_abort(
331 Address::new(0x9999, 0),
332 super::super::SdoAbortCode::ObjectDoesNotExist,
333 );
334 assert_eq!(c.on_response(&abort), SdoEvent::Aborted(0x0602_0000));
335 assert!(!c.is_busy());
336 }
337}