///|
/// Validate that a response is a normal response for a request.
fn expect_normal(
request : Frame,
response : Frame,
) -> Result[Unit, ModbusError] {
match validate_response_header(request, response) {
Err(error) => Err(error)
Ok(_) => if response.is_exception() { Err(InvalidData) } else { Ok(()) }
}
}
///|
/// Decode the exception code from a standard exception response.
pub fn exception_from_frame(
frame : Frame,
) -> Result[ExceptionCode, ModbusError] {
if !frame.is_exception() || frame.pdu.data.length() != 1 {
Err(InvalidData)
} else {
Ok(exception_code(frame.pdu.data[0]))
}
}
///|
/// Decode any response while preserving exception information.
pub fn decode_response(
request : Frame,
response : Frame,
) -> Result[ResponseValue, ModbusError] {
match validate_response_header(request, response) {
Err(error) => Err(error)
Ok(_) => Ok(response_value(response))
}
}
///|
/// Decode a read-coils or read-discrete-inputs response.
pub fn decode_bits_response(
request : Frame,
response : Frame,
) -> Result[Array[Bool], ModbusError] {
match expect_normal(request, response) {
Err(error) => return Err(error)
Ok(_) => ()
}
if response.pdu.data.length() < 1 {
return Err(Incomplete)
}
let byte_count = response.pdu.data[0].to_int()
if response.pdu.data.length() != byte_count + 1 {
return Err(InvalidByteCount)
}
let requested = request.pdu.data[2].to_uint16().shl(8) +
request.pdu.data[3].to_uint16()
let expected = (requested.to_int() + 7) / 8
if byte_count != expected {
return Err(InvalidByteCount)
}
let bytes : Array[Byte] = []
for index in 0.. Result[Array[UInt16], ModbusError] {
match expect_normal(request, response) {
Err(error) => return Err(error)
Ok(_) => ()
}
if response.pdu.data.length() < 1 {
return Err(Incomplete)
}
let byte_count = response.pdu.data[0].to_int()
let requested = request.pdu.data[2].to_uint16().shl(8) +
request.pdu.data[3].to_uint16()
if byte_count != requested.to_int() * 2 ||
response.pdu.data.length() != byte_count + 1 {
return Err(InvalidByteCount)
}
let payload : Array[Byte] = []
for index in 0.. Result[Bool, ModbusError] {
match expect_normal(request, response) {
Err(error) => return Err(error)
Ok(_) => ()
}
if response.pdu.data.length() != 4 || response.pdu.data != request.pdu.data {
return Err(InvalidData)
}
Ok(response.pdu.data[2] == 0xFF && response.pdu.data[3] == 0)
}
///|
/// Decode a single-register write echo.
pub fn decode_write_single_register(
request : Frame,
response : Frame,
) -> Result[UInt16, ModbusError] {
match expect_normal(request, response) {
Err(error) => return Err(error)
Ok(_) => ()
}
if response.pdu.data.length() != 4 || response.pdu.data != request.pdu.data {
return Err(InvalidData)
}
Ok((response.pdu.data[2].to_uint16() << 8) | response.pdu.data[3].to_uint16())
}
///|
/// Decode a multiple-write echo and return `(address, quantity)`.
pub fn decode_multiple_write(
request : Frame,
response : Frame,
) -> Result[(UInt16, UInt16), ModbusError] {
match expect_normal(request, response) {
Err(error) => return Err(error)
Ok(_) => ()
}
if response.pdu.data.length() != 4 {
return Err(InvalidLength)
}
if response.pdu.data[0] != request.pdu.data[0] ||
response.pdu.data[1] != request.pdu.data[1] ||
response.pdu.data[2] != request.pdu.data[2] ||
response.pdu.data[3] != request.pdu.data[3] {
return Err(InvalidData)
}
Ok(
(
(response.pdu.data[0].to_uint16() << 8) | response.pdu.data[1].to_uint16(),
(response.pdu.data[2].to_uint16() << 8) | response.pdu.data[3].to_uint16(),
),
)
}
///|
/// Decode a mask-write response and return `(and_mask, or_mask)`.
pub fn decode_mask_write(
request : Frame,
response : Frame,
) -> Result[(UInt16, UInt16), ModbusError] {
match expect_normal(request, response) {
Err(error) => return Err(error)
Ok(_) => ()
}
if response.pdu.data.length() != 6 || response.pdu.data != request.pdu.data {
return Err(InvalidData)
}
Ok(
(
(response.pdu.data[2].to_uint16() << 8) | response.pdu.data[3].to_uint16(),
(response.pdu.data[4].to_uint16() << 8) | response.pdu.data[5].to_uint16(),
),
)
}
///|
/// Decode the register payload of a combined read/write response.
pub fn decode_read_write_response(
request : Frame,
response : Frame,
) -> Result[Array[UInt16], ModbusError] {
match expect_normal(request, response) {
Err(error) => return Err(error)
Ok(_) => ()
}
if response.pdu.data.length() < 1 {
return Err(Incomplete)
}
let byte_count = response.pdu.data[0].to_int()
let requested = request.pdu.data[2].to_uint16().shl(8) +
request.pdu.data[3].to_uint16()
if byte_count != requested.to_int() * 2 ||
response.pdu.data.length() != byte_count + 1 {
return Err(InvalidByteCount)
}
let payload : Array[Byte] = []
for index in 0.. Result[(UInt16, Array[Byte]), ModbusError] {
match expect_normal(request, response) {
Err(error) => return Err(error)
Ok(_) => ()
}
if response.pdu.data.length() < 2 || response.pdu.data.length() % 2 != 0 {
return Err(InvalidLength)
}
let subfunction = (response.pdu.data[0].to_uint16() << 8) |
response.pdu.data[1].to_uint16()
let payload : Array[Byte] = []
for index in 2.. Result[(UInt16, UInt16), ModbusError] {
match expect_normal(request, response) {
Err(error) => return Err(error)
Ok(_) => ()
}
if response.pdu.data.length() != 4 {
return Err(InvalidLength)
}
Ok(
(
(response.pdu.data[0].to_uint16() << 8) | response.pdu.data[1].to_uint16(),
(response.pdu.data[2].to_uint16() << 8) | response.pdu.data[3].to_uint16(),
),
)
}
///|
/// Decode an exception-status response.
pub fn decode_exception_status(
request : Frame,
response : Frame,
) -> Result[Byte, ModbusError] {
match expect_normal(request, response) {
Err(error) => return Err(error)
Ok(_) => ()
}
if response.pdu.data.length() != 1 {
Err(InvalidLength)
} else {
Ok(response.pdu.data[0])
}
}
///|
/// Decode a Report Server ID response into its raw payload.
pub fn decode_server_id(
request : Frame,
response : Frame,
) -> Result[Array[Byte], ModbusError] {
match expect_normal(request, response) {
Err(error) => return Err(error)
Ok(_) => ()
}
if response.pdu.data.length() < 1 {
Err(Incomplete)
} else {
Ok(copy_bytes(response.pdu.data))
}
}
///|
/// Decode a FIFO response into the queue values.
pub fn decode_fifo_queue(
request : Frame,
response : Frame,
) -> Result[Array[UInt16], ModbusError] {
match expect_normal(request, response) {
Err(error) => return Err(error)
Ok(_) => ()
}
if response.pdu.data.length() < 4 {
return Err(Incomplete)
}
let byte_count = (response.pdu.data[0].to_uint16() << 8) |
response.pdu.data[1].to_uint16()
let fifo_count = (response.pdu.data[2].to_uint16() << 8) |
response.pdu.data[3].to_uint16()
if byte_count.to_int() != response.pdu.data.length() - 2 ||
fifo_count.to_int() * 2 != byte_count.to_int() - 2 {
return Err(InvalidByteCount)
}
let values : Array[UInt16] = []
for index in 0.. Bool {
match validate_response_header(request, response) {
Ok(_) => !response.is_exception() && response.pdu.data == request.pdu.data
Err(_) => false
}
}
///|
/// Return a short response quality classification for metrics.
pub fn response_quality(request : Frame, response : Frame) -> String {
match validate_response_header(request, response) {
Err(UnitMismatch) => "unit-mismatch"
Err(InvalidFunction) => "function-mismatch"
Err(_) => "malformed"
Ok(_) => if response.is_exception() { "exception" } else { "ok" }
}
}