#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::{vec, vec::Vec};
#[cfg(test)]
mod tests;
mod packet_common;
#[derive(Hash, Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum Format {
Analogue,
Digital,
}
#[derive(Hash, Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum ParseCOEError {
NodeDisallowed(u8),
PDOIndexDisallowed(u8),
FormatAndUnitIncompatible(Format, u8),
FormatUnknown(u8),
ValueSize(usize),
ValueNotBool([u8; 4]),
PacketBelowHeaderLength,
VersionNotImplemented(u8, u8),
PacketLengthInconsistent(u8, u8),
PacketSizeConflictsWithHeader(u8, usize),
PayloadFrameLengthIncorrect(usize),
}
impl core::fmt::Display for ParseCOEError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match self {
Self::NodeDisallowed(x) => {
write!(f, "The Nodenumber must be in 1-62, but {} was supplied.", x)
}
Self::PDOIndexDisallowed(x) => {
write!(f, "The PDO Index must be in 0-63, but {} was supplied.", x)
}
Self::FormatAndUnitIncompatible(format, unit) => match format {
Format::Analogue => {
write!(
f,
"The unit with ID {unit} is not known as an analogue value in CoE."
)
}
Format::Digital => {
write!(
f,
"The unit with ID {unit} is not known as a digital value in CoE."
)
}
},
Self::FormatUnknown(x) => {
write!(f, "The Format with ID {} is not known.", &x)
}
Self::ValueSize(x) => {
write!(
f,
"Slice containing COEValue was {x} bytes long. 4 expected."
)
}
Self::ValueNotBool(x) => {
write!(
f,
"Expected Value to be bool because of Format/Unit, but got {x:?}."
)
}
Self::PacketBelowHeaderLength => {
write!(f, "The packet is not at least 4 byte long.")
}
Self::VersionNotImplemented(major, minor) => {
write!(f, "Version {major}.{minor} is not implemented.")
}
Self::PacketLengthInconsistent(size, length) => {
write!(
f,
"The packet size ({size}) and payload length ({length}) are inconsistent."
)
}
Self::PacketSizeConflictsWithHeader(header, actual) => {
write!(
f,
"The packet size should be {header} but is actually {actual}."
)
}
Self::PayloadFrameLengthIncorrect(actual) => {
write!(f, "Got a payload frame of length {actual}. 8 expected.")
}
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseCOEError {}
#[cfg(feature = "alloc")]
mod packet_alloc;
#[cfg(feature = "alloc")]
pub use packet_alloc::Packet;
#[cfg(not(feature = "alloc"))]
mod packet_no_alloc;
#[cfg(not(feature = "alloc"))]
pub use packet_no_alloc::Packet;
#[cfg(feature = "alloc")]
pub fn packets_from_payloads(payloads: &[Payload]) -> Vec<Packet> {
payloads
.chunks(31)
.map(|c| Packet::try_from_payloads(c).expect("Length should be satisfied by chunking"))
.collect::<Vec<Packet>>()
}
#[derive(Hash, Debug, PartialEq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct COEVersion {
major: u8,
minor: u8,
}
impl COEVersion {
pub fn major(&self) -> u8 {
self.major
}
pub fn minor(&self) -> u8 {
self.minor
}
}
impl TryFrom<(u8, u8)> for COEVersion {
type Error = ParseCOEError;
fn try_from(value: (u8, u8)) -> Result<Self, Self::Error> {
if value.0 != 2 || value.1 != 0 {
return Err(Self::Error::VersionNotImplemented(value.0, value.1));
};
Ok(COEVersion { major: 2, minor: 0 })
}
}
impl core::fmt::Display for COEVersion {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "{}.{}", self.major, self.minor)
}
}
#[derive(Hash, Debug, PartialEq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Payload {
node: u8,
pdo_index: u8,
value: COEValue,
}
impl TryFrom<&[u8]> for Payload {
type Error = ParseCOEError;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
if value[0] == 0 || value[0] >= 63 {
return Err(Self::Error::NodeDisallowed(value[0]));
};
if value[1] >= 64 {
return Err(Self::Error::PDOIndexDisallowed(value[1]));
};
if value.len() != 8 {
return Err(Self::Error::PayloadFrameLengthIncorrect(value.len()));
};
match value[2] {
0 => {
let coe_value: DigitalCOEValue = (&value[3], &value[4..8]).try_into()?;
Ok(Payload {
node: value[0],
pdo_index: value[1],
value: COEValue::Digital(coe_value),
})
}
1 => {
let coe_value: AnalogueCOEValue = (&value[3], &value[4..8]).try_into()?;
Ok(Payload {
node: value[0],
pdo_index: value[1],
value: COEValue::Analogue(coe_value),
})
}
_ => Err(Self::Error::FormatUnknown(value[2])),
}
}
}
impl core::default::Default for Payload {
fn default() -> Payload {
Payload {
node: 1,
pdo_index: 0,
value: COEValue::Analogue(AnalogueCOEValue::Dimensionless(0)),
}
}
}
impl Payload {
pub fn new(node: u8, pdo_index: u8, value: COEValue) -> Payload {
Payload {
node,
pdo_index,
value,
}
}
fn serialize_into(&self, buf: &mut [u8]) {
assert_eq!(buf.len(), 8);
buf[0] = self.node;
buf[1] = self.pdo_index;
self.value.serialize_into(&mut buf[2..8]);
}
pub fn node(&self) -> u8 {
self.node
}
pub fn pdo_index(&self) -> u8 {
self.pdo_index
}
pub fn format(&self) -> Format {
self.value.format()
}
pub fn unit_id(&self) -> u8 {
self.value.unit_id()
}
pub fn value(&self) -> COEValue {
self.value
}
}
#[derive(Hash, Debug, PartialEq, Copy, Clone, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum COEValue {
Analogue(AnalogueCOEValue),
Digital(DigitalCOEValue),
}
impl From<AnalogueCOEValue> for COEValue {
fn from(value: AnalogueCOEValue) -> Self {
Self::Analogue(value)
}
}
impl From<DigitalCOEValue> for COEValue {
fn from(value: DigitalCOEValue) -> Self {
Self::Digital(value)
}
}
impl COEValue {
fn serialize_into(&self, buf: &mut [u8]) {
assert_eq!(buf.len(), 6);
match self {
COEValue::Analogue(x) => {
buf[0] = 1;
x.serialize_into(&mut buf[1..6]);
}
COEValue::Digital(x) => {
buf[0] = 0;
x.serialize_into(&mut buf[1..6]);
}
};
}
pub fn format(&self) -> Format {
match self {
Self::Analogue(_) => Format::Analogue,
Self::Digital(_) => Format::Digital,
}
}
pub fn unit_id(&self) -> u8 {
match self {
Self::Analogue(x) => x.unit_id(),
Self::Digital(x) => x.unit_id(),
}
}
}
pub fn to_day_of_month(day: u8, month: u8) -> Option<AnalogueCOEValue> {
if day > 31 {
return None;
}
if month > 12 {
return None;
}
Some(AnalogueCOEValue::DayOfMonth(
i32::from(day - 1) + i32::from(month - 1) * 31,
))
}
#[derive(Hash, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum FromDayOfMonthError {
NotDayOfMonth,
ValueOutOfBounds(i32),
}
impl core::fmt::Display for FromDayOfMonthError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match self {
Self::NotDayOfMonth => write!(f, "Value was not DayOfMonth"),
Self::ValueOutOfBounds(x) => {
write!(f, "The Value {x} cannot be parsed as a day, month pair.")
}
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for FromDayOfMonthError {}
pub fn from_day_of_month(value: AnalogueCOEValue) -> Result<(u8, u8), FromDayOfMonthError> {
match value {
AnalogueCOEValue::DayOfMonth(x) => {
if !(0..=30 + 31 * 11).contains(&x) {
Err(FromDayOfMonthError::ValueOutOfBounds(x))
} else {
Ok((
(1 + x % 31).try_into().expect("Modulo 31 yields u8"),
(1 + x / 31).try_into().expect("x should be smaller then 31 * 12 such that x / 31 is way smaller then i32::MAX"),
))
}
}
_ => Err(FromDayOfMonthError::NotDayOfMonth),
}
}
pub fn to_month_of_year(month: u8, year: u16) -> Option<AnalogueCOEValue> {
if month > 12 {
return None;
}
Some(AnalogueCOEValue::MonthOfYear(
i32::from(month - 1) + i32::from(year) * 12,
))
}
#[derive(Hash, Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum FromMonthOfYearError {
NotMonthOfYear,
ValueOutOfBounds(i32),
}
impl core::fmt::Display for FromMonthOfYearError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match self {
Self::NotMonthOfYear => write!(f, "Value was not MonthOfYear"),
Self::ValueOutOfBounds(x) => {
write!(f, "The Value {x} cannot be parsed as a month, year pair.")
}
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for FromMonthOfYearError {}
pub fn from_month_of_year(value: AnalogueCOEValue) -> Result<(u8, u16), FromMonthOfYearError> {
match value {
AnalogueCOEValue::MonthOfYear(x) => {
if (0..=11 + 12 * i32::from(u16::MAX)).contains(&x) {
Ok((
(1 + x % 12).try_into().expect("Modulo 12 has image inside u8"),
(x / 12).try_into().expect("x should be smaller then 11 + 12 * u16::MAX and therefore devisible by 12 into a u16"),
))
} else {
Err(FromMonthOfYearError::ValueOutOfBounds(x))
}
}
_ => Err(FromMonthOfYearError::NotMonthOfYear),
}
}
#[repr(u8)]
#[allow(non_camel_case_types)]
#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum AnalogueCOEValue {
Dimensionless(i32) = 0,
DegreeCentigrade_Tens(i32) = 1,
WattPerSquareMeter(i32) = 2,
LiterPerHour(i32) = 3,
Seconds(i32) = 4,
Minutes(i32) = 5,
LiterPerPulse_Tens(i32) = 6,
DegreeKelvin_Tens(i32) = 7,
Percent_Tens(i32) = 8,
Colon(i32) = 9,
KiloWatt_Hundreds(i32) = 10,
KilowattHour_Tens(i32) = 11,
MegawattHour(i32) = 12,
Volt_Hundreds(i32) = 13,
MilliAmpere_Tens(i32) = 14,
Hours(i32) = 15,
Days(i32) = 16,
Pulses(i32) = 17,
KiloOhm_Hundreds(i32) = 18,
Liters(i32) = 19,
KiloMetersPerHour(i32) = 20,
Hertz_Hundreds(i32) = 21,
LiterPerMinute(i32) = 22,
Bar_Hundreds(i32) = 23,
CoefficientOfPerformance_Hundreds(i32) = 24,
KiloMeter(i32) = 25,
Meter_Tens(i32) = 26,
MilliMeter(i32) = 27,
CubicMeter(i32) = 28,
HertzPerKiloMeterPerHour_HundredThousands(i32) = 29,
HertzPerMeterPerSecond_HundredThousands(i32) = 30,
KilowattHourPerPulse_HundredThousands(i32) = 31,
CubicMeterPerPulse_HundredThousands(i32) = 32,
MilliMeterPerPulse_HundredThousands(i32) = 33,
LiterPerPulse_HundredThousands(i32) = 34,
LiterPerDay(i32) = 35,
MetersPerSecond(i32) = 36,
CubicMeterPerMinute(i32) = 37,
CubicMeterPerHour(i32) = 38,
CubicMeterPerDay(i32) = 39,
MilliMeterPerMinute_Tens(i32) = 40,
MilliMeterPerHour_Tens(i32) = 41,
MilliMeterPerDay_Tens(i32) = 42,
DegreeCentigradePlusRAS_Tens(i32) = 46,
HeatingCircuitOpMode(i32) = 48,
HeatingCircuitOpLevel(i32) = 49,
CurrencyEuro_Hundreds(i32) = 50,
CurrencyDollar_Hundreds(i32) = 51,
AbsoluteHumidity_Tens(i32) = 52,
PricePerUnit_HundredThousands(i32) = 53,
Degree_Tens(i32) = 54,
Blinds(i32) = 55,
Degree_Millions(i32) = 56,
Second_Tens(i32) = 57,
Dimensionless_Tens(i32) = 58,
BlindsPosition(i32) = 59,
Time(i32) = 60,
DayOfMonth(i32) = 61,
Date(u8, u8, u16) = 62,
Ampere_Tens(i32) = 63,
MonthOfYear(i32) = 64,
Millibar_Tens(i32) = 65,
Pascal(i32) = 66,
CO2Content(i32) = 67,
RawHex(i32) = 68,
Watt(i32) = 69,
Tonne_Hundreds(i32) = 70,
KiloGram_Tens(i32) = 71,
Gram_Tens(i32) = 72,
CentiMeter_Tens(i32) = 73,
ColourTemperature(i32) = 74,
Lux_Tens(i32) = 75,
}
impl TryFrom<(&u8, &[u8])> for AnalogueCOEValue {
type Error = ParseCOEError;
fn try_from(value: (&u8, &[u8])) -> Result<Self, Self::Error> {
let raw_bytes: [u8; 4] = value
.1
.try_into()
.map_err(|_| Self::Error::ValueSize(value.1.len()))?;
let inner_value = i32::from_le_bytes(raw_bytes);
match value.0 {
0 => Ok(Self::Dimensionless(inner_value)),
1 => Ok(Self::DegreeCentigrade_Tens(inner_value)),
2 => Ok(Self::WattPerSquareMeter(inner_value)),
3 => Ok(Self::LiterPerHour(inner_value)),
4 => Ok(Self::Seconds(inner_value)),
5 => Ok(Self::Minutes(inner_value)),
6 => Ok(Self::LiterPerPulse_Tens(inner_value)),
7 => Ok(Self::DegreeKelvin_Tens(inner_value)),
8 => Ok(Self::Percent_Tens(inner_value)),
9 => Ok(Self::Colon(inner_value)),
10 => Ok(Self::KiloWatt_Hundreds(inner_value)),
11 => Ok(Self::KilowattHour_Tens(inner_value)),
12 => Ok(Self::MegawattHour(inner_value)),
13 => Ok(Self::Volt_Hundreds(inner_value)),
14 => Ok(Self::MilliAmpere_Tens(inner_value)),
15 => Ok(Self::Hours(inner_value)),
16 => Ok(Self::Days(inner_value)),
17 => Ok(Self::Pulses(inner_value)),
18 => Ok(Self::KiloOhm_Hundreds(inner_value)),
19 => Ok(Self::Liters(inner_value)),
20 => Ok(Self::KiloMetersPerHour(inner_value)),
21 => Ok(Self::Hertz_Hundreds(inner_value)),
22 => Ok(Self::LiterPerMinute(inner_value)),
23 => Ok(Self::Bar_Hundreds(inner_value)),
24 => Ok(Self::CoefficientOfPerformance_Hundreds(inner_value)),
25 => Ok(Self::KiloMeter(inner_value)),
26 => Ok(Self::Meter_Tens(inner_value)),
27 => Ok(Self::MilliMeter(inner_value)),
28 => Ok(Self::CubicMeter(inner_value)),
29 => Ok(Self::HertzPerKiloMeterPerHour_HundredThousands(inner_value)),
30 => Ok(Self::HertzPerMeterPerSecond_HundredThousands(inner_value)),
31 => Ok(Self::KilowattHourPerPulse_HundredThousands(inner_value)),
32 => Ok(Self::CubicMeterPerPulse_HundredThousands(inner_value)),
33 => Ok(Self::MilliMeterPerPulse_HundredThousands(inner_value)),
34 => Ok(Self::LiterPerPulse_HundredThousands(inner_value)),
35 => Ok(Self::LiterPerDay(inner_value)),
36 => Ok(Self::MetersPerSecond(inner_value)),
37 => Ok(Self::CubicMeterPerMinute(inner_value)),
38 => Ok(Self::CubicMeterPerHour(inner_value)),
39 => Ok(Self::CubicMeterPerDay(inner_value)),
40 => Ok(Self::MilliMeterPerMinute_Tens(inner_value)),
41 => Ok(Self::MilliMeterPerHour_Tens(inner_value)),
42 => Ok(Self::MilliMeterPerDay_Tens(inner_value)),
46 => Ok(Self::DegreeCentigradePlusRAS_Tens(inner_value)),
48 => Ok(Self::HeatingCircuitOpMode(inner_value)),
49 => Ok(Self::HeatingCircuitOpLevel(inner_value)),
50 => Ok(Self::CurrencyEuro_Hundreds(inner_value)),
51 => Ok(Self::CurrencyDollar_Hundreds(inner_value)),
52 => Ok(Self::AbsoluteHumidity_Tens(inner_value)),
53 => Ok(Self::PricePerUnit_HundredThousands(inner_value)),
54 => Ok(Self::Degree_Tens(inner_value)),
55 => Ok(Self::Blinds(inner_value)),
56 => Ok(Self::Degree_Millions(inner_value)),
57 => Ok(Self::Second_Tens(inner_value)),
58 => Ok(Self::Dimensionless_Tens(inner_value)),
59 => Ok(Self::BlindsPosition(inner_value)),
60 => Ok(Self::Time(inner_value)),
61 => Ok(Self::DayOfMonth(inner_value)),
62 => {
let bytes = inner_value.to_le_bytes();
let days = bytes[0];
let months = bytes[1];
let years: u16 = u16::from(bytes[2]) + (u16::from(bytes[3]) << 8);
Ok(Self::Date(days, months, years))
}
63 => Ok(Self::Ampere_Tens(inner_value)),
64 => Ok(Self::MonthOfYear(inner_value)),
65 => Ok(Self::Millibar_Tens(inner_value)),
66 => Ok(Self::Pascal(inner_value)),
67 => Ok(Self::CO2Content(inner_value)),
68 => Ok(Self::RawHex(inner_value)),
69 => Ok(Self::Watt(inner_value)),
70 => Ok(Self::Tonne_Hundreds(inner_value)),
71 => Ok(Self::KiloGram_Tens(inner_value)),
72 => Ok(Self::Gram_Tens(inner_value)),
73 => Ok(Self::CentiMeter_Tens(inner_value)),
74 => Ok(Self::ColourTemperature(inner_value)),
75 => Ok(Self::Lux_Tens(inner_value)),
m => Err(Self::Error::FormatAndUnitIncompatible(Format::Analogue, *m)),
}
}
}
impl AnalogueCOEValue {
fn serialize_into(&self, buf: &mut [u8]) {
assert_eq!(buf.len(), 5);
match self {
Self::Dimensionless(x) => {
buf[0] = 0;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::DegreeCentigrade_Tens(x) => {
buf[0] = 1;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::WattPerSquareMeter(x) => {
buf[0] = 2;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::LiterPerHour(x) => {
buf[0] = 3;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::Seconds(x) => {
buf[0] = 4;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::Minutes(x) => {
buf[0] = 5;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::LiterPerPulse_Tens(x) => {
buf[0] = 6;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::DegreeKelvin_Tens(x) => {
buf[0] = 7;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::Percent_Tens(x) => {
buf[0] = 8;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::Colon(x) => {
buf[0] = 9;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::KiloWatt_Hundreds(x) => {
buf[0] = 10;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::KilowattHour_Tens(x) => {
buf[0] = 11;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::MegawattHour(x) => {
buf[0] = 12;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::Volt_Hundreds(x) => {
buf[0] = 13;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::MilliAmpere_Tens(x) => {
buf[0] = 14;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::Hours(x) => {
buf[0] = 15;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::Days(x) => {
buf[0] = 16;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::Pulses(x) => {
buf[0] = 17;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::KiloOhm_Hundreds(x) => {
buf[0] = 18;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::Liters(x) => {
buf[0] = 19;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::KiloMetersPerHour(x) => {
buf[0] = 20;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::Hertz_Hundreds(x) => {
buf[0] = 21;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::LiterPerMinute(x) => {
buf[0] = 22;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::Bar_Hundreds(x) => {
buf[0] = 23;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::CoefficientOfPerformance_Hundreds(x) => {
buf[0] = 24;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::KiloMeter(x) => {
buf[0] = 25;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::Meter_Tens(x) => {
buf[0] = 26;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::MilliMeter(x) => {
buf[0] = 27;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::CubicMeter(x) => {
buf[0] = 28;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::HertzPerKiloMeterPerHour_HundredThousands(x) => {
buf[0] = 29;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::HertzPerMeterPerSecond_HundredThousands(x) => {
buf[0] = 30;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::KilowattHourPerPulse_HundredThousands(x) => {
buf[0] = 31;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::CubicMeterPerPulse_HundredThousands(x) => {
buf[0] = 32;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::MilliMeterPerPulse_HundredThousands(x) => {
buf[0] = 33;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::LiterPerPulse_HundredThousands(x) => {
buf[0] = 34;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::LiterPerDay(x) => {
buf[0] = 35;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::MetersPerSecond(x) => {
buf[0] = 36;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::CubicMeterPerMinute(x) => {
buf[0] = 37;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::CubicMeterPerHour(x) => {
buf[0] = 38;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::CubicMeterPerDay(x) => {
buf[0] = 39;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::MilliMeterPerMinute_Tens(x) => {
buf[0] = 40;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::MilliMeterPerHour_Tens(x) => {
buf[0] = 41;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::MilliMeterPerDay_Tens(x) => {
buf[0] = 42;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::DegreeCentigradePlusRAS_Tens(x) => {
buf[0] = 46;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::HeatingCircuitOpMode(x) => {
buf[0] = 48;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::HeatingCircuitOpLevel(x) => {
buf[0] = 49;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::CurrencyEuro_Hundreds(x) => {
buf[0] = 50;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::CurrencyDollar_Hundreds(x) => {
buf[0] = 51;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::AbsoluteHumidity_Tens(x) => {
buf[0] = 52;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::PricePerUnit_HundredThousands(x) => {
buf[0] = 53;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::Degree_Tens(x) => {
buf[0] = 54;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::Blinds(x) => {
buf[0] = 55;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::Degree_Millions(x) => {
buf[0] = 56;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::Second_Tens(x) => {
buf[0] = 57;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::Dimensionless_Tens(x) => {
buf[0] = 58;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::BlindsPosition(x) => {
buf[0] = 59;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::Time(x) => {
buf[0] = 60;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::DayOfMonth(x) => {
buf[0] = 61;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::Date(days, months, years) => {
buf[0] = 62;
buf[1] = *days;
buf[2] = *months;
buf[3..5].copy_from_slice(&years.to_le_bytes());
}
Self::Ampere_Tens(x) => {
buf[0] = 63;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::MonthOfYear(x) => {
buf[0] = 64;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::Millibar_Tens(x) => {
buf[0] = 65;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::Pascal(x) => {
buf[0] = 66;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::CO2Content(x) => {
buf[0] = 67;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::RawHex(x) => {
buf[0] = 68;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::Watt(x) => {
buf[0] = 69;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::Tonne_Hundreds(x) => {
buf[0] = 70;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::KiloGram_Tens(x) => {
buf[0] = 71;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::Gram_Tens(x) => {
buf[0] = 72;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::CentiMeter_Tens(x) => {
buf[0] = 73;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::ColourTemperature(x) => {
buf[0] = 74;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
Self::Lux_Tens(x) => {
buf[0] = 75;
buf[1..5].copy_from_slice(&x.to_le_bytes());
}
};
}
pub fn unit_id(&self) -> u8 {
match self {
Self::Dimensionless(_) => 0,
Self::DegreeCentigrade_Tens(_) => 1,
Self::WattPerSquareMeter(_) => 2,
Self::LiterPerHour(_) => 3,
Self::Seconds(_) => 4,
Self::Minutes(_) => 5,
Self::LiterPerPulse_Tens(_) => 6,
Self::DegreeKelvin_Tens(_) => 7,
Self::Percent_Tens(_) => 8,
Self::Colon(_) => 9,
Self::KiloWatt_Hundreds(_) => 10,
Self::KilowattHour_Tens(_) => 11,
Self::MegawattHour(_) => 12,
Self::Volt_Hundreds(_) => 13,
Self::MilliAmpere_Tens(_) => 14,
Self::Hours(_) => 15,
Self::Days(_) => 16,
Self::Pulses(_) => 17,
Self::KiloOhm_Hundreds(_) => 18,
Self::Liters(_) => 19,
Self::KiloMetersPerHour(_) => 20,
Self::Hertz_Hundreds(_) => 21,
Self::LiterPerMinute(_) => 22,
Self::Bar_Hundreds(_) => 23,
Self::CoefficientOfPerformance_Hundreds(_) => 24,
Self::KiloMeter(_) => 25,
Self::Meter_Tens(_) => 26,
Self::MilliMeter(_) => 27,
Self::CubicMeter(_) => 28,
Self::HertzPerKiloMeterPerHour_HundredThousands(_) => 29,
Self::HertzPerMeterPerSecond_HundredThousands(_) => 30,
Self::KilowattHourPerPulse_HundredThousands(_) => 31,
Self::CubicMeterPerPulse_HundredThousands(_) => 32,
Self::MilliMeterPerPulse_HundredThousands(_) => 33,
Self::LiterPerPulse_HundredThousands(_) => 34,
Self::LiterPerDay(_) => 35,
Self::MetersPerSecond(_) => 36,
Self::CubicMeterPerMinute(_) => 37,
Self::CubicMeterPerHour(_) => 38,
Self::CubicMeterPerDay(_) => 39,
Self::MilliMeterPerMinute_Tens(_) => 40,
Self::MilliMeterPerHour_Tens(_) => 41,
Self::MilliMeterPerDay_Tens(_) => 42,
Self::DegreeCentigradePlusRAS_Tens(_) => 46,
Self::HeatingCircuitOpMode(_) => 48,
Self::HeatingCircuitOpLevel(_) => 49,
Self::CurrencyEuro_Hundreds(_) => 50,
Self::CurrencyDollar_Hundreds(_) => 51,
Self::AbsoluteHumidity_Tens(_) => 52,
Self::PricePerUnit_HundredThousands(_) => 53,
Self::Degree_Tens(_) => 54,
Self::Blinds(_) => 55,
Self::Degree_Millions(_) => 56,
Self::Second_Tens(_) => 57,
Self::Dimensionless_Tens(_) => 58,
Self::BlindsPosition(_) => 59,
Self::Time(_) => 60,
Self::DayOfMonth(_) => 61,
Self::Date(_, _, _) => 62,
Self::Ampere_Tens(_) => 63,
Self::MonthOfYear(_) => 64,
Self::Millibar_Tens(_) => 65,
Self::Pascal(_) => 66,
Self::CO2Content(_) => 67,
Self::RawHex(_) => 68,
Self::Watt(_) => 69,
Self::Tonne_Hundreds(_) => 70,
Self::KiloGram_Tens(_) => 71,
Self::Gram_Tens(_) => 72,
Self::CentiMeter_Tens(_) => 73,
Self::ColourTemperature(_) => 74,
Self::Lux_Tens(_) => 75,
}
}
}
#[repr(u8)]
#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum DigitalCOEValue {
OnOff(bool) = 43,
YesNo(bool) = 44,
RASMode(bool) = 45,
Mixer(bool) = 47,
}
impl TryFrom<(&u8, &[u8])> for DigitalCOEValue {
type Error = ParseCOEError;
fn try_from(value: (&u8, &[u8])) -> Result<Self, Self::Error> {
if value.1.len() != 4 {
return Err(Self::Error::ValueSize(value.1.len()));
};
if value.1[3] != 0 || value.1[2] != 0 || value.1[1] != 0 {
return Err(Self::Error::ValueNotBool(
value
.1
.try_into()
.expect("I already asserted that value.1 has four elements."),
));
};
let inner_bool = match value.1[0] {
0 => false,
1 => true,
_ => {
return Err(Self::Error::ValueNotBool(
value
.1
.try_into()
.expect("I already asserted that value.1 has four elements."),
));
}
};
match value.0 {
43 => Ok(Self::OnOff(inner_bool)),
44 => Ok(Self::YesNo(inner_bool)),
45 => Ok(Self::RASMode(inner_bool)),
47 => Ok(Self::Mixer(inner_bool)),
m => Err(Self::Error::FormatAndUnitIncompatible(Format::Digital, *m)),
}
}
}
impl DigitalCOEValue {
fn serialize_into(&self, buf: &mut [u8]) {
assert_eq!(buf.len(), 5, "serialize_into must be passed a buf of len 5");
match self {
Self::OnOff(x) => {
buf[0] = 43;
buf[1] = u8::from(*x);
}
Self::YesNo(x) => {
buf[0] = 44;
buf[1] = u8::from(*x);
}
Self::RASMode(x) => {
buf[0] = 45;
buf[1] = u8::from(*x);
}
Self::Mixer(x) => {
buf[0] = 47;
buf[1] = u8::from(*x);
}
};
}
pub fn unit_id(&self) -> u8 {
match self {
Self::OnOff(_) => 43,
Self::YesNo(_) => 44,
Self::RASMode(_) => 45,
Self::Mixer(_) => 47,
}
}
}