1use heapless::Vec;
44
45use crate::datatypes::Value;
46use crate::object_dictionary::{Address, ObjectDictionary};
47use crate::types::NodeId;
48use crate::{Error, Result};
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum PdoKind {
53 Transmit,
55 Receive,
57}
58
59pub const PDO_COB_ID_INVALID: u32 = 0x8000_0000;
62
63pub const fn pdo_is_valid(comm_cob_id: u32) -> bool {
65 comm_cob_id & PDO_COB_ID_INVALID == 0
66}
67
68pub const fn pdo_can_id(comm_cob_id: u32) -> u16 {
70 (comm_cob_id & 0x7FF) as u16
71}
72
73pub const fn default_cob_id(kind: PdoKind, number: u8, node: NodeId) -> Option<u16> {
79 if number < 1 || number > 4 {
80 return None;
81 }
82 let base = match kind {
83 PdoKind::Transmit => 0x80 + (number as u16) * 0x100,
85 PdoKind::Receive => 0x100 + (number as u16) * 0x100,
87 };
88 Some(base + node.raw() as u16)
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94#[non_exhaustive]
95pub enum TransmissionType {
96 SynchronousAcyclic,
99 SynchronousCyclic(u8),
101 SynchronousRtrOnly,
103 EventDrivenRtrOnly,
105 EventDrivenManufacturer,
107 EventDrivenProfile,
109}
110
111impl TransmissionType {
112 pub const fn from_byte(byte: u8) -> Result<Self> {
117 Ok(match byte {
118 0 => TransmissionType::SynchronousAcyclic,
119 1..=240 => TransmissionType::SynchronousCyclic(byte),
120 252 => TransmissionType::SynchronousRtrOnly,
121 253 => TransmissionType::EventDrivenRtrOnly,
122 254 => TransmissionType::EventDrivenManufacturer,
123 255 => TransmissionType::EventDrivenProfile,
124 _ => return Err(Error::UnsupportedTransfer),
125 })
126 }
127
128 pub const fn to_byte(self) -> u8 {
130 match self {
131 TransmissionType::SynchronousAcyclic => 0,
132 TransmissionType::SynchronousCyclic(n) => n,
133 TransmissionType::SynchronousRtrOnly => 252,
134 TransmissionType::EventDrivenRtrOnly => 253,
135 TransmissionType::EventDrivenManufacturer => 254,
136 TransmissionType::EventDrivenProfile => 255,
137 }
138 }
139}
140
141#[derive(Debug, Clone, Copy, PartialEq, Eq)]
146pub struct MappingEntry {
147 pub address: Address,
149 pub bit_length: u8,
151}
152
153impl MappingEntry {
154 pub const fn new(index: u16, subindex: u8, bit_length: u8) -> Self {
156 Self {
157 address: Address::new(index, subindex),
158 bit_length,
159 }
160 }
161
162 pub const fn to_u32(self) -> u32 {
164 ((self.address.index as u32) << 16)
165 | ((self.address.subindex as u32) << 8)
166 | self.bit_length as u32
167 }
168
169 pub const fn from_u32(raw: u32) -> Self {
171 Self {
172 address: Address::new((raw >> 16) as u16, (raw >> 8) as u8),
173 bit_length: raw as u8,
174 }
175 }
176}
177
178#[derive(Debug)]
183pub struct PdoMapping<const N: usize> {
184 entries: Vec<MappingEntry, N>,
185}
186
187impl<const N: usize> Default for PdoMapping<N> {
188 fn default() -> Self {
189 Self::new()
190 }
191}
192
193impl<const N: usize> PdoMapping<N> {
194 pub const fn new() -> Self {
196 Self {
197 entries: Vec::new(),
198 }
199 }
200
201 pub fn push(&mut self, entry: MappingEntry) -> Result<()> {
206 if self.total_bits() + entry.bit_length as u32 > 64 {
207 return Err(Error::PdoTooLong);
208 }
209 self.entries.push(entry).map_err(|_| Error::MappingFull)
210 }
211
212 pub fn entries(&self) -> &[MappingEntry] {
214 self.entries.as_slice()
215 }
216
217 pub fn len(&self) -> usize {
219 self.entries.len()
220 }
221
222 pub fn is_empty(&self) -> bool {
224 self.entries.is_empty()
225 }
226
227 pub fn total_bits(&self) -> u32 {
229 self.entries.iter().map(|e| e.bit_length as u32).sum()
230 }
231
232 pub fn total_bytes(&self) -> usize {
234 (self.total_bits() as usize).div_ceil(8)
235 }
236}
237
238pub fn pack<const N: usize, const K: usize>(
248 mapping: &PdoMapping<N>,
249 od: &ObjectDictionary<K>,
250 buf: &mut [u8],
251) -> Result<usize> {
252 let total = mapping.total_bytes();
253 if buf.len() < total {
254 return Err(Error::BadLength);
255 }
256 let mut offset = 0;
257 for entry in mapping.entries() {
258 let width = byte_width(entry.bit_length)?;
259 let value = od.read(entry.address)?;
260 if value.data_type().is_variable() || value.size() != width {
262 return Err(Error::TypeMismatch);
263 }
264 value.encode_le(&mut buf[offset..offset + width])?;
265 offset += width;
266 }
267 Ok(offset)
268}
269
270pub fn unpack<const N: usize, const K: usize>(
281 mapping: &PdoMapping<N>,
282 od: &mut ObjectDictionary<K>,
283 data: &[u8],
284) -> Result<()> {
285 let total = mapping.total_bytes();
286 if data.len() < total {
287 return Err(Error::BadLength);
288 }
289 let mut offset = 0;
290 for entry in mapping.entries() {
291 let width = byte_width(entry.bit_length)?;
292 let data_type = od
293 .entry(entry.address)
294 .ok_or(Error::ObjectNotFound)?
295 .value
296 .data_type();
297 if data_type.size() != width {
298 return Err(Error::TypeMismatch);
299 }
300 let value = Value::decode_le(data_type, &data[offset..offset + width])?;
301 od.write(entry.address, value)?;
302 offset += width;
303 }
304 Ok(())
305}
306
307const fn byte_width(bit_length: u8) -> Result<usize> {
309 if bit_length == 0 || bit_length % 8 != 0 {
310 return Err(Error::UnsupportedTransfer);
311 }
312 Ok((bit_length / 8) as usize)
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318 use crate::datatypes::Value;
319 use crate::object_dictionary::Entry;
320
321 #[test]
323 fn default_cob_ids_follow_connection_set() {
324 let node = NodeId::new(4).unwrap();
325 assert_eq!(default_cob_id(PdoKind::Transmit, 1, node), Some(0x184));
326 assert_eq!(default_cob_id(PdoKind::Receive, 1, node), Some(0x204));
327 assert_eq!(default_cob_id(PdoKind::Transmit, 4, node), Some(0x484));
328 assert_eq!(default_cob_id(PdoKind::Receive, 4, node), Some(0x504));
329 assert_eq!(default_cob_id(PdoKind::Transmit, 5, node), None);
330 }
331
332 #[test]
333 fn comm_param_cob_id_validity() {
334 assert!(pdo_is_valid(0x0000_0184));
336 assert!(!pdo_is_valid(0x8000_0184));
337 assert_eq!(pdo_can_id(0x8000_0184), 0x184);
338 }
339
340 #[test]
342 fn transmission_types_roundtrip() {
343 for byte in [0u8, 1, 240, 252, 253, 254, 255] {
344 let t = TransmissionType::from_byte(byte).unwrap();
345 assert_eq!(t.to_byte(), byte);
346 }
347 assert_eq!(
348 TransmissionType::from_byte(1).unwrap(),
349 TransmissionType::SynchronousCyclic(1)
350 );
351 assert_eq!(
352 TransmissionType::from_byte(245),
353 Err(Error::UnsupportedTransfer)
354 );
355 }
356
357 #[test]
360 fn mapping_entry_matches_known_value() {
361 let e = MappingEntry::new(0x6000, 0x01, 8);
362 assert_eq!(e.to_u32(), 0x6000_0108);
363 assert_eq!(MappingEntry::from_u32(0x6000_0108), e);
364 }
365
366 #[test]
367 fn mapping_rejects_over_eight_bytes() {
368 let mut m: PdoMapping<8> = PdoMapping::new();
369 for i in 0..4 {
371 m.push(MappingEntry::new(0x2000 + i, 0, 16)).unwrap();
372 }
373 assert_eq!(m.total_bytes(), 8);
374 assert_eq!(
376 m.push(MappingEntry::new(0x2100, 0, 8)),
377 Err(Error::PdoTooLong)
378 );
379 }
380
381 fn sample_od() -> ObjectDictionary<8> {
384 let mut od = ObjectDictionary::new();
385 od.insert(
386 Address::new(0x6000, 1),
387 Entry::rw(Value::Unsigned16(0xBEEF)),
388 )
389 .unwrap();
390 od.insert(Address::new(0x6000, 2), Entry::rw(Value::Unsigned8(0x42)))
391 .unwrap();
392 od
393 }
394
395 fn sample_mapping() -> PdoMapping<8> {
396 let mut m = PdoMapping::new();
397 m.push(MappingEntry::new(0x6000, 1, 16)).unwrap();
398 m.push(MappingEntry::new(0x6000, 2, 8)).unwrap();
399 m
400 }
401
402 #[test]
403 fn pack_lays_out_little_endian_in_order() {
404 let od = sample_od();
405 let mapping = sample_mapping();
406 let mut buf = [0u8; 8];
407 let n = pack(&mapping, &od, &mut buf).unwrap();
408 assert_eq!(n, 3);
410 assert_eq!(&buf[..n], &[0xEF, 0xBE, 0x42]);
411 }
412
413 #[test]
414 fn unpack_writes_fields_back_into_od() {
415 let mut od = sample_od();
416 let mapping = sample_mapping();
417 unpack(&mapping, &mut od, &[0x34, 0x12, 0x99]).unwrap();
419 assert_eq!(
420 od.read(Address::new(0x6000, 1)).unwrap(),
421 Value::Unsigned16(0x1234)
422 );
423 assert_eq!(
424 od.read(Address::new(0x6000, 2)).unwrap(),
425 Value::Unsigned8(0x99)
426 );
427 }
428
429 #[test]
430 fn pack_unpack_roundtrip() {
431 let od = sample_od();
432 let mapping = sample_mapping();
433 let mut buf = [0u8; 8];
434 let n = pack(&mapping, &od, &mut buf).unwrap();
435
436 let mut dest = ObjectDictionary::<8>::new();
437 dest.insert(Address::new(0x6000, 1), Entry::rw(Value::Unsigned16(0)))
438 .unwrap();
439 dest.insert(Address::new(0x6000, 2), Entry::rw(Value::Unsigned8(0)))
440 .unwrap();
441 unpack(&mapping, &mut dest, &buf[..n]).unwrap();
442
443 assert_eq!(
444 dest.read(Address::new(0x6000, 1)).unwrap(),
445 Value::Unsigned16(0xBEEF)
446 );
447 assert_eq!(
448 dest.read(Address::new(0x6000, 2)).unwrap(),
449 Value::Unsigned8(0x42)
450 );
451 }
452
453 #[test]
454 fn pack_rejects_short_buffer() {
455 let od = sample_od();
456 let mapping = sample_mapping();
457 let mut buf = [0u8; 2];
458 assert_eq!(pack(&mapping, &od, &mut buf), Err(Error::BadLength));
459 }
460
461 #[test]
462 fn unpack_into_read_only_object_errors() {
463 let mut od = ObjectDictionary::<4>::new();
464 od.insert(Address::new(0x6000, 1), Entry::ro(Value::Unsigned16(0)))
465 .unwrap();
466 let mut m: PdoMapping<4> = PdoMapping::new();
467 m.push(MappingEntry::new(0x6000, 1, 16)).unwrap();
468 assert_eq!(unpack(&m, &mut od, &[0, 0]), Err(Error::ReadOnly));
469 }
470
471 #[test]
472 fn mapping_width_mismatch_errors() {
473 let od = sample_od();
475 let mut m: PdoMapping<4> = PdoMapping::new();
476 m.push(MappingEntry::new(0x6000, 2, 16)).unwrap();
477 let mut buf = [0u8; 8];
478 assert_eq!(pack(&m, &od, &mut buf), Err(Error::TypeMismatch));
479 }
480}