///|
/// Device-identification access category used by MEI type 0x0E.
pub(all) enum DeviceIdCategory {
Basic
Regular
Extended
Individual(Byte)
} derive(Debug, Eq)
///|
pub fn DeviceIdCategory::to_byte(category : DeviceIdCategory) -> Byte {
match category {
Basic => 1
Regular => 2
Extended => 3
Individual(value) => value
}
}
///|
pub fn device_id_category(value : Byte) -> DeviceIdCategory {
match value {
1 => Basic
2 => Regular
3 => Extended
other => Individual(other)
}
}
///|
/// One TLV object returned by a device-identification response.
pub(all) struct DeviceIdObject {
object_id : Byte
value : String
}
///|
pub fn DeviceIdObject::new(
object_id : Byte,
value : String,
) -> Result[DeviceIdObject, ModbusError] {
if value.length() > 245 {
Err(InvalidLength)
} else {
Ok({ object_id, value })
}
}
///|
/// Parsed MEI device-identification response metadata.
pub(all) struct DeviceIdentification {
category : DeviceIdCategory
conformity : Byte
more_follows : Bool
next_object_id : Byte
objects : Array[DeviceIdObject]
}
///|
/// Build a Read Device Identification request.
pub fn read_device_identification(
unit_id : Byte,
category : DeviceIdCategory,
object_id : Byte,
) -> Result[Frame, ModbusError] {
if !is_valid_unit_id(unit_id) {
Err(InvalidUnitId)
} else {
Ok({
unit_id,
pdu: { function: 43, data: [0x0E, category.to_byte(), object_id] },
})
}
}
///|
/// Encode an identification response from typed objects.
pub fn encode_device_identification(
unit_id : Byte,
category : DeviceIdCategory,
conformity : Byte,
more_follows : Bool,
next_object_id : Byte,
objects : Array[DeviceIdObject],
) -> Result[Frame, ModbusError] {
let data : Array[Byte] = [
0x0E,
0x01,
category.to_byte(),
conformity,
if more_follows {
0xFF
} else {
0
},
next_object_id,
objects.length().to_byte(),
]
for object in objects {
if object.value.length() > 255 {
return Err(InvalidLength)
}
data.push(object.object_id)
data.push(object.value.length().to_byte())
for code_unit in object.value {
data.push(code_unit.to_int().to_byte())
}
}
if data.length() > 253 {
Err(InvalidLength)
} else {
Ok({ unit_id, pdu: { function: 43, data } })
}
}
///|
/// Decode a Read Device Identification response into TLV objects.
pub fn decode_device_identification(
request : Frame,
response : Frame,
) -> Result[DeviceIdentification, ModbusError] {
match expect_normal(request, response) {
Err(error) => return Err(error)
Ok(_) => ()
}
if response.pdu.data.length() < 7 || response.pdu.data[0] != 0x0E {
return Err(InvalidData)
}
let category = device_id_category(response.pdu.data[2])
let objects : Array[DeviceIdObject] = []
let mut offset = 7
while offset < response.pdu.data.length() {
if offset + 2 > response.pdu.data.length() {
return Err(Incomplete)
}
let id = response.pdu.data[offset]
let length = response.pdu.data[offset + 1].to_int()
if offset + 2 + length > response.pdu.data.length() {
return Err(Incomplete)
}
let value = StringBuilder()
for index in 0.. objects.push(object)
Err(error) => return Err(error)
}
offset += 2 + length
}
Ok({
category,
conformity: response.pdu.data[3],
more_follows: response.pdu.data[4] != 0,
next_object_id: response.pdu.data[5],
objects,
})
}
///|
/// A reusable identity catalogue for virtual devices.
pub struct DeviceIdentityCatalog {
category : DeviceIdCategory
objects : Array[DeviceIdObject]
mut conformity : Byte
}
///|
pub fn DeviceIdentityCatalog::new(
category? : DeviceIdCategory = Basic,
) -> DeviceIdentityCatalog {
{ category, objects: [], conformity: 1 }
}
///|
pub fn DeviceIdentityCatalog::add(
self : DeviceIdentityCatalog,
object : DeviceIdObject,
) -> Result[Unit, ModbusError] {
if self.objects.length() >= 128 {
Err(CapacityExceeded)
} else {
self.objects.push(object)
Ok(())
}
}
///|
pub fn DeviceIdentityCatalog::set_conformity(
self : DeviceIdentityCatalog,
conformity : Byte,
) -> Unit {
self.conformity = conformity
}
///|
pub fn DeviceIdentityCatalog::response(
self : DeviceIdentityCatalog,
request : Frame,
) -> Result[Frame, ModbusError] {
if request.pdu.function != 43 ||
request.pdu.data.length() < 3 ||
request.pdu.data[0] != 0x0E {
return Err(InvalidFunction)
}
let requested_id = request.pdu.data[2]
let selected : Array[DeviceIdObject] = []
for object in self.objects {
if object.object_id >= requested_id {
selected.push(object)
}
}
encode_device_identification(
request.unit_id,
self.category,
self.conformity,
false,
0,
selected,
)
}
///|
pub fn DeviceIdentityCatalog::length(self : DeviceIdentityCatalog) -> Int {
self.objects.length()
}
///|
pub fn DeviceIdentityCatalog::objects(
self : DeviceIdentityCatalog,
) -> Array[DeviceIdObject] {
let out : Array[DeviceIdObject] = []
for object in self.objects {
out.push(object)
}
out
}