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.size() != width {
261 return Err(Error::TypeMismatch);
262 }
263 value.encode_le(&mut buf[offset..offset + width])?;
264 offset += width;
265 }
266 Ok(offset)
267}
268
269pub fn unpack<const N: usize, const K: usize>(
280 mapping: &PdoMapping<N>,
281 od: &mut ObjectDictionary<K>,
282 data: &[u8],
283) -> Result<()> {
284 let total = mapping.total_bytes();
285 if data.len() < total {
286 return Err(Error::BadLength);
287 }
288 let mut offset = 0;
289 for entry in mapping.entries() {
290 let width = byte_width(entry.bit_length)?;
291 let data_type = od
292 .entry(entry.address)
293 .ok_or(Error::ObjectNotFound)?
294 .value
295 .data_type();
296 if data_type.size() != width {
297 return Err(Error::TypeMismatch);
298 }
299 let value = Value::decode_le(data_type, &data[offset..offset + width])?;
300 od.write(entry.address, value)?;
301 offset += width;
302 }
303 Ok(())
304}
305
306const fn byte_width(bit_length: u8) -> Result<usize> {
308 if bit_length == 0 || bit_length % 8 != 0 {
309 return Err(Error::UnsupportedTransfer);
310 }
311 Ok((bit_length / 8) as usize)
312}
313
314#[cfg(test)]
315mod tests {
316 use super::*;
317 use crate::datatypes::Value;
318 use crate::object_dictionary::Entry;
319
320 #[test]
322 fn default_cob_ids_follow_connection_set() {
323 let node = NodeId::new(4).unwrap();
324 assert_eq!(default_cob_id(PdoKind::Transmit, 1, node), Some(0x184));
325 assert_eq!(default_cob_id(PdoKind::Receive, 1, node), Some(0x204));
326 assert_eq!(default_cob_id(PdoKind::Transmit, 4, node), Some(0x484));
327 assert_eq!(default_cob_id(PdoKind::Receive, 4, node), Some(0x504));
328 assert_eq!(default_cob_id(PdoKind::Transmit, 5, node), None);
329 }
330
331 #[test]
332 fn comm_param_cob_id_validity() {
333 assert!(pdo_is_valid(0x0000_0184));
335 assert!(!pdo_is_valid(0x8000_0184));
336 assert_eq!(pdo_can_id(0x8000_0184), 0x184);
337 }
338
339 #[test]
341 fn transmission_types_roundtrip() {
342 for byte in [0u8, 1, 240, 252, 253, 254, 255] {
343 let t = TransmissionType::from_byte(byte).unwrap();
344 assert_eq!(t.to_byte(), byte);
345 }
346 assert_eq!(
347 TransmissionType::from_byte(1).unwrap(),
348 TransmissionType::SynchronousCyclic(1)
349 );
350 assert_eq!(
351 TransmissionType::from_byte(245),
352 Err(Error::UnsupportedTransfer)
353 );
354 }
355
356 #[test]
359 fn mapping_entry_matches_known_value() {
360 let e = MappingEntry::new(0x6000, 0x01, 8);
361 assert_eq!(e.to_u32(), 0x6000_0108);
362 assert_eq!(MappingEntry::from_u32(0x6000_0108), e);
363 }
364
365 #[test]
366 fn mapping_rejects_over_eight_bytes() {
367 let mut m: PdoMapping<8> = PdoMapping::new();
368 for i in 0..4 {
370 m.push(MappingEntry::new(0x2000 + i, 0, 16)).unwrap();
371 }
372 assert_eq!(m.total_bytes(), 8);
373 assert_eq!(
375 m.push(MappingEntry::new(0x2100, 0, 8)),
376 Err(Error::PdoTooLong)
377 );
378 }
379
380 fn sample_od() -> ObjectDictionary<8> {
383 let mut od = ObjectDictionary::new();
384 od.insert(
385 Address::new(0x6000, 1),
386 Entry::rw(Value::Unsigned16(0xBEEF)),
387 )
388 .unwrap();
389 od.insert(Address::new(0x6000, 2), Entry::rw(Value::Unsigned8(0x42)))
390 .unwrap();
391 od
392 }
393
394 fn sample_mapping() -> PdoMapping<8> {
395 let mut m = PdoMapping::new();
396 m.push(MappingEntry::new(0x6000, 1, 16)).unwrap();
397 m.push(MappingEntry::new(0x6000, 2, 8)).unwrap();
398 m
399 }
400
401 #[test]
402 fn pack_lays_out_little_endian_in_order() {
403 let od = sample_od();
404 let mapping = sample_mapping();
405 let mut buf = [0u8; 8];
406 let n = pack(&mapping, &od, &mut buf).unwrap();
407 assert_eq!(n, 3);
409 assert_eq!(&buf[..n], &[0xEF, 0xBE, 0x42]);
410 }
411
412 #[test]
413 fn unpack_writes_fields_back_into_od() {
414 let mut od = sample_od();
415 let mapping = sample_mapping();
416 unpack(&mapping, &mut od, &[0x34, 0x12, 0x99]).unwrap();
418 assert_eq!(
419 od.read(Address::new(0x6000, 1)).unwrap(),
420 Value::Unsigned16(0x1234)
421 );
422 assert_eq!(
423 od.read(Address::new(0x6000, 2)).unwrap(),
424 Value::Unsigned8(0x99)
425 );
426 }
427
428 #[test]
429 fn pack_unpack_roundtrip() {
430 let od = sample_od();
431 let mapping = sample_mapping();
432 let mut buf = [0u8; 8];
433 let n = pack(&mapping, &od, &mut buf).unwrap();
434
435 let mut dest = ObjectDictionary::<8>::new();
436 dest.insert(Address::new(0x6000, 1), Entry::rw(Value::Unsigned16(0)))
437 .unwrap();
438 dest.insert(Address::new(0x6000, 2), Entry::rw(Value::Unsigned8(0)))
439 .unwrap();
440 unpack(&mapping, &mut dest, &buf[..n]).unwrap();
441
442 assert_eq!(
443 dest.read(Address::new(0x6000, 1)).unwrap(),
444 Value::Unsigned16(0xBEEF)
445 );
446 assert_eq!(
447 dest.read(Address::new(0x6000, 2)).unwrap(),
448 Value::Unsigned8(0x42)
449 );
450 }
451
452 #[test]
453 fn pack_rejects_short_buffer() {
454 let od = sample_od();
455 let mapping = sample_mapping();
456 let mut buf = [0u8; 2];
457 assert_eq!(pack(&mapping, &od, &mut buf), Err(Error::BadLength));
458 }
459
460 #[test]
461 fn unpack_into_read_only_object_errors() {
462 let mut od = ObjectDictionary::<4>::new();
463 od.insert(Address::new(0x6000, 1), Entry::ro(Value::Unsigned16(0)))
464 .unwrap();
465 let mut m: PdoMapping<4> = PdoMapping::new();
466 m.push(MappingEntry::new(0x6000, 1, 16)).unwrap();
467 assert_eq!(unpack(&m, &mut od, &[0, 0]), Err(Error::ReadOnly));
468 }
469
470 #[test]
471 fn mapping_width_mismatch_errors() {
472 let od = sample_od();
474 let mut m: PdoMapping<4> = PdoMapping::new();
475 m.push(MappingEntry::new(0x6000, 2, 16)).unwrap();
476 let mut buf = [0u8; 8];
477 assert_eq!(pack(&m, &od, &mut buf), Err(Error::TypeMismatch));
478 }
479}