use bitflags::bitflags;
use core::fmt::Display;
#[cfg(feature = "std")]
use std::error::Error;
#[cfg(feature = "std")]
use std::fmt;
#[cfg(not(feature = "std"))]
use core::fmt;
#[cfg(not(feature = "std"))]
use alloc::{string::String, vec::Vec};
#[cfg(feature = "std")]
pub type Result<T> = std::result::Result<T, ObjectError>;
#[cfg(not(feature = "std"))]
pub type Result<T> = core::result::Result<T, ObjectError>;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug)]
pub enum ObjectError {
NotFound,
InstanceNotFound,
TypeNotSupported,
PropertyNotFound,
UnknownProperty,
PropertyNotWritable,
InvalidPropertyType,
InvalidValue(String),
WriteAccessDenied,
InvalidConfiguration(String),
}
impl fmt::Display for ObjectError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ObjectError::NotFound => write!(f, "Object not found"),
ObjectError::InstanceNotFound => write!(f, "Object instance not found"),
ObjectError::TypeNotSupported => write!(f, "Object type not supported"),
ObjectError::PropertyNotFound => write!(f, "Property not found"),
ObjectError::UnknownProperty => write!(f, "Unknown property"),
ObjectError::PropertyNotWritable => write!(f, "Property not writable"),
ObjectError::InvalidPropertyType => write!(f, "Invalid property type"),
ObjectError::InvalidValue(msg) => write!(f, "Invalid value: {}", msg),
ObjectError::WriteAccessDenied => write!(f, "Write access denied"),
ObjectError::InvalidConfiguration(msg) => write!(f, "Invalid configuration: {}", msg),
}
}
}
#[cfg(feature = "std")]
impl Error for ObjectError {}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ObjectIdentifier {
pub object_type: ObjectType,
pub instance: u32,
}
impl ObjectIdentifier {
pub fn new(object_type: ObjectType, instance: u32) -> Self {
Self {
object_type,
instance,
}
}
pub fn is_valid(&self) -> bool {
self.instance <= 0x3FFFFF
}
}
impl From<u32> for ObjectIdentifier {
fn from(value: u32) -> Self {
let object_type = (value >> 22) & 0x3FF;
let object_type = object_type.into();
let instance = value & 0x3FFFFF;
Self::new(object_type, instance)
}
}
impl TryFrom<ObjectIdentifier> for u32 {
type Error = EncodingError;
fn try_from(value: ObjectIdentifier) -> std::result::Result<Self, Self::Error> {
let object_type: u32 = value.object_type.into();
if object_type > 0x3FF || value.instance > 0x3FFFFF {
Err(EncodingError::ValueOutOfRange)
} else {
Ok((object_type << 22) | (value.instance & 0x3FFFFF))
}
}
}
pub trait BacnetObject: Send + Sync {
fn identifier(&self) -> ObjectIdentifier;
fn get_property(&self, property: PropertyIdentifier) -> Result<PropertyValue>;
fn set_property(&mut self, property: PropertyIdentifier, value: PropertyValue) -> Result<()>;
fn is_property_writable(&self, property: PropertyIdentifier) -> bool;
fn property_list(&self) -> Vec<PropertyIdentifier>;
}
#[derive(Debug, Clone)]
pub enum PropertyValue {
Null,
Boolean(bool),
UnsignedInteger(u32),
SignedInt(i32),
Real(f32),
Double(f64),
OctetString(Vec<u8>),
CharacterString(String),
BitString(Vec<bool>),
Enumerated(u32),
Date(Date),
Time(Time),
ObjectIdentifier(ObjectIdentifier),
Array(Vec<PropertyValue>),
List(Vec<PropertyValue>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Date {
pub year: u16, pub month: u8, pub day: u8, pub weekday: u8, }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Time {
pub hour: u8, pub minute: u8, pub second: u8, pub hundredths: u8, }
#[derive(Debug, Clone)]
pub struct Device {
pub identifier: ObjectIdentifier,
pub object_name: String,
pub object_type: ObjectType,
pub system_status: DeviceStatus,
pub vendor_name: String,
pub vendor_identifier: u16,
pub model_name: String,
pub firmware_revision: String,
pub application_software_version: String,
pub protocol_version: u8,
pub protocol_revision: u8,
pub protocol_services_supported: ProtocolServicesSupported,
pub object_types_supported: Vec<ObjectType>,
pub max_apdu_length_accepted: u16,
pub segmentation_supported: Segmentation,
pub device_address_binding: Vec<AddressBinding>,
pub database_revision: u32,
}
impl Device {
pub fn new(instance: u32, object_name: String) -> Self {
Self {
identifier: ObjectIdentifier::new(ObjectType::Device, instance),
object_name,
object_type: ObjectType::Device,
system_status: DeviceStatus::Operational,
vendor_name: String::from("BACnet-RS"),
vendor_identifier: 999, model_name: String::from("Rust BACnet Device"),
firmware_revision: String::from("1.0.0"),
application_software_version: String::from("0.2.1"),
protocol_version: 1,
protocol_revision: 22, protocol_services_supported: ProtocolServicesSupported::default(),
object_types_supported: vec![ObjectType::Device],
max_apdu_length_accepted: 1476,
segmentation_supported: Segmentation::Both,
device_address_binding: Vec::new(),
database_revision: 1,
}
}
pub fn add_supported_object_type(&mut self, object_type: ObjectType) {
if !self.object_types_supported.contains(&object_type) {
self.object_types_supported.push(object_type);
}
}
pub fn get_vendor_info(&self) -> Option<crate::vendor::VendorInfo> {
crate::vendor::get_vendor_info(self.vendor_identifier)
}
pub fn get_official_vendor_name(&self) -> Option<&'static str> {
crate::vendor::get_vendor_name(self.vendor_identifier)
}
pub fn set_vendor_by_id(&mut self, vendor_id: u16) -> Result<()> {
if let Some(vendor_info) = crate::vendor::get_vendor_info(vendor_id) {
self.vendor_identifier = vendor_id;
self.vendor_name = vendor_info.name.to_string();
Ok(())
} else {
Err(ObjectError::InvalidPropertyType)
}
}
pub fn set_vendor_name(&mut self, name: String) {
self.vendor_name = name;
}
pub fn is_vendor_id_official(&self) -> bool {
crate::vendor::is_vendor_id_assigned(self.vendor_identifier)
&& !crate::vendor::is_vendor_id_reserved(self.vendor_identifier)
}
pub fn is_vendor_id_test(&self) -> bool {
crate::vendor::is_vendor_id_reserved(self.vendor_identifier)
}
pub fn format_vendor_display(&self) -> String {
crate::vendor::format_vendor_display(self.vendor_identifier)
}
}
impl BacnetObject for Device {
fn identifier(&self) -> ObjectIdentifier {
self.identifier
}
fn get_property(&self, property: PropertyIdentifier) -> Result<PropertyValue> {
match property {
PropertyIdentifier::ObjectIdentifier => {
Ok(PropertyValue::ObjectIdentifier(self.identifier))
}
PropertyIdentifier::ObjectName => {
Ok(PropertyValue::CharacterString(self.object_name.clone()))
}
PropertyIdentifier::ObjectType => {
Ok(PropertyValue::Enumerated(u32::from(self.object_type)))
}
PropertyIdentifier::SystemStatus => {
Ok(PropertyValue::Enumerated(self.system_status as u32))
}
PropertyIdentifier::VendorName => {
Ok(PropertyValue::CharacterString(self.vendor_name.clone()))
}
PropertyIdentifier::VendorIdentifier => Ok(PropertyValue::UnsignedInteger(
self.vendor_identifier as u32,
)),
PropertyIdentifier::ModelName => {
Ok(PropertyValue::CharacterString(self.model_name.clone()))
}
PropertyIdentifier::FirmwareRevision => Ok(PropertyValue::CharacterString(
self.firmware_revision.clone(),
)),
PropertyIdentifier::ApplicationSoftwareVersion => Ok(PropertyValue::CharacterString(
self.application_software_version.clone(),
)),
PropertyIdentifier::ProtocolVersion => {
Ok(PropertyValue::UnsignedInteger(self.protocol_version as u32))
}
PropertyIdentifier::ProtocolRevision => Ok(PropertyValue::UnsignedInteger(
self.protocol_revision as u32,
)),
PropertyIdentifier::MaxApduLengthAccepted => Ok(PropertyValue::UnsignedInteger(
self.max_apdu_length_accepted as u32,
)),
PropertyIdentifier::SegmentationSupported => Ok(PropertyValue::Enumerated(
self.segmentation_supported as u32,
)),
PropertyIdentifier::DatabaseRevision => {
Ok(PropertyValue::UnsignedInteger(self.database_revision))
}
_ => Err(ObjectError::UnknownProperty),
}
}
fn set_property(&mut self, property: PropertyIdentifier, value: PropertyValue) -> Result<()> {
match property {
PropertyIdentifier::ObjectName => {
if let PropertyValue::CharacterString(name) = value {
self.object_name = name;
Ok(())
} else {
Err(ObjectError::InvalidPropertyType)
}
}
PropertyIdentifier::VendorName => {
if let PropertyValue::CharacterString(name) = value {
self.vendor_name = name;
Ok(())
} else {
Err(ObjectError::InvalidPropertyType)
}
}
PropertyIdentifier::ModelName => {
if let PropertyValue::CharacterString(name) = value {
self.model_name = name;
Ok(())
} else {
Err(ObjectError::InvalidPropertyType)
}
}
PropertyIdentifier::FirmwareRevision => {
if let PropertyValue::CharacterString(revision) = value {
self.firmware_revision = revision;
Ok(())
} else {
Err(ObjectError::InvalidPropertyType)
}
}
PropertyIdentifier::ApplicationSoftwareVersion => {
if let PropertyValue::CharacterString(version) = value {
self.application_software_version = version;
Ok(())
} else {
Err(ObjectError::InvalidPropertyType)
}
}
PropertyIdentifier::DatabaseRevision => {
if let PropertyValue::UnsignedInteger(revision) = value {
self.database_revision = revision;
Ok(())
} else {
Err(ObjectError::InvalidPropertyType)
}
}
_ => Err(ObjectError::PropertyNotWritable),
}
}
fn is_property_writable(&self, property: PropertyIdentifier) -> bool {
matches!(
property,
PropertyIdentifier::ObjectName
| PropertyIdentifier::VendorName
| PropertyIdentifier::ModelName
| PropertyIdentifier::FirmwareRevision
| PropertyIdentifier::ApplicationSoftwareVersion
| PropertyIdentifier::DatabaseRevision
)
}
fn property_list(&self) -> Vec<PropertyIdentifier> {
vec![
PropertyIdentifier::ObjectIdentifier,
PropertyIdentifier::ObjectName,
PropertyIdentifier::ObjectType,
PropertyIdentifier::SystemStatus,
PropertyIdentifier::VendorName,
PropertyIdentifier::VendorIdentifier,
PropertyIdentifier::ModelName,
PropertyIdentifier::FirmwareRevision,
PropertyIdentifier::ApplicationSoftwareVersion,
PropertyIdentifier::ProtocolVersion,
PropertyIdentifier::ProtocolRevision,
PropertyIdentifier::MaxApduLengthAccepted,
PropertyIdentifier::SegmentationSupported,
PropertyIdentifier::DatabaseRevision,
]
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum DeviceStatus {
Operational = 0,
OperationalReadOnly = 1,
DownloadRequired = 2,
DownloadInProgress = 3,
NonOperational = 4,
BackupInProgress = 5,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum Segmentation {
Both = 0,
Transmit = 1,
Receive = 2,
NoSegmentation = 3,
}
impl TryFrom<u32> for Segmentation {
type Error = ObjectError;
fn try_from(value: u32) -> Result<Self> {
match value {
0 => Ok(Self::Both),
1 => Ok(Self::Transmit),
2 => Ok(Self::Receive),
3 => Ok(Self::NoSegmentation),
_ => Err(ObjectError::InvalidConfiguration(format!(
"Unknown segmentation: {}",
value
))),
}
}
}
impl Display for Segmentation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Both => write!(f, "Both"),
Self::Transmit => write!(f, "Transmit"),
Self::Receive => write!(f, "Receive"),
Self::NoSegmentation => write!(f, "None"),
}
}
}
bitflags! {
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Default, Eq, PartialEq)]
pub struct ProtocolServicesSupported: u64 {
const ACKNOWLEDGE_ALARM = 1 << 0;
const CONFIRMED_COV_NOTIFICATION = 1 << 1;
const CONFIRMED_EVENT_NOTIFICATION = 1 << 2;
const GET_ALARM_SUMMARY = 1 << 3;
const GET_ENROLLMENT_SUMMARY = 1 << 4;
const SUBSCRIBE_COV = 1 << 5;
const ATOMIC_READ_FILE = 1 << 6;
const ATOMIC_WRITE_FILE = 1 << 7;
const ADD_LIST_ELEMENT = 1 << 8;
const REMOVE_LIST_ELEMENT = 1 << 9;
const CREATE_OBJECT = 1 << 10;
const DELETE_OBJECT = 1 << 11;
const READ_PROPERTY = 1 << 12;
const READ_PROPERTY_CONDITIONAL = 1 << 13;
const READ_PROPERTY_MULTIPLE = 1 << 14;
const WRITE_PROPERTY = 1 << 15;
const WRITE_PROPERTY_MULTIPLE = 1 << 16;
const DEVICE_COMMUNICATION_CONTROL = 1 << 17;
const CONFIRMED_PRIVATE_TRANSFER = 1 << 18;
const CONFIRMED_TEXT_MESSAGE = 1 << 19;
const REINITIALIZE_DEVICE = 1 << 20;
const VT_OPEN = 1 << 21;
const VT_CLOSE = 1 << 22;
const VT_DATA = 1 << 23;
const AUTHENTICATE = 1 << 24;
const REQUEST_KEY = 1 << 25;
const I_AM = 1 << 26;
const I_HAVE = 1 << 27;
const UNCONFIRMED_COV_NOTIFICATION = 1 << 28;
const UNCONFIRMED_EVENT_NOTIFICATION = 1 << 29;
const UNCONFIRMED_PRIVATE_TRANSFER = 1 << 30;
const UNCONFIRMED_TEXT_MESSAGE = 1 << 31;
const TIME_SYNCHRONIZATION = 1 << 32;
const WHO_HAS = 1 << 33;
const WHO_IS = 1 << 34;
const READ_RANGE = 1 << 35;
const UTC_TIME_SYNCHRONIZATION = 1 << 36;
const LIFE_SAFETY_OPERATION = 1 << 37;
const SUBSCRIBE_COV_PROPERTY = 1 << 38;
const GET_EVENT_INFORMATION = 1 << 39;
const WRITE_GROUP = 1 << 40;
const SUBSCRIBE_COV_PROPERTY_MULTIPLE = 1 << 41;
const CONFIRMED_COV_NOTIFICATION_MULTIPLE = 1 << 42;
const UNCONFIRMED_COV_NOTIFICATION_MULTIPLE = 1 << 43;
const CONFIRMED_AUDIT_NOTIFICATION = 1 << 44;
const AUDIT_LOG_QUERY = 1 << 45;
const UNCONFIRMED_AUDIT_NOTIFICATION = 1 << 46;
const WHO_AM_I = 1 << 47;
const YOU_ARE = 1 << 48;
const AUTH_REQUEST = 1 << 49;
}
}
impl ProtocolServicesSupported {
pub fn to_bool_vec(&self) -> Vec<bool> {
let mut vec = Vec::new();
for i in 0..64 {
vec.push((self.bits() & (1 << i)) != 0);
}
vec
}
}
impl From<Vec<bool>> for ProtocolServicesSupported {
fn from(value: Vec<bool>) -> Self {
let mut services = ProtocolServicesSupported::empty();
for (v, f) in value.iter().zip(ProtocolServicesSupported::all().iter()) {
if *v {
services.insert(f);
}
}
services
}
}
#[derive(Debug, Clone)]
pub struct AddressBinding {
pub device_identifier: ObjectIdentifier,
pub network_address: Vec<u8>,
}
pub mod analog;
pub mod binary;
#[cfg(feature = "std")]
pub mod database;
pub mod device;
pub mod engineering_units;
pub mod file;
pub mod multistate;
pub mod event_state;
pub mod object_type;
pub mod reliability;
pub use object_type::ObjectType;
pub mod property_identifier;
pub use property_identifier::PropertyIdentifier;
pub use analog::{AnalogInput, AnalogOutput, AnalogValue};
pub use binary::{BinaryInput, BinaryOutput, BinaryPV, BinaryValue, Polarity};
pub use device::{DeviceObject, ObjectFunctions};
pub use engineering_units::EngineeringUnits;
pub use event_state::EventState;
pub use file::{File, FileAccessMethod};
pub use multistate::{MultiStateInput, MultiStateOutput, MultiStateValue};
pub use reliability::Reliability;
#[cfg(feature = "std")]
pub use database::{DatabaseBuilder, DatabaseStatistics, ObjectDatabase};
use crate::EncodingError;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_device_creation() {
let device = Device::new(123, "Test Device".to_string());
assert_eq!(device.identifier.instance, 123);
assert_eq!(device.object_name, "Test Device");
assert_eq!(device.object_type, ObjectType::Device);
}
#[test]
fn test_device_properties() {
let mut device = Device::new(456, "Property Test".to_string());
let name = device.get_property(PropertyIdentifier::ObjectName).unwrap();
if let PropertyValue::CharacterString(n) = name {
assert_eq!(n, "Property Test");
} else {
panic!("Expected CharacterString");
}
device
.set_property(
PropertyIdentifier::ObjectName,
PropertyValue::CharacterString("New Name".to_string()),
)
.unwrap();
let name = device.get_property(PropertyIdentifier::ObjectName).unwrap();
if let PropertyValue::CharacterString(n) = name {
assert_eq!(n, "New Name");
} else {
panic!("Expected CharacterString");
}
}
#[test]
fn test_protocol_services_supported() {
let services = ProtocolServicesSupported::ACKNOWLEDGE_ALARM
| ProtocolServicesSupported::READ_PROPERTY
| ProtocolServicesSupported::WRITE_PROPERTY;
let bools = services.to_bool_vec();
let services_new = ProtocolServicesSupported::from(bools);
assert_eq!(services, services_new);
}
}