mod bbmd;
mod discover;
mod log;
mod read;
use std::collections::HashSet;
use std::sync::atomic::AtomicBool;
use std::sync::{Arc, Mutex, PoisonError};
use bacnet_client::client::BACnetClient;
use bacnet_client::log_reader::{LogCursor, LogPage, read_log_page};
use bacnet_services::alarm_event::GetEventInformationAck;
use bacnet_services::alarm_summary::GetAlarmSummaryAck;
use bacnet_services::enrollment_summary::{GetEnrollmentSummaryAck, GetEnrollmentSummaryRequest};
use bacnet_transport::port::TransportPort;
use bacnet_types::enums::{ObjectType, PropertyIdentifier};
use bacnet_types::error::Error;
use bacnet_types::primitives::ObjectIdentifier;
pub use self::discover::WhoIsScope;
pub use self::log::Naming;
pub(crate) use self::read::rpm_rows;
pub use self::read::{
Halt, ListRead, MAX_OBJECTS, ReadVia, Reader, RpmFailure, Wanted, network_of, rpm_failure,
timed_out, unreachable,
};
use crate::format::{RawRow, object_name};
use crate::limits::{RequestLimits, Turn};
pub const DEVICE_PROPERTIES: [PropertyIdentifier; 13] = [
PropertyIdentifier::OBJECT_NAME,
PropertyIdentifier::VENDOR_NAME,
PropertyIdentifier::VENDOR_IDENTIFIER,
PropertyIdentifier::MODEL_NAME,
PropertyIdentifier::FIRMWARE_REVISION,
PropertyIdentifier::APPLICATION_SOFTWARE_VERSION,
PropertyIdentifier::PROTOCOL_VERSION,
PropertyIdentifier::PROTOCOL_REVISION,
PropertyIdentifier::PROTOCOL_SERVICES_SUPPORTED,
PropertyIdentifier::PROTOCOL_OBJECT_TYPES_SUPPORTED,
PropertyIdentifier::MAX_APDU_LENGTH_ACCEPTED,
PropertyIdentifier::SEGMENTATION_SUPPORTED,
PropertyIdentifier::DATABASE_REVISION,
];
static NEVER_STOPPED: AtomicBool = AtomicBool::new(false);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PropertyRead {
pub object: ObjectIdentifier,
pub rows: Vec<RawRow>,
pub via: ReadVia,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DeviceSummary {
pub device: u32,
pub rows: Vec<RawRow>,
pub via: ReadVia,
}
impl DeviceSummary {
pub fn name(&self) -> Option<String> {
object_name(&self.rows)
}
}
pub struct Engine<T: TransportPort + 'static> {
pub(crate) client: Arc<BACnetClient<T>>,
limits: Arc<RequestLimits>,
no_rpm: Mutex<HashSet<u32>>,
}
impl<T: TransportPort + 'static> Engine<T> {
pub fn new(client: Arc<BACnetClient<T>>, limits: Arc<RequestLimits>) -> Self {
Self {
client,
limits,
no_rpm: Mutex::new(HashSet::new()),
}
}
pub fn limits(&self) -> &RequestLimits {
&self.limits
}
pub fn client(&self) -> &Arc<BACnetClient<T>> {
&self.client
}
pub fn refused_rpm(&self, device: u32) -> bool {
self.no_rpm().contains(&device)
}
fn no_rpm(&self) -> std::sync::MutexGuard<'_, HashSet<u32>> {
self.no_rpm.lock().unwrap_or_else(PoisonError::into_inner)
}
pub(crate) fn reader(&self, device: u32) -> Reader<'_, T> {
Reader {
client: &self.client,
device,
rate: &self.limits,
cancel: &NEVER_STOPPED,
rpm: !self.refused_rpm(device),
}
}
pub(crate) fn settle(&self, reader: &Reader<'_, T>) {
if !reader.rpm {
self.no_rpm().insert(reader.device);
}
}
pub async fn read(
&self,
device: u32,
object: ObjectIdentifier,
wanted: Wanted<'_>,
) -> Result<PropertyRead, Error> {
let mut reader = self.reader(device);
let read = reader.properties(object, wanted).await;
self.settle(&reader);
let (rows, via) = read.map_err(device_error)?;
Ok(PropertyRead { object, rows, via })
}
pub async fn read_property(
&self,
device: u32,
object: ObjectIdentifier,
property: PropertyIdentifier,
index: Option<u32>,
) -> Result<Vec<u8>, Error> {
self.reader(device)
.rp(object, property, index)
.await
.map_err(device_error)
}
pub async fn list_objects(&self, device: u32) -> Result<ListRead, Error> {
self.reader(device)
.object_list(|_, _| {})
.await
.map_err(device_error)
}
pub async fn describe_device(&self, device: u32) -> Result<DeviceSummary, Error> {
let object = ObjectIdentifier::new(ObjectType::DEVICE, device)?;
let PropertyRead { rows, via, .. } = self
.read(device, object, Wanted::List(&DEVICE_PROPERTIES))
.await?;
Ok(DeviceSummary { device, rows, via })
}
pub async fn read_trend(
&self,
device: u32,
log: ObjectIdentifier,
cursor: LogCursor,
page_size: u16,
) -> Result<LogPage, Error> {
let network = network_of(&self.client, &self.limits, device).await;
let _turn = self.limits.turn_on(device, network).await;
let requester = log::PagedLog {
client: &self.client,
limits: &self.limits,
device,
network,
first: AtomicBool::new(true),
};
read_log_page(&requester, &[], log, cursor, page_size).await
}
async fn turn(&self, device: u32) -> Turn {
let network = network_of(&self.client, &self.limits, device).await;
self.limits.turn_on(device, network).await
}
pub async fn alarms(
&self,
device: u32,
after: Option<ObjectIdentifier>,
) -> Result<GetEventInformationAck, Error> {
let _turn = self.turn(device).await;
self.client
.get_event_information_from_device(device, after)
.await
}
pub async fn alarm_summary(&self, device: u32) -> Result<GetAlarmSummaryAck, Error> {
let _turn = self.turn(device).await;
self.client.get_alarm_summary_from_device(device).await
}
pub async fn enrollment_summary(
&self,
device: u32,
request: &GetEnrollmentSummaryRequest,
) -> Result<GetEnrollmentSummaryAck, Error> {
let _turn = self.turn(device).await;
self.client
.get_enrollment_summary_from_device(device, request)
.await
}
}
fn device_error(halt: Halt) -> Error {
match halt {
Halt::Device(error) => error,
Halt::Cancelled => unreachable!("an engine read is never stopped by its flag"),
}
}
#[cfg(test)]
mod tests;