1use crate::prelude::*;
19use crate::sync::Mutex;
20use alloc::sync::Arc;
21
22use bacnet_types::constructed::{BACnetDeviceObjectReference, BACnetStageLimitValue};
23use bacnet_types::enums::{EngineeringUnits, EventType, NetworkType, ObjectType};
24use bacnet_types::error::Error;
25use bacnet_types::primitives::{ObjectIdentifier, PropertyValue};
26
27use crate::access_control::{
28 AccessCredentialObject, AccessDoorObject, AccessPointObject, AccessRightsObject,
29 AccessUserObject, AccessZoneObject, CredentialDataInputObject,
30};
31use crate::accumulator::{AccumulatorObject, PulseConverterObject};
32use crate::analog::{AnalogInputObject, AnalogOutputObject, AnalogValueObject};
33use crate::audit::{AuditLogObject, AuditLogPersistence, AuditLogSnapshot, AuditReporterObject};
34use crate::averaging::AveragingObject;
35use crate::binary::{BinaryInputObject, BinaryOutputObject, BinaryValueObject};
36use crate::channel::ChannelObject;
37use crate::color::{ColorObject, ColorTemperatureObject};
38use crate::command::CommandObject;
39use crate::device::DeviceObject;
40use crate::elevator::{ElevatorGroupObject, EscalatorObject, LiftObject};
41use crate::event_enrollment::{AlertEnrollmentObject, EventEnrollmentObject};
42use crate::event_log::EventLogObject;
43use crate::file::FileObject;
44use crate::group::{GlobalGroupObject, GroupObject, StructuredViewObject};
45use crate::life_safety::{LifeSafetyPointObject, LifeSafetyZoneObject};
46use crate::lighting::{BinaryLightingOutputObject, LightingOutputObject};
47use crate::load_control::LoadControlObject;
48use crate::loop_obj::LoopObject;
49use crate::multistate::{MultiStateInputObject, MultiStateOutputObject, MultiStateValueObject};
50use crate::network_port::NetworkPortObject;
51use crate::notification_class::NotificationClass;
52use crate::notification_forwarder::NotificationForwarderObject;
53use crate::program::ProgramObject;
54use crate::schedule::{CalendarObject, ScheduleObject};
55use crate::staging::{StagingConfig, StagingObject};
56use crate::timer::TimerObject;
57use crate::traits::BACnetObject;
58use crate::trend::{TrendLogMultipleObject, TrendLogObject};
59use crate::value_types::{
60 BitStringValueObject, CharacterStringValueObject, DatePatternValueObject,
61 DateTimePatternValueObject, DateTimeValueObject, DateValueObject, IntegerValueObject,
62 LargeAnalogValueObject, OctetStringValueObject, PositiveIntegerValueObject,
63 TimePatternValueObject, TimeValueObject,
64};
65
66pub const EXCLUDED_TYPES: &[ObjectType] = &[ObjectType::NETWORK_SECURITY];
69
70#[derive(Default)]
73struct MemoryAuditLogPersistence(Mutex<Option<AuditLogSnapshot>>);
74
75impl AuditLogPersistence for MemoryAuditLogPersistence {
76 fn load(&self, _expected_object: ObjectIdentifier) -> Result<Option<AuditLogSnapshot>, Error> {
77 Ok(self.0.lock().clone())
78 }
79
80 fn commit(&self, snapshot: &AuditLogSnapshot) -> Result<(), Error> {
81 *self.0.lock() = Some(snapshot.clone());
82 Ok(())
83 }
84}
85
86pub(crate) fn staging_config() -> StagingConfig {
88 let output = |object_type, instance| BACnetDeviceObjectReference {
89 device_identifier: None,
90 object_identifier: ObjectIdentifier::new(object_type, instance)
91 .expect("a valid catalogue reference"),
92 };
93 StagingConfig {
94 present_value: 5.0,
95 min_present_value: -1.0,
96 units: EngineeringUnits::DEGREES_CELSIUS,
97 priority_for_writing: 8,
98 stages: vec![
99 BACnetStageLimitValue {
100 limit: 10.0,
101 values: vec![false, true],
102 deadband: 1.0,
103 },
104 BACnetStageLimitValue {
105 limit: 20.0,
106 values: vec![true, false],
107 deadband: 2.0,
108 },
109 BACnetStageLimitValue {
110 limit: 30.0,
111 values: vec![true, true],
112 deadband: 1.0,
113 },
114 ],
115 target_references: vec![
116 output(ObjectType::BINARY_OUTPUT, 1),
117 output(ObjectType::BINARY_VALUE, 2),
118 ],
119 stage_names: Some(vec!["Low".into(), "Middle".into(), "High".into()]),
120 }
121}
122
123pub fn representatives() -> Vec<Box<dyn BACnetObject>> {
141 let celsius = EngineeringUnits::DEGREES_CELSIUS;
142 let alert_source =
143 ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).expect("a valid catalogue reference");
144 let audit_storage: Arc<dyn AuditLogPersistence> =
145 Arc::new(MemoryAuditLogPersistence::default());
146 let objects: Vec<Result<Box<dyn BACnetObject>, Error>> = vec![
147 boxed(AnalogInputObject::new(1, "AI-1", celsius)),
148 boxed(AnalogOutputObject::new(1, "AO-1", celsius)),
149 boxed(AnalogValueObject::new(1, "AV-1", celsius)),
150 boxed(BinaryInputObject::new(1, "BI-1")),
151 boxed(BinaryOutputObject::new(1, "BO-1")),
152 boxed(BinaryValueObject::new(1, "BV-1")),
153 boxed(CalendarObject::new(1, "CAL-1")),
154 boxed(CommandObject::new(1, "CMD-1")),
155 boxed(DeviceObject::new(Default::default())),
156 boxed(EventEnrollmentObject::new(
157 1,
158 "EE-1",
159 EventType::CHANGE_OF_BITSTRING,
160 )),
161 boxed(FileObject::new(1, "FILE-1", "raw")),
162 boxed(GroupObject::new(1, "GRP-1")),
163 boxed(LoopObject::new(1, "LOOP-1", celsius)),
164 boxed(MultiStateInputObject::new(1, "MSI-1", 3)),
165 boxed(MultiStateOutputObject::new(1, "MSO-1", 3)),
166 boxed(NotificationClass::new(1, "NC-1")),
167 boxed(ProgramObject::new(1, "PRG-1")),
168 boxed(ScheduleObject::new(1, "SCH-1", PropertyValue::Null)),
169 boxed(AveragingObject::new(1, "AVG-1")),
170 boxed(MultiStateValueObject::new(1, "MSV-1", 3)),
171 boxed(TrendLogObject::new(1, "TL-1", 3)),
172 boxed(LifeSafetyPointObject::new(1, "LSP-1")),
173 boxed(LifeSafetyZoneObject::new(1, "LSZ-1")),
174 boxed(AccumulatorObject::new(
175 1,
176 "ACC-1",
177 EngineeringUnits::NO_UNITS,
178 )),
179 boxed(PulseConverterObject::new(1, "PC-1", celsius)),
180 boxed(EventLogObject::new(1, "EL-1", 3)),
181 boxed(GlobalGroupObject::new(1, "GG-1")),
182 boxed(TrendLogMultipleObject::new(1, "TLM-1", 3)),
183 boxed(LoadControlObject::new(1, "LC-1")),
184 boxed(StructuredViewObject::new(1, "SV-1")),
185 boxed(AccessDoorObject::new(1, "DOOR-1")),
186 boxed(TimerObject::new(1, "TMR-1")),
187 boxed(AccessCredentialObject::new(1, "CRED-1")),
188 boxed(AccessPointObject::new(1, "AP-1")),
189 boxed(AccessRightsObject::new(1, "AR-1")),
190 boxed(AccessUserObject::new(1, "USER-1")),
191 boxed(AccessZoneObject::new(1, "ZONE-1")),
192 boxed(CredentialDataInputObject::new(1, "CDI-1")),
193 boxed(BitStringValueObject::new(1, "BSV-1")),
195 boxed(CharacterStringValueObject::new(1, "CSV-1")),
196 boxed(DatePatternValueObject::new(1, "DPV-1")),
197 boxed(DateValueObject::new(1, "DV-1")),
198 boxed(DateTimePatternValueObject::new(1, "DTPV-1")),
199 boxed(DateTimeValueObject::new(1, "DTV-1")),
200 boxed(IntegerValueObject::new(1, "IV-1")),
201 boxed(LargeAnalogValueObject::new(1, "LAV-1")),
202 boxed(OctetStringValueObject::new(1, "OSV-1")),
203 boxed(PositiveIntegerValueObject::new(1, "PIV-1")),
204 boxed(TimePatternValueObject::new(1, "TPV-1")),
205 boxed(TimeValueObject::new(1, "TV-1")),
206 boxed(NotificationForwarderObject::new(1, "NF-1")),
207 boxed(AlertEnrollmentObject::new(1, "AE-1", alert_source)),
208 boxed(ChannelObject::new(1, "CH-1", 1)),
209 boxed(LightingOutputObject::new(1, "LO-1")),
210 boxed(BinaryLightingOutputObject::new(1, "BLO-1")),
211 boxed(NetworkPortObject::new_non_bip(
212 1,
213 "NP-1",
214 NetworkType::from_raw(0),
215 0,
216 Default::default(),
217 1476,
218 )),
219 boxed(ElevatorGroupObject::new(1, "EG-1")),
220 boxed(EscalatorObject::new(1, "ESC-1")),
221 boxed(LiftObject::new(1, "LIFT-1", 3)),
222 boxed(StagingObject::new(1, "STG-1", staging_config())),
223 boxed(AuditLogObject::new(1, "AL-1", 3, audit_storage)),
224 boxed(AuditReporterObject::new(1, "ARP-1")),
225 boxed(ColorObject::new(1, "CLR-1")),
226 boxed(ColorTemperatureObject::new(1, "CT-1")),
227 ];
228 objects
229 .into_iter()
230 .map(|object| object.expect("a catalogue object builds from its fixed arguments"))
231 .collect()
232}
233
234fn boxed<O: BACnetObject + 'static>(
235 object: Result<O, Error>,
236) -> Result<Box<dyn BACnetObject>, Error> {
237 object.map(|object| Box::new(object) as Box<dyn BACnetObject>)
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243
244 #[test]
245 fn one_object_per_type_except_the_excluded() {
246 let objects = representatives();
247 let types: Vec<u32> = objects
248 .iter()
249 .map(|object| object.object_identifier().object_type().to_raw())
250 .collect();
251 let excluded: Vec<u32> = EXCLUDED_TYPES.iter().map(|t| t.to_raw()).collect();
252 let expected: Vec<u32> = (0..=64).filter(|raw| !excluded.contains(raw)).collect();
253 assert_eq!(types, expected, "one object per type, in type order");
254 }
255
256 #[test]
257 fn the_whole_catalogue_fits_one_database() {
258 let objects = representatives();
259 let count = objects.len();
260 let mut database = crate::database::ObjectDatabase::new();
261 for object in objects {
262 let oid = object.object_identifier();
263 database
264 .add(object)
265 .unwrap_or_else(|error| panic!("{oid:?} must fit beside the others: {error:?}"));
266 }
267 assert_eq!(database.len(), count);
268 }
269}