use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u16)]
pub enum ObjectType {
AnalogInput = 0,
AnalogOutput = 1,
AnalogValue = 2,
BinaryInput = 3,
BinaryOutput = 4,
BinaryValue = 5,
Calendar = 6,
Command = 7,
Device = 8,
EventEnrollment = 9,
File = 10,
Group = 11,
Loop = 12,
MultiStateInput = 13,
MultiStateOutput = 14,
NotificationClass = 15,
Program = 16,
Schedule = 17,
Averaging = 18,
MultiStateValue = 19,
TrendLog = 20,
LifeSafetyPoint = 21,
LifeSafetyZone = 22,
Accumulator = 23,
PulseConverter = 24,
EventLog = 25,
GlobalGroup = 26,
TrendLogMultiple = 27,
LoadControl = 28,
StructuredView = 29,
AccessDoor = 30,
Timer = 31,
AccessCredential = 32,
AccessPoint = 33,
AccessRights = 34,
AccessUser = 35,
AccessZone = 36,
CredentialDataInput = 37,
NetworkSecurity = 38,
BitStringValue = 39,
CharacterStringValue = 40,
DatePatternValue = 41,
DateValue = 42,
DatetimePatternValue = 43,
DatetimeValue = 44,
IntegerValue = 45,
LargeAnalogValue = 46,
OctetStringValue = 47,
PositiveIntegerValue = 48,
TimePatternValue = 49,
TimeValue = 50,
NotificationForwarder = 51,
AlertEnrollment = 52,
Channel = 53,
LightingOutput = 54,
BinaryLightingOutput = 55,
NetworkPort = 56,
ElevatorGroup = 57,
Escalator = 58,
Lift = 59,
}
impl ObjectType {
pub fn from_u16(value: u16) -> Option<Self> {
match value {
0 => Some(Self::AnalogInput),
1 => Some(Self::AnalogOutput),
2 => Some(Self::AnalogValue),
3 => Some(Self::BinaryInput),
4 => Some(Self::BinaryOutput),
5 => Some(Self::BinaryValue),
6 => Some(Self::Calendar),
7 => Some(Self::Command),
8 => Some(Self::Device),
9 => Some(Self::EventEnrollment),
10 => Some(Self::File),
11 => Some(Self::Group),
12 => Some(Self::Loop),
13 => Some(Self::MultiStateInput),
14 => Some(Self::MultiStateOutput),
15 => Some(Self::NotificationClass),
16 => Some(Self::Program),
17 => Some(Self::Schedule),
18 => Some(Self::Averaging),
19 => Some(Self::MultiStateValue),
20 => Some(Self::TrendLog),
53 => Some(Self::Channel),
54 => Some(Self::LightingOutput),
56 => Some(Self::NetworkPort),
_ => None,
}
}
pub fn is_writable(&self) -> bool {
matches!(
self,
Self::AnalogOutput
| Self::AnalogValue
| Self::BinaryOutput
| Self::BinaryValue
| Self::MultiStateOutput
| Self::MultiStateValue
| Self::Command
| Self::Schedule
| Self::Calendar
)
}
pub fn supports_cov(&self) -> bool {
matches!(
self,
Self::AnalogInput
| Self::AnalogOutput
| Self::AnalogValue
| Self::BinaryInput
| Self::BinaryOutput
| Self::BinaryValue
| Self::MultiStateInput
| Self::MultiStateOutput
| Self::MultiStateValue
| Self::LightingOutput
| Self::Channel
)
}
pub fn display_name(&self) -> &'static str {
match self {
Self::AnalogInput => "Analog Input",
Self::AnalogOutput => "Analog Output",
Self::AnalogValue => "Analog Value",
Self::BinaryInput => "Binary Input",
Self::BinaryOutput => "Binary Output",
Self::BinaryValue => "Binary Value",
Self::Calendar => "Calendar",
Self::Command => "Command",
Self::Device => "Device",
Self::EventEnrollment => "Event Enrollment",
Self::File => "File",
Self::Group => "Group",
Self::Loop => "Loop",
Self::MultiStateInput => "Multi-state Input",
Self::MultiStateOutput => "Multi-state Output",
Self::NotificationClass => "Notification Class",
Self::Program => "Program",
Self::Schedule => "Schedule",
Self::Averaging => "Averaging",
Self::MultiStateValue => "Multi-state Value",
Self::TrendLog => "Trend Log",
Self::LifeSafetyPoint => "Life Safety Point",
Self::LifeSafetyZone => "Life Safety Zone",
Self::Accumulator => "Accumulator",
Self::PulseConverter => "Pulse Converter",
Self::EventLog => "Event Log",
Self::GlobalGroup => "Global Group",
Self::TrendLogMultiple => "Trend Log Multiple",
Self::LoadControl => "Load Control",
Self::StructuredView => "Structured View",
Self::AccessDoor => "Access Door",
Self::Timer => "Timer",
Self::AccessCredential => "Access Credential",
Self::AccessPoint => "Access Point",
Self::AccessRights => "Access Rights",
Self::AccessUser => "Access User",
Self::AccessZone => "Access Zone",
Self::CredentialDataInput => "Credential Data Input",
Self::NetworkSecurity => "Network Security",
Self::BitStringValue => "BitString Value",
Self::CharacterStringValue => "CharacterString Value",
Self::DatePatternValue => "Date Pattern Value",
Self::DateValue => "Date Value",
Self::DatetimePatternValue => "DateTime Pattern Value",
Self::DatetimeValue => "DateTime Value",
Self::IntegerValue => "Integer Value",
Self::LargeAnalogValue => "Large Analog Value",
Self::OctetStringValue => "OctetString Value",
Self::PositiveIntegerValue => "Positive Integer Value",
Self::TimePatternValue => "Time Pattern Value",
Self::TimeValue => "Time Value",
Self::NotificationForwarder => "Notification Forwarder",
Self::AlertEnrollment => "Alert Enrollment",
Self::Channel => "Channel",
Self::LightingOutput => "Lighting Output",
Self::BinaryLightingOutput => "Binary Lighting Output",
Self::NetworkPort => "Network Port",
Self::ElevatorGroup => "Elevator Group",
Self::Escalator => "Escalator",
Self::Lift => "Lift",
}
}
pub fn abbreviation(&self) -> &'static str {
match self {
Self::AnalogInput => "AI",
Self::AnalogOutput => "AO",
Self::AnalogValue => "AV",
Self::BinaryInput => "BI",
Self::BinaryOutput => "BO",
Self::BinaryValue => "BV",
Self::MultiStateInput => "MSI",
Self::MultiStateOutput => "MSO",
Self::MultiStateValue => "MSV",
Self::Device => "DEV",
_ => "OBJ",
}
}
}
impl TryFrom<u16> for ObjectType {
type Error = ();
fn try_from(value: u16) -> Result<Self, Self::Error> {
Self::from_u16(value).ok_or(())
}
}
impl fmt::Display for ObjectType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.display_name())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ObjectId {
pub object_type: ObjectType,
pub instance: u32,
}
impl ObjectId {
pub const MAX_INSTANCE: u32 = 0x3FFFFF - 1;
pub const WILDCARD_INSTANCE: u32 = 0x3FFFFF;
pub fn new(object_type: ObjectType, instance: u32) -> Self {
debug_assert!(
instance <= Self::MAX_INSTANCE,
"Instance {} exceeds maximum {}",
instance,
Self::MAX_INSTANCE
);
Self {
object_type,
instance,
}
}
pub fn device(instance: u32) -> Self {
Self::new(ObjectType::Device, instance)
}
pub fn encode(&self) -> u32 {
((self.object_type as u32) << 22) | (self.instance & 0x3FFFFF)
}
pub fn decode(value: u32) -> Option<Self> {
let type_code = (value >> 22) as u16;
let instance = value & 0x3FFFFF;
ObjectType::from_u16(type_code).map(|object_type| Self {
object_type,
instance,
})
}
pub fn is_device(&self) -> bool {
self.object_type == ObjectType::Device
}
pub fn short_string(&self) -> String {
format!("{}:{}", self.object_type.abbreviation(), self.instance)
}
}
impl fmt::Display for ObjectId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {}", self.object_type.display_name(), self.instance)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_object_type_from_u16() {
assert_eq!(ObjectType::from_u16(0), Some(ObjectType::AnalogInput));
assert_eq!(ObjectType::from_u16(8), Some(ObjectType::Device));
assert_eq!(ObjectType::from_u16(19), Some(ObjectType::MultiStateValue));
}
#[test]
fn test_object_type_writable() {
assert!(!ObjectType::AnalogInput.is_writable());
assert!(ObjectType::AnalogOutput.is_writable());
assert!(ObjectType::AnalogValue.is_writable());
assert!(!ObjectType::BinaryInput.is_writable());
assert!(ObjectType::BinaryOutput.is_writable());
}
#[test]
fn test_object_id_encode_decode() {
let id = ObjectId::new(ObjectType::AnalogInput, 100);
let encoded = id.encode();
let decoded = ObjectId::decode(encoded).unwrap();
assert_eq!(decoded.object_type, ObjectType::AnalogInput);
assert_eq!(decoded.instance, 100);
}
#[test]
fn test_object_id_max_instance() {
let id = ObjectId::new(ObjectType::Device, ObjectId::MAX_INSTANCE);
let encoded = id.encode();
let decoded = ObjectId::decode(encoded).unwrap();
assert_eq!(decoded.instance, ObjectId::MAX_INSTANCE);
}
#[test]
fn test_object_id_short_string() {
assert_eq!(
ObjectId::new(ObjectType::AnalogInput, 1).short_string(),
"AI:1"
);
assert_eq!(
ObjectId::new(ObjectType::BinaryValue, 100).short_string(),
"BV:100"
);
}
}