use std::borrow::Cow;
use std::collections::HashMap;
use std::sync::Arc;
use bacnet_types::constructed::BACnetCOVSubscription;
use bacnet_types::enums::{
ErrorClass, ErrorCode, ObjectType, PropertyIdentifier, Segmentation, ServiceSupported,
};
use bacnet_types::error::Error;
use bacnet_types::primitives::{ObjectIdentifier, PropertyValue};
use crate::clock::{ClockFrame, ClockReader};
use crate::common::read_property_list_property;
use crate::traits::BACnetObject;
pub const EXECUTED_SERVICES: &[ServiceSupported] = &[
ServiceSupported::ACKNOWLEDGE_ALARM,
ServiceSupported::GET_ALARM_SUMMARY,
ServiceSupported::GET_ENROLLMENT_SUMMARY,
ServiceSupported::SUBSCRIBE_COV,
ServiceSupported::ATOMIC_READ_FILE,
ServiceSupported::ATOMIC_WRITE_FILE,
ServiceSupported::ADD_LIST_ELEMENT,
ServiceSupported::REMOVE_LIST_ELEMENT,
ServiceSupported::CREATE_OBJECT,
ServiceSupported::DELETE_OBJECT,
ServiceSupported::READ_PROPERTY,
ServiceSupported::READ_PROPERTY_MULTIPLE,
ServiceSupported::WRITE_PROPERTY,
ServiceSupported::WRITE_PROPERTY_MULTIPLE,
ServiceSupported::DEVICE_COMMUNICATION_CONTROL,
ServiceSupported::CONFIRMED_TEXT_MESSAGE,
ServiceSupported::REINITIALIZE_DEVICE,
ServiceSupported::UNCONFIRMED_TEXT_MESSAGE,
ServiceSupported::TIME_SYNCHRONIZATION,
ServiceSupported::WHO_HAS,
ServiceSupported::WHO_IS,
ServiceSupported::READ_RANGE,
ServiceSupported::UTC_TIME_SYNCHRONIZATION,
ServiceSupported::LIFE_SAFETY_OPERATION,
ServiceSupported::SUBSCRIBE_COV_PROPERTY,
ServiceSupported::GET_EVENT_INFORMATION,
ServiceSupported::SUBSCRIBE_COV_PROPERTY_MULTIPLE,
ServiceSupported::CONFIRMED_AUDIT_NOTIFICATION,
ServiceSupported::UNCONFIRMED_AUDIT_NOTIFICATION,
ServiceSupported::AUDIT_LOG_QUERY,
];
const SERVICES_SUPPORTED_BITS: usize = 49;
fn compute_services_supported(services: &[ServiceSupported]) -> Vec<u8> {
let mut bits = vec![0u8; SERVICES_SUPPORTED_BITS.div_ceil(8)];
for service in services {
let n = service.to_raw() as usize;
if n < SERVICES_SUPPORTED_BITS {
bits[n / 8] |= 0x80 >> (n % 8);
}
}
bits
}
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>,
configured_services_supported: Vec<ServiceSupported>,
clock: Option<Arc<dyn ClockReader>>,
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::LAST_RESTART_REASON,
PropertyValue::Enumerated(0), );
properties.insert(
PropertyIdentifier::DEVICE_UUID,
PropertyValue::OctetString(vec![0u8; 16]),
);
if config.segmentation_supported != Segmentation::NONE {
let max_segments_accepted = if config.segmentation_supported == Segmentation::TRANSMIT {
1
} else {
65
};
properties.insert(
PropertyIdentifier::MAX_SEGMENTS_ACCEPTED,
PropertyValue::Unsigned(max_segments_accepted),
);
}
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::ALERT_ENROLLMENT.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_LOG.to_raw(),
ObjectType::AUDIT_REPORTER.to_raw(),
ObjectType::COLOR.to_raw(),
ObjectType::COLOR_TEMPERATURE.to_raw(),
]);
Ok(Self {
oid,
properties,
object_list: vec![oid], protocol_object_types_supported,
configured_services_supported: EXECUTED_SERVICES.to_vec(),
clock: None,
active_cov_subscriptions: Vec::new(),
})
}
pub fn set_object_list(&mut self, oids: Vec<ObjectIdentifier>) {
self.object_list = oids;
}
pub fn set_services_supported(&mut self, services: &[ServiceSupported]) {
self.configured_services_supported = services.to_vec();
}
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);
}
fn clock_frame(&self) -> Option<ClockFrame> {
self.clock.as_ref()?.read_clock()
}
fn services_supported(&self) -> Vec<u8> {
let clock_available = self.clock_frame().is_some();
let services = self
.configured_services_supported
.iter()
.copied()
.filter(|service| {
clock_available
|| (*service != ServiceSupported::TIME_SYNCHRONIZATION
&& *service != ServiceSupported::UTC_TIME_SYNCHRONIZATION)
})
.collect::<Vec<_>>();
compute_services_supported(&services)
}
}
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 matches!(
property,
PropertyIdentifier::LOCAL_DATE
| PropertyIdentifier::LOCAL_TIME
| PropertyIdentifier::UTC_OFFSET
| PropertyIdentifier::DAYLIGHT_SAVINGS_STATUS
) {
let frame = self.clock_frame().ok_or(Error::Protocol {
class: ErrorClass::PROPERTY.to_raw() as u32,
code: ErrorCode::UNKNOWN_PROPERTY.to_raw() as u32,
})?;
return Ok(match property {
PropertyIdentifier::LOCAL_DATE => PropertyValue::Date(frame.local_date),
PropertyIdentifier::LOCAL_TIME => PropertyValue::Time(frame.local_time),
PropertyIdentifier::UTC_OFFSET => {
PropertyValue::Signed(i32::from(frame.utc_offset))
}
PropertyIdentifier::DAYLIGHT_SAVINGS_STATUS => {
PropertyValue::Boolean(frame.daylight_savings_status)
}
_ => unreachable!(),
});
}
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 {
let services_supported = self.services_supported();
let unused = (services_supported.len() * 8 - SERVICES_SUPPORTED_BITS) as u8;
return Ok(PropertyValue::BitString {
unused_bits: unused,
data: services_supported,
});
}
if property == PropertyIdentifier::ACTIVE_COV_SUBSCRIPTIONS {
let mut buf = bytes::BytesMut::new();
bacnet_encoding::constructed::encode_cov_subscription_list(
&mut buf,
&self.active_cov_subscriptions,
);
return Ok(PropertyValue::ApplicationData(buf.to_vec()));
}
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);
if self.clock_frame().is_some() {
props.extend([
PropertyIdentifier::LOCAL_DATE,
PropertyIdentifier::LOCAL_TIME,
PropertyIdentifier::UTC_OFFSET,
PropertyIdentifier::DAYLIGHT_SAVINGS_STATUS,
]);
}
props.sort_by_key(|p| p.to_raw());
Cow::Owned(props)
}
fn is_createable(&self) -> bool {
false
}
fn is_deleteable(&self) -> bool {
false
}
fn bind_clock_internal(&mut self, clock: Option<Arc<dyn ClockReader>>) {
self.clock = clock;
}
}
#[cfg(test)]
mod tests;