1use std::time::Duration;
2
3use snafu::Snafu;
4use zencan_common::{
5 constants::{object_ids, values::SAVE_CMD},
6 i24,
7 lss::LssIdentity,
8 messages::CanId,
9 node_configuration::PdoConfig,
10 pdo::{PdoCommParameter, PdoMapping},
11 sdo::{AbortCode, BlockSegment, SdoRequest, SdoResponse},
12 traits::{AsyncCanReceiver, AsyncCanSender, CanSendError as _, ReadSize},
13 u24, CanMessage, TimeDifference, TimeOfDay,
14};
15
16const DEFAULT_RESPONSE_TIMEOUT: Duration = Duration::from_millis(150);
17
18#[derive(Debug, Clone, Copy, PartialEq)]
23pub enum RawAbortCode {
24 Valid(AbortCode),
26 Unknown(u32),
28}
29
30impl std::fmt::Display for RawAbortCode {
31 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32 match self {
33 RawAbortCode::Valid(abort_code) => write!(f, "{abort_code:?}"),
34 RawAbortCode::Unknown(code) => write!(f, "{code:X}"),
35 }
36 }
37}
38
39impl From<u32> for RawAbortCode {
40 fn from(value: u32) -> Self {
41 match AbortCode::try_from(value) {
42 Ok(code) => Self::Valid(code),
43 Err(_) => Self::Unknown(value),
44 }
45 }
46}
47
48#[derive(Clone, Debug, PartialEq, Snafu)]
50pub enum SdoClientError {
51 NoResponse,
53 MalformedResponse,
55 #[snafu(display("Unexpected SDO response. Expected {expecting}, got {response:?}"))]
57 UnexpectedResponse {
58 expecting: String,
60 response: SdoResponse,
62 },
63 #[snafu(display("Received abort accessing object 0x{index:X}sub{sub}: {abort_code}"))]
65 ServerAbort {
66 index: u16,
68 sub: u8,
70 abort_code: RawAbortCode,
72 },
73 ToggleNotAlternated,
75 #[snafu(display("Received object 0x{:x}sub{} after requesting 0x{:x}sub{}",
77 received.0, received.1, expected.0, expected.1))]
78 MismatchedObjectIndex {
79 expected: (u16, u8),
81 received: (u16, u8),
83 },
84 UnexpectedSize,
86 #[snafu(display("Failed to send CAN message: {message}"))]
88 SocketSendFailed {
89 message: String,
91 },
92 BlockSizeChangedTooSmall,
98 CrcMismatch,
100}
101
102type Result<T> = std::result::Result<T, SdoClientError>;
103
104macro_rules! match_response {
107 ($resp: ident, $expecting: literal, $($match:pat => $code : expr),*) => {
108 match $resp {
109 $($match => $code),*
110 SdoResponse::Abort {
111 index,
112 sub,
113 abort_code,
114 } => {
115 return ServerAbortSnafu {
116 index,
117 sub,
118 abort_code,
119 }
120 .fail()
121 }
122 _ => {
123 return UnexpectedResponseSnafu {
124 expecting: $expecting,
125 response: $resp,
126 }
127 .fail()
128 }
129 }
130 };
131}
132
133use paste::paste;
134macro_rules! access_methods {
135 ($type: ty) => {
136
137 paste! {
138 #[doc = concat!("Read a ", stringify!($type), " sub object from the SDO server")]
139 pub async fn [<read_ $type>](&mut self, index: u16, sub: u8) -> Result<$type> {
140 let data = self.upload(index, sub).await?;
141 if data.len() != <$type as ReadSize>::READ_SIZE {
142 return UnexpectedSizeSnafu.fail();
143 }
144 Ok($type::from_le_bytes(data.try_into().unwrap()))
145 }
146
147 #[doc = concat!("Write a ", stringify!($type), " sub object from the SDO server")]
148 pub async fn [<write_ $type>](&mut self, index: u16, sub: u8, value: $type) -> Result<()> {
149 let data = value.to_le_bytes();
150 self.download(index, sub, &data).await
151 }
152 }
153 };
154}
155
156#[derive(Debug)]
157pub struct SdoClient<S, R> {
161 req_cob_id: CanId,
162 resp_cob_id: CanId,
163 timeout: Duration,
164 sender: S,
165 receiver: R,
166}
167
168impl<S: AsyncCanSender, R: AsyncCanReceiver> SdoClient<S, R> {
169 pub fn new_std(server_node_id: u8, sender: S, receiver: R) -> Self {
177 let req_cob_id = CanId::Std(0x600 + server_node_id as u16);
178 let resp_cob_id = CanId::Std(0x580 + server_node_id as u16);
179 Self::new(req_cob_id, resp_cob_id, sender, receiver)
180 }
181
182 pub fn new(req_cob_id: CanId, resp_cob_id: CanId, sender: S, receiver: R) -> Self {
184 Self {
185 req_cob_id,
186 resp_cob_id,
187 timeout: DEFAULT_RESPONSE_TIMEOUT,
188 sender,
189 receiver,
190 }
191 }
192
193 pub fn set_timeout(&mut self, timeout: Duration) {
195 self.timeout = timeout;
196 }
197
198 pub fn get_timeout(&self) -> Duration {
200 self.timeout
201 }
202
203 async fn send(&mut self, data: [u8; 8]) -> Result<()> {
204 let frame = CanMessage::new(self.req_cob_id, &data);
205 let mut tries = 3;
206 loop {
207 match self.sender.send(frame).await {
208 Ok(()) => return Ok(()),
209 Err(e) => {
210 tries -= 1;
211 tokio::time::sleep(Duration::from_millis(5)).await;
212 if tries == 0 {
213 return SocketSendFailedSnafu {
214 message: e.message(),
215 }
216 .fail();
217 }
218 }
219 }
220 }
221 }
222
223 pub async fn download(&mut self, index: u16, sub: u8, data: &[u8]) -> Result<()> {
225 if data.len() <= 4 {
226 self.send(SdoRequest::expedited_download(index, sub, data).to_bytes())
228 .await?;
229
230 let resp = self.wait_for_response().await?;
231 match_response!(
232 resp,
233 "ConfirmDownload",
234 SdoResponse::ConfirmDownload { index: _, sub: _ } => {
235 Ok(()) }
237 )
238 } else {
239 self.send(
240 SdoRequest::initiate_download(index, sub, Some(data.len() as u32)).to_bytes(),
241 )
242 .await?;
243
244 let resp = self.wait_for_response().await?;
245 match_response!(
246 resp,
247 "ConfirmDownload",
248 SdoResponse::ConfirmDownload { index: _, sub: _ } => { }
249 );
250
251 let mut toggle = false;
252 let total_segments = data.len().div_ceil(7);
254 for n in 0..total_segments {
255 let last_segment = n == total_segments - 1;
256 let segment_size = (data.len() - n * 7).min(7);
257 let seg_msg = SdoRequest::download_segment(
258 toggle,
259 last_segment,
260 &data[n * 7..n * 7 + segment_size],
261 );
262 self.send(seg_msg.to_bytes()).await?;
263 let resp = self.wait_for_response().await?;
264 match_response!(
265 resp,
266 "ConfirmDownloadSegment",
267 SdoResponse::ConfirmDownloadSegment { t } => {
268 if t != toggle {
270 let abort_msg =
271 SdoRequest::abort(index, sub, AbortCode::ToggleNotAlternated);
272
273 self.send(abort_msg.to_bytes())
274 .await?;
275 return ToggleNotAlternatedSnafu.fail();
276 }
277 }
279 );
280 toggle = !toggle;
281 }
282 Ok(())
283 }
284 }
285
286 pub async fn upload(&mut self, index: u16, sub: u8) -> Result<Vec<u8>> {
288 let mut read_buf = Vec::new();
289
290 self.send(SdoRequest::initiate_upload(index, sub).to_bytes())
291 .await?;
292
293 let resp = self.wait_for_response().await?;
294
295 let expedited = match_response!(
296 resp,
297 "ConfirmUpload",
298 SdoResponse::ConfirmUpload {
299 n,
300 e,
301 s,
302 index: _,
303 sub: _,
304 data,
305 } => {
306 if e {
307 let mut len = 0;
308 if s {
309 len = 4 - n as usize;
310 }
311 read_buf.extend_from_slice(&data[0..len]);
312 }
313 e
314 }
315 );
316
317 if !expedited {
318 let mut toggle = false;
320 loop {
321 self.send(SdoRequest::upload_segment_request(toggle).to_bytes())
322 .await?;
323
324 let resp = self.wait_for_response().await?;
325 match_response!(
326 resp,
327 "UploadSegment",
328 SdoResponse::UploadSegment { t, n, c, data } => {
329 if t != toggle {
330 self.send(
331 SdoRequest::abort(index, sub, AbortCode::ToggleNotAlternated)
332 .to_bytes(),
333 )
334 .await?;
335 return ToggleNotAlternatedSnafu.fail();
336 }
337 read_buf.extend_from_slice(&data[0..7 - n as usize]);
338 if c {
339 break;
341 }
342 }
343 );
344 toggle = !toggle;
345 }
346 }
347 Ok(read_buf)
348 }
349
350 pub async fn block_download(&mut self, index: u16, sub: u8, data: &[u8]) -> Result<()> {
355 self.send(
356 SdoRequest::InitiateBlockDownload {
357 cc: true, s: true, index,
360 sub,
361 size: data.len() as u32,
362 }
363 .to_bytes(),
364 )
365 .await?;
366
367 let resp = self.wait_for_response().await?;
368
369 let (crc_enabled, mut blksize) = match_response!(
370 resp,
371 "ConfirmBlockDownload",
372 SdoResponse::ConfirmBlockDownload {
373 sc,
374 index: resp_index,
375 sub: resp_sub,
376 blksize,
377 } => {
378 if index != resp_index || sub != resp_sub {
379 return MismatchedObjectIndexSnafu {
380 expected: (index, sub),
381 received: (resp_index, resp_sub),
382 }
383 .fail();
384 }
385 (sc, blksize)
386 }
387 );
388
389 let mut seqnum = 1;
390 let mut last_block_start = 0;
391 let mut segment_num = 0;
392 let total_segments = data.len().div_ceil(7);
393
394 while segment_num < total_segments {
395 let segment_start = segment_num * 7;
396 let segment_len = (data.len() - segment_start).min(7);
397 let c = segment_start + segment_len == data.len();
399 let mut segment_data = [0; 7];
400 segment_data[0..segment_len]
401 .copy_from_slice(&data[segment_start..segment_start + segment_len]);
402
403 let segment = BlockSegment {
405 c,
406 seqnum,
407 data: segment_data,
408 };
409 self.send(segment.to_bytes()).await?;
410
411 if c || seqnum == blksize {
414 let resp = self.wait_for_response().await?;
415 match_response!(
416 resp,
417 "ConfirmBlock",
418 SdoResponse::ConfirmBlock {
419 ackseq,
420 blksize: new_blksize,
421 } => {
422 if ackseq == blksize {
423 seqnum = 1;
425 segment_num += 1;
426 last_block_start = segment_num;
427 } else {
428 seqnum = ackseq;
430 segment_num = last_block_start + ackseq as usize;
431 if new_blksize < seqnum {
438 return BlockSizeChangedTooSmallSnafu.fail();
439 }
440 }
441 blksize = new_blksize;
442 }
443 );
444 } else {
445 seqnum += 1;
446 segment_num += 1;
447 }
448 }
449
450 let crc = if crc_enabled {
452 crc16::State::<crc16::XMODEM>::calculate(data)
453 } else {
454 0
455 };
456
457 let n = ((7 - data.len() % 7) % 7) as u8;
458
459 self.send(SdoRequest::EndBlockDownload { n, crc }.to_bytes())
460 .await?;
461
462 let resp = self.wait_for_response().await?;
463 match_response!(
464 resp,
465 "ConfirmBlockDownloadEnd",
466 SdoResponse::ConfirmBlockDownloadEnd => { Ok(()) }
467 )
468 }
469
470 pub async fn block_upload(&mut self, index: u16, sub: u8) -> Result<Vec<u8>> {
472 const CRC_SUPPORTED: bool = true;
473 const BLKSIZE: u8 = 127;
474 const PST: u8 = 0;
475 self.send(
476 SdoRequest::initiate_block_upload(index, sub, CRC_SUPPORTED, BLKSIZE, PST).to_bytes(),
477 )
478 .await?;
479
480 let resp = self.wait_for_response().await?;
481
482 let server_supports_crc = match_response!(
483 resp,
484 "ConfirmBlockUpload",
485 SdoResponse::ConfirmBlockUpload { sc, s: _, index: _, sub: _, size: _ } => {sc}
486 );
487
488 self.send(SdoRequest::StartBlockUpload.to_bytes()).await?;
489
490 let mut rx_data = Vec::new();
491 let last_segment;
492 loop {
493 let segment = self.wait_for_block_segment().await?;
494 rx_data.extend_from_slice(&segment.data);
495 if !segment.c && segment.seqnum == BLKSIZE {
496 self.send(
498 SdoRequest::ConfirmBlock {
499 ackseq: BLKSIZE,
500 blksize: BLKSIZE,
501 }
502 .to_bytes(),
503 )
504 .await?;
505 }
506 if segment.c {
507 last_segment = segment.seqnum;
508 break;
509 }
510 }
511
512 self.send(
515 SdoRequest::ConfirmBlock {
516 ackseq: last_segment,
517 blksize: BLKSIZE,
518 }
519 .to_bytes(),
520 )
521 .await?;
522
523 let resp = self.wait_for_response().await?;
524 let (n, crc) = match_response!(
525 resp,
526 "BlockUploadEnd",
527 SdoResponse::BlockUploadEnd { n, crc } => {(n, crc)}
528 );
529
530 rx_data.resize(rx_data.len() - n as usize, 0);
532
533 if server_supports_crc {
534 let computed_crc = crc16::State::<crc16::XMODEM>::calculate(&rx_data);
535 if crc != computed_crc {
536 self.send(SdoRequest::abort(index, sub, AbortCode::CrcError).to_bytes())
537 .await?;
538 return Err(SdoClientError::CrcMismatch);
539 }
540 }
541
542 self.send(SdoRequest::EndBlockUpload.to_bytes()).await?;
543
544 Ok(rx_data)
545 }
546
547 access_methods!(f64);
548 access_methods!(f32);
549 access_methods!(u64);
550 access_methods!(u32);
551 access_methods!(u24);
552 access_methods!(u16);
553 access_methods!(u8);
554 access_methods!(i64);
555 access_methods!(i32);
556 access_methods!(i24);
557 access_methods!(i16);
558 access_methods!(i8);
559
560 pub async fn write_time_of_day(&mut self, index: u16, sub: u8, data: TimeOfDay) -> Result<()> {
562 let data = data.to_le_bytes();
563 self.download(index, sub, &data).await
564 }
565
566 pub async fn write_time_difference(
568 &mut self,
569 index: u16,
570 sub: u8,
571 data: TimeDifference,
572 ) -> Result<()> {
573 let data = data.to_le_bytes();
574 self.download(index, sub, &data).await
575 }
576
577 pub async fn read_utf8(&mut self, index: u16, sub: u8) -> Result<String> {
579 let data = self.upload(index, sub).await?;
580 Ok(String::from_utf8_lossy(&data).into())
581 }
582
583 pub async fn read_time_of_day(&mut self, index: u16, sub: u8) -> Result<TimeOfDay> {
585 let data = self.upload(index, sub).await?;
586 if data.len() != TimeOfDay::SIZE {
587 UnexpectedSizeSnafu.fail()
588 } else {
589 Ok(TimeOfDay::from_le_bytes(data.try_into().unwrap()))
590 }
591 }
592
593 pub async fn read_time_difference(&mut self, index: u16, sub: u8) -> Result<TimeDifference> {
595 let data = self.upload(index, sub).await?;
596 if data.len() != TimeDifference::SIZE {
597 UnexpectedSizeSnafu.fail()
598 } else {
599 Ok(TimeDifference::from_le_bytes(data.try_into().unwrap()))
600 }
601 }
602
603 pub async fn read_visible_string(&mut self, index: u16, sub: u8) -> Result<String> {
607 let bytes = self.upload(index, sub).await?;
608 Ok(String::from_utf8_lossy(&bytes).into())
609 }
610
611 pub async fn read_bool(&mut self, index: u16, sub: u8) -> Result<bool> {
613 let bytes = self.upload(index, sub).await?;
614 if bytes.len() != 1 {
615 return UnexpectedSizeSnafu.fail();
616 }
617 Ok(bytes[0] != 0)
618 }
619
620 pub async fn write_bool(&mut self, index: u16, sub: u8, value: bool) -> Result<()> {
622 let data = if value { [1u8] } else { [0u8] };
623 self.download(index, sub, &data).await
624 }
625
626 pub async fn read_identity(&mut self) -> Result<LssIdentity> {
630 let vendor_id = self.read_u32(object_ids::IDENTITY, 1).await?;
631 let product_code = self.read_u32(object_ids::IDENTITY, 2).await?;
632 let revision_number = self.read_u32(object_ids::IDENTITY, 3).await?;
633 let serial = self.read_u32(object_ids::IDENTITY, 4).await?;
634 Ok(LssIdentity::new(
635 vendor_id,
636 product_code,
637 revision_number,
638 serial,
639 ))
640 }
641
642 pub async fn save_objects(&mut self) -> Result<()> {
644 self.write_u32(object_ids::SAVE_OBJECTS, 1, SAVE_CMD).await
645 }
646
647 pub async fn read_device_name(&mut self) -> Result<String> {
651 self.read_visible_string(object_ids::DEVICE_NAME, 0).await
652 }
653
654 pub async fn read_software_version(&mut self) -> Result<String> {
658 self.read_visible_string(object_ids::SOFTWARE_VERSION, 0)
659 .await
660 }
661
662 pub async fn read_hardware_version(&mut self) -> Result<String> {
666 self.read_visible_string(object_ids::HARDWARE_VERSION, 0)
667 .await
668 }
669
670 pub async fn configure_tpdo(&mut self, pdo_num: usize, cfg: &PdoConfig) -> Result<()> {
675 let comm_index = 0x1800 + pdo_num as u16;
676 let mapping_index = 0x1a00 + pdo_num as u16;
677 self.store_pdo_config(comm_index, mapping_index, cfg).await
678 }
679
680 pub async fn configure_rpdo(&mut self, pdo_num: usize, cfg: &PdoConfig) -> Result<()> {
685 let comm_index = 0x1400 + pdo_num as u16;
686 let mapping_index = 0x1600 + pdo_num as u16;
687 self.store_pdo_config(comm_index, mapping_index, cfg).await
688 }
689
690 pub async fn set_rpdo_cob_id(
694 &mut self,
695 pdo_num: usize,
696 cob_id: CanId,
697 valid: bool,
698 rtr_disabled: bool,
699 ) -> Result<()> {
700 let comm_index = 0x1400 + pdo_num as u16;
701 self.set_pdo_cob_id(comm_index, cob_id, valid, rtr_disabled)
702 .await
703 }
704
705 pub async fn set_tpdo_cob_id(
709 &mut self,
710 pdo_num: usize,
711 cob_id: CanId,
712 valid: bool,
713 rtr_disabled: bool,
714 ) -> Result<()> {
715 let comm_index = 0x1800 + pdo_num as u16;
716 self.set_pdo_cob_id(comm_index, cob_id, valid, rtr_disabled)
717 .await
718 }
719
720 async fn set_pdo_cob_id(
721 &mut self,
722 comm_index: u16,
723 cob_id: CanId,
724 valid: bool,
725 rtr_disabled: bool,
726 ) -> Result<()> {
727 let mut cob_value = cob_id.raw() & 0x1FFFFFFF;
728 if !valid {
729 cob_value |= 1 << 31;
730 }
731 if cob_id.is_extended() {
732 cob_value |= 1 << 29;
733 }
734 if rtr_disabled {
735 cob_value |= 1 << 30;
736 }
737 self.write_u32(comm_index, 1, cob_value).await?;
738
739 Ok(())
740 }
741
742 async fn set_pdo_comm_parameter(
744 &mut self,
745 comm_index: u16,
746 comm: PdoCommParameter,
747 ) -> Result<()> {
748 self.write_u8(comm_index, 2, comm.transmission_type).await?;
749 self.set_pdo_cob_id(comm_index, comm.cob_id, comm.valid, comm.rtr_disabled)
750 .await?;
751 Ok(())
752 }
753
754 async fn store_pdo_config(
755 &mut self,
756 comm_index: u16,
757 mapping_index: u16,
758 cfg: &PdoConfig,
759 ) -> Result<()> {
760 let disabled_comm = PdoCommParameter {
761 valid: false,
762 ..cfg.comm
763 };
764
765 self.set_pdo_comm_parameter(comm_index, disabled_comm)
767 .await?;
768
769 self.write_u8(mapping_index, 0, 0).await?;
771
772 assert!(cfg.mappings.len() < 0x40);
774 for (i, m) in cfg.mappings.iter().enumerate() {
775 let mapping_value = m.to_object_value();
776 self.write_u32(mapping_index, (i + 1) as u8, mapping_value)
777 .await?;
778 }
779
780 let num_mappings = cfg.mappings.len() as u8;
782 self.write_u8(mapping_index, 0, num_mappings).await?;
783
784 if cfg.comm.valid {
786 self.set_pdo_comm_parameter(comm_index, cfg.comm).await?;
787 }
788 Ok(())
789 }
790
791 pub async fn read_rpdo_config(&mut self, pdo_num: usize) -> Result<PdoConfig> {
793 let comm_index = 0x1400 + pdo_num as u16;
794 let mapping_index = 0x1600 + pdo_num as u16;
795 self.read_pdo_config(comm_index, mapping_index).await
796 }
797
798 pub async fn read_tpdo_config(&mut self, pdo_num: usize) -> Result<PdoConfig> {
800 let comm_index = 0x1800 + pdo_num as u16;
801 let mapping_index = 0x1a00 + pdo_num as u16;
802 self.read_pdo_config(comm_index, mapping_index).await
803 }
804
805 async fn read_pdo_config(&mut self, comm_index: u16, mapping_index: u16) -> Result<PdoConfig> {
806 let cob_word = self.read_u32(comm_index, 1).await?;
807 let transmission_type = self.read_u8(comm_index, 2).await?;
808 let num_mappings = self.read_u8(mapping_index, 0).await?;
809 let mut mappings = Vec::with_capacity(num_mappings as usize);
810 for i in 0..num_mappings {
811 let mapping_raw = self.read_u32(mapping_index, i + 1).await?;
812 mappings.push(PdoMapping::from_object_value(mapping_raw));
813 }
814 let valid = cob_word & (1 << 31) == 0;
815 let rtr_disabled = cob_word & (1 << 30) != 0;
816 let extended = cob_word & (1 << 29) != 0;
817 let cob_id = cob_word & 0x1FFFFFFF;
818 let cob_id = if extended {
819 CanId::extended(cob_id)
820 } else {
821 CanId::std(cob_id as u16)
822 };
823 Ok(PdoConfig {
824 comm: PdoCommParameter {
825 valid,
826 rtr_disabled,
827 cob_id,
828 transmission_type,
829 },
830 mappings,
831 })
832 }
833
834 async fn wait_for_block_segment(&mut self) -> Result<BlockSegment> {
835 let wait_until = tokio::time::Instant::now() + self.timeout;
836 loop {
837 match tokio::time::timeout_at(wait_until, self.receiver.recv()).await {
838 Err(_) => return NoResponseSnafu.fail(),
840 Ok(Ok(msg)) => {
842 if msg.id == self.resp_cob_id {
843 return msg
844 .data()
845 .try_into()
846 .map_err(|_| MalformedResponseSnafu.build());
847 }
848 }
849 Ok(Err(e)) => {
851 log::error!("Error reading from socket: {e:?}");
852 return NoResponseSnafu.fail();
853 }
854 }
855 }
856 }
857
858 async fn wait_for_response(&mut self) -> Result<SdoResponse> {
859 let wait_until = tokio::time::Instant::now() + self.timeout;
860 loop {
861 match tokio::time::timeout_at(wait_until, self.receiver.recv()).await {
862 Err(_) => return NoResponseSnafu.fail(),
864 Ok(Ok(msg)) => {
866 if msg.id == self.resp_cob_id {
867 return msg.try_into().map_err(|_| MalformedResponseSnafu.build());
868 }
869 }
870 Ok(Err(e)) => {
872 log::error!("Error reading from socket: {e:?}");
873 return NoResponseSnafu.fail();
874 }
875 }
876 }
877 }
878}