///|
/// Helper to read a little-endian UInt16 from Bytes at the given offset.
fn read_u16_le(data : Bytes, offset : Int) -> UInt {
data[offset].to_int().reinterpret_as_uint() |
(data[offset + 1].to_int().reinterpret_as_uint() << 8)
}
///|
/// Helper to read a little-endian UInt32 from Bytes at the given offset.
fn read_u32_le(data : Bytes, offset : Int) -> UInt {
data[offset].to_int().reinterpret_as_uint() |
(data[offset + 1].to_int().reinterpret_as_uint() << 8) |
(data[offset + 2].to_int().reinterpret_as_uint() << 16) |
(data[offset + 3].to_int().reinterpret_as_uint() << 24)
}
///|
/// Helper to write a little-endian UInt16 to a Buffer.
fn write_u16_le(buf : @buffer.Buffer, value : UInt) -> Unit {
buf.write_byte((value & 0xFFU).reinterpret_as_int().to_byte())
buf.write_byte(((value >> 8) & 0xFFU).reinterpret_as_int().to_byte())
}
///|
/// Helper to write a little-endian UInt32 to a Buffer.
fn write_u32_le(buf : @buffer.Buffer, value : UInt) -> Unit {
buf.write_byte((value & 0xFFU).reinterpret_as_int().to_byte())
buf.write_byte(((value >> 8) & 0xFFU).reinterpret_as_int().to_byte())
buf.write_byte(((value >> 16) & 0xFFU).reinterpret_as_int().to_byte())
buf.write_byte(((value >> 24) & 0xFFU).reinterpret_as_int().to_byte())
}
///|
/// Encode a single PDU into the buffer.
fn encode_pdu(buf : @buffer.Buffer, pdu : Pdu, is_last : Bool) -> Unit {
// Command byte
buf.write_byte(pdu.header.command.to_byte())
// Index
buf.write_byte(pdu.header.index)
// Address (4 bytes LE)
write_u32_le(buf, pdu.header.address)
// Length and flags: data_len[10:0], R[13:11], C[14], M[15]
let data_len = pdu.data.length().reinterpret_as_uint()
let more : UInt = if is_last { 0U } else { 1U }
let len_flags = (data_len & 0x7FFU) | (more << 15)
write_u16_le(buf, len_flags)
// IRQ (2 bytes)
write_u16_le(buf, pdu.header.irq)
// Data payload
buf.write_bytes(pdu.data)
// Working counter (2 bytes LE)
write_u16_le(buf, pdu.wkc)
}
///|
/// Encode an EcFrame into raw Ethernet bytes.
pub fn EcFrame::encode(self : EcFrame) -> Bytes {
let buf = Buffer(size_hint=@hal.max_frame_size)
// Ethernet II header: dst MAC
for i in 0..<6 {
buf.write_byte(self.dst_mac[i])
}
// Src MAC
for i in 0..<6 {
buf.write_byte(self.src_mac[i])
}
// EtherType: 0x88A4 big-endian
buf.write_byte(b'\x88')
buf.write_byte(b'\xA4')
// Calculate total PDU payload length (for EtherCAT header)
let mut pdu_total_len = 0
for pdu in self.pdus {
pdu_total_len = pdu_total_len + pdu_header_len + pdu.data.length() + wkc_len
}
// EtherCAT header: length[10:0] + reserved[11] + type[15:12]
let ecat_hdr = (pdu_total_len.reinterpret_as_uint() & 0x7FFU) |
(ecat_frame_type.reinterpret_as_uint() << 12)
write_u16_le(buf, ecat_hdr)
// Encode each PDU
for i, pdu in self.pdus {
let is_last = i == self.pdus.length() - 1
encode_pdu(buf, pdu, is_last)
}
buf.to_bytes()
}
///|
/// Decode an EcFrame from raw Ethernet bytes.
pub fn EcFrame::decode(data : Bytes) -> EcFrame raise @hal.EcError {
if data.length() < ethernet_header_len + ecat_header_len {
raise @hal.EcError::InvalidFrame(
"frame too short: " + data.length().to_string(),
)
}
// Parse Ethernet header
let dst_mac : FixedArray[Byte] = FixedArray::make(6, b'\x00')
let src_mac : FixedArray[Byte] = FixedArray::make(6, b'\x00')
for i in 0..<6 {
dst_mac[i] = data[i]
src_mac[i] = data[i + 6]
}
// Check EtherType
let ethertype = (data[12].to_int() << 8) | data[13].to_int()
if ethertype != @hal.ethercat_ethertype {
raise @hal.EcError::InvalidFrame(
"wrong ethertype: 0x" + ethertype.to_string(),
)
}
// Parse EtherCAT header
let ecat_hdr = read_u16_le(data, ethernet_header_len)
let payload_len = (ecat_hdr & 0x7FFU).reinterpret_as_int()
let frame_type = ((ecat_hdr >> 12) & 0xFU).reinterpret_as_int()
if frame_type != ecat_frame_type {
raise @hal.EcError::InvalidFrame(
"wrong ecat type: " + frame_type.to_string(),
)
}
// Parse PDUs
let pdus : Array[Pdu] = []
let mut offset = ethernet_header_len + ecat_header_len
let end_offset = ethernet_header_len + ecat_header_len + payload_len
while offset < end_offset {
if offset + pdu_header_len > data.length() {
raise @hal.EcError::InvalidPdu(
"PDU header truncated at offset " + offset.to_string(),
)
}
let command = EcCommand::from_byte(data[offset])
let index = data[offset + 1]
let address = read_u32_le(data, offset + 2)
let len_flags = read_u16_le(data, offset + 6)
let data_len = (len_flags & 0x7FFU).reinterpret_as_int()
let more = ((len_flags >> 15) & 1U).reinterpret_as_int()
let irq = read_u16_le(data, offset + 8)
offset = offset + pdu_header_len
if offset + data_len + wkc_len > data.length() {
raise @hal.EcError::InvalidPdu("PDU data truncated")
}
// Extract data payload
let pdu_data = if data_len > 0 {
Bytes::makei(data_len, fn(i) { data[offset + i] })
} else {
Bytes::new(0)
}
offset = offset + data_len
// Working counter
let wkc = read_u16_le(data, offset)
offset = offset + wkc_len
pdus.push({
header: { command, index, address, len_flags, irq },
data: pdu_data,
wkc,
})
if more == 0 {
break
}
}
{ dst_mac, src_mac, pdus }
}