1use std::time::Duration;
2
3use snafu::Snafu;
4use zencan_common::{
5 constants::{object_ids, values::SAVE_CMD},
6 lss::LssIdentity,
7 messages::CanId,
8 sdo::{AbortCode, BlockSegment, SdoRequest, SdoResponse},
9 traits::{AsyncCanReceiver, AsyncCanSender},
10};
11
12use crate::node_configuration::PdoConfig;
13
14const RESPONSE_TIMEOUT: Duration = Duration::from_millis(100);
15
16#[derive(Debug, Clone, Copy, PartialEq)]
21pub enum RawAbortCode {
22 Valid(AbortCode),
24 Unknown(u32),
26}
27
28impl std::fmt::Display for RawAbortCode {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 match self {
31 RawAbortCode::Valid(abort_code) => write!(f, "{abort_code:?}"),
32 RawAbortCode::Unknown(code) => write!(f, "{code:X}"),
33 }
34 }
35}
36
37impl From<u32> for RawAbortCode {
38 fn from(value: u32) -> Self {
39 match AbortCode::try_from(value) {
40 Ok(code) => Self::Valid(code),
41 Err(_) => Self::Unknown(value),
42 }
43 }
44}
45
46#[derive(Clone, Debug, PartialEq, Snafu)]
48pub enum SdoClientError {
49 NoResponse,
51 MalformedResponse,
53 #[snafu(display("Unexpected SDO response. Expected {expecting}, got {response:?}"))]
55 UnexpectedResponse {
56 expecting: String,
58 response: SdoResponse,
60 },
61 #[snafu(display("Received abort accessing object 0x{index:X}sub{sub}: {abort_code}"))]
63 ServerAbort {
64 index: u16,
66 sub: u8,
68 abort_code: RawAbortCode,
70 },
71 ToggleNotAlternated,
73 #[snafu(display("Received object 0x{:x}sub{} after requesting 0x{:x}sub{}",
75 received.0, received.1, expected.0, expected.1))]
76 MismatchedObjectIndex {
77 expected: (u16, u8),
79 received: (u16, u8),
81 },
82 UnexpectedSize,
84 #[snafu(display("Error sending CAN message"))]
86 SocketSendFailed,
87 BlockSizeChangedTooSmall,
93}
94
95type Result<T> = std::result::Result<T, SdoClientError>;
96
97macro_rules! match_response {
100 ($resp: ident, $expecting: literal, $($match:pat => $code : expr),*) => {
101 match $resp {
102 $($match => $code),*
103 SdoResponse::Abort {
104 index,
105 sub,
106 abort_code,
107 } => {
108 return ServerAbortSnafu {
109 index,
110 sub,
111 abort_code,
112 }
113 .fail()
114 }
115 _ => {
116 return UnexpectedResponseSnafu {
117 expecting: $expecting,
118 response: $resp,
119 }
120 .fail()
121 }
122 }
123 };
124}
125
126#[derive(Debug)]
127pub struct SdoClient<S, R> {
131 req_cob_id: CanId,
132 resp_cob_id: CanId,
133 sender: S,
134 receiver: R,
135}
136
137impl<S: AsyncCanSender, R: AsyncCanReceiver> SdoClient<S, R> {
138 pub fn new_std(server_node_id: u8, sender: S, receiver: R) -> Self {
146 let req_cob_id = CanId::Std(0x600 + server_node_id as u16);
147 let resp_cob_id = CanId::Std(0x580 + server_node_id as u16);
148 Self::new(req_cob_id, resp_cob_id, sender, receiver)
149 }
150
151 pub fn new(req_cob_id: CanId, resp_cob_id: CanId, sender: S, receiver: R) -> Self {
153 Self {
154 req_cob_id,
155 resp_cob_id,
156 sender,
157 receiver,
158 }
159 }
160
161 pub async fn download(&mut self, index: u16, sub: u8, data: &[u8]) -> Result<()> {
163 if data.len() <= 4 {
164 let msg =
166 SdoRequest::expedited_download(index, sub, data).to_can_message(self.req_cob_id);
167 self.sender.send(msg).await.unwrap(); let resp = self.wait_for_response(RESPONSE_TIMEOUT).await?;
170 match_response!(
171 resp,
172 "ConfirmDownload",
173 SdoResponse::ConfirmDownload { index: _, sub: _ } => {
174 Ok(()) }
176 )
177 } else {
178 let msg = SdoRequest::initiate_download(index, sub, Some(data.len() as u32))
179 .to_can_message(self.req_cob_id);
180 self.sender.send(msg).await.unwrap();
181
182 let resp = self.wait_for_response(RESPONSE_TIMEOUT).await?;
183 match_response!(
184 resp,
185 "ConfirmDownload",
186 SdoResponse::ConfirmDownload { index: _, sub: _ } => { }
187 );
188
189 let mut toggle = false;
190 let total_segments = data.len().div_ceil(7);
192 for n in 0..total_segments {
193 let last_segment = n == total_segments - 1;
194 let segment_size = (data.len() - n * 7).min(7);
195 let seg_msg = SdoRequest::download_segment(
196 toggle,
197 last_segment,
198 &data[n * 7..n * 7 + segment_size],
199 )
200 .to_can_message(self.req_cob_id);
201 self.sender
202 .send(seg_msg)
203 .await
204 .expect("failed sending DL segment");
205 let resp = self.wait_for_response(RESPONSE_TIMEOUT).await?;
206 match_response!(
207 resp,
208 "ConfirmDownloadSegment",
209 SdoResponse::ConfirmDownloadSegment { t } => {
210 if t != toggle {
212 let abort_msg =
213 SdoRequest::abort(index, sub, AbortCode::ToggleNotAlternated)
214 .to_can_message(self.req_cob_id);
215 self.sender
216 .send(abort_msg)
217 .await
218 .expect("Error sending abort");
219 return ToggleNotAlternatedSnafu.fail();
220 }
221 }
223 );
224 toggle = !toggle;
225 }
226 Ok(())
227 }
228 }
229
230 pub async fn upload(&mut self, index: u16, sub: u8) -> Result<Vec<u8>> {
232 let mut read_buf = Vec::new();
233
234 let msg = SdoRequest::initiate_upload(index, sub).to_can_message(self.req_cob_id);
235 self.sender.send(msg).await.unwrap();
236
237 let resp = self.wait_for_response(RESPONSE_TIMEOUT).await?;
238
239 let expedited = match_response!(
240 resp,
241 "ConfirmUpload",
242 SdoResponse::ConfirmUpload {
243 n,
244 e,
245 s,
246 index: _,
247 sub: _,
248 data,
249 } => {
250 if e {
251 let mut len = 0;
252 if s {
253 len = 4 - n as usize;
254 }
255 read_buf.extend_from_slice(&data[0..len]);
256 }
257 e
258 }
259 );
260
261 if !expedited {
262 let mut toggle = false;
264 loop {
265 let msg =
266 SdoRequest::upload_segment_request(toggle).to_can_message(self.req_cob_id);
267
268 self.sender.send(msg).await.unwrap();
269
270 let resp = self.wait_for_response(RESPONSE_TIMEOUT).await?;
271 match_response!(
272 resp,
273 "UploadSegment",
274 SdoResponse::UploadSegment { t, n, c, data } => {
275 if t != toggle {
276 self.sender
277 .send(
278 SdoRequest::abort(index, sub, AbortCode::ToggleNotAlternated)
279 .to_can_message(self.req_cob_id),
280 )
281 .await
282 .expect("Error sending abort");
283 return ToggleNotAlternatedSnafu.fail();
284 }
285 read_buf.extend_from_slice(&data[0..7 - n as usize]);
286 if c {
287 break;
289 }
290 }
291 );
292 toggle = !toggle;
293 }
294 }
295 Ok(read_buf)
296 }
297
298 pub async fn block_download(&mut self, index: u16, sub: u8, data: &[u8]) -> Result<()> {
303 self.sender
304 .send(
305 SdoRequest::InitiateBlockDownload {
306 cc: true, s: true, index,
309 sub,
310 size: data.len() as u32,
311 }
312 .to_can_message(self.req_cob_id),
313 )
314 .await
315 .map_err(|_| SocketSendFailedSnafu {}.build())?;
316
317 let resp = self.wait_for_response(RESPONSE_TIMEOUT).await?;
318
319 let (crc_enabled, mut blksize) = match_response!(
320 resp,
321 "ConfirmBlockDownload",
322 SdoResponse::ConfirmBlockDownload {
323 sc,
324 index: resp_index,
325 sub: resp_sub,
326 blksize,
327 } => {
328 if index != resp_index || sub != resp_sub {
329 return MismatchedObjectIndexSnafu {
330 expected: (index, sub),
331 received: (resp_index, resp_sub),
332 }
333 .fail();
334 }
335 (sc, blksize)
336 }
337 );
338
339 let mut seqnum = 1;
340 let mut last_block_start = 0;
341 let mut segment_num = 0;
342 let total_segments = data.len().div_ceil(7);
343
344 while segment_num < total_segments {
345 let segment_start = segment_num * 7;
346 let segment_len = (data.len() - segment_start).min(7);
347 let c = segment_start + segment_len == data.len();
349 let mut segment_data = [0; 7];
350 segment_data[0..segment_len]
351 .copy_from_slice(&data[segment_start..segment_start + segment_len]);
352
353 let segment = BlockSegment {
355 c,
356 seqnum,
357 data: segment_data,
358 };
359 self.sender
360 .send(segment.to_can_message(self.req_cob_id))
361 .await
362 .map_err(|_| SocketSendFailedSnafu.build())?;
363
364 if c || seqnum == blksize {
367 let resp = self.wait_for_response(RESPONSE_TIMEOUT).await?;
368 match_response!(
369 resp,
370 "ConfirmBlock",
371 SdoResponse::ConfirmBlock {
372 ackseq,
373 blksize: new_blksize,
374 } => {
375 if ackseq == blksize {
376 seqnum = 1;
378 segment_num += 1;
379 last_block_start = segment_num;
380 } else {
381 seqnum = ackseq;
383 segment_num = last_block_start + ackseq as usize;
384 if new_blksize < seqnum {
391 return BlockSizeChangedTooSmallSnafu.fail();
392 }
393 }
394 blksize = new_blksize;
395 }
396 );
397 } else {
398 seqnum += 1;
399 segment_num += 1;
400 }
401 }
402
403 let crc = if crc_enabled {
405 crc16::State::<crc16::XMODEM>::calculate(data)
406 } else {
407 0
408 };
409
410 let n = ((7 - data.len() % 7) % 7) as u8;
411
412 self.sender
413 .send(SdoRequest::EndBlockDownload { n, crc }.to_can_message(self.req_cob_id))
414 .await
415 .map_err(|_| SocketSendFailedSnafu.build())?;
416
417 let resp = self.wait_for_response(RESPONSE_TIMEOUT).await?;
418 match_response!(
419 resp,
420 "ConfirmBlockDownloadEnd",
421 SdoResponse::ConfirmBlockDownloadEnd => { Ok(()) }
422 )
423 }
424
425 pub async fn download_u32(&mut self, index: u16, sub: u8, data: u32) -> Result<()> {
427 let data = data.to_le_bytes();
428 self.download(index, sub, &data).await
429 }
430
431 pub async fn write_u32(&mut self, index: u16, sub: u8, data: u32) -> Result<()> {
435 self.download_u32(index, sub, data).await
436 }
437
438 pub async fn download_u16(&mut self, index: u16, sub: u8, data: u16) -> Result<()> {
440 let data = data.to_le_bytes();
441 self.download(index, sub, &data).await
442 }
443
444 pub async fn write_u16(&mut self, index: u16, sub: u8, data: u16) -> Result<()> {
448 self.download_u16(index, sub, data).await
449 }
450
451 pub async fn download_u8(&mut self, index: u16, sub: u8, data: u8) -> Result<()> {
453 let data = data.to_le_bytes();
454 self.download(index, sub, &data).await
455 }
456
457 pub async fn write_u8(&mut self, index: u16, sub: u8, data: u8) -> Result<()> {
461 self.download_u8(index, sub, data).await
462 }
463
464 pub async fn download_i32(&mut self, index: u16, sub: u8, data: i32) -> Result<()> {
466 let data = data.to_le_bytes();
467 self.download(index, sub, &data).await
468 }
469
470 pub async fn write_i32(&mut self, index: u16, sub: u8, data: i32) -> Result<()> {
474 self.download_i32(index, sub, data).await
475 }
476
477 pub async fn download_i16(&mut self, index: u16, sub: u8, data: i16) -> Result<()> {
479 let data = data.to_le_bytes();
480 self.download(index, sub, &data).await
481 }
482
483 pub async fn write_i16(&mut self, index: u16, sub: u8, data: i16) -> Result<()> {
487 self.download_i16(index, sub, data).await
488 }
489
490 pub async fn download_i8(&mut self, index: u16, sub: u8, data: i8) -> Result<()> {
492 let data = data.to_le_bytes();
493 self.download(index, sub, &data).await
494 }
495
496 pub async fn write_i8(&mut self, index: u16, sub: u8, data: i8) -> Result<()> {
500 self.download_i8(index, sub, data).await
501 }
502
503 pub async fn upload_utf8(&mut self, index: u16, sub: u8) -> Result<String> {
505 let data = self.upload(index, sub).await?;
506 Ok(String::from_utf8_lossy(&data).into())
507 }
508 pub async fn read_utf8(&mut self, index: u16, sub: u8) -> Result<String> {
510 self.upload_utf8(index, sub).await
511 }
512
513 pub async fn upload_u8(&mut self, index: u16, sub: u8) -> Result<u8> {
515 let data = self.upload(index, sub).await?;
516 if data.len() != 1 {
517 return UnexpectedSizeSnafu.fail();
518 }
519 Ok(data[0])
520 }
521 pub async fn read_u8(&mut self, index: u16, sub: u8) -> Result<u8> {
525 self.upload_u8(index, sub).await
526 }
527
528 pub async fn upload_u16(&mut self, index: u16, sub: u8) -> Result<u16> {
530 let data = self.upload(index, sub).await?;
531 if data.len() != 2 {
532 return UnexpectedSizeSnafu.fail();
533 }
534 Ok(u16::from_le_bytes(data.try_into().unwrap()))
535 }
536
537 pub async fn read_u16(&mut self, index: u16, sub: u8) -> Result<u16> {
541 self.upload_u16(index, sub).await
542 }
543
544 pub async fn upload_u32(&mut self, index: u16, sub: u8) -> Result<u32> {
546 let data = self.upload(index, sub).await?;
547 if data.len() != 4 {
548 return UnexpectedSizeSnafu.fail();
549 }
550 Ok(u32::from_le_bytes(data.try_into().unwrap()))
551 }
552
553 pub async fn read_u32(&mut self, index: u16, sub: u8) -> Result<u32> {
557 self.upload_u32(index, sub).await
558 }
559
560 pub async fn upload_i8(&mut self, index: u16, sub: u8) -> Result<i8> {
562 let data = self.upload(index, sub).await?;
563 if data.len() != 1 {
564 return UnexpectedSizeSnafu.fail();
565 }
566 Ok(i8::from_le_bytes(data.try_into().unwrap()))
567 }
568
569 pub async fn read_i8(&mut self, index: u16, sub: u8) -> Result<i8> {
573 self.upload_i8(index, sub).await
574 }
575
576 pub async fn upload_i16(&mut self, index: u16, sub: u8) -> Result<i16> {
578 let data = self.upload(index, sub).await?;
579 if data.len() != 2 {
580 return UnexpectedSizeSnafu.fail();
581 }
582 Ok(i16::from_le_bytes(data.try_into().unwrap()))
583 }
584
585 pub async fn read_i16(&mut self, index: u16, sub: u8) -> Result<i16> {
589 self.upload_i16(index, sub).await
590 }
591
592 pub async fn upload_i32(&mut self, index: u16, sub: u8) -> Result<i32> {
594 let data = self.upload(index, sub).await?;
595 if data.len() != 4 {
596 return UnexpectedSizeSnafu.fail();
597 }
598 Ok(i32::from_le_bytes(data.try_into().unwrap()))
599 }
600
601 pub async fn read_i32(&mut self, index: u16, sub: u8) -> Result<i32> {
605 self.upload_i32(index, sub).await
606 }
607
608 pub async fn read_visible_string(&mut self, index: u16, sub: u8) -> Result<String> {
612 let bytes = self.upload(index, sub).await?;
613 Ok(String::from_utf8_lossy(&bytes).into())
614 }
615
616 pub async fn read_identity(&mut self) -> Result<LssIdentity> {
620 let vendor_id = self.upload_u32(object_ids::IDENTITY, 1).await?;
621 let product_code = self.upload_u32(object_ids::IDENTITY, 2).await?;
622 let revision_number = self.upload_u32(object_ids::IDENTITY, 3).await?;
623 let serial = self.upload_u32(object_ids::IDENTITY, 4).await?;
624 Ok(LssIdentity::new(
625 vendor_id,
626 product_code,
627 revision_number,
628 serial,
629 ))
630 }
631
632 pub async fn save_objects(&mut self) -> Result<()> {
634 self.download_u32(object_ids::SAVE_OBJECTS, 1, SAVE_CMD)
635 .await
636 }
637
638 pub async fn read_device_name(&mut self) -> Result<String> {
642 self.read_visible_string(object_ids::DEVICE_NAME, 0).await
643 }
644
645 pub async fn read_software_version(&mut self) -> Result<String> {
649 self.read_visible_string(object_ids::SOFTWARE_VERSION, 0)
650 .await
651 }
652
653 pub async fn read_hardware_version(&mut self) -> Result<String> {
657 self.read_visible_string(object_ids::HARDWARE_VERSION, 0)
658 .await
659 }
660
661 pub async fn configure_tpdo(&mut self, pdo_num: usize, cfg: &PdoConfig) -> Result<()> {
666 let comm_index = 0x1800 + pdo_num as u16;
667 let mapping_index = 0x1a00 + pdo_num as u16;
668 self.store_pdo(comm_index, mapping_index, cfg).await
669 }
670
671 pub async fn configure_rpdo(&mut self, pdo_num: usize, cfg: &PdoConfig) -> Result<()> {
676 let comm_index = 0x1400 + pdo_num as u16;
677 let mapping_index = 0x1600 + pdo_num as u16;
678 self.store_pdo(comm_index, mapping_index, cfg).await
679 }
680
681 async fn store_pdo(
682 &mut self,
683 comm_index: u16,
684 mapping_index: u16,
685 cfg: &PdoConfig,
686 ) -> Result<()> {
687 assert!(cfg.mappings.len() < 0x40);
688 for (i, m) in cfg.mappings.iter().enumerate() {
689 let mapping_value = m.to_object_value();
690 self.write_u32(mapping_index, (i + 1) as u8, mapping_value)
691 .await?;
692 }
693
694 let num_mappings = cfg.mappings.len() as u8;
695 self.write_u8(mapping_index, 0, num_mappings).await?;
696
697 let mut cob_value = cfg.cob.raw() & 0xFFFFFFF;
698 if !cfg.enabled {
699 cob_value |= 1 << 31;
700 }
701 if cfg.cob.is_extended() {
702 cob_value |= 1 << 29;
703 }
704 self.write_u8(comm_index, 2, cfg.transmission_type).await?;
705 self.write_u32(comm_index, 1, cob_value).await?;
706
707 Ok(())
708 }
709
710 async fn wait_for_response(&mut self, timeout: Duration) -> Result<SdoResponse> {
711 let wait_until = tokio::time::Instant::now() + timeout;
712 loop {
713 match tokio::time::timeout_at(wait_until, self.receiver.recv()).await {
714 Err(_) => return NoResponseSnafu.fail(),
716 Ok(Ok(msg)) => {
718 if msg.id == self.resp_cob_id {
719 return msg.try_into().map_err(|_| MalformedResponseSnafu.build());
720 }
721 }
722 Ok(Err(e)) => {
724 log::error!("Error reading from socket: {e:?}");
725 return NoResponseSnafu.fail();
726 }
727 }
728 }
729 }
730}