1use bacnet_types::constructed::{
4 BACnetCalendarEntry, BACnetDateRange, BACnetObjectPropertyReference, BACnetSpecialEvent,
5 BACnetTimeValue,
6};
7use bacnet_types::enums::{ErrorClass, ErrorCode, ObjectType, PropertyIdentifier};
8use bacnet_types::error::Error;
9use bacnet_types::primitives::{ObjectIdentifier, PropertyValue, StatusFlags};
10use std::borrow::Cow;
11
12use crate::common::read_property_list_property;
13use crate::traits::BACnetObject;
14
15pub struct CalendarObject {
25 oid: ObjectIdentifier,
26 name: String,
27 description: String,
28 present_value: bool,
29 status_flags: StatusFlags,
30 date_list: Vec<BACnetCalendarEntry>,
31}
32
33impl CalendarObject {
34 pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
35 let oid = ObjectIdentifier::new(ObjectType::CALENDAR, instance)?;
36 Ok(Self {
37 oid,
38 name: name.into(),
39 description: String::new(),
40 present_value: false,
41 status_flags: StatusFlags::empty(),
42 date_list: Vec::new(),
43 })
44 }
45
46 pub fn set_present_value(&mut self, value: bool) {
48 self.present_value = value;
49 }
50
51 pub fn set_description(&mut self, desc: impl Into<String>) {
53 self.description = desc.into();
54 }
55
56 pub fn add_date_entry(&mut self, entry: BACnetCalendarEntry) {
58 self.date_list.push(entry);
59 }
60
61 pub fn clear_date_list(&mut self) {
63 self.date_list.clear();
64 }
65}
66
67impl BACnetObject for CalendarObject {
68 fn object_identifier(&self) -> ObjectIdentifier {
69 self.oid
70 }
71
72 fn object_name(&self) -> &str {
73 &self.name
74 }
75
76 fn read_property(
77 &self,
78 property: PropertyIdentifier,
79 array_index: Option<u32>,
80 ) -> Result<PropertyValue, Error> {
81 match property {
82 p if p == PropertyIdentifier::OBJECT_IDENTIFIER => {
83 Ok(PropertyValue::ObjectIdentifier(self.oid))
84 }
85 p if p == PropertyIdentifier::OBJECT_NAME => {
86 Ok(PropertyValue::CharacterString(self.name.clone()))
87 }
88 p if p == PropertyIdentifier::DESCRIPTION => {
89 Ok(PropertyValue::CharacterString(self.description.clone()))
90 }
91 p if p == PropertyIdentifier::OBJECT_TYPE => {
92 Ok(PropertyValue::Enumerated(ObjectType::CALENDAR.to_raw()))
93 }
94 p if p == PropertyIdentifier::PRESENT_VALUE => {
95 Ok(PropertyValue::Boolean(self.present_value))
96 }
97 p if p == PropertyIdentifier::STATUS_FLAGS => Ok(PropertyValue::BitString {
98 unused_bits: 4,
99 data: vec![self.status_flags.bits() << 4],
100 }),
101 p if p == PropertyIdentifier::EVENT_STATE => Ok(PropertyValue::Enumerated(0)),
102 p if p == PropertyIdentifier::OUT_OF_SERVICE => Ok(PropertyValue::Boolean(false)),
103 p if p == PropertyIdentifier::DATE_LIST => Ok(PropertyValue::List(
104 self.date_list
105 .iter()
106 .map(|entry| match entry {
107 BACnetCalendarEntry::Date(d) => PropertyValue::Date(*d),
108 BACnetCalendarEntry::DateRange(dr) => {
109 PropertyValue::OctetString(dr.encode().to_vec())
110 }
111 BACnetCalendarEntry::WeekNDay(wnd) => {
112 PropertyValue::OctetString(wnd.encode().to_vec())
113 }
114 })
115 .collect(),
116 )),
117 p if p == PropertyIdentifier::PROPERTY_LIST => {
118 read_property_list_property(&self.property_list(), array_index)
119 }
120 _ => Err(Error::Protocol {
121 class: ErrorClass::PROPERTY.to_raw() as u32,
122 code: ErrorCode::UNKNOWN_PROPERTY.to_raw() as u32,
123 }),
124 }
125 }
126
127 fn write_property(
128 &mut self,
129 property: PropertyIdentifier,
130 _array_index: Option<u32>,
131 value: PropertyValue,
132 _priority: Option<u8>,
133 ) -> Result<(), Error> {
134 if property == PropertyIdentifier::DESCRIPTION {
135 if let PropertyValue::CharacterString(s) = value {
136 self.description = s;
137 return Ok(());
138 }
139 return Err(Error::Protocol {
140 class: ErrorClass::PROPERTY.to_raw() as u32,
141 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
142 });
143 }
144 if property == PropertyIdentifier::PRESENT_VALUE {
145 return Err(Error::Protocol {
146 class: ErrorClass::PROPERTY.to_raw() as u32,
147 code: ErrorCode::WRITE_ACCESS_DENIED.to_raw() as u32,
148 });
149 }
150 Err(Error::Protocol {
151 class: ErrorClass::PROPERTY.to_raw() as u32,
152 code: ErrorCode::WRITE_ACCESS_DENIED.to_raw() as u32,
153 })
154 }
155
156 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
157 static PROPS: &[PropertyIdentifier] = &[
158 PropertyIdentifier::OBJECT_IDENTIFIER,
159 PropertyIdentifier::OBJECT_NAME,
160 PropertyIdentifier::DESCRIPTION,
161 PropertyIdentifier::OBJECT_TYPE,
162 PropertyIdentifier::PRESENT_VALUE,
163 PropertyIdentifier::DATE_LIST,
164 PropertyIdentifier::STATUS_FLAGS,
165 PropertyIdentifier::EVENT_STATE,
166 PropertyIdentifier::OUT_OF_SERVICE,
167 ];
168 Cow::Borrowed(PROPS)
169 }
170}
171
172pub struct ScheduleObject {
182 oid: ObjectIdentifier,
183 name: String,
184 description: String,
185 present_value: PropertyValue,
186 schedule_default: PropertyValue,
187 out_of_service: bool,
188 reliability: u32,
189 status_flags: StatusFlags,
190 weekly_schedule: [Vec<BACnetTimeValue>; 7],
192 exception_schedule: Vec<BACnetSpecialEvent>,
193 effective_period: Option<BACnetDateRange>,
194 list_of_object_property_references: Vec<BACnetObjectPropertyReference>,
195 priority_for_writing: u8,
197}
198
199impl ScheduleObject {
200 pub fn new(
201 instance: u32,
202 name: impl Into<String>,
203 schedule_default: PropertyValue,
204 ) -> Result<Self, Error> {
205 let oid = ObjectIdentifier::new(ObjectType::SCHEDULE, instance)?;
206 Ok(Self {
207 oid,
208 name: name.into(),
209 description: String::new(),
210 present_value: schedule_default.clone(),
211 schedule_default,
212 out_of_service: false,
213 reliability: 0,
214 status_flags: StatusFlags::empty(),
215 weekly_schedule: [vec![], vec![], vec![], vec![], vec![], vec![], vec![]],
216 exception_schedule: Vec::new(),
217 effective_period: None,
218 list_of_object_property_references: Vec::new(),
219 priority_for_writing: 16, })
221 }
222
223 pub fn set_present_value(&mut self, value: PropertyValue) {
225 self.present_value = value;
226 }
227
228 pub fn set_description(&mut self, desc: impl Into<String>) {
230 self.description = desc.into();
231 }
232
233 pub fn set_weekly_schedule(&mut self, day_index: usize, entries: Vec<BACnetTimeValue>) {
235 if day_index < 7 {
236 self.weekly_schedule[day_index] = entries;
237 }
238 }
239
240 pub fn add_exception(&mut self, event: BACnetSpecialEvent) {
242 self.exception_schedule.push(event);
243 }
244
245 pub fn set_effective_period(&mut self, period: BACnetDateRange) {
247 self.effective_period = Some(period);
248 }
249
250 pub fn add_object_property_reference(&mut self, r: BACnetObjectPropertyReference) {
252 self.list_of_object_property_references.push(r);
253 }
254
255 pub fn present_value(&self) -> &PropertyValue {
257 &self.present_value
258 }
259
260 pub fn evaluate(&self, day_of_week: u8, hour: u8, minute: u8) -> PropertyValue {
265 if self.out_of_service {
266 return self.present_value.clone();
267 }
268
269 let mut best_exception: Option<(u8, &[u8])> = None;
271 for event in &self.exception_schedule {
272 if let Some(raw) = find_active_time_value(&event.list_of_time_values, hour, minute) {
273 match best_exception {
274 None => best_exception = Some((event.event_priority, raw)),
275 Some((p, _)) if event.event_priority < p => {
276 best_exception = Some((event.event_priority, raw));
277 }
278 _ => {}
279 }
280 }
281 }
282 if let Some((_, raw)) = best_exception {
283 return PropertyValue::OctetString(raw.to_vec());
284 }
285
286 if (day_of_week as usize) < 7 {
288 if let Some(raw) =
289 find_active_time_value(&self.weekly_schedule[day_of_week as usize], hour, minute)
290 {
291 return PropertyValue::OctetString(raw.to_vec());
292 }
293 }
294
295 self.schedule_default.clone()
297 }
298}
299
300fn find_active_time_value(entries: &[BACnetTimeValue], hour: u8, minute: u8) -> Option<&[u8]> {
304 let mut result = None;
305 for tv in entries {
306 let t = &tv.time;
307 if t.hour < hour || (t.hour == hour && t.minute <= minute) {
308 result = Some(tv.value.as_slice());
309 }
310 }
311 result
312}
313
314impl BACnetObject for ScheduleObject {
315 fn object_identifier(&self) -> ObjectIdentifier {
316 self.oid
317 }
318
319 fn object_name(&self) -> &str {
320 &self.name
321 }
322
323 fn read_property(
324 &self,
325 property: PropertyIdentifier,
326 array_index: Option<u32>,
327 ) -> Result<PropertyValue, Error> {
328 match property {
329 p if p == PropertyIdentifier::OBJECT_IDENTIFIER => {
330 Ok(PropertyValue::ObjectIdentifier(self.oid))
331 }
332 p if p == PropertyIdentifier::OBJECT_NAME => {
333 Ok(PropertyValue::CharacterString(self.name.clone()))
334 }
335 p if p == PropertyIdentifier::DESCRIPTION => {
336 Ok(PropertyValue::CharacterString(self.description.clone()))
337 }
338 p if p == PropertyIdentifier::OBJECT_TYPE => {
339 Ok(PropertyValue::Enumerated(ObjectType::SCHEDULE.to_raw()))
340 }
341 p if p == PropertyIdentifier::PRESENT_VALUE => Ok(self.present_value.clone()),
342 p if p == PropertyIdentifier::SCHEDULE_DEFAULT => Ok(self.schedule_default.clone()),
343 p if p == PropertyIdentifier::STATUS_FLAGS => Ok(PropertyValue::BitString {
344 unused_bits: 4,
345 data: vec![self.status_flags.bits() << 4],
346 }),
347 p if p == PropertyIdentifier::EVENT_STATE => Ok(PropertyValue::Enumerated(0)),
348 p if p == PropertyIdentifier::RELIABILITY => {
349 Ok(PropertyValue::Enumerated(self.reliability))
350 }
351 p if p == PropertyIdentifier::OUT_OF_SERVICE => {
352 Ok(PropertyValue::Boolean(self.out_of_service))
353 }
354 p if p == PropertyIdentifier::WEEKLY_SCHEDULE => match array_index {
355 None => {
356 let days: Vec<PropertyValue> = self
357 .weekly_schedule
358 .iter()
359 .map(|day| {
360 PropertyValue::List(
361 day.iter()
362 .map(|tv| {
363 PropertyValue::List(vec![
364 PropertyValue::Time(tv.time),
365 PropertyValue::OctetString(tv.value.clone()),
366 ])
367 })
368 .collect(),
369 )
370 })
371 .collect();
372 Ok(PropertyValue::List(days))
373 }
374 Some(0) => Ok(PropertyValue::Unsigned(7)),
375 Some(idx) if (1..=7).contains(&idx) => {
376 let day = &self.weekly_schedule[(idx - 1) as usize];
377 Ok(PropertyValue::List(
378 day.iter()
379 .map(|tv| {
380 PropertyValue::List(vec![
381 PropertyValue::Time(tv.time),
382 PropertyValue::OctetString(tv.value.clone()),
383 ])
384 })
385 .collect(),
386 ))
387 }
388 _ => Err(Error::Protocol {
389 class: ErrorClass::PROPERTY.to_raw() as u32,
390 code: ErrorCode::INVALID_ARRAY_INDEX.to_raw() as u32,
391 }),
392 },
393 p if p == PropertyIdentifier::EXCEPTION_SCHEDULE => match array_index {
394 None => {
395 let events: Vec<PropertyValue> = self
396 .exception_schedule
397 .iter()
398 .map(|ev| {
399 let tvs: Vec<PropertyValue> = ev
400 .list_of_time_values
401 .iter()
402 .map(|tv| {
403 PropertyValue::List(vec![
404 PropertyValue::Time(tv.time),
405 PropertyValue::OctetString(tv.value.clone()),
406 ])
407 })
408 .collect();
409 PropertyValue::List(vec![
410 PropertyValue::Unsigned(ev.event_priority as u64),
411 PropertyValue::List(tvs),
412 ])
413 })
414 .collect();
415 Ok(PropertyValue::List(events))
416 }
417 Some(0) => Ok(PropertyValue::Unsigned(self.exception_schedule.len() as u64)),
418 Some(i) => {
419 let idx = (i as usize).checked_sub(1).ok_or(Error::Protocol {
420 class: ErrorClass::PROPERTY.to_raw() as u32,
421 code: ErrorCode::INVALID_ARRAY_INDEX.to_raw() as u32,
422 })?;
423 let ev = self.exception_schedule.get(idx).ok_or(Error::Protocol {
424 class: ErrorClass::PROPERTY.to_raw() as u32,
425 code: ErrorCode::INVALID_ARRAY_INDEX.to_raw() as u32,
426 })?;
427 let tvs: Vec<PropertyValue> = ev
428 .list_of_time_values
429 .iter()
430 .map(|tv| {
431 PropertyValue::List(vec![
432 PropertyValue::Time(tv.time),
433 PropertyValue::OctetString(tv.value.clone()),
434 ])
435 })
436 .collect();
437 Ok(PropertyValue::List(vec![
438 PropertyValue::Unsigned(ev.event_priority as u64),
439 PropertyValue::List(tvs),
440 ]))
441 }
442 },
443 p if p == PropertyIdentifier::EFFECTIVE_PERIOD => match &self.effective_period {
444 Some(dr) => Ok(PropertyValue::OctetString(dr.encode().to_vec())),
445 None => Ok(PropertyValue::Null),
446 },
447 p if p == PropertyIdentifier::LIST_OF_OBJECT_PROPERTY_REFERENCES => {
448 Ok(PropertyValue::List(
449 self.list_of_object_property_references
450 .iter()
451 .map(|r| {
452 PropertyValue::List(vec![
453 PropertyValue::ObjectIdentifier(r.object_identifier),
454 PropertyValue::Enumerated(r.property_identifier),
455 ])
456 })
457 .collect(),
458 ))
459 }
460 p if p == PropertyIdentifier::PRIORITY_FOR_WRITING => {
461 Ok(PropertyValue::Unsigned(self.priority_for_writing as u64))
462 }
463 p if p == PropertyIdentifier::PROPERTY_LIST => {
464 read_property_list_property(&self.property_list(), array_index)
465 }
466 _ => Err(Error::Protocol {
467 class: ErrorClass::PROPERTY.to_raw() as u32,
468 code: ErrorCode::UNKNOWN_PROPERTY.to_raw() as u32,
469 }),
470 }
471 }
472
473 fn write_property(
474 &mut self,
475 property: PropertyIdentifier,
476 _array_index: Option<u32>,
477 value: PropertyValue,
478 _priority: Option<u8>,
479 ) -> Result<(), Error> {
480 if property == PropertyIdentifier::SCHEDULE_DEFAULT {
481 self.schedule_default = value;
482 return Ok(());
483 }
484 if property == PropertyIdentifier::RELIABILITY {
485 if let PropertyValue::Enumerated(v) = value {
486 self.reliability = v;
487 return Ok(());
488 }
489 return Err(Error::Protocol {
490 class: ErrorClass::PROPERTY.to_raw() as u32,
491 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
492 });
493 }
494 if property == PropertyIdentifier::OUT_OF_SERVICE {
495 if let PropertyValue::Boolean(v) = value {
496 self.out_of_service = v;
497 return Ok(());
498 }
499 return Err(Error::Protocol {
500 class: ErrorClass::PROPERTY.to_raw() as u32,
501 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
502 });
503 }
504 if property == PropertyIdentifier::DESCRIPTION {
505 if let PropertyValue::CharacterString(s) = value {
506 self.description = s;
507 return Ok(());
508 }
509 return Err(Error::Protocol {
510 class: ErrorClass::PROPERTY.to_raw() as u32,
511 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
512 });
513 }
514 Err(Error::Protocol {
515 class: ErrorClass::PROPERTY.to_raw() as u32,
516 code: ErrorCode::WRITE_ACCESS_DENIED.to_raw() as u32,
517 })
518 }
519
520 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
521 static PROPS: &[PropertyIdentifier] = &[
522 PropertyIdentifier::OBJECT_IDENTIFIER,
523 PropertyIdentifier::OBJECT_NAME,
524 PropertyIdentifier::DESCRIPTION,
525 PropertyIdentifier::OBJECT_TYPE,
526 PropertyIdentifier::PRESENT_VALUE,
527 PropertyIdentifier::SCHEDULE_DEFAULT,
528 PropertyIdentifier::WEEKLY_SCHEDULE,
529 PropertyIdentifier::EXCEPTION_SCHEDULE,
530 PropertyIdentifier::EFFECTIVE_PERIOD,
531 PropertyIdentifier::LIST_OF_OBJECT_PROPERTY_REFERENCES,
532 PropertyIdentifier::STATUS_FLAGS,
533 PropertyIdentifier::EVENT_STATE,
534 PropertyIdentifier::RELIABILITY,
535 PropertyIdentifier::OUT_OF_SERVICE,
536 ];
537 Cow::Borrowed(PROPS)
538 }
539
540 fn tick_schedule(
541 &mut self,
542 day_of_week: u8,
543 hour: u8,
544 minute: u8,
545 ) -> Option<(PropertyValue, Vec<(ObjectIdentifier, u32)>)> {
546 if self.out_of_service || self.list_of_object_property_references.is_empty() {
547 return None;
548 }
549
550 let new_value = self.evaluate(day_of_week, hour, minute);
551 if new_value == self.present_value {
552 return None;
553 }
554
555 self.present_value = new_value.clone();
556
557 let refs = self
558 .list_of_object_property_references
559 .iter()
560 .map(|r| (r.object_identifier, r.property_identifier))
561 .collect();
562
563 Some((new_value, refs))
564 }
565}
566
567#[cfg(test)]
568mod tests;