use bacnet_types::enums::PropertyIdentifier;
use bacnet_types::primitives::ObjectIdentifier;
pub(crate) type ReqId = u64;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum BrowseRead {
Device,
Object(ObjectIdentifier),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct NameAsk {
pub(crate) device: u32,
pub(crate) address: String,
pub(crate) max_apdu: Option<u32>,
pub(crate) objects: Vec<ObjectIdentifier>,
pub(crate) retry: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum ReadName {
Named(String),
Refused,
Unreachable,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ReadVia {
Rpm,
Rp,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct PropertyRow {
pub(crate) property: PropertyIdentifier,
pub(crate) value: Result<String, String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(crate) struct DeviceCard {
pub(crate) rows: Vec<(&'static str, String)>,
pub(crate) via: Option<ReadVia>,
pub(crate) name: Option<String>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub(crate) struct Health {
pub(crate) alarm: bool,
pub(crate) fault: bool,
pub(crate) overridden: bool,
pub(crate) out_of_service: bool,
}
impl Health {
pub(crate) fn badges(self) -> Vec<&'static str> {
[
(self.alarm, "[ALM]"),
(self.fault, "[FLT]"),
(self.overridden, "[OVR]"),
(self.out_of_service, "[OOS]"),
]
.into_iter()
.filter_map(|(set, badge)| set.then_some(badge))
.collect()
}
}
pub(crate) const PRIORITIES: usize = 16;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(crate) struct PriorityView {
pub(crate) slots: [Option<String>; PRIORITIES],
pub(crate) relinquish_default: Option<String>,
pub(crate) present_value: Option<String>,
}
impl PriorityView {
pub(crate) fn winner(&self) -> Option<usize> {
self.slots.iter().position(Option::is_some).map(|i| i + 1)
}
}
pub(crate) fn priority_label(priority: usize) -> &'static str {
match priority {
1 => "life safety, manual",
2 => "life safety, automatic",
5 => "critical equipment",
6 => "minimum on/off time",
8 => "operator, manual",
_ => "site defined",
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ObjectView {
pub(crate) object: ObjectIdentifier,
pub(crate) properties: Vec<PropertyRow>,
pub(crate) health: Health,
pub(crate) priority: Option<PriorityView>,
pub(crate) via: ReadVia,
pub(crate) name: Option<String>,
}
impl ObjectView {
#[cfg(test)]
pub(crate) fn value(&self, property: PropertyIdentifier) -> Option<&str> {
self.properties
.iter()
.find(|row| row.property == property)
.and_then(|row| row.value.as_deref().ok())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum BrowseResult {
Card(DeviceCard),
Progress {
done: u32,
total: u32,
},
Objects {
objects: Vec<ObjectIdentifier>,
indexed: bool,
stopped: Option<String>,
},
Object(Box<ObjectView>),
Names {
device: u32,
address: String,
names: Vec<(ObjectIdentifier, ReadName)>,
},
Failed(String),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_winner_is_the_lowest_commanded_slot() {
let mut view = PriorityView::default();
assert_eq!(view.winner(), None, "all relinquished");
view.slots[15] = Some("1".into());
view.slots[9] = Some("55".into());
assert_eq!(view.winner(), Some(10));
view.slots[7] = Some("20".into());
assert_eq!(view.winner(), Some(8));
}
#[test]
fn badges_follow_a_fixed_order() {
let health = Health {
alarm: false,
fault: true,
overridden: false,
out_of_service: true,
};
assert_eq!(health.badges(), ["[FLT]", "[OOS]"]);
assert!(Health::default().badges().is_empty());
}
}