519 lines
19 KiB
Rust
519 lines
19 KiB
Rust
use log::{warn, error};
|
|
|
|
extern crate alloc;
|
|
|
|
use alloc::vec;
|
|
use alloc::vec::Vec;
|
|
use alloc::borrow::ToOwned;
|
|
|
|
use thiserror::Error;
|
|
use crate::{enums::{FunctionCode, MessageIdentifier}, utils::calculate_checksum};
|
|
|
|
#[derive(PartialEq, Eq, Debug)]
|
|
#[must_use]
|
|
pub struct Protocol300Message {
|
|
/// Length of the user data in the telegram
|
|
pub telegram_length: u8,
|
|
/// Message Identifier, Request, Response etc.
|
|
pub message_identifier: MessageIdentifier,
|
|
/// Function Code, Read Write or RPC
|
|
pub function_code: FunctionCode,
|
|
/// Data address, 2 bytes represented as u16
|
|
pub data_address: u16,
|
|
/// Expected return bytes (if it is a request) or
|
|
/// Length of the data to write (if it is a write)
|
|
pub data_length: u8,
|
|
/// Return value of the Vitocontrol if it's a read, if it's a write message
|
|
/// it contains the number ofexpected return bytes
|
|
pub data: Vec<u8>,
|
|
/// Checksum
|
|
pub checksum: u8,
|
|
}
|
|
|
|
impl Protocol300Message {
|
|
#[inline]
|
|
/// 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);
|
|
Self {
|
|
telegram_length,
|
|
message_identifier,
|
|
function_code,
|
|
data_address,
|
|
data_length,
|
|
data: vec![],
|
|
checksum
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
/// Creates a new `Protocol300message` used to respond to a message requesting data
|
|
///
|
|
/// # Arguments
|
|
/// * `data_address`: Address of the data to read
|
|
/// * `data`: Data which should be used to respond to the message
|
|
///
|
|
/// # Panics
|
|
/// Panics if the data vec is longer than 256 (u8), can never happen in protocol 300
|
|
pub fn new_read_response_message(data_address: u16, data: &[u8]) -> Self{
|
|
let message_identifier = MessageIdentifier::Response;
|
|
let function_code = FunctionCode::VirtualREAD;
|
|
let data_length = u8::try_from(data.len()).expect("Data should never be longer than 256");
|
|
let telegram_length: u8 = 5 + data_length;
|
|
let data = data.to_owned();
|
|
let checksum = calculate_checksum(telegram_length, message_identifier, function_code, data_address, data_length, &data);
|
|
Self {
|
|
telegram_length,
|
|
message_identifier,
|
|
function_code,
|
|
data_address,
|
|
data_length,
|
|
data,
|
|
checksum
|
|
}
|
|
}
|
|
|
|
|
|
#[inline]
|
|
/// Creates a new `Protocol300message` used to respond to a message writing data
|
|
///
|
|
/// # Arguments
|
|
/// * `data_address`: Address of the data to read
|
|
/// * `data`: Data which should be used to respond to the message
|
|
///
|
|
/// # Panics
|
|
/// Panics if the data vec is longer than 256 (u8), can never happen in protocol 300
|
|
pub fn new_write_response_message(data_address: u16, written_data_length: u8) -> Self{
|
|
let message_identifier = MessageIdentifier::Response;
|
|
let function_code = FunctionCode::VirtualWRITE;
|
|
let telegram_length: u8 = 5;
|
|
let checksum = calculate_checksum(telegram_length, message_identifier, function_code, data_address, written_data_length, &[]);
|
|
Self {
|
|
telegram_length,
|
|
message_identifier,
|
|
function_code,
|
|
data_address,
|
|
data_length: written_data_length,
|
|
data: vec![],
|
|
checksum
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
/// 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);
|
|
Ok(Self {
|
|
telegram_length,
|
|
message_identifier,
|
|
function_code,
|
|
data_address,
|
|
data_length,
|
|
data: data.to_vec(),
|
|
checksum
|
|
})
|
|
}
|
|
|
|
#[inline]
|
|
/// Decode a byte string into a `Protocol300Message`
|
|
///
|
|
/// Tries to decode a vec of bytes into an `Protocol300Message`. Includes
|
|
/// checking for valid start bytes and checking the checksum to detect
|
|
/// transmission errors
|
|
///
|
|
/// # Errors
|
|
/// Returns a `MessageDecodingError` on failure. Can be caused by multiple
|
|
/// things (for details look at `MessageDecodingError`):
|
|
/// - Invalid Start byte
|
|
/// - Length does not match actual length
|
|
/// - Invalid values for Message Identifier or Function code
|
|
/// - Checksum mismatch
|
|
pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, MessageDecodingError> {
|
|
if bytes.len() < 8 {
|
|
return Err(MessageDecodingError::VecTooShort)
|
|
}
|
|
|
|
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_calculated = calculate_checksum(telegram_length, message_identifier, function_code, data_address, data_length, &data);
|
|
let checksum = bytes[telegram_length as usize + 2];
|
|
|
|
// Fail if the checksums do not match
|
|
if checksum != checksum_calculated {
|
|
return Err(MessageDecodingError::Checksum)
|
|
}
|
|
|
|
Ok(Self {
|
|
telegram_length,
|
|
message_identifier,
|
|
function_code,
|
|
data_address,
|
|
data_length,
|
|
data,
|
|
checksum
|
|
})
|
|
|
|
}
|
|
|
|
#[must_use]
|
|
#[inline]
|
|
/// Converts a `Protocol300Message` to a vec of bytes
|
|
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
|
|
|
|
}
|
|
|
|
#[inline]
|
|
/// Returns the expected length of the return telegram, include start byte but excluding ACK byte
|
|
///
|
|
/// Currently only implemented for Read and Write requests, as the rest is not needed and/or
|
|
/// unknown behaviour
|
|
///
|
|
/// # Errors
|
|
/// - `MessageReturnLengthError` - Describes the error which occured when converting
|
|
pub const fn get_expected_return_telegram_length(&self) -> Result<usize, MessageReturnLengthError> {
|
|
match (self.message_identifier, self.function_code) {
|
|
(MessageIdentifier::Request, FunctionCode::VirtualREAD) => Ok(8 + self.data_length as usize),
|
|
(MessageIdentifier::Request, FunctionCode::VirtualWRITE) => Ok(8),
|
|
(MessageIdentifier::Response, _) => Err(MessageReturnLengthError::NonsensicalRequest),
|
|
_ => Err(MessageReturnLengthError::Unimplemented())
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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] core::num::TryFromIntError),
|
|
|
|
}
|
|
|
|
/// Errors that can occur while getting the expected return length of a message
|
|
#[derive(Error, Debug, PartialEq, Eq)]
|
|
pub enum MessageReturnLengthError {
|
|
/// The data length is longer than u8
|
|
#[error("Unimplemented")]
|
|
Unimplemented(),
|
|
/// It makes no sense to get the expected length for a response
|
|
#[error("Nonsensical request")]
|
|
NonsensicalRequest
|
|
}
|
|
|
|
/// 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] core::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>),
|
|
/// Checksum does not match the calculated checksum
|
|
#[error("Checksums do not match")]
|
|
Checksum,
|
|
/// The message vec is too short
|
|
#[error("The message vec is too short")]
|
|
VecTooShort
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::{enums::{FunctionCode, MessageIdentifier}, message::{MessageCreationError, Protocol300Message}};
|
|
#[cfg(not(feature = "std"))]
|
|
extern crate alloc;
|
|
|
|
#[cfg(not(feature = "std"))]
|
|
use alloc::vec;
|
|
|
|
#[cfg(not(feature = "std"))]
|
|
use alloc::vec::Vec;
|
|
|
|
|
|
#[test]
|
|
fn new_request_message_works() {
|
|
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 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 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 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 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 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 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 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 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 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 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);
|
|
}
|
|
|
|
#[test]
|
|
fn expected_return_length_read() {
|
|
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
|
|
};
|
|
assert_eq!(10_usize, expected_message.get_expected_return_telegram_length().unwrap());
|
|
}
|
|
|
|
#[test]
|
|
fn expected_return_length_write() {
|
|
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
|
|
};
|
|
assert_eq!(8_usize, expected_message.get_expected_return_telegram_length().unwrap());
|
|
}
|
|
|
|
#[test]
|
|
fn create_read_response_message() {
|
|
let generated_message = Protocol300Message::new_read_response_message(0x5525, &[0x07, 0x01]).to_bytes();
|
|
let expected_message = vec![0x41, 0x07, 0x01, 0x01, 0x55, 0x25, 0x02, 0x07, 0x01, 0x8D];
|
|
assert_eq!(generated_message, expected_message);
|
|
}
|
|
|
|
#[test]
|
|
fn create_write_response_message() {
|
|
let generated_message = Protocol300Message::new_write_response_message(0x2323, 1).to_bytes();
|
|
let expected_message = vec![0x41, 0x05, 0x01, 0x02, 0x23, 0x23, 0x01, 0x4F];
|
|
assert_eq!(generated_message, expected_message);
|
|
}
|
|
|
|
|
|
} |