///|
fn push_u16_be(output : Array[Byte], value : Int) -> Unit {
output.push(((value >> 8) & 0xFF).to_byte())
output.push((value & 0xFF).to_byte())
}
///|
fn push_u32_be(output : Array[Byte], value : Int) -> Unit {
output.push(((value >> 24) & 0xFF).to_byte())
output.push(((value >> 16) & 0xFF).to_byte())
output.push(((value >> 8) & 0xFF).to_byte())
output.push((value & 0xFF).to_byte())
}
///|
fn read_u16_be(data : Bytes, offset : Int) -> Int {
(data[offset].to_int() << 8) + data[offset + 1].to_int()
}
///|
fn read_u32_be(data : Bytes, offset : Int) -> Int {
(data[offset].to_int() << 24) +
(data[offset + 1].to_int() << 16) +
(data[offset + 2].to_int() << 8) +
data[offset + 3].to_int()
}
///|
fn append_bytes(output : Array[Byte], data : Bytes) -> Unit {
for byte in data {
output.push(byte)
}
}
///|
/// IO slot definition.
pub(all) struct IoSlot {
slot_number : Int
module_ident_number : Int
subslots : Array[IoSubslot]
} derive(Eq, Debug)
///|
/// IO subslot definition.
pub(all) struct IoSubslot {
subslot_number : Int
submodule_ident_number : Int
} derive(Eq, Debug)
///|
/// Expected/Real identification data — block for AR connect.
pub(all) struct IdentificationData {
api : Int
slots : Array[IoSlot]
} derive(Eq, Debug)
///|
/// Block type for expected identification data.
pub let block_type_expected_identification : Int = 18
///|
/// Block type for real identification data.
pub let block_type_real_identification : Int = 19
///|
/// Encode expected/real identification data body.
pub fn encode_identification_data(data : IdentificationData) -> Bytes {
let output : Array[Byte] = []
// Number of APIs
push_u16_be(output, 1)
push_u32_be(output, data.api)
// Number of slots
push_u16_be(output, data.slots.length())
for slot in data.slots {
push_u16_be(output, slot.slot_number)
push_u32_be(output, slot.module_ident_number)
// Number of subslots
push_u16_be(output, slot.subslots.length())
for sub in slot.subslots {
push_u16_be(output, sub.subslot_number)
push_u32_be(output, sub.submodule_ident_number)
}
}
Bytes::from_array(output)
}
///|
/// Parse identification data from body bytes.
pub fn parse_identification_data(
data : Bytes,
offset : Int,
) -> IdentificationData raise @frame.FrameError {
guard data.length() >= offset + 8 else {
raise @frame.FrameError::InvalidMacLength(data.length())
}
let api = read_u32_be(data, offset + 2)
let num_slots = read_u16_be(data, offset + 6)
let slots : Array[IoSlot] = []
let mut pos = offset + 8
for i = 0; i < num_slots; i = i + 1 {
guard data.length() >= pos + 8 else {
raise @frame.FrameError::InvalidMacLength(data.length())
}
let slot_number = read_u16_be(data, pos)
let module_ident = read_u32_be(data, pos + 2)
let num_subslots = read_u16_be(data, pos + 6)
pos = pos + 8
let subslots : Array[IoSubslot] = []
for j = 0; j < num_subslots; j = j + 1 {
guard data.length() >= pos + 6 else {
raise @frame.FrameError::InvalidMacLength(data.length())
}
subslots.push(IoSubslot::{
subslot_number: read_u16_be(data, pos),
submodule_ident_number: read_u32_be(data, pos + 2),
})
pos = pos + 6
}
slots.push(IoSlot::{
slot_number,
module_ident_number: module_ident,
subslots,
})
}
IdentificationData::{ api, slots }
}
///|
/// IO connection state — models an active cyclic data connection.
pub(all) struct IoConnection {
uuid : @rpc.Uuid
rx_data_length : Int
tx_data_length : Int
is_tx_station_ok : Bool
is_tx_provider_in_run : Bool
is_tx_data_valid : Bool
is_tx_primary : Bool
} derive(Eq, Debug)
///|
pub fn IoConnection::default(uuid : @rpc.Uuid) -> IoConnection {
IoConnection::{
uuid,
rx_data_length: 40,
tx_data_length: 40,
is_tx_station_ok: true,
is_tx_provider_in_run: true,
is_tx_data_valid: true,
is_tx_primary: true,
}
}
///|
/// IO cyclic data frame — provider status byte + data.
pub(all) struct IoCyclicFrame {
frame_id : Int
cycle_counter : Int
data_status : Byte
transfer_status : Byte
payload : Bytes
} derive(Eq, Debug)
///|
pub(all) struct DataStatusFields {
state : String
redundancy : String
data_valid : String
provider_state : String
station_problem : String
ignore : String
} derive(Eq, Debug)
///|
fn data_status_bit_set(status : Byte, mask : Int) -> Bool {
(status.to_int() & mask) != 0
}
///|
pub fn decode_data_status(status : Byte) -> DataStatusFields {
let primary = data_status_bit_set(status, 0x01)
let redundancy_set = data_status_bit_set(status, 0x02)
let state = if primary { "Primary" } else { "Backup" }
let redundancy = if primary {
if redundancy_set {
"BackupAr"
} else {
"PrimaryAr"
}
} else if redundancy_set {
"NoPrimaryArPresent"
} else {
"PrimaryArPresent"
}
let data_valid = if data_status_bit_set(status, 0x04) {
"DataItemValid"
} else {
"DataItemInvalid"
}
let provider_state = if data_status_bit_set(status, 0x10) {
"Run"
} else {
"Stop"
}
let station_problem = if data_status_bit_set(status, 0x20) {
"NormalOperation"
} else {
"ProblemDetected"
}
let ignore = if data_status_bit_set(status, 0x80) {
"Ignore"
} else {
"Evaluate"
}
{ state, redundancy, data_valid, provider_state, station_problem, ignore }
}
///|
/// Encode a cyclic IO frame (for provider).
pub fn encode_cyclic_frame(frame : IoCyclicFrame) -> Bytes {
let output : Array[Byte] = []
push_u16_be(output, frame.frame_id)
append_bytes(output, frame.payload)
push_u16_be(output, frame.cycle_counter)
output.push(frame.data_status)
output.push(frame.transfer_status)
Bytes::from_array(output)
}
///|
/// Parse a cyclic IO frame.
pub fn parse_cyclic_frame(
data : Bytes,
offset : Int,
data_length : Int,
) -> IoCyclicFrame raise @frame.FrameError {
guard data.length() >= offset + 2 + data_length + 4 else {
raise @frame.FrameError::InvalidMacLength(data.length())
}
let frame_id = read_u16_be(data, offset)
let payload_bytes : Array[Byte] = []
for i = 0; i < data_length; i = i + 1 {
payload_bytes.push(data[offset + 2 + i])
}
let status_offset = offset + 2 + data_length
IoCyclicFrame::{
frame_id,
cycle_counter: read_u16_be(data, status_offset),
data_status: data[status_offset + 2],
transfer_status: data[status_offset + 3],
payload: Bytes::from_array(payload_bytes),
}
}
///|
/// I&M0 (Identification & Maintenance) data.
pub(all) struct IAndM0 {
vendor_id : Int
order_id : String
serial_number : String
hardware_revision : Int
sw_revision_prefix : Byte
sw_revision_functional_enhancement : Byte
sw_revision_bugfix : Byte
sw_revision_internal_change : Byte
revision_counter : Int
profile_id : Int
profile_specific_type : Int
im_version_major : Byte
im_version_minor : Byte
im_supported : Int
} derive(Eq, Debug)
///|
/// Block type for I&M0.
pub let block_type_iam0 : Int = 32
///|
/// Block type for I&M1.
pub let block_type_iam1 : Int = 33
///|
/// Parse I&M0 from body bytes.
pub fn parse_iam0(data : Bytes, offset : Int) -> IAndM0 raise @frame.FrameError {
guard data.length() >= offset + 54 else {
raise @frame.FrameError::InvalidMacLength(data.length())
}
let vendor_id = read_u16_be(data, offset)
// order_id: 20 bytes ASCII
let order_chars : Array[Byte] = []
for i = 0; i < 20; i = i + 1 {
order_chars.push(data[offset + 2 + i])
}
let order_id = ascii_bytes_to_string(order_chars)
// serial_number: 16 bytes ASCII
let serial_chars : Array[Byte] = []
for i = 0; i < 16; i = i + 1 {
serial_chars.push(data[offset + 22 + i])
}
let serial_number = ascii_bytes_to_string(serial_chars)
IAndM0::{
vendor_id,
order_id,
serial_number,
hardware_revision: read_u16_be(data, offset + 38),
sw_revision_prefix: data[offset + 40],
sw_revision_functional_enhancement: data[offset + 41],
sw_revision_bugfix: data[offset + 42],
sw_revision_internal_change: data[offset + 43],
revision_counter: read_u16_be(data, offset + 44),
profile_id: read_u16_be(data, offset + 46),
profile_specific_type: read_u16_be(data, offset + 48),
im_version_major: data[offset + 50],
im_version_minor: data[offset + 51],
im_supported: read_u16_be(data, offset + 52),
}
}
///|
/// I&M1 data.
pub(all) struct IAndM1 {
function_tag : String
location_tag : String
} derive(Eq, Debug)
///|
/// Parse I&M1 from body bytes.
pub fn parse_iam1(data : Bytes, offset : Int) -> IAndM1 raise @frame.FrameError {
guard data.length() >= offset + 54 else {
raise @frame.FrameError::InvalidMacLength(data.length())
}
let func_chars : Array[Byte] = []
for i = 0; i < 32; i = i + 1 {
func_chars.push(data[offset + i])
}
let loc_chars : Array[Byte] = []
for i = 0; i < 22; i = i + 1 {
loc_chars.push(data[offset + 32 + i])
}
IAndM1::{
function_tag: ascii_bytes_to_trimmed_string(func_chars),
location_tag: ascii_bytes_to_trimmed_string(loc_chars),
}
}
///|
pub fn encode_iam1(data : IAndM1) -> Bytes {
let output : Array[Byte] = []
append_ascii_padded(output, data.function_tag, 32)
append_ascii_padded(output, data.location_tag, 22)
Bytes::from_array(output)
}
///|
pub fn encode_iam1_block(data : IAndM1) -> Bytes {
let output : Array[Byte] = []
push_u16_be(output, block_type_iam1)
push_u16_be(output, 0x0038)
output.push(b'\x01')
output.push(b'\x00')
append_bytes(output, encode_iam1(data))
Bytes::from_array(output)
}
///|
fn append_ascii_padded(
output : Array[Byte],
text : String,
width : Int,
) -> Unit {
let mut count = 0
for char in text {
if count < width {
output.push((char.to_int() & 0x7F).to_byte())
count = count + 1
}
}
for i = count; i < width; i = i + 1 {
output.push(b'\x20')
}
}
///|
fn ascii_bytes_to_string(bytes : Array[Byte]) -> String {
let mut s = ""
for b in bytes {
if b.to_int() == 0 {
break
}
s = s + b.to_int().unsafe_to_char().to_string()
}
s
}
///|
fn ascii_bytes_to_trimmed_string(bytes : Array[Byte]) -> String {
let mut end = bytes.length()
while end > 0 && (bytes[end - 1].to_int() == 0 || bytes[end - 1] == b'\x20') {
end = end - 1
}
let mut s = ""
for i = 0; i < end; i = i + 1 {
if bytes[i].to_int() == 0 {
return s
}
s = s + bytes[i].to_int().unsafe_to_char().to_string()
}
s
}
///|
/// Format I&M0 summary.
pub fn format_iam0(data : IAndM0) -> String {
let lines : Array[String] = []
lines.push("vendor_id=" + data.vendor_id.to_string())
lines.push("order_id=" + data.order_id)
lines.push("serial_number=" + data.serial_number)
lines.push("hardware_revision=" + data.hardware_revision.to_string())
lines.push(
"sw_revision=" +
data.sw_revision_prefix.to_int().unsafe_to_char().to_string() +
data.sw_revision_functional_enhancement.to_int().to_string() +
"." +
data.sw_revision_bugfix.to_int().to_string() +
"." +
data.sw_revision_internal_change.to_int().to_string(),
)
lines.push("profile_id=" + data.profile_id.to_string())
lines.push(
"im_version=" +
data.im_version_major.to_int().to_string() +
"." +
data.im_version_minor.to_int().to_string(),
)
lines.push("im_supported=0x" + @frame.uint16_to_hex(data.im_supported))
lines.join("\n")
}