///|
/// EoE (Ethernet over EtherCAT) frame encoding and decoding per ETG.1000.6 ยง5.7.
///
/// EoE header (4 bytes, follows 6-byte mailbox header):
/// Bytes 0-1: frameinfo1 (LE u16)
/// bits [3:0] = frame type
/// bits [7:4] = port
/// bit [8] = last fragment
/// bit [9] = time appended
/// bit [10] = time request
/// Bytes 2-3: frameinfo2 (LE u16)
/// bits [5:0] = fragment number
/// bits [11:6] = frame offset (in 32-byte units)
/// bits [15:12] = frame number
/// Bytes 4+: payload
///|
let eoe_header_len : Int = 4
///|
/// EoE frame type values per ETG.1000.6.
pub(all) enum EoeFrameType {
/// Ethernet frame fragment data.
FragData
/// Timestamp response (Init response with timestamp).
InitRespTimestamp
/// Set IP parameter request (INIT_REQ).
SetIpReq
/// Set IP parameter response (INIT_RESP).
SetIpResp
/// Set address filter request.
SetAddrFilterReq
/// Set address filter response.
SetAddrFilterResp
/// Get IP parameter request.
GetIpReq
/// Get IP parameter response.
GetIpResp
/// Get address filter request.
GetAddrFilterReq
/// Get address filter response.
GetAddrFilterResp
} derive(Eq, Debug)
///|
pub fn EoeFrameType::to_uint(self : EoeFrameType) -> UInt {
match self {
FragData => 0U
InitRespTimestamp => 1U
SetIpReq => 2U
SetIpResp => 3U
SetAddrFilterReq => 4U
SetAddrFilterResp => 5U
GetIpReq => 6U
GetIpResp => 7U
GetAddrFilterReq => 8U
GetAddrFilterResp => 9U
}
}
///|
pub fn EoeFrameType::from_uint(v : UInt) -> EoeFrameType raise @hal.EcError {
match v {
0U => FragData
1U => InitRespTimestamp
2U => SetIpReq
3U => SetIpResp
4U => SetAddrFilterReq
5U => SetAddrFilterResp
6U => GetIpReq
7U => GetIpResp
8U => GetAddrFilterReq
9U => GetAddrFilterResp
_ =>
raise @hal.EcError::MailboxProtocol(
"unknown EoE frame type: " + v.reinterpret_as_int().to_string(),
)
}
}
///|
/// EoE result codes for Set IP / Get IP responses.
pub(all) enum EoeResult {
Success
UnspecifiedError
UnsupportedFrameType
NoIpSupport
NoDhcpSupport
NoFilterSupport
Unknown(UInt)
} derive(Eq, Debug)
///|
pub fn EoeResult::from_uint(v : UInt) -> EoeResult {
match v {
0x0000U => Success
0x0001U => UnspecifiedError
0x0002U => UnsupportedFrameType
0x0201U => NoIpSupport
0x0202U => NoDhcpSupport
0x0401U => NoFilterSupport
_ => Unknown(v)
}
}
///|
pub fn EoeResult::to_uint(self : EoeResult) -> UInt {
match self {
Success => 0x0000U
UnspecifiedError => 0x0001U
UnsupportedFrameType => 0x0002U
NoIpSupport => 0x0201U
NoDhcpSupport => 0x0202U
NoFilterSupport => 0x0401U
Unknown(v) => v
}
}
///|
/// Decoded EoE response.
pub(all) enum EoeResponse {
/// Received an Ethernet frame fragment.
EoeFragment(
frame_type~ : EoeFrameType,
last_fragment~ : Bool,
frag_number~ : UInt,
frame_offset~ : UInt,
frame_number~ : UInt,
data~ : Bytes
)
/// Set/Get IP parameter response with result code.
EoeIpResponse(result~ : EoeResult, data~ : Bytes)
} derive(Eq, Debug)
///|
/// Build frameinfo1 LE u16 value.
fn build_frameinfo1(
frame_type : EoeFrameType,
port : UInt,
last_fragment : Bool,
) -> UInt {
let ft = frame_type.to_uint() & 0xFU
let p = (port & 0xFU) << 4
let lf : UInt = if last_fragment { 1U << 8 } else { 0U }
ft | p | lf
}
///|
/// Build frameinfo2 LE u16 value.
fn build_frameinfo2(
frag_number : UInt,
frame_offset : UInt,
frame_number : UInt,
) -> UInt {
let fn_ = frag_number & 0x3FU
let fo = (frame_offset & 0x3FU) << 6
let fno = (frame_number & 0xFU) << 12
fn_ | fo | fno
}
///|
/// Encode an EoE Ethernet frame fragment for sending to a slave.
pub fn encode_eoe_fragment(
counter : Byte,
port : UInt,
last_fragment : Bool,
frag_number : UInt,
frame_offset : UInt,
frame_number : UInt,
data : Bytes,
) -> Bytes {
let payload_len = eoe_header_len + data.length()
let buf = Buffer(size_hint=mailbox_header_len + payload_len)
let mbh : MailboxHeader = {
length: payload_len.reinterpret_as_uint(),
address: 0U,
channel: b'\x00',
priority: b'\x00',
mb_type: EoE,
counter,
}
let hdr = mbh.to_bytes()
for i = 0; i < hdr.length(); i = i + 1 {
buf.write_byte(hdr[i])
}
let fi1 = build_frameinfo1(FragData, port, last_fragment)
buf.write_byte((fi1 & 0xFFU).to_byte())
buf.write_byte(((fi1 >> 8) & 0xFFU).to_byte())
let fi2 = build_frameinfo2(frag_number, frame_offset, frame_number)
buf.write_byte((fi2 & 0xFFU).to_byte())
buf.write_byte(((fi2 >> 8) & 0xFFU).to_byte())
for i = 0; i < data.length(); i = i + 1 {
buf.write_byte(data[i])
}
buf.to_bytes()
}
///|
/// Encode an EoE Set IP parameter request.
pub fn encode_eoe_set_ip_req(
counter : Byte,
port : UInt,
ip : Bytes,
subnet : Bytes,
gateway : Bytes,
) -> Bytes {
// Flags byte: bit 1=IP, bit 2=subnet, bit 3=gateway
let mut flags : Byte = b'\x00'
let params : Array[Bytes] = []
if ip.length() == 4 {
flags = flags | b'\x02'
params.push(ip)
}
if subnet.length() == 4 {
flags = flags | b'\x04'
params.push(subnet)
}
if gateway.length() == 4 {
flags = flags | b'\x08'
params.push(gateway)
}
let mut param_total = 0
for p in params {
param_total += p.length()
}
let payload_len = eoe_header_len + 1 + param_total
let buf = Buffer(size_hint=mailbox_header_len + payload_len)
let mbh : MailboxHeader = {
length: payload_len.reinterpret_as_uint(),
address: 0U,
channel: b'\x00',
priority: b'\x00',
mb_type: EoE,
counter,
}
let hdr = mbh.to_bytes()
for i = 0; i < hdr.length(); i = i + 1 {
buf.write_byte(hdr[i])
}
let fi1 = build_frameinfo1(SetIpReq, port, true)
buf.write_byte((fi1 & 0xFFU).to_byte())
buf.write_byte(((fi1 >> 8) & 0xFFU).to_byte())
// frameinfo2 = 0
buf.write_byte(b'\x00')
buf.write_byte(b'\x00')
buf.write_byte(flags)
for p in params {
for i = 0; i < p.length(); i = i + 1 {
buf.write_byte(p[i])
}
}
buf.to_bytes()
}
///|
/// Encode an EoE Get IP parameter request.
pub fn encode_eoe_get_ip_req(counter : Byte, port : UInt) -> Bytes {
let payload_len = eoe_header_len
let buf = Buffer(size_hint=mailbox_header_len + payload_len)
let mbh : MailboxHeader = {
length: payload_len.reinterpret_as_uint(),
address: 0U,
channel: b'\x00',
priority: b'\x00',
mb_type: EoE,
counter,
}
let hdr = mbh.to_bytes()
for i = 0; i < hdr.length(); i = i + 1 {
buf.write_byte(hdr[i])
}
let fi1 = build_frameinfo1(GetIpReq, port, true)
buf.write_byte((fi1 & 0xFFU).to_byte())
buf.write_byte(((fi1 >> 8) & 0xFFU).to_byte())
buf.write_byte(b'\x00')
buf.write_byte(b'\x00')
buf.to_bytes()
}
///|
/// Decode an EoE response from a mailbox frame (after mailbox header).
pub fn decode_eoe_response(frame : Bytes) -> EoeResponse raise @hal.EcError {
if frame.length() < mailbox_header_len + eoe_header_len {
raise @hal.EcError::MailboxProtocol(
"EoE frame too short: " + frame.length().to_string(),
)
}
let mbh = MailboxHeader::from_bytes(frame)
if mbh.mb_type != EoE {
raise @hal.EcError::MailboxProtocol(
"expected EoE mailbox type, got " + @debug.to_string(mbh.mb_type),
)
}
let off = mailbox_header_len
let fi1 = frame[off].to_uint() | (frame[off + 1].to_uint() << 8)
let fi2 = frame[off + 2].to_uint() | (frame[off + 3].to_uint() << 8)
let frame_type = EoeFrameType::from_uint(fi1 & 0xFU)
let last_fragment = ((fi1 >> 8) & 1U) != 0U
let frag_number = fi2 & 0x3FU
let frame_offset = (fi2 >> 6) & 0x3FU
let frame_number = (fi2 >> 12) & 0xFU
match frame_type {
SetIpResp | GetIpResp | SetAddrFilterResp | GetAddrFilterResp => {
// frameinfo2 doubles as result code for IP responses
let result = EoeResult::from_uint(fi2)
let data_start = off + eoe_header_len
let data = if data_start < frame.length() {
Bytes::makei(frame.length() - data_start, fn(i) {
frame[data_start + i]
})
} else {
Bytes::new(0)
}
EoeIpResponse(result~, data~)
}
_ => {
let data_start = off + eoe_header_len
let data = if data_start < frame.length() {
Bytes::makei(frame.length() - data_start, fn(i) {
frame[data_start + i]
})
} else {
Bytes::new(0)
}
EoeFragment(
frame_type~,
last_fragment~,
frag_number~,
frame_offset~,
frame_number~,
data~,
)
}
}
}