///|
/// ESI JSON interchange model shared between XML import tools and SII-facing consumers.
///|
let esi_mailbox_aoe_mask : UInt = 0x0001U
///|
let esi_mailbox_coe_mask : UInt = 0x0004U
///|
let esi_mailbox_eoe_mask : UInt = 0x0002U
///|
let esi_mailbox_foe_mask : UInt = 0x0008U
///|
let esi_mailbox_soe_mask : UInt = 0x0010U
///|
let esi_mailbox_voe_mask : UInt = 0x0020U
///|
pub(all) struct EsiJsonMailbox {
data_link_layer : Bool
aoe : Bool
coe_present : Bool
coe_sdo_info : Bool
coe_pdo_assign : Bool
coe_pdo_config : Bool
coe_complete_access : Bool
eoe : Bool
foe : Bool
soe : Bool
voe : Bool
} derive(Eq, Debug, ToJson)
///|
pub(all) struct EsiJsonSmEntry {
physical_start : UInt
length : UInt
control : Byte
status : Byte
enable : Bool
sm_type : String
} derive(Eq, Debug, ToJson)
///|
pub(all) struct EsiJsonPdoEntry {
base_index : UInt
depend_on_slot_index : Bool
subindex : Byte
bit_length : Byte
name : String
data_type_name : String
} derive(Eq, Debug, ToJson)
///|
pub(all) struct EsiJsonPdo {
direction : String
base_index : UInt
depend_on_slot_pdo : Bool
sync_manager : Byte
name : String
entries : Array[EsiJsonPdoEntry]
} derive(Eq, Debug, ToJson)
///|
pub(all) struct EsiJsonDcMode {
name : String
description : String
assign_activate : UInt
cycle_time_sync0 : UInt
sync0_factor : Int
sync1_factor : Int
} derive(Eq, Debug, ToJson)
///|
pub(all) struct EsiJsonSelectedModule {
module_ident : UInt
slot_index : Int
} derive(Eq, Debug, ToJson)
///|
pub(all) struct EsiJsonInitCommand {
transition : String
index : UInt
subindex : Byte
data : Bytes
} derive(Eq, Debug, ToJson)
///|
pub(all) struct EsiJsonDevice {
vendor_id : UInt
product_code : UInt
revision : UInt
serial : UInt
physics : String
device_name : String
type_name : String
profile_no : UInt?
slot_index_increment : UInt
slot_pdo_increment : UInt
mailbox : EsiJsonMailbox
fmmu : Array[String]
sync_managers : Array[EsiJsonSmEntry]
dc_modes : Array[EsiJsonDcMode]
eeprom_byte_size : UInt
config_data : Bytes
bootstrap : Bytes
default_modules : Array[EsiJsonSelectedModule]
init_commands : Array[EsiJsonInitCommand]
rx_pdos : Array[EsiJsonPdo]
tx_pdos : Array[EsiJsonPdo]
} derive(Eq, Debug, ToJson)
///|
pub(all) struct EsiJsonModule {
module_ident : UInt
module_name : String
rx_pdos : Array[EsiJsonPdo]
tx_pdos : Array[EsiJsonPdo]
} derive(Eq, Debug, ToJson)
///|
pub(all) struct EsiJsonDocument {
devices : Array[EsiJsonDevice]
modules : Array[EsiJsonModule]
} derive(Eq, Debug, ToJson)
///|
pub(all) struct EsiOfflineSiiReport {
position : UInt
word_count : Int
eeprom : Bytes
sii : @mailbox.SiiFullInfo
preamble : @mailbox.SiiPreamble
standard : @mailbox.SiiStandardMetadata
header : @mailbox.SiiData
general : @mailbox.SiiGeneral?
strings : Array[String]
fmmu : Array[@mailbox.FmmuDirection]
sync_managers : Array[@mailbox.SiiSmEntry]
dc_clocks : Array[@mailbox.SiiDcClock]
profile_no : UInt?
startup_mailbox_commands : Array[StartupMailboxCommand]
tx_pdo : Array[@mailbox.SiiPdo]
rx_pdo : Array[@mailbox.SiiPdo]
categories : Array[@mailbox.SiiCategoryEntry]
}
///|
pub fn parse_esi_json_document(
json_content : String,
) -> EsiJsonDocument raise @hal.EcError {
let parsed = @json.parse(json_content[:]) catch {
err =>
raise @hal.EcError::EsiParseFailed(
"ESI JSON parse failed: " + err.to_string(),
)
}
parse_esi_json_document_value(parsed)
}
///|
pub fn esi_json_to_string(
document : EsiJsonDocument,
indent? : Int = 2,
) -> String {
document.to_json().stringify(indent~)
}
///|
pub fn offline_sii_report_from_esi_json(
json_content : String,
device_index : Int,
) -> EsiOfflineSiiReport raise @hal.EcError {
offline_sii_report_from_esi_document(
parse_esi_json_document(json_content),
device_index,
)
}
///|
pub fn offline_sii_report_from_esi_document(
document : EsiJsonDocument,
device_index : Int,
) -> EsiOfflineSiiReport raise @hal.EcError {
if device_index < 0 || device_index >= document.devices.length() {
raise @hal.EcError::EsiParseFailed(
"device index out of range: " +
device_index.to_string() +
" (devices=" +
document.devices.length().to_string() +
")",
)
}
build_offline_sii_report(
document.devices[device_index],
document.modules,
device_index,
)
}
///|
fn parse_esi_json_document_value(
json : Json,
) -> EsiJsonDocument raise @hal.EcError {
guard json
is { "devices": Array(devices_json), "modules": Array(modules_json), .. } else {
raise @hal.EcError::EsiParseFailed(
"ESI JSON decode failed: missing devices/modules arrays",
)
}
{
devices: devices_json.map(device_json => parse_esi_json_device(device_json)),
modules: modules_json.map(module_json => parse_esi_json_module(module_json)),
}
}
///|
fn parse_esi_json_device(json : Json) -> EsiJsonDevice raise @hal.EcError {
guard json
is {
"vendor_id": vendor_id_json,
"product_code": product_code_json,
"revision": revision_json,
"serial": serial_json,
"physics": physics_json,
"device_name": device_name_json,
"type_name": type_name_json,
"profile_no": profile_no_json,
"slot_index_increment": slot_index_increment_json,
"slot_pdo_increment": slot_pdo_increment_json,
"mailbox": mailbox_json,
"fmmu": Array(fmmu_json),
"sync_managers": Array(sync_managers_json),
"dc_modes": Array(dc_modes_json),
"eeprom_byte_size": eeprom_byte_size_json,
"config_data": config_data_json,
"bootstrap": bootstrap_json,
"default_modules": Array(default_modules_json),
"init_commands": Array(init_commands_json),
"rx_pdos": Array(rx_pdos_json),
"tx_pdos": Array(tx_pdos_json),
..
} else {
raise @hal.EcError::EsiParseFailed(
"ESI JSON decode failed: invalid device entry",
)
}
{
vendor_id: decode_esi_json_uint(vendor_id_json),
product_code: decode_esi_json_uint(product_code_json),
revision: decode_esi_json_uint(revision_json),
serial: decode_esi_json_uint(serial_json),
physics: decode_esi_json_string(physics_json),
device_name: decode_esi_json_string(device_name_json),
type_name: decode_esi_json_string(type_name_json),
profile_no: decode_esi_json_optional_uint(profile_no_json),
slot_index_increment: decode_esi_json_uint(slot_index_increment_json),
slot_pdo_increment: decode_esi_json_uint(slot_pdo_increment_json),
mailbox: parse_esi_json_mailbox(mailbox_json),
fmmu: fmmu_json.map(item => decode_esi_json_string(item)),
sync_managers: sync_managers_json.map(item => parse_esi_json_sm_entry(item)),
dc_modes: dc_modes_json.map(item => parse_esi_json_dc_mode(item)),
eeprom_byte_size: decode_esi_json_uint(eeprom_byte_size_json),
config_data: decode_esi_json_bytes(config_data_json),
bootstrap: decode_esi_json_bytes(bootstrap_json),
default_modules: default_modules_json.map(item => {
parse_esi_json_selected_module(item)
}),
init_commands: init_commands_json.map(item => {
parse_esi_json_init_command(item)
}),
rx_pdos: rx_pdos_json.map(item => parse_esi_json_pdo(item)),
tx_pdos: tx_pdos_json.map(item => parse_esi_json_pdo(item)),
}
}
///|
fn parse_esi_json_init_command(
json : Json,
) -> EsiJsonInitCommand raise @hal.EcError {
guard json
is {
"transition": transition_json,
"index": index_json,
"subindex": subindex_json,
"data": data_json,
..
} else {
raise @hal.EcError::EsiParseFailed(
"ESI JSON decode failed: invalid init command entry",
)
}
{
transition: decode_esi_json_string(transition_json),
index: decode_esi_json_uint(index_json),
subindex: decode_esi_json_byte(subindex_json),
data: decode_esi_json_bytes(data_json),
}
}
///|
fn parse_esi_json_module(json : Json) -> EsiJsonModule raise @hal.EcError {
guard json
is {
"module_ident": module_ident_json,
"module_name": module_name_json,
"rx_pdos": Array(rx_pdos_json),
"tx_pdos": Array(tx_pdos_json),
..
} else {
raise @hal.EcError::EsiParseFailed(
"ESI JSON decode failed: invalid module entry",
)
}
{
module_ident: decode_esi_json_uint(module_ident_json),
module_name: decode_esi_json_string(module_name_json),
rx_pdos: rx_pdos_json.map(item => parse_esi_json_pdo(item)),
tx_pdos: tx_pdos_json.map(item => parse_esi_json_pdo(item)),
}
}
///|
fn parse_esi_json_mailbox(json : Json) -> EsiJsonMailbox raise @hal.EcError {
guard json
is {
"data_link_layer": data_link_layer_json,
"aoe": aoe_json,
"coe_present": coe_present_json,
"coe_sdo_info": coe_sdo_info_json,
"coe_pdo_assign": coe_pdo_assign_json,
"coe_pdo_config": coe_pdo_config_json,
"coe_complete_access": coe_complete_access_json,
"eoe": eoe_json,
"foe": foe_json,
"soe": soe_json,
"voe": voe_json,
..
} else {
raise @hal.EcError::EsiParseFailed(
"ESI JSON decode failed: invalid mailbox entry",
)
}
{
data_link_layer: decode_esi_json_bool(data_link_layer_json),
aoe: decode_esi_json_bool(aoe_json),
coe_present: decode_esi_json_bool(coe_present_json),
coe_sdo_info: decode_esi_json_bool(coe_sdo_info_json),
coe_pdo_assign: decode_esi_json_bool(coe_pdo_assign_json),
coe_pdo_config: decode_esi_json_bool(coe_pdo_config_json),
coe_complete_access: decode_esi_json_bool(coe_complete_access_json),
eoe: decode_esi_json_bool(eoe_json),
foe: decode_esi_json_bool(foe_json),
soe: decode_esi_json_bool(soe_json),
voe: decode_esi_json_bool(voe_json),
}
}
///|
fn parse_esi_json_sm_entry(json : Json) -> EsiJsonSmEntry raise @hal.EcError {
guard json
is {
"physical_start": physical_start_json,
"length": length_json,
"control": control_json,
"status": status_json,
"enable": enable_json,
"sm_type": sm_type_json,
..
} else {
raise @hal.EcError::EsiParseFailed(
"ESI JSON decode failed: invalid sync manager entry",
)
}
{
physical_start: decode_esi_json_uint(physical_start_json),
length: decode_esi_json_uint(length_json),
control: decode_esi_json_byte(control_json),
status: decode_esi_json_byte(status_json),
enable: decode_esi_json_bool(enable_json),
sm_type: decode_esi_json_string(sm_type_json),
}
}
///|
fn parse_esi_json_pdo(json : Json) -> EsiJsonPdo raise @hal.EcError {
guard json
is {
"direction": direction_json,
"base_index": base_index_json,
"depend_on_slot_pdo": depend_on_slot_pdo_json,
"sync_manager": sync_manager_json,
"name": name_json,
"entries": Array(entries_json),
..
} else {
raise @hal.EcError::EsiParseFailed(
"ESI JSON decode failed: invalid PDO entry",
)
}
{
direction: decode_esi_json_string(direction_json),
base_index: decode_esi_json_uint(base_index_json),
depend_on_slot_pdo: decode_esi_json_bool(depend_on_slot_pdo_json),
sync_manager: decode_esi_json_byte(sync_manager_json),
name: decode_esi_json_string(name_json),
entries: entries_json.map(item => parse_esi_json_pdo_entry(item)),
}
}
///|
fn parse_esi_json_pdo_entry(json : Json) -> EsiJsonPdoEntry raise @hal.EcError {
guard json
is {
"base_index": base_index_json,
"depend_on_slot_index": depend_on_slot_index_json,
"subindex": subindex_json,
"bit_length": bit_length_json,
"name": name_json,
"data_type_name": data_type_name_json,
..
} else {
raise @hal.EcError::EsiParseFailed(
"ESI JSON decode failed: invalid PDO entry item",
)
}
{
base_index: decode_esi_json_uint(base_index_json),
depend_on_slot_index: decode_esi_json_bool(depend_on_slot_index_json),
subindex: decode_esi_json_byte(subindex_json),
bit_length: decode_esi_json_byte(bit_length_json),
name: decode_esi_json_string(name_json),
data_type_name: decode_esi_json_string(data_type_name_json),
}
}
///|
fn parse_esi_json_dc_mode(json : Json) -> EsiJsonDcMode raise @hal.EcError {
guard json
is {
"name": name_json,
"description": description_json,
"assign_activate": assign_activate_json,
"cycle_time_sync0": cycle_time_sync0_json,
"sync0_factor": sync0_factor_json,
"sync1_factor": sync1_factor_json,
..
} else {
raise @hal.EcError::EsiParseFailed(
"ESI JSON decode failed: invalid DC entry",
)
}
{
name: decode_esi_json_string(name_json),
description: decode_esi_json_string(description_json),
assign_activate: decode_esi_json_uint(assign_activate_json),
cycle_time_sync0: decode_esi_json_uint(cycle_time_sync0_json),
sync0_factor: decode_esi_json_int(sync0_factor_json),
sync1_factor: decode_esi_json_int(sync1_factor_json),
}
}
///|
fn parse_esi_json_selected_module(
json : Json,
) -> EsiJsonSelectedModule raise @hal.EcError {
guard json
is { "module_ident": module_ident_json, "slot_index": slot_index_json, .. } else {
raise @hal.EcError::EsiParseFailed(
"ESI JSON decode failed: invalid selected module entry",
)
}
{
module_ident: decode_esi_json_uint(module_ident_json),
slot_index: decode_esi_json_int(slot_index_json),
}
}
///|
fn decode_esi_json_string(json : Json) -> String raise @hal.EcError {
@json.from_json(json) catch {
err =>
raise @hal.EcError::EsiParseFailed(
"ESI JSON decode failed: " + err.to_string(),
)
}
}
///|
fn decode_esi_json_uint(json : Json) -> UInt raise @hal.EcError {
@json.from_json(json) catch {
err =>
raise @hal.EcError::EsiParseFailed(
"ESI JSON decode failed: " + err.to_string(),
)
}
}
///|
fn decode_esi_json_optional_uint(json : Json) -> UInt? raise @hal.EcError {
if json is Null {
return None
}
Some(decode_esi_json_uint(json))
}
///|
fn decode_esi_json_int(json : Json) -> Int raise @hal.EcError {
@json.from_json(json) catch {
err =>
raise @hal.EcError::EsiParseFailed(
"ESI JSON decode failed: " + err.to_string(),
)
}
}
///|
fn decode_esi_json_bool(json : Json) -> Bool raise @hal.EcError {
@json.from_json(json) catch {
err =>
raise @hal.EcError::EsiParseFailed(
"ESI JSON decode failed: " + err.to_string(),
)
}
}
///|
fn decode_esi_json_byte(json : Json) -> Byte raise @hal.EcError {
let value = decode_esi_json_int(json)
if value < 0 || value > 255 {
raise @hal.EcError::EsiParseFailed(
"ESI JSON decode failed: byte value out of range",
)
}
value.to_byte()
}
///|
fn decode_esi_json_bytes(json : Json) -> Bytes raise @hal.EcError {
let direct_result : Result[Bytes, Error] = Result::Ok(
{
let decoded : Bytes = @json.from_json(json)
decoded
},
) catch {
err => Result::Err(err)
}
match direct_result {
Ok(decoded) => return decoded
Err(_) => ()
}
guard json is Array(byte_json) else {
raise @hal.EcError::EsiParseFailed(
"ESI JSON decode failed: expected byte array",
)
}
let values : Array[Byte] = []
for item in byte_json {
values.push(decode_esi_json_byte(item))
}
Bytes::from_array(values)
}
///|
fn build_offline_sii_report(
device : EsiJsonDevice,
modules : Array[EsiJsonModule],
device_index : Int,
) -> EsiOfflineSiiReport raise @hal.EcError {
let (selected_tx_pdos, selected_rx_pdos) = resolve_selected_module_pdos(
device, modules,
)
let tx_pdo_projection = if selected_tx_pdos.is_empty() {
device.tx_pdos
} else {
selected_tx_pdos
}
let rx_pdo_projection = if selected_rx_pdos.is_empty() {
device.rx_pdos
} else {
selected_rx_pdos
}
let strings = collect_projected_sii_strings(
device, tx_pdo_projection, rx_pdo_projection,
)
let general = build_general_projection(device, strings)
let fmmu = device.fmmu.map(direction => map_esi_json_fmmu_direction(direction))
let sync_managers = device.sync_managers.map(sm => {
materialize_sii_sm_entry(sm)
})
let tx_pdo = tx_pdo_projection.map(pdo => materialize_sii_pdo(pdo, strings))
let rx_pdo = rx_pdo_projection.map(pdo => materialize_sii_pdo(pdo, strings))
let dc_clocks = device.dc_modes.map(dc => materialize_sii_dc(dc, strings))
let startup_mailbox_commands = project_esi_startup_mailbox_commands(device)
let categories = build_sii_categories(
fmmu, sync_managers, strings, general, tx_pdo, rx_pdo, dc_clocks,
)
let byte_size = max_uint(
device.eeprom_byte_size,
minimum_eeprom_size(categories),
)
let normalized_byte_size = max_uint(byte_size, 128U)
let preamble = build_sii_preamble(device)
let standard : @mailbox.SiiStandardMetadata = {
eeprom_size_raw: encode_eeprom_size_raw(normalized_byte_size),
version: 1U,
}
let header = build_sii_header(device, sync_managers)
let eeprom = serialize_sii_to_eeprom(
normalized_byte_size.reinterpret_as_int(),
preamble,
standard,
header,
categories,
)
let sii : @mailbox.SiiFullInfo = {
eeprom,
preamble,
standard,
header,
categories,
strings,
general: Some(general),
timeouts: None,
fmmu,
fmmu_ex: [],
sync_managers,
sync_units: [],
tx_pdo,
rx_pdo,
dc_clocks,
}
{
position: device_index.reinterpret_as_uint(),
word_count: (normalized_byte_size / 2U).reinterpret_as_int(),
eeprom,
sii,
preamble,
standard,
header,
general: Some(general),
strings,
fmmu,
sync_managers,
dc_clocks,
profile_no: device.profile_no,
startup_mailbox_commands,
tx_pdo,
rx_pdo,
categories,
}
}
///|
fn project_esi_startup_mailbox_commands(
device : EsiJsonDevice,
) -> Array[StartupMailboxCommand] {
let commands : Array[StartupMailboxCommand] = []
for command in device.init_commands {
match map_esi_init_transition(command.transition) {
Some(transition) =>
commands.push({
transition,
index: command.index,
subindex: command.subindex,
data: command.data,
})
None => ()
}
}
commands
}
///|
fn map_esi_init_transition(transition : String) -> StartupCommandTransition? {
match transition {
"IP" => Some(InitToPreOp)
"IB" => Some(InitToBoot)
"PI" => Some(PreOpToInit)
"PS" => Some(PreOpToSafeOp)
"SI" => Some(SafeOpToInit)
"SP" => Some(SafeOpToPreOp)
"SO" => Some(SafeOpToOp)
"OI" => Some(OpToInit)
"OP" => Some(OpToPreOp)
"OS" => Some(OpToSafeOp)
"BI" => Some(BootToInit)
_ => None
}
}
///|
fn resolve_selected_module_pdos(
device : EsiJsonDevice,
modules : Array[EsiJsonModule],
) -> (Array[EsiJsonPdo], Array[EsiJsonPdo]) {
let tx_pdos : Array[EsiJsonPdo] = []
let rx_pdos : Array[EsiJsonPdo] = []
for selection in device.default_modules {
match find_module_projection(modules, selection.module_ident) {
Some(module_projection) => {
for pdo in module_projection.tx_pdos {
tx_pdos.push(apply_slot_selection(device, pdo, selection.slot_index))
}
for pdo in module_projection.rx_pdos {
rx_pdos.push(apply_slot_selection(device, pdo, selection.slot_index))
}
}
None => ()
}
}
(tx_pdos, rx_pdos)
}
///|
fn find_module_projection(
modules : Array[EsiJsonModule],
module_ident : UInt,
) -> EsiJsonModule? {
for module_projection in modules {
if module_projection.module_ident == module_ident {
return Some(module_projection)
}
}
None
}
///|
fn apply_slot_selection(
device : EsiJsonDevice,
pdo : EsiJsonPdo,
slot_index : Int,
) -> EsiJsonPdo {
let updated_entries = pdo.entries.map(fn(entry) {
{
..entry,
base_index: if entry.depend_on_slot_index {
entry.base_index +
device.slot_index_increment * slot_index.reinterpret_as_uint()
} else {
entry.base_index
},
depend_on_slot_index: false,
}
})
{
..pdo,
base_index: if pdo.depend_on_slot_pdo {
pdo.base_index +
device.slot_pdo_increment * slot_index.reinterpret_as_uint()
} else {
pdo.base_index
},
depend_on_slot_pdo: false,
entries: updated_entries,
}
}
///|
fn collect_projected_sii_strings(
device : EsiJsonDevice,
tx_pdos : Array[EsiJsonPdo],
rx_pdos : Array[EsiJsonPdo],
) -> Array[String] raise @hal.EcError {
let strings : Array[String] = []
ignore(intern_sii_string(strings, preferred_device_group_name(device)))
ignore(intern_sii_string(strings, preferred_device_name(device)))
for dc in device.dc_modes {
ignore(intern_sii_string(strings, dc.name))
ignore(intern_sii_string(strings, dc.description))
}
for pdo in tx_pdos {
ignore(intern_sii_string(strings, pdo.name))
for entry in pdo.entries {
ignore(intern_sii_string(strings, entry.name))
}
}
for pdo in rx_pdos {
ignore(intern_sii_string(strings, pdo.name))
for entry in pdo.entries {
ignore(intern_sii_string(strings, entry.name))
}
}
strings
}
///|
fn preferred_device_group_name(device : EsiJsonDevice) -> String {
if device.type_name != "" {
device.type_name
} else {
device.device_name
}
}
///|
fn preferred_device_name(device : EsiJsonDevice) -> String {
if device.device_name != "" {
device.device_name
} else {
device.type_name
}
}
///|
fn intern_sii_string(
strings : Array[String],
text : String,
) -> Byte raise @hal.EcError {
if text == "" {
return b'\x00'
}
for i = 0; i < strings.length(); i = i + 1 {
if strings[i] == text {
return (i + 1).to_byte()
}
}
if strings.length() >= 255 {
raise @hal.EcError::EsiParseFailed("too many synthesized SII strings")
}
strings.push(text)
strings.length().to_byte()
}
///|
fn build_general_projection(
device : EsiJsonDevice,
strings : Array[String],
) -> @mailbox.SiiGeneral raise @hal.EcError {
let ports = decode_physical_ports(device.physics)
{
group_idx: intern_sii_string(strings, preferred_device_group_name(device)),
image_idx: b'\x00',
order_idx: b'\x00',
name_idx: intern_sii_string(strings, preferred_device_name(device)),
coe_details: build_coe_details_byte(device.mailbox),
foe_supported: device.mailbox.foe,
eoe_supported: device.mailbox.eoe,
soe_supported: device.mailbox.soe,
soe_channels: if device.mailbox.soe {
b'\x01'
} else {
b'\x00'
},
ds402_channels: if is_ds402_profile(device.profile_no) {
b'\x01'
} else {
b'\x00'
},
sysman_class: b'\x00',
flags: build_general_flags(device),
current_on_ebus: 0,
group_idx_dup: intern_sii_string(
strings,
preferred_device_group_name(device),
),
physical_port_0: ports[0],
physical_port_1: ports[1],
physical_port_2: ports[2],
physical_port_3: ports[3],
physical_memory_address: 0U,
}
}
///|
fn is_ds402_profile(profile_no : UInt?) -> Bool {
match profile_no {
Some(402U) => true
_ => false
}
}
///|
fn build_coe_details_byte(mailbox : EsiJsonMailbox) -> Byte {
(if mailbox.coe_present { b'\x01' } else { b'\x00' }) |
(if mailbox.coe_sdo_info { b'\x02' } else { b'\x00' }) |
(if mailbox.coe_pdo_assign { b'\x04' } else { b'\x00' }) |
(if mailbox.coe_pdo_config { b'\x08' } else { b'\x00' }) |
(if mailbox.coe_complete_access { b'\x20' } else { b'\x00' })
}
///|
fn build_general_flags(device : EsiJsonDevice) -> Byte {
let mut has_active_dc = false
for dc in device.dc_modes {
if dc.assign_activate != 0U {
has_active_dc = true
break
}
}
(if has_active_dc { b'\x01' } else { b'\x00' }) |
(if device.mailbox.data_link_layer { b'\x04' } else { b'\x00' })
}
///|
fn decode_physical_ports(physics : String) -> Array[Byte] {
let ports : Array[Byte] = [b'\x00', b'\x00', b'\x00', b'\x00']
let bytes = @utf8.encode(physics)
for i = 0; i < bytes.length() && i < 4; i = i + 1 {
ports[i] = match bytes[i] {
b'Y' | b'H' | b'K' => b'\x01'
b'E' => b'\x03'
_ => b'\x00'
}
}
ports
}
///|
fn materialize_sii_sm_entry(sm : EsiJsonSmEntry) -> @mailbox.SiiSmEntry {
{
physical_start: sm.physical_start,
length: sm.length,
control: sm.control,
status: sm.status,
activate: if sm.enable {
b'\x01'
} else {
b'\x00'
},
sm_type: map_esi_json_sm_type(sm.sm_type),
}
}
///|
fn materialize_sii_pdo(
pdo : EsiJsonPdo,
strings : Array[String],
) -> @mailbox.SiiPdo raise @hal.EcError {
{
pdo_index: pdo.base_index,
entry_count: pdo.entries.length().to_byte(),
sync_manager: pdo.sync_manager,
synchronization: b'\x00',
name_idx: intern_sii_string(strings, pdo.name),
flags: 0U,
entries: pdo.entries.map(entry => {
entry_index: entry.base_index,
subindex: entry.subindex,
name_idx: intern_sii_string(strings, entry.name),
data_type: map_esi_data_type(entry.data_type_name, entry.bit_length),
bit_length: entry.bit_length,
flags: 0U,
}),
}
}
///|
fn map_esi_data_type(type_name : String, bit_length : Byte) -> Byte {
match type_name {
"BOOL" => b'\x01'
"SINT" => b'\x02'
"INT" => b'\x03'
"DINT" => b'\x04'
"USINT" | "BYTE" => b'\x05'
"UINT" | "WORD" => b'\x06'
"UDINT" | "DWORD" => b'\x07'
_ =>
match bit_length {
b'\x08' => b'\x05'
b'\x10' => b'\x06'
b'\x20' => b'\x07'
_ => b'\x00'
}
}
}
///|
fn materialize_sii_dc(
dc : EsiJsonDcMode,
strings : Array[String],
) -> @mailbox.SiiDcClock raise @hal.EcError {
{
cycle_time_0: dc.cycle_time_sync0,
shift_time_0: 0U,
shift_time_1: 0U,
sync1_cycle_factor: dc.sync1_factor,
assign_activate: dc.assign_activate,
sync0_cycle_factor: dc.sync0_factor,
name_idx: intern_sii_string(strings, dc.name),
description_idx: intern_sii_string(strings, dc.description),
}
}
///|
fn map_esi_json_fmmu_direction(direction : String) -> @mailbox.FmmuDirection {
match direction {
"outputs" => Outputs
"inputs" => Inputs
"dynamic-outputs" => DynamicOutputs
"dynamic-inputs" => DynamicInputs
_ => MailboxState
}
}
///|
fn map_esi_json_sm_type(sm_type : String) -> @mailbox.SmType {
match sm_type {
"mailbox-out" => MailboxOut
"mailbox-in" => MailboxIn
"process-data-out" => ProcessDataOut
"process-data-in" => ProcessDataIn
"dynamic-process-data-out" => DynamicProcessDataOut
"dynamic-process-data-in" => DynamicProcessDataIn
_ => Unused
}
}
///|
fn write_sii_word(
eeprom : FixedArray[Byte],
word_offset : Int,
value : UInt,
) -> Unit {
let byte_offset = word_offset * 2
if byte_offset + 1 < eeprom.length() {
eeprom[byte_offset] = (value & 0xFFU).reinterpret_as_int().to_byte()
eeprom[byte_offset + 1] = ((value >> 8) & 0xFFU)
.reinterpret_as_int()
.to_byte()
}
}
///|
fn write_sii_dword(
eeprom : FixedArray[Byte],
word_offset : Int,
value : UInt,
) -> Unit {
write_sii_word(eeprom, word_offset, value & 0xFFFFU)
write_sii_word(eeprom, word_offset + 1, (value >> 16) & 0xFFFFU)
}
///|
fn serialize_sii_to_eeprom(
size : Int,
preamble : @mailbox.SiiPreamble,
standard : @mailbox.SiiStandardMetadata,
header : @mailbox.SiiData,
categories : Array[@mailbox.SiiCategoryEntry],
) -> Bytes {
let buf : FixedArray[Byte] = FixedArray::make(size, b'\x00')
// Preamble: words 0-7
write_sii_word(buf, 0, preamble.pdi_control)
write_sii_word(buf, 1, preamble.pdi_config)
write_sii_word(buf, 2, preamble.sync_impulse_length_raw)
write_sii_word(buf, 3, preamble.pdi_config2)
write_sii_word(buf, 4, preamble.configured_station_alias)
// Header: words 8+
write_sii_dword(buf, 8, header.vendor_id)
write_sii_dword(buf, 10, header.product_code)
write_sii_dword(buf, 12, header.revision)
write_sii_dword(buf, 14, header.serial)
write_sii_word(buf, 20, header.bootstrap_rx_offset)
write_sii_word(buf, 21, header.bootstrap_rx_size)
write_sii_word(buf, 22, header.bootstrap_tx_offset)
write_sii_word(buf, 23, header.bootstrap_tx_size)
write_sii_word(buf, 24, header.mailbox_rx_offset)
write_sii_word(buf, 25, header.mailbox_rx_size)
write_sii_word(buf, 26, header.mailbox_tx_offset)
write_sii_word(buf, 27, header.mailbox_tx_size)
write_sii_word(buf, 28, header.mailbox_protocols)
// Standard metadata: words 62-63
write_sii_word(buf, 62, standard.eeprom_size_raw)
write_sii_word(buf, 63, standard.version)
// Categories: word 64+
let mut word_pos = 64
for entry in categories {
let data_byte_len = entry.data.length()
let size_words = (data_byte_len + 1) / 2
write_sii_word(buf, word_pos, entry.type_code)
write_sii_word(buf, word_pos + 1, size_words.reinterpret_as_uint())
let byte_pos = (word_pos + 2) * 2
for i in 0.. @mailbox.SiiPreamble {
{
pdi_control: read_u16_from_bytes(device.config_data, 0),
pdi_config: read_u16_from_bytes(device.config_data, 2),
sync_impulse_length_raw: read_u16_from_bytes(device.config_data, 4),
pdi_config2: read_u16_from_bytes(device.config_data, 6),
configured_station_alias: 0U,
checksum: 0U,
checksum_ok: false,
}
}
///|
fn build_sii_header(
device : EsiJsonDevice,
sync_managers : Array[@mailbox.SiiSmEntry],
) -> @mailbox.SiiData {
{
vendor_id: device.vendor_id,
product_code: device.product_code,
revision: device.revision,
serial: device.serial,
bootstrap_rx_offset: read_u16_from_bytes(device.bootstrap, 0),
bootstrap_rx_size: read_u16_from_bytes(device.bootstrap, 2),
bootstrap_tx_offset: read_u16_from_bytes(device.bootstrap, 4),
bootstrap_tx_size: read_u16_from_bytes(device.bootstrap, 6),
mailbox_rx_offset: if sync_managers.length() > 0 {
sync_managers[0].physical_start
} else {
0U
},
mailbox_rx_size: if sync_managers.length() > 0 {
sync_managers[0].length
} else {
0U
},
mailbox_tx_offset: if sync_managers.length() > 1 {
sync_managers[1].physical_start
} else {
0U
},
mailbox_tx_size: if sync_managers.length() > 1 {
sync_managers[1].length
} else {
0U
},
mailbox_protocols: build_mailbox_protocol_mask(device.mailbox),
firmware_version: 0U,
hardware_version: 0U,
production_date_raw: 0U,
pdi_control_1: b'\x00',
esc_config_b0: b'\x00',
pdi_config_1: 0U,
pdi_config_1_ext: 0U,
esc_config_b4: 0U,
esc_config_b5: 0U,
esc_config_b6: 0U,
checksum_b: 0U,
esc_config_b_present: false,
}
}
///|
fn known_sii_category_code(category : @mailbox.SiiCategory) -> UInt {
match category {
Strings => 0x0AU
DataTypes => 0x14U
General => 0x1EU
Fmmu => 0x28U
SyncManager => 0x29U
FmmuEx => 0x2AU
SyncUnit => 0x2BU
TxPdo => 0x32U
RxPdo => 0x33U
Dc => 0x3CU
Timeouts => 0x46U
Dictionary => 0x50U
Hardware => 0x5AU
VendorInformation => 0x64U
Images => 0x6EU
Raw => 0U
}
}
///|
fn build_mailbox_protocol_mask(mailbox : EsiJsonMailbox) -> UInt {
(if mailbox.aoe { esi_mailbox_aoe_mask } else { 0U }) |
(if mailbox.eoe { esi_mailbox_eoe_mask } else { 0U }) |
(if mailbox.coe_present { esi_mailbox_coe_mask } else { 0U }) |
(if mailbox.foe { esi_mailbox_foe_mask } else { 0U }) |
(if mailbox.soe { esi_mailbox_soe_mask } else { 0U }) |
(if mailbox.voe { esi_mailbox_voe_mask } else { 0U })
}
///|
fn read_u16_from_bytes(data : Bytes, byte_offset : Int) -> UInt {
if byte_offset + 1 >= data.length() {
0U
} else {
data[byte_offset].to_uint() | (data[byte_offset + 1].to_uint() << 8)
}
}
///|
fn build_sii_categories(
fmmu : Array[@mailbox.FmmuDirection],
sync_managers : Array[@mailbox.SiiSmEntry],
strings : Array[String],
general : @mailbox.SiiGeneral,
tx_pdo : Array[@mailbox.SiiPdo],
rx_pdo : Array[@mailbox.SiiPdo],
dc_clocks : Array[@mailbox.SiiDcClock],
) -> Array[@mailbox.SiiCategoryEntry] {
let categories : Array[@mailbox.SiiCategoryEntry] = []
if !strings.is_empty() {
categories.push({
category: Strings,
type_code: known_sii_category_code(Strings),
data: encode_sii_strings(strings),
})
}
categories.push({
category: General,
type_code: known_sii_category_code(General),
data: encode_sii_general(general),
})
if !fmmu.is_empty() {
categories.push({
category: Fmmu,
type_code: known_sii_category_code(Fmmu),
data: encode_sii_fmmu(fmmu),
})
}
if !sync_managers.is_empty() {
categories.push({
category: SyncManager,
type_code: known_sii_category_code(SyncManager),
data: encode_sii_sync_managers(sync_managers),
})
}
if !tx_pdo.is_empty() {
categories.push({
category: TxPdo,
type_code: known_sii_category_code(TxPdo),
data: encode_sii_pdos(tx_pdo),
})
}
if !rx_pdo.is_empty() {
categories.push({
category: RxPdo,
type_code: known_sii_category_code(RxPdo),
data: encode_sii_pdos(rx_pdo),
})
}
for dc in dc_clocks {
categories.push({
category: Dc,
type_code: known_sii_category_code(Dc),
data: encode_sii_dc(dc),
})
}
categories
}
///|
fn encode_sii_strings(strings : Array[String]) -> Bytes {
let values : Array[Byte] = [strings.length().to_byte()]
for text in strings {
let bytes = @utf8.encode(text)
values.push(bytes.length().to_byte())
for byte in bytes {
values.push(byte)
}
}
pad_even_bytes(Bytes::from_array(values))
}
///|
fn encode_sii_general(general : @mailbox.SiiGeneral) -> Bytes {
let values : Array[Byte] = []
values.push(general.group_idx)
values.push(general.image_idx)
values.push(general.order_idx)
values.push(general.name_idx)
values.push(b'\x00')
values.push(general.coe_details)
values.push(if general.foe_supported { b'\x01' } else { b'\x00' })
values.push(if general.eoe_supported { b'\x01' } else { b'\x00' })
values.push(general.soe_channels)
values.push(general.ds402_channels)
values.push(general.sysman_class)
values.push(general.flags)
push_i16(values, general.current_on_ebus)
values.push(b'\x00')
values.push(b'\x00')
values.push(
((general.physical_port_1.to_uint() << 4) |
general.physical_port_0.to_uint())
.reinterpret_as_int()
.to_byte(),
)
values.push(
((general.physical_port_3.to_uint() << 4) |
general.physical_port_2.to_uint())
.reinterpret_as_int()
.to_byte(),
)
push_u16(values, general.physical_memory_address)
pad_even_bytes(Bytes::from_array(values))
}
///|
fn encode_sii_fmmu(fmmu : Array[@mailbox.FmmuDirection]) -> Bytes {
let values : Array[Byte] = []
for direction in fmmu {
values.push(
match direction {
Outputs => b'\x01'
Inputs => b'\x02'
MailboxState => b'\x03'
DynamicOutputs => b'\x05'
DynamicInputs => b'\x06'
},
)
}
pad_even_bytes(Bytes::from_array(values))
}
///|
fn encode_sii_sync_managers(sms : Array[@mailbox.SiiSmEntry]) -> Bytes {
let values : Array[Byte] = []
for sm in sms {
push_u16(values, sm.physical_start)
push_u16(values, sm.length)
values.push(sm.control)
values.push(sm.status)
values.push(sm.activate)
values.push(
match sm.sm_type {
MailboxOut => b'\x01'
MailboxIn => b'\x02'
ProcessDataOut => b'\x03'
ProcessDataIn => b'\x04'
DynamicProcessDataOut => b'\x05'
DynamicProcessDataIn => b'\x06'
Unused => b'\x00'
},
)
}
Bytes::from_array(values)
}
///|
fn encode_sii_pdos(pdos : Array[@mailbox.SiiPdo]) -> Bytes {
let values : Array[Byte] = []
for pdo in pdos {
push_u16(values, pdo.pdo_index)
values.push(pdo.entry_count)
values.push(pdo.sync_manager)
values.push(pdo.synchronization)
values.push(pdo.name_idx)
push_u16(values, pdo.flags)
for entry in pdo.entries {
push_u16(values, entry.entry_index)
values.push(entry.subindex)
values.push(entry.name_idx)
values.push(entry.data_type)
values.push(entry.bit_length)
push_u16(values, entry.flags)
}
}
Bytes::from_array(values)
}
///|
fn encode_sii_dc(dc : @mailbox.SiiDcClock) -> Bytes {
let values : Array[Byte] = []
push_u32(values, dc.cycle_time_0)
push_u32(values, dc.shift_time_0)
push_u32(values, dc.shift_time_1)
push_i16(values, dc.sync1_cycle_factor)
push_u16(values, dc.assign_activate)
push_i16(values, dc.sync0_cycle_factor)
values.push(dc.name_idx)
values.push(dc.description_idx)
values.push(b'\x00')
values.push(b'\x00')
values.push(b'\x00')
values.push(b'\x00')
Bytes::from_array(values)
}
///|
fn push_u16(values : Array[Byte], value : UInt) -> Unit {
values.push((value & 0xFFU).reinterpret_as_int().to_byte())
values.push(((value >> 8) & 0xFFU).reinterpret_as_int().to_byte())
}
///|
fn push_u32(values : Array[Byte], value : UInt) -> Unit {
values.push((value & 0xFFU).reinterpret_as_int().to_byte())
values.push(((value >> 8) & 0xFFU).reinterpret_as_int().to_byte())
values.push(((value >> 16) & 0xFFU).reinterpret_as_int().to_byte())
values.push(((value >> 24) & 0xFFU).reinterpret_as_int().to_byte())
}
///|
fn push_i16(values : Array[Byte], value : Int) -> Unit {
let encoded = if value < 0 {
(value + 0x10000).reinterpret_as_uint()
} else {
value.reinterpret_as_uint()
}
push_u16(values, encoded)
}
///|
fn pad_even_bytes(data : Bytes) -> Bytes {
if data.length() % 2 == 0 {
data
} else {
let values : Array[Byte] = []
for byte in data {
values.push(byte)
}
values.push(b'\x00')
Bytes::from_array(values)
}
}
///|
fn minimum_eeprom_size(categories : Array[@mailbox.SiiCategoryEntry]) -> UInt {
let mut total = 128U + 4U
for entry in categories {
total = total + 4U + entry.data.length().reinterpret_as_uint()
}
total
}
///|
fn encode_eeprom_size_raw(byte_size : UInt) -> UInt {
if byte_size <= 0x80U {
0U
} else {
(byte_size - 0x80U) >> 7
}
}
///|
fn max_uint(left : UInt, right : UInt) -> UInt {
if left >= right {
left
} else {
right
}
}