Implemented all

This commit is contained in:
2025-11-11 22:39:18 +01:00
parent 1ce1537487
commit 4b8073c614
7 changed files with 327 additions and 39 deletions

27
Cargo.lock generated
View File

@@ -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"

View File

@@ -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"

0
README.md Normal file
View File

View File

@@ -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,

View File

@@ -1,3 +1,5 @@
mod message;
mod enums;
mod utils;
mod utils;
pub use message::Protocol300Message;

View File

@@ -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);
}
}

View File

@@ -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);
}
}