use std::borrow::Cow;
use std::collections::HashMap;
use bacnet_types::constructed::BACnetCOVSubscription;
use bacnet_types::enums::{ErrorClass, ErrorCode, ObjectType, PropertyIdentifier, Segmentation};
use bacnet_types::error::Error;
use bacnet_types::primitives::{Date, ObjectIdentifier, PropertyValue, Time};
use crate::common::read_property_list_property;
use crate::traits::BACnetObject;
fn compute_object_types_supported(types: &[u32]) -> Vec<u8> {
let max_type = types.iter().copied().max().unwrap_or(0) as usize;
let num_bytes = (max_type / 8) + 1;
let mut bitstring = vec![0u8; num_bytes];
for &t in types {
let byte_idx = (t as usize) / 8;
let bit_pos = 7 - ((t as usize) % 8);
if byte_idx < bitstring.len() {
bitstring[byte_idx] |= 1 << bit_pos;
}
}
bitstring
}
pub struct DeviceConfig {
pub instance: u32,
pub name: String,
pub vendor_name: String,
pub vendor_id: u16,
pub model_name: String,
pub firmware_revision: String,
pub application_software_version: String,
pub max_apdu_length: u32,
pub segmentation_supported: Segmentation,
pub apdu_timeout: u32,
pub apdu_retries: u32,
}
impl Default for DeviceConfig {
fn default() -> Self {
Self {
instance: 1,
name: "BACnet Device".into(),
vendor_name: "Rusty BACnet".into(),
vendor_id: 0,
model_name: "rusty-bacnet".into(),
firmware_revision: "0.1.0".into(),
application_software_version: "0.1.0".into(),
max_apdu_length: 1476,
segmentation_supported: Segmentation::NONE,
apdu_timeout: 6000,
apdu_retries: 3,
}
}
}
pub struct DeviceObject {
oid: ObjectIdentifier,
properties: HashMap<PropertyIdentifier, PropertyValue>,
object_list: Vec<ObjectIdentifier>,
protocol_object_types_supported: Vec<u8>,
protocol_services_supported: Vec<u8>,
active_cov_subscriptions: Vec<BACnetCOVSubscription>,
}
impl DeviceObject {
pub fn new(config: DeviceConfig) -> Result<Self, Error> {
let oid = ObjectIdentifier::new(ObjectType::DEVICE, config.instance)?;
let mut properties = HashMap::new();
properties.insert(
PropertyIdentifier::OBJECT_IDENTIFIER,
PropertyValue::ObjectIdentifier(oid),
);
properties.insert(
PropertyIdentifier::OBJECT_NAME,
PropertyValue::CharacterString(config.name),
);
properties.insert(
PropertyIdentifier::OBJECT_TYPE,
PropertyValue::Enumerated(ObjectType::DEVICE.to_raw()),
);
properties.insert(
PropertyIdentifier::SYSTEM_STATUS,
PropertyValue::Enumerated(0), );
properties.insert(
PropertyIdentifier::VENDOR_NAME,
PropertyValue::CharacterString(config.vendor_name),
);
properties.insert(
PropertyIdentifier::VENDOR_IDENTIFIER,
PropertyValue::Unsigned(config.vendor_id as u64),
);
properties.insert(
PropertyIdentifier::MODEL_NAME,
PropertyValue::CharacterString(config.model_name),
);
properties.insert(
PropertyIdentifier::FIRMWARE_REVISION,
PropertyValue::CharacterString(config.firmware_revision),
);
properties.insert(
PropertyIdentifier::APPLICATION_SOFTWARE_VERSION,
PropertyValue::CharacterString(config.application_software_version),
);
properties.insert(
PropertyIdentifier::PROTOCOL_VERSION,
PropertyValue::Unsigned(1),
);
properties.insert(
PropertyIdentifier::PROTOCOL_REVISION,
PropertyValue::Unsigned(22), );
properties.insert(
PropertyIdentifier::MAX_APDU_LENGTH_ACCEPTED,
PropertyValue::Unsigned(config.max_apdu_length as u64),
);
properties.insert(
PropertyIdentifier::SEGMENTATION_SUPPORTED,
PropertyValue::Enumerated(config.segmentation_supported.to_raw() as u32),
);
properties.insert(
PropertyIdentifier::APDU_TIMEOUT,
PropertyValue::Unsigned(config.apdu_timeout as u64),
);
properties.insert(
PropertyIdentifier::NUMBER_OF_APDU_RETRIES,
PropertyValue::Unsigned(config.apdu_retries as u64),
);
properties.insert(
PropertyIdentifier::DATABASE_REVISION,
PropertyValue::Unsigned(0),
);
properties.insert(
PropertyIdentifier::DESCRIPTION,
PropertyValue::CharacterString(String::new()),
);
properties.insert(
PropertyIdentifier::DEVICE_ADDRESS_BINDING,
PropertyValue::List(Vec::new()),
);
properties.insert(
PropertyIdentifier::LOCAL_DATE,
PropertyValue::Date(Date {
year: 126, month: 3,
day: 18,
day_of_week: 3, }),
);
properties.insert(
PropertyIdentifier::LOCAL_TIME,
PropertyValue::Time(Time {
hour: 12,
minute: 0,
second: 0,
hundredths: 0,
}),
);
properties.insert(
PropertyIdentifier::UTC_OFFSET,
PropertyValue::Signed(0), );
properties.insert(
PropertyIdentifier::LAST_RESTART_REASON,
PropertyValue::Enumerated(0), );
properties.insert(
PropertyIdentifier::DEVICE_UUID,
PropertyValue::OctetString(vec![0u8; 16]),
);
if config.segmentation_supported != Segmentation::NONE {
properties.insert(
PropertyIdentifier::MAX_SEGMENTS_ACCEPTED,
PropertyValue::Unsigned(65), );
}
let protocol_object_types_supported = compute_object_types_supported(&[
ObjectType::ANALOG_INPUT.to_raw(),
ObjectType::ANALOG_OUTPUT.to_raw(),
ObjectType::ANALOG_VALUE.to_raw(),
ObjectType::BINARY_INPUT.to_raw(),
ObjectType::BINARY_OUTPUT.to_raw(),
ObjectType::BINARY_VALUE.to_raw(),
ObjectType::CALENDAR.to_raw(),
ObjectType::COMMAND.to_raw(),
ObjectType::DEVICE.to_raw(),
ObjectType::EVENT_ENROLLMENT.to_raw(),
ObjectType::FILE.to_raw(),
ObjectType::GROUP.to_raw(),
ObjectType::LOOP.to_raw(),
ObjectType::MULTI_STATE_INPUT.to_raw(),
ObjectType::MULTI_STATE_OUTPUT.to_raw(),
ObjectType::NOTIFICATION_CLASS.to_raw(),
ObjectType::PROGRAM.to_raw(),
ObjectType::SCHEDULE.to_raw(),
ObjectType::AVERAGING.to_raw(),
ObjectType::MULTI_STATE_VALUE.to_raw(),
ObjectType::TREND_LOG.to_raw(),
ObjectType::LIFE_SAFETY_POINT.to_raw(),
ObjectType::LIFE_SAFETY_ZONE.to_raw(),
ObjectType::ACCUMULATOR.to_raw(),
ObjectType::PULSE_CONVERTER.to_raw(),
ObjectType::EVENT_LOG.to_raw(),
ObjectType::GLOBAL_GROUP.to_raw(),
ObjectType::TREND_LOG_MULTIPLE.to_raw(),
ObjectType::LOAD_CONTROL.to_raw(),
ObjectType::STRUCTURED_VIEW.to_raw(),
ObjectType::ACCESS_DOOR.to_raw(),
ObjectType::TIMER.to_raw(),
ObjectType::ACCESS_CREDENTIAL.to_raw(),
ObjectType::ACCESS_POINT.to_raw(),
ObjectType::ACCESS_RIGHTS.to_raw(),
ObjectType::ACCESS_USER.to_raw(),
ObjectType::ACCESS_ZONE.to_raw(),
ObjectType::CREDENTIAL_DATA_INPUT.to_raw(),
ObjectType::BITSTRING_VALUE.to_raw(),
ObjectType::CHARACTERSTRING_VALUE.to_raw(),
ObjectType::DATEPATTERN_VALUE.to_raw(),
ObjectType::DATE_VALUE.to_raw(),
ObjectType::DATETIMEPATTERN_VALUE.to_raw(),
ObjectType::DATETIME_VALUE.to_raw(),
ObjectType::INTEGER_VALUE.to_raw(),
ObjectType::LARGE_ANALOG_VALUE.to_raw(),
ObjectType::OCTETSTRING_VALUE.to_raw(),
ObjectType::POSITIVE_INTEGER_VALUE.to_raw(),
ObjectType::TIMEPATTERN_VALUE.to_raw(),
ObjectType::TIME_VALUE.to_raw(),
ObjectType::NOTIFICATION_FORWARDER.to_raw(),
ObjectType::ALERT_ENROLLMENT.to_raw(),
ObjectType::CHANNEL.to_raw(),
ObjectType::LIGHTING_OUTPUT.to_raw(),
ObjectType::BINARY_LIGHTING_OUTPUT.to_raw(),
ObjectType::NETWORK_PORT.to_raw(),
ObjectType::ELEVATOR_GROUP.to_raw(),
ObjectType::ESCALATOR.to_raw(),
ObjectType::LIFT.to_raw(),
ObjectType::STAGING.to_raw(),
ObjectType::AUDIT_REPORTER.to_raw(),
ObjectType::AUDIT_LOG.to_raw(),
ObjectType::COLOR.to_raw(),
ObjectType::COLOR_TEMPERATURE.to_raw(),
]);
let protocol_services_supported = vec![0xA4, 0x0B, 0x80, 0x35, 0x80, 0x00];
Ok(Self {
oid,
properties,
object_list: vec![oid], protocol_object_types_supported,
protocol_services_supported,
active_cov_subscriptions: Vec::new(),
})
}
pub fn set_object_list(&mut self, oids: Vec<ObjectIdentifier>) {
self.object_list = oids;
}
pub fn instance(&self) -> u32 {
self.oid.instance_number()
}
pub fn set_description(&mut self, desc: impl Into<String>) {
self.properties.insert(
PropertyIdentifier::DESCRIPTION,
PropertyValue::CharacterString(desc.into()),
);
}
pub fn set_active_cov_subscriptions(&mut self, subs: Vec<BACnetCOVSubscription>) {
self.active_cov_subscriptions = subs;
}
pub fn add_cov_subscription(&mut self, sub: BACnetCOVSubscription) {
self.active_cov_subscriptions.push(sub);
}
}
impl BACnetObject for DeviceObject {
fn object_identifier(&self) -> ObjectIdentifier {
self.oid
}
fn object_name(&self) -> &str {
match self.properties.get(&PropertyIdentifier::OBJECT_NAME) {
Some(PropertyValue::CharacterString(s)) => s,
_ => "Unknown",
}
}
fn read_property(
&self,
property: PropertyIdentifier,
array_index: Option<u32>,
) -> Result<PropertyValue, Error> {
if property == PropertyIdentifier::OBJECT_LIST {
return match array_index {
None => {
let elements = self
.object_list
.iter()
.map(|oid| PropertyValue::ObjectIdentifier(*oid))
.collect();
Ok(PropertyValue::List(elements))
}
Some(0) => {
Ok(PropertyValue::Unsigned(self.object_list.len() as u64))
}
Some(idx) => {
let i = (idx - 1) as usize; if i < self.object_list.len() {
Ok(PropertyValue::ObjectIdentifier(self.object_list[i]))
} else {
Err(Error::Protocol {
class: ErrorClass::PROPERTY.to_raw() as u32,
code: ErrorCode::INVALID_ARRAY_INDEX.to_raw() as u32,
})
}
}
};
}
if property == PropertyIdentifier::PROPERTY_LIST {
return read_property_list_property(&self.property_list(), array_index);
}
if property == PropertyIdentifier::PROTOCOL_OBJECT_TYPES_SUPPORTED {
let num_bytes = self.protocol_object_types_supported.len();
let total_bits = num_bytes * 8;
let mut max_type = 0u32;
for (byte_idx, &byte) in self.protocol_object_types_supported.iter().enumerate() {
for bit in 0..8 {
if byte & (1 << (7 - bit)) != 0 {
max_type = (byte_idx * 8 + bit) as u32;
}
}
}
let used_bits = max_type as usize + 1;
let unused = (total_bits - used_bits) as u8;
return Ok(PropertyValue::BitString {
unused_bits: unused,
data: self.protocol_object_types_supported.clone(),
});
}
if property == PropertyIdentifier::PROTOCOL_SERVICES_SUPPORTED {
return Ok(PropertyValue::BitString {
unused_bits: 7,
data: self.protocol_services_supported.clone(),
});
}
if property == PropertyIdentifier::ACTIVE_COV_SUBSCRIPTIONS {
let elements: Vec<PropertyValue> = self
.active_cov_subscriptions
.iter()
.map(|sub| {
let mut entry = vec![
PropertyValue::ObjectIdentifier(
sub.monitored_property_reference.object_identifier,
),
PropertyValue::Unsigned(sub.recipient.process_identifier as u64),
PropertyValue::Boolean(sub.issue_confirmed_notifications),
PropertyValue::Unsigned(sub.time_remaining as u64),
];
if let Some(inc) = sub.cov_increment {
entry.push(PropertyValue::Real(inc));
}
PropertyValue::List(entry)
})
.collect();
return Ok(PropertyValue::List(elements));
}
self.properties
.get(&property)
.cloned()
.ok_or(Error::Protocol {
class: ErrorClass::PROPERTY.to_raw() as u32,
code: ErrorCode::UNKNOWN_PROPERTY.to_raw() as u32,
})
}
fn write_property(
&mut self,
property: PropertyIdentifier,
_array_index: Option<u32>,
value: PropertyValue,
_priority: Option<u8>,
) -> Result<(), Error> {
if property == PropertyIdentifier::DESCRIPTION {
if let PropertyValue::CharacterString(_) = &value {
self.properties.insert(property, value);
return Ok(());
}
return Err(Error::Protocol {
class: ErrorClass::PROPERTY.to_raw() as u32,
code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
});
}
Err(Error::Protocol {
class: ErrorClass::PROPERTY.to_raw() as u32,
code: ErrorCode::WRITE_ACCESS_DENIED.to_raw() as u32,
})
}
fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
let mut props: Vec<PropertyIdentifier> = self.properties.keys().copied().collect();
props.push(PropertyIdentifier::OBJECT_LIST);
props.push(PropertyIdentifier::PROPERTY_LIST);
props.push(PropertyIdentifier::PROTOCOL_OBJECT_TYPES_SUPPORTED);
props.push(PropertyIdentifier::PROTOCOL_SERVICES_SUPPORTED);
props.push(PropertyIdentifier::ACTIVE_COV_SUBSCRIPTIONS);
props.sort_by_key(|p| p.to_raw());
Cow::Owned(props)
}
}
#[cfg(test)]
mod tests;