Implemented all
This commit is contained in:
27
Cargo.lock
generated
27
Cargo.lock
generated
@@ -132,12 +132,6 @@ dependencies = [
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.82"
|
||||
@@ -227,9 +221,9 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"enum_stringify",
|
||||
"log",
|
||||
"num_enum",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
@@ -248,12 +242,6 @@ version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
@@ -283,19 +271,6 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.145"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
|
||||
21
Cargo.toml
21
Cargo.toml
@@ -2,15 +2,30 @@
|
||||
name = "protocol_300"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
description = "Crate to encode/decode and send/receive telegrams using the Protocol 300 by Viessmann to communicate with most of their control units"
|
||||
license = "GPLv3"
|
||||
repository = "gitea.tobasmaier.me/tobimai/protocol_300"
|
||||
readme = "README.md"
|
||||
keywords = ["decode", "encode", "serial"]
|
||||
categories = ["encoding", "decoding"]
|
||||
|
||||
# [features]
|
||||
# json = ["serde_json"]
|
||||
|
||||
[dependencies]
|
||||
serde = "1.0.228"
|
||||
serde_json = "1.0.145"
|
||||
serde = { version = "1.0"}
|
||||
# serde_json = { version = "1.0", optional = true }
|
||||
thiserror = "2.0.17"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
enum_stringify = "0.6.4"
|
||||
num_enum = "0.7.5"
|
||||
log = "0.4.28"
|
||||
|
||||
|
||||
[lints.clippy]
|
||||
pedantic = "warn"
|
||||
pedantic = "warn"
|
||||
suspicious = { level = "deny", priority = -1 }
|
||||
perf = { level = "deny", priority = -1 }
|
||||
cargo = "warn"
|
||||
nursery = "warn"
|
||||
restrictions = "warn"
|
||||
|
||||
@@ -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