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 node_configuration::PdoConfig,
9 pdo::PdoMapping,
10 sdo::{AbortCode, BlockSegment, SdoRequest, SdoResponse},
11 traits::{AsyncCanReceiver, AsyncCanSender, CanSendError as _},
12 CanMessage,
13};
14
15const DEFAULT_RESPONSE_TIMEOUT: Duration = Duration::from_millis(150);
16
17#[derive(Debug, Clone, Copy, PartialEq)]
22pub enum RawAbortCode {
23 Valid(AbortCode),
25 Unknown(u32),
27}
28
29impl std::fmt::Display for RawAbortCode {
30 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31 match self {
32 RawAbortCode::Valid(abort_code) => write!(f, "{abort_code:?}"),
33 RawAbortCode::Unknown(code) => write!(f, "{code:X}"),
34 }
35 }
36}
37
38impl From<u32> for RawAbortCode {
39 fn from(value: u32) -> Self {
40 match AbortCode::try_from(value) {
41 Ok(code) => Self::Valid(code),
42 Err(_) => Self::Unknown(value),
43 }
44 }
45}
46
47#[derive(Clone, Debug, PartialEq, Snafu)]
49pub enum SdoClientError {
50 NoResponse,
52 MalformedResponse,
54 #[snafu(display("Unexpected SDO response. Expected {expecting}, got {response:?}"))]
56 UnexpectedResponse {
57 expecting: String,
59 response: SdoResponse,
61 },
62 #[snafu(display("Received abort accessing object 0x{index:X}sub{sub}: {abort_code}"))]
64 ServerAbort {
65 index: u16,
67 sub: u8,
69 abort_code: RawAbortCode,
71 },
72 ToggleNotAlternated,
74 #[snafu(display("Received object 0x{:x}sub{} after requesting 0x{:x}sub{}",
76 received.0, received.1, expected.0, expected.1))]
77 MismatchedObjectIndex {
78 expected: (u16, u8),
80 received: (u16, u8),
82 },
83 UnexpectedSize,
85 #[snafu(display("Failed to send CAN message: {message}"))]
87 SocketSendFailed {
88 message: String,
90 },
91 BlockSizeChangedTooSmall,
97 CrcMismatch,
99}
100
101type Result<T> = std::result::Result<T, SdoClientError>;
102
103macro_rules! match_response {
106 ($resp: ident, $expecting: literal, $($match:pat => $code : expr),*) => {
107 match $resp {
108 $($match => $code),*
109 SdoResponse::Abort {
110 index,
111 sub,
112 abort_code,
113 } => {
114 return ServerAbortSnafu {
115 index,
116 sub,
117 abort_code,
118 }
119 .fail()
120 }
121 _ => {
122 return UnexpectedResponseSnafu {
123 expecting: $expecting,
124 response: $resp,
125 }
126 .fail()
127 }
128 }
129 };
130}
131
132#[derive(Debug)]
133pub struct SdoClient<S, R> {
137 req_cob_id: CanId,
138 resp_cob_id: CanId,
139 timeout: Duration,
140 sender: S,
141 receiver: R,
142}
143
144impl<S: AsyncCanSender, R: AsyncCanReceiver> SdoClient<S, R> {
145 pub fn new_std(server_node_id: u8, sender: S, receiver: R) -> Self {
153 let req_cob_id = CanId::Std(0x600 + server_node_id as u16);
154 let resp_cob_id = CanId::Std(0x580 + server_node_id as u16);
155 Self::new(req_cob_id, resp_cob_id, sender, receiver)
156 }
157
158 pub fn new(req_cob_id: CanId, resp_cob_id: CanId, sender: S, receiver: R) -> Self {
160 Self {
161 req_cob_id,
162 resp_cob_id,
163 timeout: DEFAULT_RESPONSE_TIMEOUT,
164 sender,
165 receiver,
166 }
167 }
168
169 pub fn set_timeout(&mut self, timeout: Duration) {
171 self.timeout = timeout;
172 }
173
174 pub fn get_timeout(&self) -> Duration {
176 self.timeout
177 }
178
179 async fn send(&mut self, data: [u8; 8]) -> Result<()> {
180 let frame = CanMessage::new(self.req_cob_id, &data);
181 let mut tries = 3;
182 loop {
183 match self.sender.send(frame).await {
184 Ok(()) => return Ok(()),
185 Err(e) => {
186 tries -= 1;
187 tokio::time::sleep(Duration::from_millis(5)).await;
188 if tries == 0 {
189 return SocketSendFailedSnafu {
190 message: e.message(),
191 }
192 .fail();
193 }
194 }
195 }
196 }
197 }
198
199 pub async fn download(&mut self, index: u16, sub: u8, data: &[u8]) -> Result<()> {
201 if data.len() <= 4 {
202 self.send(SdoRequest::expedited_download(index, sub, data).to_bytes())
204 .await?;
205
206 let resp = self.wait_for_response().await?;
207 match_response!(
208 resp,
209 "ConfirmDownload",
210 SdoResponse::ConfirmDownload { index: _, sub: _ } => {
211 Ok(()) }
213 )
214 } else {
215 self.send(
216 SdoRequest::initiate_download(index, sub, Some(data.len() as u32)).to_bytes(),
217 )
218 .await?;
219
220 let resp = self.wait_for_response().await?;
221 match_response!(
222 resp,
223 "ConfirmDownload",
224 SdoResponse::ConfirmDownload { index: _, sub: _ } => { }
225 );
226
227 let mut toggle = false;
228 let total_segments = data.len().div_ceil(7);
230 for n in 0..total_segments {
231 let last_segment = n == total_segments - 1;
232 let segment_size = (data.len() - n * 7).min(7);
233 let seg_msg = SdoRequest::download_segment(
234 toggle,
235 last_segment,
236 &data[n * 7..n * 7 + segment_size],
237 );
238 self.send(seg_msg.to_bytes()).await?;
239 let resp = self.wait_for_response().await?;
240 match_response!(
241 resp,
242 "ConfirmDownloadSegment",
243 SdoResponse::ConfirmDownloadSegment { t } => {
244 if t != toggle {
246 let abort_msg =
247 SdoRequest::abort(index, sub, AbortCode::ToggleNotAlternated);
248
249 self.send(abort_msg.to_bytes())
250 .await?;
251 return ToggleNotAlternatedSnafu.fail();
252 }
253 }
255 );
256 toggle = !toggle;
257 }
258 Ok(())
259 }
260 }
261
262 pub async fn upload(&mut self, index: u16, sub: u8) -> Result<Vec<u8>> {
264 let mut read_buf = Vec::new();
265
266 self.send(SdoRequest::initiate_upload(index, sub).to_bytes())
267 .await?;
268
269 let resp = self.wait_for_response().await?;
270
271 let expedited = match_response!(
272 resp,
273 "ConfirmUpload",
274 SdoResponse::ConfirmUpload {
275 n,
276 e,
277 s,
278 index: _,
279 sub: _,
280 data,
281 } => {
282 if e {
283 let mut len = 0;
284 if s {
285 len = 4 - n as usize;
286 }
287 read_buf.extend_from_slice(&data[0..len]);
288 }
289 e
290 }
291 );
292
293 if !expedited {
294 let mut toggle = false;
296 loop {
297 self.send(SdoRequest::upload_segment_request(toggle).to_bytes())
298 .await?;
299
300 let resp = self.wait_for_response().await?;
301 match_response!(
302 resp,
303 "UploadSegment",
304 SdoResponse::UploadSegment { t, n, c, data } => {
305 if t != toggle {
306 self.send(
307 SdoRequest::abort(index, sub, AbortCode::ToggleNotAlternated)
308 .to_bytes(),
309 )
310 .await?;
311 return ToggleNotAlternatedSnafu.fail();
312 }
313 read_buf.extend_from_slice(&data[0..7 - n as usize]);
314 if c {
315 break;
317 }
318 }
319 );
320 toggle = !toggle;
321 }
322 }
323 Ok(read_buf)
324 }
325
326 pub async fn block_download(&mut self, index: u16, sub: u8, data: &[u8]) -> Result<()> {
331 self.send(
332 SdoRequest::InitiateBlockDownload {
333 cc: true, s: true, index,
336 sub,
337 size: data.len() as u32,
338 }
339 .to_bytes(),
340 )
341 .await?;
342
343 let resp = self.wait_for_response().await?;
344
345 let (crc_enabled, mut blksize) = match_response!(
346 resp,
347 "ConfirmBlockDownload",
348 SdoResponse::ConfirmBlockDownload {
349 sc,
350 index: resp_index,
351 sub: resp_sub,
352 blksize,
353 } => {
354 if index != resp_index || sub != resp_sub {
355 return MismatchedObjectIndexSnafu {
356 expected: (index, sub),
357 received: (resp_index, resp_sub),
358 }
359 .fail();
360 }
361 (sc, blksize)
362 }
363 );
364
365 let mut seqnum = 1;
366 let mut last_block_start = 0;
367 let mut segment_num = 0;
368 let total_segments = data.len().div_ceil(7);
369
370 while segment_num < total_segments {
371 let segment_start = segment_num * 7;
372 let segment_len = (data.len() - segment_start).min(7);
373 let c = segment_start + segment_len == data.len();
375 let mut segment_data = [0; 7];
376 segment_data[0..segment_len]
377 .copy_from_slice(&data[segment_start..segment_start + segment_len]);
378
379 let segment = BlockSegment {
381 c,
382 seqnum,
383 data: segment_data,
384 };
385 self.send(segment.to_bytes()).await?;
386
387 if c || seqnum == blksize {
390 let resp = self.wait_for_response().await?;
391 match_response!(
392 resp,
393 "ConfirmBlock",
394 SdoResponse::ConfirmBlock {
395 ackseq,
396 blksize: new_blksize,
397 } => {
398 if ackseq == blksize {
399 seqnum = 1;
401 segment_num += 1;
402 last_block_start = segment_num;
403 } else {
404 seqnum = ackseq;
406 segment_num = last_block_start + ackseq as usize;
407 if new_blksize < seqnum {
414 return BlockSizeChangedTooSmallSnafu.fail();
415 }
416 }
417 blksize = new_blksize;
418 }
419 );
420 } else {
421 seqnum += 1;
422 segment_num += 1;
423 }
424 }
425
426 let crc = if crc_enabled {
428 crc16::State::<crc16::XMODEM>::calculate(data)
429 } else {
430 0
431 };
432
433 let n = ((7 - data.len() % 7) % 7) as u8;
434
435 self.send(SdoRequest::EndBlockDownload { n, crc }.to_bytes())
436 .await?;
437
438 let resp = self.wait_for_response().await?;
439 match_response!(
440 resp,
441 "ConfirmBlockDownloadEnd",
442 SdoResponse::ConfirmBlockDownloadEnd => { Ok(()) }
443 )
444 }
445
446 pub async fn block_upload(&mut self, index: u16, sub: u8) -> Result<Vec<u8>> {
448 const CRC_SUPPORTED: bool = true;
449 const BLKSIZE: u8 = 127;
450 const PST: u8 = 0;
451 self.send(
452 SdoRequest::initiate_block_upload(index, sub, CRC_SUPPORTED, BLKSIZE, PST).to_bytes(),
453 )
454 .await?;
455
456 let resp = self.wait_for_response().await?;
457
458 let server_supports_crc = match_response!(
459 resp,
460 "ConfirmBlockUpload",
461 SdoResponse::ConfirmBlockUpload { sc, s: _, index: _, sub: _, size: _ } => {sc}
462 );
463
464 self.send(SdoRequest::StartBlockUpload.to_bytes()).await?;
465
466 let mut rx_data = Vec::new();
467 let last_segment;
468 loop {
469 let segment = self.wait_for_block_segment().await?;
470 rx_data.extend_from_slice(&segment.data);
471 if !segment.c && segment.seqnum == BLKSIZE {
472 self.send(
474 SdoRequest::ConfirmBlock {
475 ackseq: BLKSIZE,
476 blksize: BLKSIZE,
477 }
478 .to_bytes(),
479 )
480 .await?;
481 }
482 if segment.c {
483 last_segment = segment.seqnum;
484 break;
485 }
486 }
487
488 self.send(
491 SdoRequest::ConfirmBlock {
492 ackseq: last_segment,
493 blksize: BLKSIZE,
494 }
495 .to_bytes(),
496 )
497 .await?;
498
499 let resp = self.wait_for_response().await?;
500 let (n, crc) = match_response!(
501 resp,
502 "BlockUploadEnd",
503 SdoResponse::BlockUploadEnd { n, crc } => {(n, crc)}
504 );
505
506 rx_data.resize(rx_data.len() - n as usize, 0);
508
509 if server_supports_crc {
510 let computed_crc = crc16::State::<crc16::XMODEM>::calculate(&rx_data);
511 if crc != computed_crc {
512 self.send(SdoRequest::abort(index, sub, AbortCode::CrcError).to_bytes())
513 .await?;
514 return Err(SdoClientError::CrcMismatch);
515 }
516 }
517
518 self.send(SdoRequest::EndBlockUpload.to_bytes()).await?;
519
520 Ok(rx_data)
521 }
522
523 pub async fn download_u32(&mut self, index: u16, sub: u8, data: u32) -> Result<()> {
525 let data = data.to_le_bytes();
526 self.download(index, sub, &data).await
527 }
528
529 pub async fn write_u32(&mut self, index: u16, sub: u8, data: u32) -> Result<()> {
533 self.download_u32(index, sub, data).await
534 }
535
536 pub async fn download_u16(&mut self, index: u16, sub: u8, data: u16) -> Result<()> {
538 let data = data.to_le_bytes();
539 self.download(index, sub, &data).await
540 }
541
542 pub async fn write_u16(&mut self, index: u16, sub: u8, data: u16) -> Result<()> {
546 self.download_u16(index, sub, data).await
547 }
548
549 pub async fn download_u8(&mut self, index: u16, sub: u8, data: u8) -> Result<()> {
551 let data = data.to_le_bytes();
552 self.download(index, sub, &data).await
553 }
554
555 pub async fn write_u8(&mut self, index: u16, sub: u8, data: u8) -> Result<()> {
559 self.download_u8(index, sub, data).await
560 }
561
562 pub async fn download_i32(&mut self, index: u16, sub: u8, data: i32) -> Result<()> {
564 let data = data.to_le_bytes();
565 self.download(index, sub, &data).await
566 }
567
568 pub async fn write_i32(&mut self, index: u16, sub: u8, data: i32) -> Result<()> {
572 self.download_i32(index, sub, data).await
573 }
574
575 pub async fn download_i16(&mut self, index: u16, sub: u8, data: i16) -> Result<()> {
577 let data = data.to_le_bytes();
578 self.download(index, sub, &data).await
579 }
580
581 pub async fn write_i16(&mut self, index: u16, sub: u8, data: i16) -> Result<()> {
585 self.download_i16(index, sub, data).await
586 }
587
588 pub async fn download_i8(&mut self, index: u16, sub: u8, data: i8) -> Result<()> {
590 let data = data.to_le_bytes();
591 self.download(index, sub, &data).await
592 }
593
594 pub async fn write_i8(&mut self, index: u16, sub: u8, data: i8) -> Result<()> {
598 self.download_i8(index, sub, data).await
599 }
600
601 pub async fn upload_utf8(&mut self, index: u16, sub: u8) -> Result<String> {
603 let data = self.upload(index, sub).await?;
604 Ok(String::from_utf8_lossy(&data).into())
605 }
606 pub async fn read_utf8(&mut self, index: u16, sub: u8) -> Result<String> {
608 self.upload_utf8(index, sub).await
609 }
610
611 pub async fn upload_u8(&mut self, index: u16, sub: u8) -> Result<u8> {
613 let data = self.upload(index, sub).await?;
614 if data.len() != 1 {
615 return UnexpectedSizeSnafu.fail();
616 }
617 Ok(data[0])
618 }
619 pub async fn read_u8(&mut self, index: u16, sub: u8) -> Result<u8> {
623 self.upload_u8(index, sub).await
624 }
625
626 pub async fn upload_u16(&mut self, index: u16, sub: u8) -> Result<u16> {
628 let data = self.upload(index, sub).await?;
629 if data.len() != 2 {
630 return UnexpectedSizeSnafu.fail();
631 }
632 Ok(u16::from_le_bytes(data.try_into().unwrap()))
633 }
634
635 pub async fn read_u16(&mut self, index: u16, sub: u8) -> Result<u16> {
639 self.upload_u16(index, sub).await
640 }
641
642 pub async fn upload_u32(&mut self, index: u16, sub: u8) -> Result<u32> {
644 let data = self.upload(index, sub).await?;
645 if data.len() != 4 {
646 return UnexpectedSizeSnafu.fail();
647 }
648 Ok(u32::from_le_bytes(data.try_into().unwrap()))
649 }
650
651 pub async fn read_u32(&mut self, index: u16, sub: u8) -> Result<u32> {
655 self.upload_u32(index, sub).await
656 }
657
658 pub async fn upload_i8(&mut self, index: u16, sub: u8) -> Result<i8> {
660 let data = self.upload(index, sub).await?;
661 if data.len() != 1 {
662 return UnexpectedSizeSnafu.fail();
663 }
664 Ok(i8::from_le_bytes(data.try_into().unwrap()))
665 }
666
667 pub async fn read_i8(&mut self, index: u16, sub: u8) -> Result<i8> {
671 self.upload_i8(index, sub).await
672 }
673
674 pub async fn upload_i16(&mut self, index: u16, sub: u8) -> Result<i16> {
676 let data = self.upload(index, sub).await?;
677 if data.len() != 2 {
678 return UnexpectedSizeSnafu.fail();
679 }
680 Ok(i16::from_le_bytes(data.try_into().unwrap()))
681 }
682
683 pub async fn read_i16(&mut self, index: u16, sub: u8) -> Result<i16> {
687 self.upload_i16(index, sub).await
688 }
689
690 pub async fn upload_i32(&mut self, index: u16, sub: u8) -> Result<i32> {
692 let data = self.upload(index, sub).await?;
693 if data.len() != 4 {
694 return UnexpectedSizeSnafu.fail();
695 }
696 Ok(i32::from_le_bytes(data.try_into().unwrap()))
697 }
698
699 pub async fn read_i32(&mut self, index: u16, sub: u8) -> Result<i32> {
703 self.upload_i32(index, sub).await
704 }
705
706 pub async fn read_visible_string(&mut self, index: u16, sub: u8) -> Result<String> {
710 let bytes = self.upload(index, sub).await?;
711 Ok(String::from_utf8_lossy(&bytes).into())
712 }
713
714 pub async fn read_identity(&mut self) -> Result<LssIdentity> {
718 let vendor_id = self.upload_u32(object_ids::IDENTITY, 1).await?;
719 let product_code = self.upload_u32(object_ids::IDENTITY, 2).await?;
720 let revision_number = self.upload_u32(object_ids::IDENTITY, 3).await?;
721 let serial = self.upload_u32(object_ids::IDENTITY, 4).await?;
722 Ok(LssIdentity::new(
723 vendor_id,
724 product_code,
725 revision_number,
726 serial,
727 ))
728 }
729
730 pub async fn save_objects(&mut self) -> Result<()> {
732 self.download_u32(object_ids::SAVE_OBJECTS, 1, SAVE_CMD)
733 .await
734 }
735
736 pub async fn read_device_name(&mut self) -> Result<String> {
740 self.read_visible_string(object_ids::DEVICE_NAME, 0).await
741 }
742
743 pub async fn read_software_version(&mut self) -> Result<String> {
747 self.read_visible_string(object_ids::SOFTWARE_VERSION, 0)
748 .await
749 }
750
751 pub async fn read_hardware_version(&mut self) -> Result<String> {
755 self.read_visible_string(object_ids::HARDWARE_VERSION, 0)
756 .await
757 }
758
759 pub async fn configure_tpdo(&mut self, pdo_num: usize, cfg: &PdoConfig) -> Result<()> {
764 let comm_index = 0x1800 + pdo_num as u16;
765 let mapping_index = 0x1a00 + pdo_num as u16;
766 self.store_pdo(comm_index, mapping_index, cfg).await
767 }
768
769 pub async fn configure_rpdo(&mut self, pdo_num: usize, cfg: &PdoConfig) -> Result<()> {
774 let comm_index = 0x1400 + pdo_num as u16;
775 let mapping_index = 0x1600 + pdo_num as u16;
776 self.store_pdo(comm_index, mapping_index, cfg).await
777 }
778
779 async fn store_pdo(
780 &mut self,
781 comm_index: u16,
782 mapping_index: u16,
783 cfg: &PdoConfig,
784 ) -> Result<()> {
785 assert!(cfg.mappings.len() < 0x40);
786 for (i, m) in cfg.mappings.iter().enumerate() {
787 let mapping_value = m.to_object_value();
788 self.write_u32(mapping_index, (i + 1) as u8, mapping_value)
789 .await?;
790 }
791
792 let num_mappings = cfg.mappings.len() as u8;
793 self.write_u8(mapping_index, 0, num_mappings).await?;
794
795 let mut cob_value = cfg.cob_id.raw() & 0x1FFFFFFF;
796 if !cfg.enabled {
797 cob_value |= 1 << 31;
798 }
799 if cfg.cob_id.is_extended() {
800 cob_value |= 1 << 29;
801 }
802 self.write_u8(comm_index, 2, cfg.transmission_type).await?;
803 self.write_u32(comm_index, 1, cob_value).await?;
804
805 Ok(())
806 }
807
808 pub async fn read_rpdo_config(&mut self, pdo_num: usize) -> Result<PdoConfig> {
810 let comm_index = 0x1400 + pdo_num as u16;
811 let mapping_index = 0x1600 + pdo_num as u16;
812 self.read_pdo_config(comm_index, mapping_index).await
813 }
814
815 pub async fn read_tpdo_config(&mut self, pdo_num: usize) -> Result<PdoConfig> {
817 let comm_index = 0x1800 + pdo_num as u16;
818 let mapping_index = 0x1a00 + pdo_num as u16;
819 self.read_pdo_config(comm_index, mapping_index).await
820 }
821
822 async fn read_pdo_config(&mut self, comm_index: u16, mapping_index: u16) -> Result<PdoConfig> {
823 let cob_word = self.read_u32(comm_index, 1).await?;
824 let transmission_type = self.read_u8(comm_index, 2).await?;
825 let num_mappings = self.read_u8(mapping_index, 0).await?;
826 let mut mappings = Vec::with_capacity(num_mappings as usize);
827 for i in 0..num_mappings {
828 let mapping_raw = self.read_u32(mapping_index, i + 1).await?;
829 mappings.push(PdoMapping::from_object_value(mapping_raw));
830 }
831 let enabled = cob_word & (1 << 31) == 0;
832 let rtr_disabled = cob_word & (1 << 30) != 0;
833 let extended = cob_word & (1 << 29) != 0;
834 let cob_id = cob_word & 0x1FFFFFFF;
835 let cob_id = if extended {
836 CanId::extended(cob_id)
837 } else {
838 CanId::std(cob_id as u16)
839 };
840 Ok(PdoConfig {
841 cob_id,
842 enabled,
843 rtr_disabled,
844 mappings,
845 transmission_type,
846 })
847 }
848
849 async fn wait_for_block_segment(&mut self) -> Result<BlockSegment> {
850 let wait_until = tokio::time::Instant::now() + self.timeout;
851 loop {
852 match tokio::time::timeout_at(wait_until, self.receiver.recv()).await {
853 Err(_) => return NoResponseSnafu.fail(),
855 Ok(Ok(msg)) => {
857 if msg.id == self.resp_cob_id {
858 return msg
859 .data()
860 .try_into()
861 .map_err(|_| MalformedResponseSnafu.build());
862 }
863 }
864 Ok(Err(e)) => {
866 log::error!("Error reading from socket: {e:?}");
867 return NoResponseSnafu.fail();
868 }
869 }
870 }
871 }
872
873 async fn wait_for_response(&mut self) -> Result<SdoResponse> {
874 let wait_until = tokio::time::Instant::now() + self.timeout;
875 loop {
876 match tokio::time::timeout_at(wait_until, self.receiver.recv()).await {
877 Err(_) => return NoResponseSnafu.fail(),
879 Ok(Ok(msg)) => {
881 if msg.id == self.resp_cob_id {
882 return msg.try_into().map_err(|_| MalformedResponseSnafu.build());
883 }
884 }
885 Ok(Err(e)) => {
887 log::error!("Error reading from socket: {e:?}");
888 return NoResponseSnafu.fail();
889 }
890 }
891 }
892 }
893}