Implemented all
This commit is contained in:
@@ -4,7 +4,7 @@ use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
/// Message Identifier used in the communication protocol. This is specified by Viessmann
|
||||
/// and UNACKED is rarely to never used.
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, TryFromPrimitive, IntoPrimitive, Clone, Copy, PartialEq, EnumStringify)]
|
||||
#[derive(Debug, TryFromPrimitive, IntoPrimitive, Clone, Copy, PartialEq, Eq, EnumStringify)]
|
||||
pub enum MessageIdentifier {
|
||||
Request = 0x00,
|
||||
Response = 0x01,
|
||||
@@ -16,7 +16,7 @@ pub enum MessageIdentifier {
|
||||
/// and Read and Write are usually used for simpler values, RPC can maybe be used for
|
||||
/// more complex things, but this is currently unclear
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, TryFromPrimitive, IntoPrimitive, Clone, Copy, PartialEq, EnumStringify)]
|
||||
#[derive(Debug, TryFromPrimitive, IntoPrimitive, Clone, Copy, PartialEq, Eq, EnumStringify)]
|
||||
pub enum FunctionCode {
|
||||
VirtualREAD = 0x01,
|
||||
VirtualWRITE = 0x02,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
mod message;
|
||||
mod enums;
|
||||
mod utils;
|
||||
mod utils;
|
||||
|
||||
pub use message::Protocol300Message;
|
||||
304
src/message.rs
304
src/message.rs
@@ -1,6 +1,10 @@
|
||||
use log::warn;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{enums::{FunctionCode, MessageIdentifier}, utils::calculate_checksum};
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
#[must_use]
|
||||
pub struct Protocol300Message {
|
||||
/// Length of the user data in the telegram
|
||||
pub telegram_length: u8,
|
||||
@@ -21,13 +25,18 @@ pub struct Protocol300Message {
|
||||
}
|
||||
|
||||
impl Protocol300Message {
|
||||
pub fn new_request_message(data_address: u16, data_length: u8) -> Protocol300Message{
|
||||
/// Creates a new `Protocol300message` used to request data from the device
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `data_address`: Address of the data to read
|
||||
/// * `data_length`: Length of the data to be read.
|
||||
pub fn new_request_message(data_address: u16, data_length: u8) -> Self{
|
||||
let telegram_length: u8 = 5;
|
||||
let message_identifier = MessageIdentifier::Request;
|
||||
let function_code = FunctionCode::VirtualREAD;
|
||||
let data: Vec<u8> = vec![];
|
||||
let checksum = calculate_checksum(telegram_length, message_identifier, function_code, data_address, data_length, &data);
|
||||
Protocol300Message {
|
||||
Self {
|
||||
telegram_length,
|
||||
message_identifier,
|
||||
function_code,
|
||||
@@ -38,13 +47,22 @@ impl Protocol300Message {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_write_message(data_address: u16, data: &[u8]) -> Protocol300Message{
|
||||
let data_length: u8 = data.len().try_into().expect("Data too long");
|
||||
/// Creates a new `Protocol300message` used to write data to the device
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `data_address`: Address of the data to read
|
||||
/// * `data`: The data that should be written as vec of u8. Data length will be automatically infered from the length
|
||||
/// of that vec
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if the data vec is longer than u8. This is also against the spec of Protocol 300, so it's safe to panic
|
||||
pub fn try_new_write_message(data_address: u16, data: &[u8]) -> Result<Self, MessageCreationError>{
|
||||
let data_length: u8 = data.len().try_into()?;
|
||||
let telegram_length: u8 = 5 + data_length;
|
||||
let message_identifier = MessageIdentifier::Request;
|
||||
let function_code = FunctionCode::VirtualWRITE;
|
||||
let checksum = calculate_checksum(telegram_length, message_identifier, function_code, data_address, data_length, data);
|
||||
Protocol300Message {
|
||||
Ok(Self {
|
||||
telegram_length,
|
||||
message_identifier,
|
||||
function_code,
|
||||
@@ -52,6 +70,278 @@ impl Protocol300Message {
|
||||
data_length,
|
||||
data: data.to_vec(),
|
||||
checksum
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Decode a byte string into a `Protocol300Message`
|
||||
pub fn try_from_bytes(bytes: &[u8]) -> Result<Protocol300Message, MessageDecodingError> {
|
||||
if bytes[0] != 0x41 {
|
||||
return Err(MessageDecodingError::InvalidTelegramStart(bytes[0]));
|
||||
}
|
||||
|
||||
let telegram_length: u8 = bytes[1];
|
||||
if u8::try_from(bytes.len())? != telegram_length + 3 {
|
||||
return Err(MessageDecodingError::LengthMismatch());
|
||||
}
|
||||
|
||||
let message_identifier = MessageIdentifier::try_from(bytes[2])?;
|
||||
let function_code = FunctionCode::try_from(bytes[3])?;
|
||||
let data_address = (u16::from(bytes[4]) << 8) | u16::from(bytes[5]);
|
||||
|
||||
let data_length = bytes[6];
|
||||
|
||||
let data = match (message_identifier, function_code) {
|
||||
// Read requests, write responses and CommError messages have no data content
|
||||
(MessageIdentifier::Request, FunctionCode::VirtualREAD) | (MessageIdentifier::Response, FunctionCode::VirtualWRITE) | (MessageIdentifier::CommError, _) => vec![],
|
||||
(MessageIdentifier::Request, FunctionCode::VirtualWRITE) | (MessageIdentifier::Response, FunctionCode::VirtualREAD) => {
|
||||
let mut data: Vec<u8> = Vec::new();
|
||||
for i in 0..data_length {
|
||||
data.push(bytes[7 + i as usize]);
|
||||
}
|
||||
data
|
||||
}
|
||||
(_, FunctionCode::RemoteProcedureCall) => {
|
||||
warn!("Decoding of RPCs is currently not implemented, do not expect correct data, mainly in the 'data' field");
|
||||
vec![]
|
||||
},
|
||||
(MessageIdentifier::Unacked, _) => {
|
||||
warn!("No idea what 'unacked' messages are, these will not decode correctly");
|
||||
vec![]
|
||||
},
|
||||
};
|
||||
|
||||
let checksum = calculate_checksum(telegram_length, message_identifier, function_code, data_address, data_length, &data);
|
||||
|
||||
Ok(Self {
|
||||
telegram_length,
|
||||
message_identifier,
|
||||
function_code,
|
||||
data_address,
|
||||
data_length,
|
||||
data,
|
||||
checksum
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let telegram_start_byte = 0x41;
|
||||
let address_as_bytes = self.data_address.to_be_bytes();
|
||||
let checksum = calculate_checksum(self.telegram_length, self.message_identifier, self.function_code, self.data_address, self.data_length, &self.data);
|
||||
|
||||
let mut bytes_vec = vec![
|
||||
telegram_start_byte,
|
||||
self.telegram_length,
|
||||
self.message_identifier.into(),
|
||||
self.function_code.into(),
|
||||
address_as_bytes[0],
|
||||
address_as_bytes[1],
|
||||
self.data_length,
|
||||
];
|
||||
bytes_vec.extend_from_slice(&self.data);
|
||||
bytes_vec.push(checksum);
|
||||
bytes_vec
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors that can occur while creating a message
|
||||
#[derive(Error, Debug, PartialEq, Eq)]
|
||||
pub enum MessageCreationError {
|
||||
/// The data length is longer than u8
|
||||
#[error("Data too long")]
|
||||
DataTooLong(#[from] std::num::TryFromIntError),
|
||||
}
|
||||
|
||||
/// Errors that can occur while decoding a message from bytes
|
||||
#[derive(Error, Debug, PartialEq, Eq)]
|
||||
pub enum MessageDecodingError {
|
||||
/// The data length is longer than u8
|
||||
#[error("Start of the telegram is not 0x41 but {0:02X?}")]
|
||||
InvalidTelegramStart(u8),
|
||||
/// Error getiting the length of the bytes slice, should never happen
|
||||
#[error("Error getting the length of the bytes slice")]
|
||||
NoLength(#[from] std::num::TryFromIntError),
|
||||
/// The advertiesed telegram length and actual length do not match
|
||||
#[error("The advertiesed telegram length and actual length do not match")]
|
||||
LengthMismatch(),
|
||||
/// Error getting message identifier enum variant by number
|
||||
#[error("Message Identifier not valid")]
|
||||
InvalidMessageIdentifier(#[from] num_enum::TryFromPrimitiveError<MessageIdentifier>),
|
||||
/// Error getting function code enum variant by number
|
||||
#[error("Function Code not valid")]
|
||||
InvalidFunctionCode(#[from] num_enum::TryFromPrimitiveError<FunctionCode>),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{enums::{FunctionCode, MessageIdentifier}, message::{MessageCreationError, Protocol300Message}, utils::calculate_checksum};
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_new_request_message() {
|
||||
let expected_message = Protocol300Message{
|
||||
telegram_length: 5,
|
||||
message_identifier: MessageIdentifier::Request,
|
||||
function_code: FunctionCode::VirtualREAD,
|
||||
data_address: 0x0101,
|
||||
data_length: 2,
|
||||
data: vec![],
|
||||
checksum: 0x0A
|
||||
};
|
||||
let generated_message = Protocol300Message::new_request_message(0x0101, 2);
|
||||
assert_eq!(expected_message, generated_message);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_write_message() {
|
||||
let expected_message = Protocol300Message{
|
||||
telegram_length: 9,
|
||||
message_identifier: MessageIdentifier::Request,
|
||||
function_code: FunctionCode::VirtualWRITE,
|
||||
data_address: 0x0101,
|
||||
data_length: 4,
|
||||
data: vec![0x01, 0x02, 0x03, 0x04],
|
||||
checksum: 0x1B
|
||||
};
|
||||
let generated_message = Protocol300Message::try_new_write_message(0x0101, &[0x01, 0x02, 0x03, 0x04]);
|
||||
assert_eq!(expected_message, generated_message.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_write_message_data_too_long() {
|
||||
let generated_message = Protocol300Message::try_new_write_message(0x0101, &vec![0x00; 300]);
|
||||
assert!(generated_message.is_err());
|
||||
assert!(matches!(generated_message.err().unwrap(), MessageCreationError::DataTooLong(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_received_message_read_response_from_bytes() {
|
||||
let expected_msg = Protocol300Message{
|
||||
data_address: 0x5525,
|
||||
telegram_length: 0x09,
|
||||
function_code: FunctionCode::VirtualREAD,
|
||||
message_identifier: MessageIdentifier::Response,
|
||||
data_length: 0x04,
|
||||
data: vec![0x07, 0x01, 0x27, 0x11],
|
||||
checksum: 0xC9
|
||||
};
|
||||
let bytes = vec![0x41, 0x09, 0x01, 0x01, 0x55, 0x25, 0x04, 0x07, 0x01, 0x27, 0x11, 0xC9];
|
||||
let message = Protocol300Message::try_from_bytes(&bytes).unwrap();
|
||||
assert_eq!(expected_msg, message);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_received_message_write_response_from_bytes() {
|
||||
let expected_msg = Protocol300Message{
|
||||
data_address: 0x2101,
|
||||
telegram_length: 0x05,
|
||||
function_code: FunctionCode::VirtualWRITE,
|
||||
message_identifier: MessageIdentifier::Response,
|
||||
data_length: 0x04,
|
||||
data: vec![],
|
||||
checksum: 0x2E
|
||||
};
|
||||
let bytes = vec![0x41, 0x05, 0x01, 0x02, 0x21, 0x01, 0x04, 0x2E];
|
||||
let message = Protocol300Message::try_from_bytes(&bytes).unwrap();
|
||||
assert_eq!(expected_msg, message);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_received_message_read_request_from_bytes() {
|
||||
let expected_msg = Protocol300Message{
|
||||
data_address: 0x5525,
|
||||
telegram_length: 0x05,
|
||||
function_code: FunctionCode::VirtualREAD,
|
||||
message_identifier: MessageIdentifier::Request,
|
||||
data_length: 0x02,
|
||||
data: vec![],
|
||||
checksum: 0x82
|
||||
};
|
||||
let bytes = vec![0x41, 0x05, 0x00, 0x01, 0x55, 0x25, 0x02, 0x82];
|
||||
let message = Protocol300Message::try_from_bytes(&bytes).unwrap();
|
||||
assert_eq!(expected_msg, message);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_received_message_write_request_from_bytes() {
|
||||
let expected_msg = Protocol300Message{
|
||||
data_address: 0x2323,
|
||||
telegram_length: 0x06,
|
||||
function_code: FunctionCode::VirtualWRITE,
|
||||
message_identifier: MessageIdentifier::Request,
|
||||
data_length: 0x01,
|
||||
data: vec![0x02],
|
||||
checksum: 0x51
|
||||
};
|
||||
let bytes = vec![0x41, 0x06, 0x00, 0x02, 0x23, 0x23, 0x01, 0x02, 0x51];
|
||||
let message = Protocol300Message::try_from_bytes(&bytes).unwrap();
|
||||
assert_eq!(expected_msg, message);
|
||||
}
|
||||
// TO BYTES
|
||||
#[test]
|
||||
fn test_received_message_write_request_to_bytes() {
|
||||
let test_msg = Protocol300Message{
|
||||
data_address: 0x2323,
|
||||
telegram_length: 0x06,
|
||||
function_code: FunctionCode::VirtualWRITE,
|
||||
message_identifier: MessageIdentifier::Request,
|
||||
data_length: 0x01,
|
||||
data: vec![0x02],
|
||||
checksum: 0x51
|
||||
};
|
||||
let bytes_expected = vec![0x41, 0x06, 0x00, 0x02, 0x23, 0x23, 0x01, 0x02, 0x51];
|
||||
let bytes_generated = test_msg.to_bytes();
|
||||
assert_eq!(bytes_expected, bytes_generated);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_received_message_read_request_to_bytes() {
|
||||
let test_msg = Protocol300Message{
|
||||
data_address: 0x5525,
|
||||
telegram_length: 0x05,
|
||||
function_code: FunctionCode::VirtualREAD,
|
||||
message_identifier: MessageIdentifier::Request,
|
||||
data_length: 0x02,
|
||||
data: vec![],
|
||||
checksum: 0x82
|
||||
};
|
||||
let bytes_expected = vec![0x41, 0x05, 0x00, 0x01, 0x55, 0x25, 0x02, 0x82];
|
||||
let bytes_generated = test_msg.to_bytes();
|
||||
assert_eq!(bytes_expected, bytes_generated);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_received_message_write_response_to_bytes() {
|
||||
let test_msg = Protocol300Message{
|
||||
data_address: 0x2101,
|
||||
telegram_length: 0x05,
|
||||
function_code: FunctionCode::VirtualWRITE,
|
||||
message_identifier: MessageIdentifier::Response,
|
||||
data_length: 0x04,
|
||||
data: vec![],
|
||||
checksum: 0x2E
|
||||
};
|
||||
let bytes_expected = vec![0x41, 0x05, 0x01, 0x02, 0x21, 0x01, 0x04, 0x2E];
|
||||
let bytes_generated = test_msg.to_bytes();
|
||||
assert_eq!(bytes_expected, bytes_generated);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_received_message_read_response_to_bytes() {
|
||||
let test_msg = Protocol300Message{
|
||||
data_address: 0x5525,
|
||||
telegram_length: 0x09,
|
||||
function_code: FunctionCode::VirtualREAD,
|
||||
message_identifier: MessageIdentifier::Response,
|
||||
data_length: 0x04,
|
||||
data: vec![0x07, 0x01, 0x27, 0x11],
|
||||
checksum: 0xC9
|
||||
};
|
||||
let bytes_expected = vec![0x41, 0x09, 0x01, 0x01, 0x55, 0x25, 0x04, 0x07, 0x01, 0x27, 0x11, 0xC9];
|
||||
let bytes_generated = test_msg.to_bytes();
|
||||
assert_eq!(bytes_expected, bytes_generated);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -46,4 +46,10 @@ mod tests {
|
||||
};
|
||||
assert_eq!(calculate_checksum(test_msg.telegram_length, test_msg.message_identifier, test_msg.function_code, test_msg.data_address, test_msg.data_length, &test_msg.data), 0x8D);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_received_message_wrapping_add_from_bytes() {
|
||||
let checksum = calculate_checksum(0x06, MessageIdentifier::Request, FunctionCode::VirtualWRITE, 0x2323, 0x01, &[0xB6]);
|
||||
assert_eq!(checksum, 0x05);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user