///|
/// A Modbus file-record selector.
pub(all) struct FileRecordReference {
reference_type : Byte
file_number : UInt16
record_number : UInt16
record_length : UInt16
}
///|
pub fn FileRecordReference::new(
file_number : UInt16,
record_number : UInt16,
record_length : UInt16,
reference_type? : Byte = 6,
) -> Result[FileRecordReference, ModbusError] {
if record_length < 1 || record_length > 125 {
Err(InvalidQuantity)
} else {
Ok({ reference_type, file_number, record_number, record_length })
}
}
///|
pub fn FileRecordReference::encoded_length(_self : FileRecordReference) -> Int {
7
}
///|
fn encode_file_reference(
out : Array[Byte],
reference : FileRecordReference,
) -> Unit {
out.push(reference.reference_type)
out.push((reference.file_number >> 8).to_byte())
out.push(reference.file_number.to_byte())
out.push((reference.record_number >> 8).to_byte())
out.push(reference.record_number.to_byte())
out.push((reference.record_length >> 8).to_byte())
out.push(reference.record_length.to_byte())
}
///|
/// Build a Read File Record request (function 20).
pub fn read_file_record(
unit_id : Byte,
references : Array[FileRecordReference],
) -> Result[Frame, ModbusError] {
if !is_valid_unit_id(unit_id) {
return Err(InvalidUnitId)
}
if references.length() < 1 || references.length() > 35 {
return Err(InvalidQuantity)
}
let data : Array[Byte] = [0]
for reference in references {
if reference.reference_type != 6 {
return Err(InvalidData)
}
encode_file_reference(data, reference)
}
data[0] = (data.length() - 1).to_byte()
Ok({ unit_id, pdu: { function: 20, data } })
}
///|
/// A file-record write item combines a selector and its register payload.
pub(all) struct FileRecordWrite {
reference : FileRecordReference
values : Array[UInt16]
}
///|
pub fn FileRecordWrite::new(
reference : FileRecordReference,
values : Array[UInt16],
) -> Result[FileRecordWrite, ModbusError] {
if values.length() != reference.record_length.to_int() {
Err(InvalidQuantity)
} else {
Ok({ reference, values })
}
}
///|
/// Build a Write File Record request (function 21).
pub fn write_file_record(
unit_id : Byte,
records : Array[FileRecordWrite],
) -> Result[Frame, ModbusError] {
if !is_valid_unit_id(unit_id) {
return Err(InvalidUnitId)
}
if records.length() < 1 || records.length() > 35 {
return Err(InvalidQuantity)
}
let data : Array[Byte] = [0]
for record in records {
if record.reference.reference_type != 6 ||
record.values.length() != record.reference.record_length.to_int() {
return Err(InvalidData)
}
encode_file_reference(data, record.reference)
for value in record.values {
data.push((value >> 8).to_byte())
data.push(value.to_byte())
}
}
data[0] = (data.length() - 1).to_byte()
if data.length() > 253 {
Err(InvalidLength)
} else {
Ok({ unit_id, pdu: { function: 21, data } })
}
}
///|
/// Decode the values returned for a Read File Record response.
pub fn decode_file_record_response(
request : Frame,
response : Frame,
) -> Result[Array[Array[UInt16]], ModbusError] {
match expect_file_response(request, response, 20) {
Err(error) => return Err(error)
Ok(_) => ()
}
if response.pdu.data.length() < 1 ||
response.pdu.data[0].to_int() != response.pdu.data.length() - 1 {
return Err(InvalidByteCount)
}
let references = match parse_file_references(request.pdu.data) {
Ok(value) => value
Err(error) => return Err(error)
}
let output : Array[Array[UInt16]] = []
let mut offset = 1
for reference in references {
let value_count = reference.record_length.to_int()
let value_bytes = value_count * 2
if offset >= response.pdu.data.length() ||
response.pdu.data[offset] != reference.reference_type {
return Err(InvalidData)
}
if offset + 1 + value_bytes > response.pdu.data.length() {
return Err(Incomplete)
}
let values : Array[UInt16] = []
for index in 0.. Result[Unit, ModbusError] {
match expect_file_response(request, response, 21) {
Err(error) => Err(error)
Ok(_) =>
if response.pdu.data == request.pdu.data {
Ok(())
} else {
Err(InvalidData)
}
}
}
///|
fn expect_file_response(
request : Frame,
response : Frame,
function : Byte,
) -> Result[Unit, ModbusError] {
if request.pdu.function != function ||
response.pdu.function != function ||
request.unit_id != response.unit_id {
Err(InvalidFunction)
} else if response.is_exception() {
Err(InvalidData)
} else {
Ok(())
}
}
///|
/// A bounded in-memory store for file records used by simulators.
pub(all) struct FileRecordStore {
records : Array[FileRecordWrite]
max_records : Int
}
///|
pub fn FileRecordStore::new(
max_records? : Int = 64,
) -> Result[FileRecordStore, ModbusError] {
if max_records < 1 {
Err(CapacityExceeded)
} else {
Ok({ records: [], max_records })
}
}
///|
pub fn FileRecordStore::put(
self : FileRecordStore,
record : FileRecordWrite,
) -> Result[Unit, ModbusError] {
for index in 0..= self.max_records {
Err(CapacityExceeded)
} else {
self.records.push(record)
Ok(())
}
}
///|
pub fn FileRecordStore::get(
self : FileRecordStore,
reference : FileRecordReference,
) -> Result[Array[UInt16], ModbusError] {
for record in self.records {
if record.reference.file_number == reference.file_number &&
record.reference.record_number == reference.record_number {
if reference.record_length.to_int() > record.values.length() {
return Err(InvalidQuantity)
}
let out : Array[UInt16] = []
for index in 0.. Int {
self.records.length()
}
///|
pub fn FileRecordStore::clear(self : FileRecordStore) -> Unit {
self.records.clear()
}