use std::fmt;
use std::str::FromStr;
use serde::{Deserialize, Serialize};
use super::DptValue;
use crate::error::{KnxError, KnxResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct DptId {
pub main: u16,
pub sub: u16,
}
impl DptId {
pub const fn new(main: u16, sub: u16) -> Self {
Self { main, sub }
}
pub const fn main_only(main: u16) -> Self {
Self { main, sub: 0 }
}
pub fn is_main_only(&self) -> bool {
self.sub == 0
}
pub fn main_type(&self) -> DptId {
Self::main_only(self.main)
}
}
impl fmt::Display for DptId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.sub == 0 {
write!(f, "DPT {}", self.main)
} else {
write!(f, "DPT {}.{:03}", self.main, self.sub)
}
}
}
impl FromStr for DptId {
type Err = KnxError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s
.trim()
.strip_prefix("DPT ")
.or_else(|| s.strip_prefix("dpt "))
.or_else(|| s.strip_prefix("DPT"))
.or_else(|| s.strip_prefix("dpt"))
.unwrap_or(s)
.trim();
if let Some((main, sub)) = s.split_once('.') {
let main: u16 = main
.parse()
.map_err(|_| KnxError::InvalidDpt(format!("Invalid main type: {}", main)))?;
let sub: u16 = sub
.parse()
.map_err(|_| KnxError::InvalidDpt(format!("Invalid subtype: {}", sub)))?;
Ok(Self::new(main, sub))
} else {
let main: u16 = s
.parse()
.map_err(|_| KnxError::InvalidDpt(format!("Invalid DPT: {}", s)))?;
Ok(Self::main_only(main))
}
}
}
pub trait DptCodec: Send + Sync {
fn id(&self) -> DptId;
fn name(&self) -> &'static str;
fn size(&self) -> usize;
fn encode(&self, value: &DptValue) -> KnxResult<Vec<u8>>;
fn decode(&self, data: &[u8]) -> KnxResult<DptValue>;
fn default_value(&self) -> DptValue {
DptValue::default()
}
fn validate(&self, value: &DptValue) -> KnxResult<()> {
self.encode(value).map(|_| ())
}
fn unit(&self) -> Option<&'static str> {
None
}
fn min_value(&self) -> Option<f64> {
None
}
fn max_value(&self) -> Option<f64> {
None
}
fn is_small(&self) -> bool {
self.size() == 0
}
fn description(&self) -> &'static str {
self.name()
}
}
pub type BoxedDptCodec = Box<dyn DptCodec>;
pub fn validate_length(data: &[u8], expected: usize, dpt: &str) -> KnxResult<()> {
if data.len() < expected {
Err(KnxError::DptDecoding {
dpt: dpt.to_string(),
reason: format!("Expected at least {} bytes, got {}", expected, data.len()),
})
} else {
Ok(())
}
}
#[macro_export]
macro_rules! impl_simple_dpt {
($name:ident, $main:expr, $sub:expr, $size:expr, $type_name:expr, $unit:expr) => {
impl $crate::dpt::DptCodec for $name {
fn id(&self) -> $crate::dpt::DptId {
$crate::dpt::DptId::new($main, $sub)
}
fn name(&self) -> &'static str {
$type_name
}
fn size(&self) -> usize {
$size
}
fn unit(&self) -> Option<&'static str> {
$unit
}
fn encode(&self, value: &$crate::dpt::DptValue) -> $crate::error::KnxResult<Vec<u8>> {
self.encode_impl(value)
}
fn decode(&self, data: &[u8]) -> $crate::error::KnxResult<$crate::dpt::DptValue> {
self.decode_impl(data)
}
}
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dpt_id_display() {
assert_eq!(DptId::new(9, 1).to_string(), "DPT 9.001");
assert_eq!(DptId::main_only(1).to_string(), "DPT 1");
}
#[test]
fn test_dpt_id_parse() {
assert_eq!("9.001".parse::<DptId>().unwrap(), DptId::new(9, 1));
assert_eq!("DPT 9.001".parse::<DptId>().unwrap(), DptId::new(9, 1));
assert_eq!("dpt 1".parse::<DptId>().unwrap(), DptId::main_only(1));
assert_eq!("1".parse::<DptId>().unwrap(), DptId::main_only(1));
}
#[test]
fn test_dpt_id_main_type() {
let id = DptId::new(9, 1);
assert_eq!(id.main_type(), DptId::main_only(9));
}
}