mod convert;
use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Dpt {
pub main: u16,
pub sub: u16,
pub index: u16,
}
impl Dpt {
pub const fn new(main: u16, sub: u16) -> Self {
Self {
main,
sub,
index: 0,
}
}
pub const fn with_index(main: u16, sub: u16, index: u16) -> Self {
Self { main, sub, index }
}
pub const fn data_length(self) -> u8 {
match self.main {
7 | 8 | 9 | 22 | 207 | 217 | 234 | 237 | 239 | 244 | 246 => 2,
10 | 11 | 30 | 206 | 225 | 232 | 240 | 250 | 254 => 3,
12 | 13 | 14 | 15 | 27 | 231 | 241 | 251 => 4,
252 => 5,
219 | 221 | 222 | 229 | 235 | 242 | 245 | 249 => 6,
19 | 29 | 230 | 255 | 275 => 8,
16 => 14,
285 => 16,
_ => 1,
}
}
}
impl fmt::Display for Dpt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.index == 0 {
write!(f, "{}.{:03}", self.main, self.sub)
} else {
write!(f, "{}.{:03}.{}", self.main, self.sub, self.index)
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum DptValue {
Bool(bool),
UInt(u32),
Int(i32),
Float(f64),
Int64(i64),
Text(String),
Bytes(Vec<u8>),
}
impl fmt::Display for DptValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Bool(v) => write!(f, "{v}"),
Self::UInt(v) => write!(f, "{v}"),
Self::Int(v) => write!(f, "{v}"),
Self::Float(v) => write!(f, "{v}"),
Self::Int64(v) => write!(f, "{v}"),
Self::Text(s) => f.write_str(s),
Self::Bytes(b) => {
for (i, byte) in b.iter().enumerate() {
if i > 0 {
f.write_str(" ")?;
}
write!(f, "{byte:02X}")?;
}
Ok(())
}
}
}
}
impl DptValue {
pub const fn as_bool(&self) -> Option<bool> {
match self {
Self::Bool(v) => Some(*v),
_ => None,
}
}
pub const fn as_u32(&self) -> Option<u32> {
match self {
Self::UInt(v) => Some(*v),
_ => None,
}
}
pub const fn as_i32(&self) -> Option<i32> {
match self {
Self::Int(v) => Some(*v),
_ => None,
}
}
#[expect(
clippy::cast_precision_loss,
reason = "i64→f64 precision loss is inherent and documented"
)]
pub const fn as_f64(&self) -> Option<f64> {
match self {
Self::Float(v) => Some(*v),
Self::Bool(v) => Some(if *v { 1.0 } else { 0.0 }),
Self::UInt(v) => Some(*v as f64), Self::Int(v) => Some(*v as f64), Self::Int64(v) => Some(*v as f64), _ => None,
}
}
pub const fn as_i64(&self) -> Option<i64> {
match self {
Self::Int64(v) => Some(*v),
_ => None,
}
}
pub fn as_str(&self) -> Option<&str> {
match self {
Self::Text(s) => Some(s),
_ => None,
}
}
pub fn as_bytes(&self) -> Option<&[u8]> {
match self {
Self::Bytes(b) => Some(b),
_ => None,
}
}
}
impl From<bool> for DptValue {
fn from(v: bool) -> Self {
Self::Bool(v)
}
}
impl From<u8> for DptValue {
fn from(v: u8) -> Self {
Self::UInt(u32::from(v))
}
}
impl From<u16> for DptValue {
fn from(v: u16) -> Self {
Self::UInt(u32::from(v))
}
}
impl From<u32> for DptValue {
fn from(v: u32) -> Self {
Self::UInt(v)
}
}
impl From<i8> for DptValue {
fn from(v: i8) -> Self {
Self::Int(i32::from(v))
}
}
impl From<i16> for DptValue {
fn from(v: i16) -> Self {
Self::Int(i32::from(v))
}
}
impl From<i32> for DptValue {
fn from(v: i32) -> Self {
Self::Int(v)
}
}
impl From<i64> for DptValue {
fn from(v: i64) -> Self {
Self::Int64(v)
}
}
impl From<f32> for DptValue {
fn from(v: f32) -> Self {
Self::Float(f64::from(v))
}
}
impl From<f64> for DptValue {
fn from(v: f64) -> Self {
Self::Float(v)
}
}
impl From<String> for DptValue {
fn from(s: String) -> Self {
Self::Text(s)
}
}
impl From<&str> for DptValue {
fn from(s: &str) -> Self {
Self::Text(String::from(s))
}
}
impl From<Vec<u8>> for DptValue {
fn from(b: Vec<u8>) -> Self {
Self::Bytes(b)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DptError {
PayloadTooShort,
UnsupportedDpt(Dpt),
OutOfRange {
context: &'static str,
},
TypeMismatch,
NoDpt,
}
impl DptError {
pub const fn out_of_range(context: &'static str) -> Self {
Self::OutOfRange { context }
}
}
impl fmt::Display for DptError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::PayloadTooShort => f.write_str("payload too short for DPT"),
Self::UnsupportedDpt(dpt) => write!(f, "unsupported DPT: {dpt}"),
Self::OutOfRange { context } => write!(f, "value out of range: {context}"),
Self::TypeMismatch => f.write_str("wrong value type for DPT"),
Self::NoDpt => f.write_str("no DPT configured on group object"),
}
}
}
impl core::error::Error for DptError {}
pub fn decode(dpt: Dpt, payload: &[u8]) -> Result<DptValue, DptError> {
convert::decode(dpt, payload)
}
pub fn encode(dpt: Dpt, value: &DptValue) -> Result<Vec<u8>, DptError> {
convert::encode(dpt, value)
}
pub const DPT_SWITCH: Dpt = Dpt::new(1, 1);
pub const DPT_BOOL: Dpt = Dpt::new(1, 2);
pub const DPT_CHAR_ASCII: Dpt = Dpt::new(4, 1);
pub const DPT_SCALING: Dpt = Dpt::new(5, 1);
pub const DPT_ANGLE: Dpt = Dpt::new(5, 3);
pub const DPT_VALUE_1_UCOUNT: Dpt = Dpt::new(5, 10);
pub const DPT_VALUE_2_UCOUNT: Dpt = Dpt::new(7, 1);
pub const DPT_VALUE_2_COUNT: Dpt = Dpt::new(8, 1);
pub const DPT_VALUE_TEMP: Dpt = Dpt::new(9, 1);
pub const DPT_VALUE_LUX: Dpt = Dpt::new(9, 4);
pub const DPT_TIMEOFDAY: Dpt = Dpt::with_index(10, 1, 1);
pub const DPT_DATE: Dpt = Dpt::new(11, 1);
pub const DPT_VALUE_4_UCOUNT: Dpt = Dpt::new(12, 1);
pub const DPT_VALUE_4_COUNT: Dpt = Dpt::new(13, 1);
pub const DPT_VALUE_POWER: Dpt = Dpt::new(14, 56);
pub const DPT_ACCESS_DATA: Dpt = Dpt::new(15, 0);
pub const DPT_STRING_ASCII: Dpt = Dpt::new(16, 0);
pub const DPT_STRING_8859_1: Dpt = Dpt::new(16, 1);
pub const DPT_SCENE_NUMBER: Dpt = Dpt::new(17, 1);
pub const DPT_SCENE_CONTROL: Dpt = Dpt::new(18, 1);
pub const DPT_DATETIME: Dpt = Dpt::new(19, 1);
pub const DPT_ACTIVE_ENERGY_V64: Dpt = Dpt::new(29, 10);
pub const DPT_COLOUR_RGB: Dpt = Dpt::new(232, 600);
pub const DPT_COLOUR_RGBW: Dpt = Dpt::new(251, 600);