///|
/// AR Connect request assembly.
///
/// Two layers:
/// 1. IOD block layer (`encode_connect_iod_blocks`) — byte-exact per the C#
/// reference: ARBlockReq, IOCR(input), IOCR(output), ExpectedSubmodule,
/// AlarmCR, each wrapped in the standard IO block header.
/// 2. CL-RPC envelope (`encode_connect_request`) — DCE/RPC header (opnum 0,
/// PNIO device interface) + NDR args wrapper around the IOD blocks.
///
/// NOTE: the IOD block layer uses big-endian PROFINET block fields, while the
/// DCE/RPC header and NDR argument wrapper follow DREP=0x10 little-endian, as
/// in the C# reference `DceClient`/`z`/`IoRequest` path.
///|
/// Concatenate the AR Connect IOD blocks (each with its block header) in the
/// canonical Connect.req order.
pub fn encode_connect_iod_blocks(
ar : ARBlockReq,
input_iocr : IOCRBlockReq,
output_iocr : IOCRBlockReq,
expected : ExpectedSubmoduleBlockReq,
alarm : AlarmCRBlockReq,
) -> Bytes {
let output : Array[Byte] = []
append_bytes(
output,
encode_io_block(block_type_ar_req, encode_ar_block_req(ar)),
)
append_bytes(
output,
encode_io_block(block_type_iocr_req, encode_iocr_block_req(input_iocr)),
)
append_bytes(
output,
encode_io_block(block_type_iocr_req, encode_iocr_block_req(output_iocr)),
)
append_bytes(
output,
encode_io_block(
block_type_expected_submodule_req,
encode_expected_submodule_block_req(expected),
),
)
append_bytes(
output,
encode_io_block(block_type_alarm_cr_req, encode_alarm_cr_block_req(alarm)),
)
Bytes::from_array(output)
}
///|
/// Assemble a full CL-RPC Connect request UDP payload: DCE/RPC header
/// (opnum 0, PNIO device interface) + NDR args wrapper + IOD blocks.
pub fn encode_connect_request(
blocks : Bytes,
activity_uuid : @rpc.Uuid,
object_uuid? : @rpc.Uuid = @rpc.Uuid::empty(),
sequence_number? : Int = 0,
) -> Bytes raise @frame.FrameError {
let ndr = @rpc.IoRequest::{
..@rpc.IoRequest::default(),
args_length: blocks.length(),
actual_count: blocks.length(),
payload: blocks,
}
let ndr_bytes = @rpc.encode_io_request(ndr)
let hdr = @rpc.DceHeader::{
..@rpc.DceHeader::default(),
packet_type: @rpc.packet_type_request,
object_uuid,
interface_uuid: @rpc.Uuid::from_hex(@rpc.uuid_pnio_device),
activity_uuid,
interface_version: 1,
opnum: 0,
sequence_number,
body_length: ndr_bytes.length(),
}
let output : Array[Byte] = []
append_bytes(output, @rpc.encode_dce_header(hdr))
append_bytes(output, ndr_bytes)
Bytes::from_array(output)
}
///|
/// ExpectedSubmodule layout for the VTS-EX-CA0400 drive, derived from its GSDML
/// (`GSDML-V2.3-V_T-VTS-EX-CA0400`):
/// - API 0, Slot 1 = Drive (module 0x00000002): Standard telegram 1
/// (submodule 0x00000002) at subslot 0x0001, INPUT+OUTPUT 10 bytes each
/// (PZD-5/5).
/// Per PN-AL, ExpectedSubmoduleBlockReq contains only submodules referenced by
/// the IOCRs, so the DAP/system submodules are intentionally omitted here.
/// IOCR/frame-offset wiring and exact properties are tuned live in b3.
pub fn ca400_expected_submodule() -> ExpectedSubmoduleBlockReq {
let drive_slot = ExpectedSlot::{
api: 0,
slot_number: 1,
module_ident: 0x00000002,
module_properties: 0,
submodules: [
ExpectedSubmodule::{
subslot_number: 0x0001,
submodule_ident: 0x00000002,
submodule_properties: 0x0003, // INPUT_AND_OUTPUT
data_descriptions: [
DataDescription::{
data_description: data_description_input,
data_length: 10,
iocs_length: 1,
iops_length: 1,
},
DataDescription::{
data_description: data_description_output,
data_length: 10,
iocs_length: 1,
iops_length: 1,
},
],
},
],
}
ExpectedSubmoduleBlockReq::{ apis: [drive_slot] }
}
///|
/// CA400 Standard Telegram 1 startup parameter record from the GSDML
/// `IDS_TEL1/ParameterRecordDataItem Index=1234 Length=15` Const data.
pub fn ca400_tel1_parameter_record_data() -> Bytes {
Bytes::from_array([
b'\x03', b'\x03', b'\x01', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00',
b'\x60', b'\x08', b'\x60', b'\x05', b'\x60', b'\x0A',
])
}
///|
fn ca400_iocr_api() -> IocrApi {
IocrApi::{
api: 0,
io_data_objects: [
IoDataObject::{ slot_number: 1, subslot_number: 1, frame_offset: 0 },
],
iocs: [
IoDataObject::{ slot_number: 1, subslot_number: 1, frame_offset: 11 },
],
}
}
///|
/// Initial CA400 input IOCR mapping for Standard Telegram 1. With the RT
/// Ethernet transport (`LT=0x8892`, `IOCRProperties=2`), DataLength uses the
/// RT_CLASS minimum of 40 bytes; telegram data/status occupy the leading bytes.
pub fn ca400_input_iocr(
frame_id? : Int = 0x8000,
send_clock_factor? : Int = 32,
reduction_ratio? : Int = 2,
watchdog_factor? : Int = 3,
data_hold_factor? : Int = 3,
) -> IOCRBlockReq {
IOCRBlockReq::{
..IOCRBlockReq::default_input(frame_id),
data_length: 40,
send_clock_factor,
reduction_ratio,
watchdog_factor,
data_hold_factor,
apis: [ca400_iocr_api()],
}
}
///|
/// Initial CA400 output IOCR mapping for Standard Telegram 1. See
/// `ca400_input_iocr` for the 40-byte DataLength rationale.
pub fn ca400_output_iocr(
frame_id? : Int = 0x8001,
send_clock_factor? : Int = 32,
reduction_ratio? : Int = 2,
watchdog_factor? : Int = 3,
data_hold_factor? : Int = 3,
) -> IOCRBlockReq {
IOCRBlockReq::{
..IOCRBlockReq::default_output(frame_id),
data_length: 40,
send_clock_factor,
reduction_ratio,
watchdog_factor,
data_hold_factor,
apis: [ca400_iocr_api()],
}
}