add sml + powermeter

This commit is contained in:
Paul Zinselmeyer 2024-05-03 16:30:31 +02:00
parent 4e48060bfa
commit 2395014400
Signed by: pfzetto
GPG key ID: B471A1AF06C895FD
12 changed files with 1307 additions and 4 deletions

2
.gitignore vendored
View file

@ -1,4 +1,4 @@
Cargo.toml Cargo.lock
target target
.env .env
result result

62
Cargo.lock generated
View file

@ -70,6 +70,14 @@ dependencies = [
"stm32f1xx-hal", "stm32f1xx-hal",
] ]
[[package]]
name = "canome_derive"
version = "0.1.0"
dependencies = [
"quote",
"syn 2.0.60",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.0" version = "1.0.0"
@ -108,6 +116,21 @@ dependencies = [
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "crc"
version = "3.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636"
dependencies = [
"crc-catalog",
]
[[package]]
name = "crc-catalog"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
[[package]] [[package]]
name = "critical-section" name = "critical-section"
version = "1.1.2" version = "1.1.2"
@ -236,6 +259,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
dependencies = [ dependencies = [
"hash32", "hash32",
"serde",
"stable_deref_trait", "stable_deref_trait",
] ]
@ -365,6 +389,44 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "serde"
version = "1.0.200"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.200"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
]
[[package]]
name = "sml_parser"
version = "0.1.0"
dependencies = [
"crc",
"defmt",
"heapless",
"sml_parser_derive",
]
[[package]]
name = "sml_parser_derive"
version = "0.1.0"
dependencies = [
"quote",
"syn 1.0.109",
]
[[package]] [[package]]
name = "stable_deref_trait" name = "stable_deref_trait"
version = "1.2.0" version = "1.2.0"

View file

@ -1,7 +1,8 @@
[workspace] [workspace]
resolver = "2" resolver = "2"
members = [ members = [
"canome" "canome",
"canome_derive",
"sml_parser",
"sml_parser/sml_parser_derive",
] ]

View file

@ -8,6 +8,7 @@ pub mod can;
pub mod contact; pub mod contact;
pub mod cover; pub mod cover;
pub mod light; pub mod light;
pub mod power;
pub mod state; pub mod state;
#[cfg(feature = "stm32")] #[cfg(feature = "stm32")]
pub mod stm32; pub mod stm32;

41
canome/src/power.rs Normal file
View file

@ -0,0 +1,41 @@
use defmt::Format;
#[derive(Default, Debug, Format, Clone, Copy, PartialEq, Eq)]
pub struct PowermeterState {
pub total: u32,
pub current: u32,
}
#[cfg(feature = "can")]
mod can {
use crate::{can::CanDataFormat, Decode, Encode};
use super::PowermeterState;
impl Encode<CanDataFormat> for PowermeterState {
fn encode(self) -> CanDataFormat {
let total = self.total.to_be_bytes();
let current = self.current.to_be_bytes();
CanDataFormat::new(&[
total[0], total[1], total[2], total[3], current[0], current[1], current[2],
current[3],
])
}
}
impl Decode<CanDataFormat> for PowermeterState {
fn decode(value: CanDataFormat) -> Result<Self, CanDataFormat>
where
Self: Sized,
{
let data = value.data();
if data.len() != 8 {
return Err(value);
}
let total = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
let current = u32::from_be_bytes([data[4], data[4], data[5], data[6]]);
Ok(Self { total, current })
}
}
}

11
canome_derive/Cargo.toml Normal file
View file

@ -0,0 +1,11 @@
[package]
name = "canome_derive"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = true
[dependencies]
syn = "2.0"
quote = "1.0"

62
canome_derive/src/lib.rs Normal file
View file

@ -0,0 +1,62 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{parenthesized, Data, Expr, Fields, GenericParam, Path};
#[proc_macro_derive(Channel)]
pub fn derive_channel(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
impl_channel(&ast)
}
fn impl_channel(input: &syn::DeriveInput) -> TokenStream {
let name = &input.ident;
let canome_attr = input
.attrs
.iter()
.find(|x| x.path().is_ident("canome"))
.unwrap();
let mut group: Expr = syn::parse_quote!(());
canome_attr
.parse_nested_meta(|meta| {
if meta.path.is_ident("group") {
let content;
parenthesized!(content in meta.input );
group = content.parse()?;
return Ok(());
}
Err(meta.error("unrecognized canome attr"))
})
.unwrap();
let mut generics = input.generics.clone();
let tx_cap_param = syn::parse_quote!(const TX_CAP: usize);
generics.params.push(GenericParam::Const(tx_cap_param));
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
match &input.data {
Data::Struct(str) => match &str.fields {
Fields::Unnamed(fields) => {
if fields.unnamed.len() != 1 {
unimplemented!();
}
let field = fields.unnamed.first().unwrap();
let field_type = &field.ty;
let group = "";
quote! {
impl #impl_generics Channel<BxCanBus<TX_CAP>> for #name #ty_generics #where_clause{
const ID: BxCanId = #group;
type State = #field_type;
}
}
.into()
}
_ => unimplemented!(),
},
_ => unimplemented!(),
}
}

10
sml_parser/Cargo.toml Normal file
View file

@ -0,0 +1,10 @@
[package]
name = "sml_parser"
version = "0.1.0"
edition = "2021"
[dependencies]
heapless = { version = "0.8.0", features = ["serde"]}
sml_parser_derive = {path = "sml_parser_derive"}
crc = "3.2"
defmt = "0.3.6"

View file

@ -0,0 +1,11 @@
[package]
name = "sml_parser_derive"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = true
[dependencies]
syn = "1.0"
quote = "1.0"

View file

@ -0,0 +1,52 @@
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::Data;
#[proc_macro_derive(SmlType)]
pub fn derive_sml_type(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
impl_syl_type(&ast)
}
fn impl_syl_type(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
match &ast.data {
Data::Struct(s) => {
let fields = &s
.fields
.iter()
.map(|x| {
let name = &x.ident.as_ref().unwrap();
quote! {
#name: buf.parse()?
}
})
.collect::<Vec<_>>();
let num_fields = s.fields.len();
let lifetimes = ast.generics.lifetimes().collect::<Vec<_>>();
quote! {
impl<'a> SmlType<'a> for #name<#(#lifetimes),*> {
fn parse(buf: &mut ParserContext<'a>) -> Result<Self, Error>
where
Self: Sized,
{
let tlf = TypeLengthField::parse(buf)?;
if tlf.kind() != FieldType::List || tlf.len() != #num_fields {
return Err(Error::ExpectedStruct(core::any::type_name::<Self>()));
}
Ok(Self {
#(#fields),*
})
}
}
}
.into()
}
_ => unimplemented!(),
}
}

805
sml_parser/src/lib.rs Normal file
View file

@ -0,0 +1,805 @@
#![no_std]
use core::fmt::Debug;
use core::slice::SliceIndex;
use defmt::Format;
use primitives::{FieldType, ListIter, ListOf, OctetString, TypeLengthField};
use sml_parser_derive::SmlType;
pub mod primitives;
const CRC16: crc::Crc<u16> = crc::Crc::<u16>::new(&crc::CRC_16_IBM_SDLC);
#[derive(Debug, Format)]
pub enum Error {
ExpectedSigned,
ExpectedUnsigned,
ExpectedOctetString,
ExpectedBool,
ExpectedList,
ExpectedSmlMessage,
ExpectedEndOfSmlMsg,
ExpectedSmlPublicOpenReq,
ExpectedSmlTimestampLocal,
ExpectedSmlPublicCloseReq,
ExpectedSmlPublicCloseRes,
UnsupportedTLF,
ExpectedStruct(&'static str),
ExpectedEnum(&'static str),
ExpectedOption,
InvalidCRC,
ExpectedEscapeSeq,
InvalidPadding,
ExpectedAbortOnError,
Custom,
}
pub trait SmlType<'a> {
fn parse(buf: &mut ParserContext<'a>) -> Result<Self, Error>
where
Self: Sized;
}
#[derive(Clone)]
pub struct ParserContext<'a>(pub &'a [u8]);
impl<'a> ParserContext<'a> {
pub fn parse<T: SmlType<'a>>(&mut self) -> Result<T, Error> {
T::parse(self)
}
pub fn peek<I: SliceIndex<[u8]>>(&self, range: I) -> Result<&'a I::Output, Error> {
Ok(&self.0[range])
}
pub fn peek_until(&self, other: &ParserContext<'a>) -> Result<&'a [u8], Error> {
if let Some(offset) = other.index_within(self) {
self.peek(..offset)
} else {
self.peek(..)
}
}
pub fn read(&mut self, length: usize) -> Result<&'a [u8], Error> {
let val = &self.0[..length];
self.0 = &self.0[length..];
Ok(val)
}
pub fn move_fwd(&mut self, dist: usize) -> Result<(), Error> {
self.0 = &self.0[dist..];
Ok(())
}
fn index_within(&self, outer: &Self) -> Option<usize> {
let outer_beg = outer.0.as_ptr() as usize;
let outer_end = outer_beg + outer.0.len();
let inner_beg = self.0.as_ptr() as usize;
let inner_end = inner_beg + self.0.len();
if inner_beg >= outer_beg && inner_end <= outer_end {
Some(inner_beg - outer_beg)
} else {
None
}
}
}
/// representation of an SML File that holds multiple [`SmlMessage`].
pub struct SmlFile<'a> {
data: &'a [u8],
len: usize,
}
impl<'a> SmlType<'a> for SmlFile<'a> {
fn parse(buf: &mut ParserContext<'a>) -> Result<Self, Error>
where
Self: Sized,
{
let crc_buf = buf.clone();
if buf.peek(..8)? != [0x1b, 0x1b, 0x1b, 0x1b, 0x01, 0x01, 0x01, 0x01] {
return Err(Error::ExpectedEscapeSeq);
}
buf.move_fwd(8)?;
let prev_buf = buf.clone();
let mut len = 0;
while ![0x00, 0x1b].contains(buf.peek(0)?) {
SmlMessage::parse(buf)?;
len += 1;
}
let data = prev_buf.peek_until(buf)?;
let mut fill = 0;
while *buf.peek(0)? == 0 {
fill += 1;
buf.move_fwd(1)?;
}
if buf.peek(..5)? != [0x1b, 0x1b, 0x1b, 0x1b, 0x1a] {
return Err(Error::ExpectedEscapeSeq);
}
buf.move_fwd(5)?;
let should_fill = *buf.peek(0)?;
buf.move_fwd(1)?;
if fill != should_fill {
return Err(Error::InvalidPadding);
}
let crc_data = crc_buf.peek_until(buf)?;
let mut should_crc = [0; 2];
should_crc.copy_from_slice(buf.peek(..2)?);
let should_crc = u16::from_be_bytes(should_crc);
let crc = CRC16.checksum(crc_data).swap_bytes();
if crc != should_crc {
return Err(Error::InvalidCRC);
}
Ok(Self { data, len })
}
}
impl<'a> SmlFile<'a> {
pub fn iter(&self) -> ListIter<'a, SmlMessage<'a>> {
ListIter::new(self.data, self.len)
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl<'a> Debug for SmlFile<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("SmlFile")
.field("len", &self.len)
.field("messages", &self.iter())
.finish()
}
}
/// representation of the SML_Message Sequence
#[derive(Debug)]
pub struct SmlMessage<'a> {
pub transaction_id: OctetString<'a>,
pub group_no: u8,
pub abort_on_error: AbortOnError,
pub message_body: SmlMessageBody<'a>,
pub crc16: u16,
pub end_of_sml_msg: EndOfSmlMsg,
}
impl<'a> SmlType<'a> for SmlMessage<'a> {
fn parse(buf: &mut ParserContext<'a>) -> Result<Self, Error>
where
Self: Sized,
{
let pre_buf = buf.clone();
let tlf = TypeLengthField::parse(buf)?;
if tlf.kind() != FieldType::List || tlf.len() != 6usize {
return Err(Error::ExpectedStruct(core::any::type_name::<Self>()));
}
let transaction_id = buf.parse()?;
let group_no = buf.parse()?;
let abort_on_error = buf.parse()?;
let message_body = buf.parse()?;
let data_for_crc = pre_buf.peek_until(buf)?;
let crc16 = buf.parse()?;
if CRC16.checksum(data_for_crc).swap_bytes() != crc16 {
return Err(Error::InvalidCRC);
}
Ok(Self {
transaction_id,
group_no,
abort_on_error,
message_body,
crc16,
end_of_sml_msg: buf.parse()?,
})
}
}
#[derive(Debug)]
pub enum SmlMessageBody<'a> {
OpenRequest(SmlPublicOpenReq<'a>),
OpenResponse(SmlPublicOpenRes<'a>),
CloseRequest(SmlPublicCloseReq<'a>),
CloseResponse(SmlPublicCloseRes<'a>),
GetProfilePackRequest(SmlGetProfilePackReq<'a>),
GetProfilePackResponse(SmlGetProfilePackRes<'a>),
GetProfileListRequest(SmlGetProfileListReq<'a>),
GetProfileListResponse(SmlGetProfileListRes<'a>),
GetProcParameterRequest(SmlGetProcParameterReq<'a>),
GetProcParameterResponse(SmlGetProcParameterRes<'a>),
SetProcParameterRequest(SmlSetProcParameterReq<'a>),
GetListRequest(SmlGetListReq<'a>),
GetListResponse(SmlGetListRes<'a>),
GetCosemRequest(SmlGetCosemReq<'a>),
GetCosemResponse(SmlGetCosemRes<'a>),
SetCosemRequest(SmlSetCosemReq<'a>),
SetCosemResponse(SmlSetCosemRes<'a>),
ActionCosemRequest(SmlActionCosemReq<'a>),
ActionCosemResponse(SmlActionCosemRes<'a>),
AttentionResponse(SmlAttentionRes<'a>),
}
impl<'a> SmlType<'a> for SmlMessageBody<'a> {
fn parse(buf: &mut ParserContext<'a>) -> Result<Self, Error>
where
Self: Sized,
{
use SmlMessageBody as S;
let tlf = TypeLengthField::parse(buf)?;
if tlf.kind() != FieldType::List || tlf.len() != 2 {
return Err(Error::ExpectedEnum(core::any::type_name::<Self>()));
}
let disc: u32 = buf.parse()?;
match disc {
0x00000100 => buf.parse().map(S::OpenRequest),
0x00000101 => buf.parse().map(S::OpenResponse),
0x00000200 => buf.parse().map(S::CloseRequest),
0x00000201 => buf.parse().map(S::CloseResponse),
0x00000300 => buf.parse().map(S::GetProfilePackRequest),
0x00000301 => buf.parse().map(S::GetProfilePackResponse),
0x00000400 => buf.parse().map(S::GetProfileListRequest),
0x00000401 => buf.parse().map(S::GetProfileListResponse),
0x00000500 => buf.parse().map(S::GetProcParameterRequest),
0x00000501 => buf.parse().map(S::GetProcParameterResponse),
0x00000600 => buf.parse().map(S::SetProcParameterRequest),
0x00000700 => buf.parse().map(S::GetListRequest),
0x00000701 => buf.parse().map(S::GetListResponse),
0x00000800 => buf.parse().map(S::GetCosemRequest),
0x00000801 => buf.parse().map(S::GetCosemResponse),
0x00000900 => buf.parse().map(S::SetCosemRequest),
0x00000901 => buf.parse().map(S::SetCosemResponse),
0x00000a00 => buf.parse().map(S::ActionCosemRequest),
0x00000a01 => buf.parse().map(S::ActionCosemResponse),
0x0000ff01 => buf.parse().map(S::AttentionResponse),
_ => Err(Error::ExpectedEnum(core::any::type_name::<Self>())),
}
}
}
#[derive(Debug, SmlType)]
pub struct SmlPublicOpenReq<'a> {
pub codepage: Option<OctetString<'a>>,
pub client_id: OctetString<'a>,
pub req_file_id: OctetString<'a>,
pub server_id: Option<OctetString<'a>>,
pub username: Option<OctetString<'a>>,
pub password: Option<OctetString<'a>>,
pub sml_version: Option<u8>,
}
#[derive(Debug, SmlType)]
pub struct SmlPublicOpenRes<'a> {
pub codepage: Option<OctetString<'a>>,
pub client_id: Option<OctetString<'a>>,
pub req_file_id: OctetString<'a>,
pub server_id: OctetString<'a>,
pub ref_time: Option<SmlTime>,
pub sml_version: Option<u8>,
}
#[derive(Debug, SmlType)]
pub struct SmlPublicCloseReq<'a> {
pub global_signature: Option<SmlSignature<'a>>,
}
#[derive(Debug, SmlType)]
pub struct SmlPublicCloseRes<'a> {
pub global_signature: Option<SmlSignature<'a>>,
}
#[derive(Debug, SmlType)]
pub struct SmlGetProfilePackReq<'a> {
pub server_id: Option<OctetString<'a>>,
pub username: Option<OctetString<'a>>,
pub password: Option<OctetString<'a>>,
pub with_rawdata: Option<bool>,
pub begin_time: Option<SmlTime>,
pub end_time: Option<SmlTime>,
pub paramter_tree_path: SmlTreePath<'a>,
pub object_list: Option<ListOf<'a, SmlObjReqEntry<'a>>>,
pub das_details: Option<SmlTree<'a>>,
}
pub type SmlObjReqEntry<'a> = OctetString<'a>;
#[derive(Debug, SmlType)]
pub struct SmlGetProfilePackRes<'a> {
pub server_id: OctetString<'a>,
pub act_time: SmlTime,
pub reg_period: u32,
pub paramter_tree_path: SmlTreePath<'a>,
pub header_list: ListOf<'a, SmlProfObjHeaderEntry<'a>>,
pub period_list: ListOf<'a, SmlProfObjPeriodEntry<'a>>,
pub rawdata: Option<OctetString<'a>>,
pub profile_signature: Option<SmlSignature<'a>>,
}
#[derive(Debug, SmlType)]
pub struct SmlProfObjHeaderEntry<'a> {
pub obj_name: OctetString<'a>,
pub unit: SmlUnit,
pub scaler: u8,
}
#[derive(Debug, SmlType)]
pub struct SmlProfObjPeriodEntry<'a> {
pub val_time: SmlTime,
pub status: u64,
pub value_list: ListOf<'a, SmlValueEntry<'a>>,
pub period_signature: Option<SmlSignature<'a>>,
}
#[derive(Debug, SmlType)]
pub struct SmlValueEntry<'a> {
pub value: SmlValue<'a>,
pub value_signature: Option<SmlSignature<'a>>,
}
pub type SmlUnit = u8;
#[derive(Debug, SmlType)]
pub struct SmlGetProfileListReq<'a> {
pub server_id: Option<OctetString<'a>>,
pub username: Option<OctetString<'a>>,
pub password: Option<OctetString<'a>>,
pub with_rawdata: Option<bool>,
pub begin_time: Option<SmlTime>,
pub end_time: Option<SmlTime>,
pub paramter_tree_path: SmlTreePath<'a>,
pub object_list: Option<ListOf<'a, SmlObjReqEntry<'a>>>,
pub das_details: Option<SmlTree<'a>>,
}
#[derive(Debug, SmlType)]
pub struct SmlGetProfileListRes<'a> {
pub server_id: OctetString<'a>,
pub act_time: SmlTime,
pub reg_period: u32,
pub paramter_tree_path: SmlTreePath<'a>,
pub val_time: SmlTime,
pub status: u64,
pub period_list: ListOf<'a, SmlPeriodEntry<'a>>,
pub rawdata: Option<OctetString<'a>>,
pub period_signature: Option<SmlSignature<'a>>,
}
#[derive(Debug, SmlType)]
pub struct SmlPeriodEntry<'a> {
pub obj_name: OctetString<'a>,
pub unit: SmlUnit,
pub scaler: i8,
pub value: SmlValue<'a>,
pub value_signature: Option<SmlSignature<'a>>,
}
#[derive(Debug, SmlType)]
pub struct SmlGetProcParameterReq<'a> {
pub server_id: Option<OctetString<'a>>,
pub username: Option<OctetString<'a>>,
pub password: Option<OctetString<'a>>,
pub paramter_tree_path: SmlTreePath<'a>,
pub attribute: Option<OctetString<'a>>,
}
pub type SmlTreePath<'a> = ListOf<'a, OctetString<'a>>;
#[derive(Debug, SmlType)]
pub struct SmlGetProcParameterRes<'a> {
pub server_id: OctetString<'a>,
pub paramter_tree_path: SmlTreePath<'a>,
pub paramter_tree: SmlTree<'a>,
}
#[derive(Debug, SmlType)]
pub struct SmlTree<'a> {
pub parameter_name: OctetString<'a>,
pub parameter_value: Option<SmlProcParValue<'a>>,
pub child_list: Option<ListOf<'a, SmlTree<'a>>>,
}
#[derive(Debug, SmlType)]
pub struct SmlSetProcParameterReq<'a> {
pub server_id: Option<OctetString<'a>>,
pub username: Option<OctetString<'a>>,
pub password: Option<OctetString<'a>>,
pub paramter_tree_path: SmlTreePath<'a>,
pub paramter_tree: SmlTree<'a>,
}
#[derive(Debug, SmlType)]
pub struct SmlAttentionRes<'a> {
pub server_id: OctetString<'a>,
pub attention_no: OctetString<'a>,
pub attention_msg: Option<OctetString<'a>>,
pub attention_details: Option<SmlTree<'a>>,
}
#[derive(Debug, SmlType)]
pub struct SmlGetListReq<'a> {
pub client_id: OctetString<'a>,
pub server_id: Option<OctetString<'a>>,
pub username: Option<OctetString<'a>>,
pub password: Option<OctetString<'a>>,
pub list_name: Option<OctetString<'a>>,
}
#[derive(Debug, SmlType)]
pub struct SmlGetListRes<'a> {
pub client_id: Option<OctetString<'a>>,
pub server_id: OctetString<'a>,
pub list_name: OctetString<'a>,
pub act_sensor_time: Option<SmlTime>,
pub val_list: ListOf<'a, SmlListEntry<'a>>,
pub list_signature: Option<SmlSignature<'a>>,
pub act_gateway_time: Option<SmlTime>,
}
#[derive(Debug, SmlType)]
pub struct SmlListEntry<'a> {
pub obj_name: OctetString<'a>,
pub status: Option<SmlStatus>,
pub val_time: Option<SmlTime>,
pub unit: Option<SmlUnit>,
pub scaler: Option<i8>,
pub value: SmlValue<'a>,
pub value_signature: Option<SmlSignature<'a>>,
}
pub type SmlStatus = u64;
#[derive(Debug, SmlType)]
pub struct SmlGetCosemReq<'a> {
pub client_id: OctetString<'a>,
pub server_id: Option<OctetString<'a>>,
pub username: Option<OctetString<'a>>,
pub password: Option<OctetString<'a>>,
pub obj_name: OctetString<'a>,
pub class_id: i16,
pub class_version: i16,
pub attribute_index_list: Option<ListOf<'a, SmlCosemAttributeDesc<'a>>>,
}
#[derive(Debug, SmlType)]
pub struct SmlCosemAttributeDesc<'a> {
pub attribute_index: i16,
pub selective_access_descriptor: Option<SmlCosemSelAccessDesc<'a>>,
}
#[derive(Debug, SmlType)]
pub struct SmlCosemSelAccessDesc<'a> {
pub access_selector: u8,
pub access_parameters: SmlCosemValue<'a>,
}
#[derive(Debug, SmlType)]
pub struct SmlGetCosemRes<'a> {
pub client_id: Option<OctetString<'a>>,
pub server_id: OctetString<'a>,
pub obj_name: OctetString<'a>,
pub class_id: i16,
pub class_version: i16,
pub attribute_list: ListOf<'a, SmlCosemAttribute<'a>>,
}
#[derive(Debug, SmlType)]
pub struct SmlCosemAttribute<'a> {
pub attribute_description: SmlCosemAttributeDesc<'a>,
pub attribute_content: SmlCosemAttributeContent<'a>,
}
#[derive(Debug, SmlType)]
pub struct SmlSetCosemReq<'a> {
pub client_id: OctetString<'a>,
pub server_id: Option<OctetString<'a>>,
pub username: Option<OctetString<'a>>,
pub password: Option<OctetString<'a>>,
pub obj_name: OctetString<'a>,
pub class_id: i16,
pub class_version: i16,
pub attribute_list: ListOf<'a, SmlCosemAttribute<'a>>,
}
#[derive(Debug, SmlType)]
pub struct SmlSetCosemRes<'a> {
pub client_id: Option<OctetString<'a>>,
pub server_id: OctetString<'a>,
pub obj_name: OctetString<'a>,
pub class_id: i16,
pub class_version: i16,
pub attribute_list: Option<ListOf<'a, SmlCosemAttribute<'a>>>,
}
#[derive(Debug, SmlType)]
pub struct SmlActionCosemReq<'a> {
pub client_id: OctetString<'a>,
pub server_id: Option<OctetString<'a>>,
pub username: Option<OctetString<'a>>,
pub password: Option<OctetString<'a>>,
pub obj_name: OctetString<'a>,
pub class_id: i16,
pub class_version: i16,
pub service_index: u8,
pub service_parameter: Option<SmlCosemValue<'a>>,
}
#[derive(Debug, SmlType)]
pub struct SmlActionCosemRes<'a> {
pub client_id: Option<OctetString<'a>>,
pub server_id: OctetString<'a>,
pub obj_name: OctetString<'a>,
pub class_id: i16,
pub class_version: i16,
pub attribute_list: Option<ListOf<'a, SmlCosemAttribute<'a>>>,
}
#[derive(Debug)]
pub struct EndOfSmlMsg;
impl<'a> SmlType<'a> for EndOfSmlMsg {
fn parse(buf: &mut ParserContext<'a>) -> Result<Self, Error>
where
Self: Sized,
{
if *buf.peek(0)? == 0 {
buf.move_fwd(1)?;
Ok(Self)
} else {
Err(Error::ExpectedEndOfSmlMsg)
}
}
}
#[derive(Debug)]
pub enum AbortOnError {
Continue,
ContinueWithNextGroup,
AbortAfterGroup,
Abort,
}
impl<'a> SmlType<'a> for AbortOnError {
fn parse(buf: &mut ParserContext<'a>) -> Result<Self, Error>
where
Self: Sized,
{
let disc: u8 = buf.read(1)?[0];
Ok(match disc {
0x00 => Self::Continue,
0x01 => Self::ContinueWithNextGroup,
0x02 => Self::AbortAfterGroup,
0x03 => Self::Abort,
_ => return Err(Error::ExpectedAbortOnError),
})
}
}
#[derive(Debug, Format)]
pub enum SmlValue<'a> {
Boolean(bool),
OctetString(OctetString<'a>),
Integer8(i8),
Integer16(i16),
Integer32(i32),
Integer64(i64),
Unsigned8(u8),
Unsigned16(u16),
Unsigned32(u32),
Unsigned64(u64),
List(SmlListType),
}
impl<'a> SmlType<'a> for SmlValue<'a> {
fn parse(buf: &mut ParserContext<'a>) -> Result<Self, Error>
where
Self: Sized,
{
let tlf = TypeLengthField::peek(buf)?;
match (tlf.kind(), tlf.data_len()) {
(FieldType::Boolean, _) => buf.parse().map(Self::Boolean),
(FieldType::OctetString, _) => buf.parse().map(Self::OctetString),
(FieldType::Integer, 1) => buf.parse().map(Self::Integer8),
(FieldType::Integer, 2) => buf.parse().map(Self::Integer16),
(FieldType::Integer, x) if x <= 4 => buf.parse().map(Self::Integer32),
(FieldType::Integer, x) if x <= 8 => buf.parse().map(Self::Integer64),
(FieldType::Unsigned, 1) => buf.parse().map(Self::Unsigned8),
(FieldType::Unsigned, 2) => buf.parse().map(Self::Unsigned16),
(FieldType::Unsigned, x) if x <= 4 => buf.parse().map(Self::Unsigned32),
(FieldType::Unsigned, x) if x <= 8 => buf.parse().map(Self::Unsigned64),
(FieldType::List, _) => buf.parse().map(Self::List),
_ => unimplemented!(),
}
}
}
#[derive(Debug, Format)]
pub enum SmlListType {
Time(SmlTime),
}
impl<'a> SmlType<'a> for SmlListType {
fn parse(buf: &mut ParserContext<'a>) -> Result<Self, Error>
where
Self: Sized,
{
use SmlListType as S;
let tlf = TypeLengthField::parse(buf)?;
if tlf.kind() != FieldType::List || tlf.len() != 2 {
return Err(Error::ExpectedEnum(core::any::type_name::<Self>()));
}
let disc: u8 = buf.parse()?;
match disc {
0x01 => buf.parse().map(S::Time),
_ => Err(Error::ExpectedEnum(core::any::type_name::<Self>())),
}
}
}
#[derive(Debug)]
pub enum SmlProcParValue<'a> {
Value(SmlValue<'a>),
PeriodEntry(SmlPeriodEntry<'a>),
TupelEntry(SmlTupelEntry<'a>),
Time(SmlTime),
ListEntry(SmlListEntry<'a>),
}
impl<'a> SmlType<'a> for SmlProcParValue<'a> {
fn parse(buf: &mut ParserContext<'a>) -> Result<Self, Error>
where
Self: Sized,
{
use SmlProcParValue as S;
let tlf = TypeLengthField::parse(buf)?;
if tlf.kind() != FieldType::List || tlf.len() != 2 {
return Err(Error::ExpectedEnum(core::any::type_name::<Self>()));
}
let disc: u8 = buf.parse()?;
match disc {
0x01 => buf.parse().map(S::Value),
0x02 => buf.parse().map(S::PeriodEntry),
0x03 => buf.parse().map(S::TupelEntry),
0x04 => buf.parse().map(S::Time),
0x05 => buf.parse().map(S::ListEntry),
_ => Err(Error::ExpectedEnum(core::any::type_name::<Self>())),
}
}
}
#[derive(Debug, SmlType)]
pub struct SmlTupelEntry<'a> {
pub server_id: OctetString<'a>,
pub sec_index: SmlTime,
pub status: u64,
pub unit_pa: SmlUnit,
pub scaler_pa: i8,
pub value_pa: i64,
pub unit_r1: SmlUnit,
pub scaler_r1: i8,
pub value_r1: i64,
pub unit_r4: SmlUnit,
pub scaler_r4: i8,
pub value_r4: i64,
pub signature_pa_r1_r4: OctetString<'a>,
pub unit_ma: SmlUnit,
pub scaler_ma: i8,
pub value_ma: i64,
pub unit_r2: SmlUnit,
pub scaler_r2: i8,
pub value_r2: i64,
pub unit_r3: SmlUnit,
pub scaler_r3: i8,
pub value_r3: i64,
pub signature_ma_r2_r3: OctetString<'a>,
}
#[derive(Debug, Format)]
pub enum SmlTime {
SecIndex(u32),
Timestamp(SmlTimestamp),
TimestampLocal(SmlTimestampLocal),
}
impl<'a> SmlType<'a> for SmlTime {
fn parse(buf: &mut ParserContext<'a>) -> Result<Self, Error>
where
Self: Sized,
{
use SmlTime as S;
let tlf = TypeLengthField::parse(buf)?;
if tlf.kind() != FieldType::List || tlf.len() != 2 {
return Err(Error::ExpectedEnum(core::any::type_name::<Self>()));
}
let disc: u8 = buf.parse()?;
match disc {
0x01 => buf.parse().map(S::SecIndex),
0x02 => buf.parse().map(S::Timestamp),
0x03 => buf.parse().map(S::TimestampLocal),
_ => Err(Error::ExpectedEnum(core::any::type_name::<Self>())),
}
}
}
pub type SmlTimestamp = u32;
#[derive(Debug, SmlType, Format)]
pub struct SmlTimestampLocal {
pub timestamp: SmlTimestamp,
pub local_offset: u16,
pub season_time_offset: u16,
}
#[derive(Debug)]
pub enum SmlCosemValue<'a> {
NullData,
Boolean(bool),
BitString,
OctetString(OctetString<'a>),
Integer8(i8),
Integer16(i16),
Integer32(i32),
Integer64(i64),
Unsigned8(u8),
Unsigned16(u16),
Unsigned32(u32),
Unsigned64(u64),
Struct(ListOf<'a, SmlCosemValue<'a>>),
Array(ListOf<'a, SmlCosemValue<'a>>),
}
impl<'a> SmlType<'a> for SmlCosemValue<'a> {
fn parse(buf: &mut ParserContext<'a>) -> Result<Self, Error>
where
Self: Sized,
{
let tlf = TypeLengthField::peek(buf)?;
match (tlf.kind(), tlf.data_len()) {
(FieldType::Boolean, _) => buf.parse().map(Self::Boolean),
(FieldType::OctetString, _) => buf.parse().map(Self::OctetString),
(FieldType::Integer, 1) => buf.parse().map(Self::Integer8),
(FieldType::Integer, 2) => buf.parse().map(Self::Integer16),
(FieldType::Integer, 4) => buf.parse().map(Self::Integer32),
(FieldType::Integer, 8) => buf.parse().map(Self::Integer64),
(FieldType::Unsigned, 1) => buf.parse().map(Self::Unsigned8),
(FieldType::Unsigned, 2) => buf.parse().map(Self::Unsigned16),
(FieldType::Unsigned, 4) => buf.parse().map(Self::Unsigned32),
(FieldType::Unsigned, 8) => buf.parse().map(Self::Unsigned64),
(FieldType::List, _) => buf.parse().map(Self::Struct),
_ => unimplemented!(),
}
}
}
#[derive(Debug)]
pub enum SmlCosemAttributeContent<'a> {
Data(SmlCosemValue<'a>),
DataAccessResult(u8),
}
impl<'a> SmlType<'a> for SmlCosemAttributeContent<'a> {
fn parse(buf: &mut ParserContext<'a>) -> Result<Self, Error>
where
Self: Sized,
{
use SmlCosemAttributeContent as S;
let tlf = TypeLengthField::parse(buf)?;
if tlf.kind() != FieldType::List || tlf.len() != 2 {
return Err(Error::ExpectedEnum(core::any::type_name::<Self>()));
}
let disc: u8 = buf.parse()?;
match disc {
0x01 => buf.parse().map(S::Data),
0x02 => buf.parse().map(S::DataAccessResult),
_ => Err(Error::ExpectedEnum(core::any::type_name::<Self>())),
}
}
}
pub type SmlSignature<'a> = OctetString<'a>;

View file

@ -0,0 +1,247 @@
use core::{fmt::Debug, marker::PhantomData};
use defmt::Format;
use crate::{Error, ParserContext, SmlType};
#[derive(Debug)]
pub struct TypeLengthField {
header_len: usize,
len: usize,
kind: FieldType,
}
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum FieldType {
OctetString,
Boolean,
Integer,
Unsigned,
List,
Reserved,
}
impl<'a> SmlType<'a> for TypeLengthField {
fn parse(buf: &mut ParserContext<'a>) -> Result<Self, Error> {
let val = Self::peek(buf)?;
buf.move_fwd(val.header_len)?;
Ok(val)
}
}
impl TypeLengthField {
pub fn peek(buf: &ParserContext) -> Result<Self, Error> {
let mut len: usize = 0;
let mut header_len = 0;
for b in buf.peek(0..)? {
let additional = (*b >> 7) & 1 == 1;
header_len += 1;
if additional {
len += (*b & 0b1111) as usize;
len <<= 4;
} else {
len += (*b & 0b1111) as usize;
break;
}
}
let kind = (*buf.peek(0)? >> 4) & 0b111;
let kind = match kind {
0b000 => FieldType::OctetString,
0b100 => FieldType::Boolean,
0b101 => FieldType::Integer,
0b110 => FieldType::Unsigned,
0b111 => FieldType::List,
0b001 => FieldType::Reserved,
0b010 => FieldType::Reserved,
0b011 => FieldType::Reserved,
_ => return Err(Error::UnsupportedTLF),
};
Ok(Self {
header_len,
len,
kind,
})
}
pub fn data_len(&self) -> usize {
self.len - self.header_len
}
pub fn header_len(&self) -> usize {
self.header_len
}
pub fn len(&self) -> usize {
self.len
}
pub fn kind(&self) -> FieldType {
self.kind
}
}
#[derive(Debug, Format)]
pub struct OctetString<'a>(pub &'a [u8]);
impl<'a> SmlType<'a> for OctetString<'a> {
fn parse(buf: &mut ParserContext<'a>) -> Result<Self, Error>
where
Self: Sized,
{
let tlf = TypeLengthField::parse(buf)?;
if tlf.kind != FieldType::OctetString {
return Err(Error::ExpectedOctetString);
}
Ok(Self(buf.read(tlf.data_len())?))
}
}
pub struct ListOf<'a, T: SmlType<'a>> {
data: &'a [u8],
len: usize,
_t: PhantomData<T>,
}
impl<'a, T: SmlType<'a>> SmlType<'a> for ListOf<'a, T> {
fn parse(buf: &mut ParserContext<'a>) -> Result<Self, Error>
where
Self: Sized,
{
let tlf = TypeLengthField::parse(buf)?;
if tlf.kind != FieldType::List {
return Err(Error::ExpectedList);
}
let pre_buf = buf.clone();
for res in (0..tlf.len).map(|_| T::parse(buf)) {
res?;
}
Ok(Self {
data: pre_buf.peek_until(buf)?,
len: tlf.len,
_t: PhantomData,
})
}
}
impl<'a, T: SmlType<'a> + Debug> Debug for ListOf<'a, T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ListOf")
.field("len", &self.len)
.field("items", &self.iter())
.finish()
}
}
impl<'a, T: SmlType<'a>> ListOf<'a, T> {
pub fn iter(&self) -> ListIter<'a, T> {
ListIter::new(self.data, self.len)
}
}
pub struct ListIter<'a, T: SmlType<'a>> {
len: usize,
cursor: ParserContext<'a>,
index: usize,
_t: PhantomData<T>,
}
impl<'a, T: SmlType<'a>> Clone for ListIter<'a, T> {
fn clone(&self) -> Self {
Self {
len: self.len,
cursor: self.cursor.clone(),
index: self.index,
_t: PhantomData,
}
}
}
impl<'a, T: SmlType<'a> + Debug> Debug for ListIter<'a, T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let mut list = f.debug_list();
for i in self.clone().filter_map(|x| x.ok()) {
list.entry(&i);
}
list.finish()
}
}
impl<'a, T: SmlType<'a>> ListIter<'a, T> {
pub const fn new(data: &'a [u8], len: usize) -> Self {
Self {
len,
cursor: ParserContext(data),
index: 0,
_t: PhantomData,
}
}
}
impl<'a: 'b, 'b, T: SmlType<'a>> Iterator for ListIter<'a, T> {
type Item = Result<T, Error>;
fn next(&mut self) -> Option<Self::Item> {
self.index += 1;
if self.index < self.len {
Some(T::parse(&mut self.cursor).map_err(|x| {
self.index = self.len;
x
}))
} else {
None
}
}
}
impl<'a> SmlType<'a> for bool {
fn parse(buf: &mut ParserContext<'a>) -> Result<Self, Error>
where
Self: Sized,
{
let tlf = TypeLengthField::parse(buf)?;
if tlf.kind != FieldType::Boolean {
return Err(Error::ExpectedBool);
}
let val = buf.read(tlf.data_len())?;
Ok(val.iter().any(|x| *x > 0))
}
}
macro_rules! impl_sml_type {
($ident:ident, $type:expr, $len:expr) => {
impl<'a> SmlType<'a> for $ident {
fn parse(buf: &mut ParserContext<'a>) -> Result<Self, Error>
where
Self: Sized,
{
let tlf = TypeLengthField::parse(buf)?;
if tlf.kind != $type && tlf.data_len() <= $len {
return Err(Error::ExpectedUnsigned);
}
const SIZE: usize = $len;
let mut val = [0; SIZE];
val[SIZE - tlf.data_len()..].copy_from_slice(buf.read(tlf.data_len())?);
Ok(Self::from_be_bytes(val))
}
}
};
}
impl_sml_type!(u8, FieldType::Unsigned, 1);
impl_sml_type!(u16, FieldType::Unsigned, 2);
impl_sml_type!(u32, FieldType::Unsigned, 4);
impl_sml_type!(u64, FieldType::Unsigned, 8);
impl_sml_type!(i8, FieldType::Integer, 1);
impl_sml_type!(i16, FieldType::Integer, 2);
impl_sml_type!(i32, FieldType::Integer, 4);
impl_sml_type!(i64, FieldType::Integer, 8);
impl<'a, T: SmlType<'a>> SmlType<'a> for Option<T> {
fn parse(buf: &mut ParserContext<'a>) -> Result<Self, Error>
where
Self: Sized,
{
if *buf.peek(0)? == 1 {
buf.move_fwd(1)?;
Ok(None)
} else {
T::parse(buf).map(Some)
}
}
}