use heapless::Vec;
use crate::datatypes::Value;
use crate::object_dictionary::{Address, ObjectDictionary};
use crate::types::NodeId;
use crate::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PdoKind {
Transmit,
Receive,
}
pub const PDO_COB_ID_INVALID: u32 = 0x8000_0000;
pub const fn pdo_is_valid(comm_cob_id: u32) -> bool {
comm_cob_id & PDO_COB_ID_INVALID == 0
}
pub const fn pdo_can_id(comm_cob_id: u32) -> u16 {
(comm_cob_id & 0x7FF) as u16
}
pub const fn default_cob_id(kind: PdoKind, number: u8, node: NodeId) -> Option<u16> {
if number < 1 || number > 4 {
return None;
}
let base = match kind {
PdoKind::Transmit => 0x80 + (number as u16) * 0x100,
PdoKind::Receive => 0x100 + (number as u16) * 0x100,
};
Some(base + node.raw() as u16)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum TransmissionType {
SynchronousAcyclic,
SynchronousCyclic(u8),
SynchronousRtrOnly,
EventDrivenRtrOnly,
EventDrivenManufacturer,
EventDrivenProfile,
}
impl TransmissionType {
pub const fn from_byte(byte: u8) -> Result<Self> {
Ok(match byte {
0 => TransmissionType::SynchronousAcyclic,
1..=240 => TransmissionType::SynchronousCyclic(byte),
252 => TransmissionType::SynchronousRtrOnly,
253 => TransmissionType::EventDrivenRtrOnly,
254 => TransmissionType::EventDrivenManufacturer,
255 => TransmissionType::EventDrivenProfile,
_ => return Err(Error::UnsupportedTransfer),
})
}
pub const fn to_byte(self) -> u8 {
match self {
TransmissionType::SynchronousAcyclic => 0,
TransmissionType::SynchronousCyclic(n) => n,
TransmissionType::SynchronousRtrOnly => 252,
TransmissionType::EventDrivenRtrOnly => 253,
TransmissionType::EventDrivenManufacturer => 254,
TransmissionType::EventDrivenProfile => 255,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MappingEntry {
pub address: Address,
pub bit_length: u8,
}
impl MappingEntry {
pub const fn new(index: u16, subindex: u8, bit_length: u8) -> Self {
Self {
address: Address::new(index, subindex),
bit_length,
}
}
pub const fn to_u32(self) -> u32 {
((self.address.index as u32) << 16)
| ((self.address.subindex as u32) << 8)
| self.bit_length as u32
}
pub const fn from_u32(raw: u32) -> Self {
Self {
address: Address::new((raw >> 16) as u16, (raw >> 8) as u8),
bit_length: raw as u8,
}
}
}
#[derive(Debug)]
pub struct PdoMapping<const N: usize> {
entries: Vec<MappingEntry, N>,
}
impl<const N: usize> Default for PdoMapping<N> {
fn default() -> Self {
Self::new()
}
}
impl<const N: usize> PdoMapping<N> {
pub const fn new() -> Self {
Self {
entries: Vec::new(),
}
}
pub fn push(&mut self, entry: MappingEntry) -> Result<()> {
if self.total_bits() + entry.bit_length as u32 > 64 {
return Err(Error::PdoTooLong);
}
self.entries.push(entry).map_err(|_| Error::MappingFull)
}
pub fn entries(&self) -> &[MappingEntry] {
self.entries.as_slice()
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn total_bits(&self) -> u32 {
self.entries.iter().map(|e| e.bit_length as u32).sum()
}
pub fn total_bytes(&self) -> usize {
(self.total_bits() as usize).div_ceil(8)
}
}
pub fn pack<const N: usize, const K: usize>(
mapping: &PdoMapping<N>,
od: &ObjectDictionary<K>,
buf: &mut [u8],
) -> Result<usize> {
let total = mapping.total_bytes();
if buf.len() < total {
return Err(Error::BadLength);
}
let mut offset = 0;
for entry in mapping.entries() {
let width = byte_width(entry.bit_length)?;
let value = od.read(entry.address)?;
if value.data_type().is_variable() || value.size() != width {
return Err(Error::TypeMismatch);
}
value.encode_le(&mut buf[offset..offset + width])?;
offset += width;
}
Ok(offset)
}
pub fn unpack<const N: usize, const K: usize>(
mapping: &PdoMapping<N>,
od: &mut ObjectDictionary<K>,
data: &[u8],
) -> Result<()> {
let total = mapping.total_bytes();
if data.len() < total {
return Err(Error::BadLength);
}
let mut offset = 0;
for entry in mapping.entries() {
let width = byte_width(entry.bit_length)?;
let data_type = od
.entry(entry.address)
.ok_or(Error::ObjectNotFound)?
.value
.data_type();
if data_type.size() != width {
return Err(Error::TypeMismatch);
}
let value = Value::decode_le(data_type, &data[offset..offset + width])?;
od.write(entry.address, value)?;
offset += width;
}
Ok(())
}
const fn byte_width(bit_length: u8) -> Result<usize> {
if bit_length == 0 || bit_length % 8 != 0 {
return Err(Error::UnsupportedTransfer);
}
Ok((bit_length / 8) as usize)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::datatypes::Value;
use crate::object_dictionary::Entry;
#[test]
fn default_cob_ids_follow_connection_set() {
let node = NodeId::new(4).unwrap();
assert_eq!(default_cob_id(PdoKind::Transmit, 1, node), Some(0x184));
assert_eq!(default_cob_id(PdoKind::Receive, 1, node), Some(0x204));
assert_eq!(default_cob_id(PdoKind::Transmit, 4, node), Some(0x484));
assert_eq!(default_cob_id(PdoKind::Receive, 4, node), Some(0x504));
assert_eq!(default_cob_id(PdoKind::Transmit, 5, node), None);
}
#[test]
fn comm_param_cob_id_validity() {
assert!(pdo_is_valid(0x0000_0184));
assert!(!pdo_is_valid(0x8000_0184));
assert_eq!(pdo_can_id(0x8000_0184), 0x184);
}
#[test]
fn transmission_types_roundtrip() {
for byte in [0u8, 1, 240, 252, 253, 254, 255] {
let t = TransmissionType::from_byte(byte).unwrap();
assert_eq!(t.to_byte(), byte);
}
assert_eq!(
TransmissionType::from_byte(1).unwrap(),
TransmissionType::SynchronousCyclic(1)
);
assert_eq!(
TransmissionType::from_byte(245),
Err(Error::UnsupportedTransfer)
);
}
#[test]
fn mapping_entry_matches_known_value() {
let e = MappingEntry::new(0x6000, 0x01, 8);
assert_eq!(e.to_u32(), 0x6000_0108);
assert_eq!(MappingEntry::from_u32(0x6000_0108), e);
}
#[test]
fn mapping_rejects_over_eight_bytes() {
let mut m: PdoMapping<8> = PdoMapping::new();
for i in 0..4 {
m.push(MappingEntry::new(0x2000 + i, 0, 16)).unwrap();
}
assert_eq!(m.total_bytes(), 8);
assert_eq!(
m.push(MappingEntry::new(0x2100, 0, 8)),
Err(Error::PdoTooLong)
);
}
fn sample_od() -> ObjectDictionary<8> {
let mut od = ObjectDictionary::new();
od.insert(
Address::new(0x6000, 1),
Entry::rw(Value::Unsigned16(0xBEEF)),
)
.unwrap();
od.insert(Address::new(0x6000, 2), Entry::rw(Value::Unsigned8(0x42)))
.unwrap();
od
}
fn sample_mapping() -> PdoMapping<8> {
let mut m = PdoMapping::new();
m.push(MappingEntry::new(0x6000, 1, 16)).unwrap();
m.push(MappingEntry::new(0x6000, 2, 8)).unwrap();
m
}
#[test]
fn pack_lays_out_little_endian_in_order() {
let od = sample_od();
let mapping = sample_mapping();
let mut buf = [0u8; 8];
let n = pack(&mapping, &od, &mut buf).unwrap();
assert_eq!(n, 3);
assert_eq!(&buf[..n], &[0xEF, 0xBE, 0x42]);
}
#[test]
fn unpack_writes_fields_back_into_od() {
let mut od = sample_od();
let mapping = sample_mapping();
unpack(&mapping, &mut od, &[0x34, 0x12, 0x99]).unwrap();
assert_eq!(
od.read(Address::new(0x6000, 1)).unwrap(),
Value::Unsigned16(0x1234)
);
assert_eq!(
od.read(Address::new(0x6000, 2)).unwrap(),
Value::Unsigned8(0x99)
);
}
#[test]
fn pack_unpack_roundtrip() {
let od = sample_od();
let mapping = sample_mapping();
let mut buf = [0u8; 8];
let n = pack(&mapping, &od, &mut buf).unwrap();
let mut dest = ObjectDictionary::<8>::new();
dest.insert(Address::new(0x6000, 1), Entry::rw(Value::Unsigned16(0)))
.unwrap();
dest.insert(Address::new(0x6000, 2), Entry::rw(Value::Unsigned8(0)))
.unwrap();
unpack(&mapping, &mut dest, &buf[..n]).unwrap();
assert_eq!(
dest.read(Address::new(0x6000, 1)).unwrap(),
Value::Unsigned16(0xBEEF)
);
assert_eq!(
dest.read(Address::new(0x6000, 2)).unwrap(),
Value::Unsigned8(0x42)
);
}
#[test]
fn pack_rejects_short_buffer() {
let od = sample_od();
let mapping = sample_mapping();
let mut buf = [0u8; 2];
assert_eq!(pack(&mapping, &od, &mut buf), Err(Error::BadLength));
}
#[test]
fn unpack_into_read_only_object_errors() {
let mut od = ObjectDictionary::<4>::new();
od.insert(Address::new(0x6000, 1), Entry::ro(Value::Unsigned16(0)))
.unwrap();
let mut m: PdoMapping<4> = PdoMapping::new();
m.push(MappingEntry::new(0x6000, 1, 16)).unwrap();
assert_eq!(unpack(&m, &mut od, &[0, 0]), Err(Error::ReadOnly));
}
#[test]
fn mapping_width_mismatch_errors() {
let od = sample_od();
let mut m: PdoMapping<4> = PdoMapping::new();
m.push(MappingEntry::new(0x6000, 2, 16)).unwrap();
let mut buf = [0u8; 8];
assert_eq!(pack(&m, &od, &mut buf), Err(Error::TypeMismatch));
}
}