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::{self, 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 reliability_before_out_of_service: Option<u32>,
192 status_flags: StatusFlags,
193 weekly_schedule: [Vec<BACnetTimeValue>; 7],
195 exception_schedule: Vec<BACnetSpecialEvent>,
196 effective_period: Option<BACnetDateRange>,
197 list_of_object_property_references: Vec<BACnetObjectPropertyReference>,
198 priority_for_writing: u8,
200}
201
202impl ScheduleObject {
203 pub fn new(
204 instance: u32,
205 name: impl Into<String>,
206 schedule_default: PropertyValue,
207 ) -> Result<Self, Error> {
208 let oid = ObjectIdentifier::new(ObjectType::SCHEDULE, instance)?;
209 Ok(Self {
210 oid,
211 name: name.into(),
212 description: String::new(),
213 present_value: schedule_default.clone(),
214 schedule_default,
215 out_of_service: false,
216 reliability: 0,
217 reliability_before_out_of_service: None,
218 status_flags: StatusFlags::empty(),
219 weekly_schedule: [vec![], vec![], vec![], vec![], vec![], vec![], vec![]],
220 exception_schedule: Vec::new(),
221 effective_period: None,
222 list_of_object_property_references: Vec::new(),
223 priority_for_writing: 16, })
225 }
226
227 pub fn set_present_value(&mut self, value: PropertyValue) {
229 self.present_value = value;
230 }
231
232 pub fn set_description(&mut self, desc: impl Into<String>) {
234 self.description = desc.into();
235 }
236
237 pub fn set_weekly_schedule(&mut self, day_index: usize, entries: Vec<BACnetTimeValue>) {
239 if day_index < 7 {
240 self.weekly_schedule[day_index] = entries;
241 }
242 }
243
244 pub fn add_exception(&mut self, event: BACnetSpecialEvent) {
246 self.exception_schedule.push(event);
247 }
248
249 pub fn set_effective_period(&mut self, period: BACnetDateRange) {
251 self.effective_period = Some(period);
252 }
253
254 pub fn add_object_property_reference(&mut self, r: BACnetObjectPropertyReference) {
256 self.list_of_object_property_references.push(r);
257 }
258
259 pub fn present_value(&self) -> &PropertyValue {
261 &self.present_value
262 }
263
264 pub fn evaluate(&self, day_of_week: u8, hour: u8, minute: u8) -> PropertyValue {
269 if self.out_of_service {
270 return self.present_value.clone();
271 }
272
273 let mut best_exception: Option<(u8, &[u8])> = None;
275 for event in &self.exception_schedule {
276 if let Some(raw) = find_active_time_value(&event.list_of_time_values, hour, minute) {
277 match best_exception {
278 None => best_exception = Some((event.event_priority, raw)),
279 Some((p, _)) if event.event_priority < p => {
280 best_exception = Some((event.event_priority, raw));
281 }
282 _ => {}
283 }
284 }
285 }
286 if let Some((_, raw)) = best_exception {
287 return PropertyValue::OctetString(raw.to_vec());
288 }
289
290 if (day_of_week as usize) < 7 {
292 if let Some(raw) =
293 find_active_time_value(&self.weekly_schedule[day_of_week as usize], hour, minute)
294 {
295 return PropertyValue::OctetString(raw.to_vec());
296 }
297 }
298
299 self.schedule_default.clone()
301 }
302}
303
304fn find_active_time_value(entries: &[BACnetTimeValue], hour: u8, minute: u8) -> Option<&[u8]> {
308 let mut result = None;
309 for tv in entries {
310 let t = &tv.time;
311 if t.hour < hour || (t.hour == hour && t.minute <= minute) {
312 result = Some(tv.value.as_slice());
313 }
314 }
315 result
316}
317
318impl BACnetObject for ScheduleObject {
319 fn object_identifier(&self) -> ObjectIdentifier {
320 self.oid
321 }
322
323 fn object_name(&self) -> &str {
324 &self.name
325 }
326
327 fn read_property(
328 &self,
329 property: PropertyIdentifier,
330 array_index: Option<u32>,
331 ) -> Result<PropertyValue, Error> {
332 match property {
333 p if p == PropertyIdentifier::OBJECT_IDENTIFIER => {
334 Ok(PropertyValue::ObjectIdentifier(self.oid))
335 }
336 p if p == PropertyIdentifier::OBJECT_NAME => {
337 Ok(PropertyValue::CharacterString(self.name.clone()))
338 }
339 p if p == PropertyIdentifier::DESCRIPTION => {
340 Ok(PropertyValue::CharacterString(self.description.clone()))
341 }
342 p if p == PropertyIdentifier::OBJECT_TYPE => {
343 Ok(PropertyValue::Enumerated(ObjectType::SCHEDULE.to_raw()))
344 }
345 p if p == PropertyIdentifier::PRESENT_VALUE => Ok(self.present_value.clone()),
346 p if p == PropertyIdentifier::SCHEDULE_DEFAULT => Ok(self.schedule_default.clone()),
347 p if p == PropertyIdentifier::STATUS_FLAGS => Ok(PropertyValue::BitString {
348 unused_bits: 4,
349 data: vec![self.status_flags.bits() << 4],
350 }),
351 p if p == PropertyIdentifier::EVENT_STATE => Ok(PropertyValue::Enumerated(0)),
352 p if p == PropertyIdentifier::RELIABILITY => {
353 Ok(PropertyValue::Enumerated(self.reliability))
354 }
355 p if p == PropertyIdentifier::OUT_OF_SERVICE => {
356 Ok(PropertyValue::Boolean(self.out_of_service))
357 }
358 p if p == PropertyIdentifier::WEEKLY_SCHEDULE => match array_index {
359 None => {
360 let days: Vec<PropertyValue> = self
361 .weekly_schedule
362 .iter()
363 .map(|day| {
364 PropertyValue::List(
365 day.iter()
366 .map(|tv| {
367 PropertyValue::List(vec![
368 PropertyValue::Time(tv.time),
369 PropertyValue::OctetString(tv.value.clone()),
370 ])
371 })
372 .collect(),
373 )
374 })
375 .collect();
376 Ok(PropertyValue::List(days))
377 }
378 Some(0) => Ok(PropertyValue::Unsigned(7)),
379 Some(idx) if (1..=7).contains(&idx) => {
380 let day = &self.weekly_schedule[(idx - 1) as usize];
381 Ok(PropertyValue::List(
382 day.iter()
383 .map(|tv| {
384 PropertyValue::List(vec![
385 PropertyValue::Time(tv.time),
386 PropertyValue::OctetString(tv.value.clone()),
387 ])
388 })
389 .collect(),
390 ))
391 }
392 _ => Err(Error::Protocol {
393 class: ErrorClass::PROPERTY.to_raw() as u32,
394 code: ErrorCode::INVALID_ARRAY_INDEX.to_raw() as u32,
395 }),
396 },
397 p if p == PropertyIdentifier::EXCEPTION_SCHEDULE => match array_index {
398 None => {
399 let events: Vec<PropertyValue> = self
400 .exception_schedule
401 .iter()
402 .map(|ev| {
403 let tvs: Vec<PropertyValue> = ev
404 .list_of_time_values
405 .iter()
406 .map(|tv| {
407 PropertyValue::List(vec![
408 PropertyValue::Time(tv.time),
409 PropertyValue::OctetString(tv.value.clone()),
410 ])
411 })
412 .collect();
413 PropertyValue::List(vec![
414 PropertyValue::Unsigned(ev.event_priority as u64),
415 PropertyValue::List(tvs),
416 ])
417 })
418 .collect();
419 Ok(PropertyValue::List(events))
420 }
421 Some(0) => Ok(PropertyValue::Unsigned(self.exception_schedule.len() as u64)),
422 Some(i) => {
423 let idx = (i as usize).checked_sub(1).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 ev = self.exception_schedule.get(idx).ok_or(Error::Protocol {
428 class: ErrorClass::PROPERTY.to_raw() as u32,
429 code: ErrorCode::INVALID_ARRAY_INDEX.to_raw() as u32,
430 })?;
431 let tvs: Vec<PropertyValue> = ev
432 .list_of_time_values
433 .iter()
434 .map(|tv| {
435 PropertyValue::List(vec![
436 PropertyValue::Time(tv.time),
437 PropertyValue::OctetString(tv.value.clone()),
438 ])
439 })
440 .collect();
441 Ok(PropertyValue::List(vec![
442 PropertyValue::Unsigned(ev.event_priority as u64),
443 PropertyValue::List(tvs),
444 ]))
445 }
446 },
447 p if p == PropertyIdentifier::EFFECTIVE_PERIOD => match &self.effective_period {
448 Some(dr) => Ok(PropertyValue::OctetString(dr.encode().to_vec())),
449 None => Ok(PropertyValue::Null),
450 },
451 p if p == PropertyIdentifier::LIST_OF_OBJECT_PROPERTY_REFERENCES => {
452 Ok(PropertyValue::List(
453 self.list_of_object_property_references
454 .iter()
455 .map(|r| {
456 PropertyValue::List(vec![
457 PropertyValue::ObjectIdentifier(r.object_identifier),
458 PropertyValue::Enumerated(r.property_identifier),
459 ])
460 })
461 .collect(),
462 ))
463 }
464 p if p == PropertyIdentifier::PRIORITY_FOR_WRITING => {
465 Ok(PropertyValue::Unsigned(self.priority_for_writing as u64))
466 }
467 p if p == PropertyIdentifier::PROPERTY_LIST => {
468 read_property_list_property(&self.property_list(), array_index)
469 }
470 _ => Err(Error::Protocol {
471 class: ErrorClass::PROPERTY.to_raw() as u32,
472 code: ErrorCode::UNKNOWN_PROPERTY.to_raw() as u32,
473 }),
474 }
475 }
476
477 fn write_property(
478 &mut self,
479 property: PropertyIdentifier,
480 _array_index: Option<u32>,
481 value: PropertyValue,
482 _priority: Option<u8>,
483 ) -> Result<(), Error> {
484 if property == PropertyIdentifier::SCHEDULE_DEFAULT {
485 self.schedule_default = value;
486 return Ok(());
487 }
488 if property == PropertyIdentifier::RELIABILITY {
498 if !self.out_of_service {
499 return Err(common::write_access_denied_error());
500 }
501 if let PropertyValue::Enumerated(v) = value {
502 if !common::is_reliability_value_valid(v) {
503 return Err(common::value_out_of_range_error());
504 }
505 self.reliability = v;
506 return Ok(());
507 }
508 return Err(Error::Protocol {
509 class: ErrorClass::PROPERTY.to_raw() as u32,
510 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
511 });
512 }
513 if let Some(result) = common::write_out_of_service_with_reliability_restore(
514 &mut self.out_of_service,
515 &mut self.reliability,
516 &mut self.reliability_before_out_of_service,
517 property,
518 &value,
519 ) {
520 return result;
521 }
522 if property == PropertyIdentifier::DESCRIPTION {
523 if let PropertyValue::CharacterString(s) = value {
524 self.description = s;
525 return Ok(());
526 }
527 return Err(Error::Protocol {
528 class: ErrorClass::PROPERTY.to_raw() as u32,
529 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
530 });
531 }
532 Err(Error::Protocol {
533 class: ErrorClass::PROPERTY.to_raw() as u32,
534 code: ErrorCode::WRITE_ACCESS_DENIED.to_raw() as u32,
535 })
536 }
537
538 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
539 static PROPS: &[PropertyIdentifier] = &[
540 PropertyIdentifier::OBJECT_IDENTIFIER,
541 PropertyIdentifier::OBJECT_NAME,
542 PropertyIdentifier::DESCRIPTION,
543 PropertyIdentifier::OBJECT_TYPE,
544 PropertyIdentifier::PRESENT_VALUE,
545 PropertyIdentifier::SCHEDULE_DEFAULT,
546 PropertyIdentifier::WEEKLY_SCHEDULE,
547 PropertyIdentifier::EXCEPTION_SCHEDULE,
548 PropertyIdentifier::EFFECTIVE_PERIOD,
549 PropertyIdentifier::LIST_OF_OBJECT_PROPERTY_REFERENCES,
550 PropertyIdentifier::STATUS_FLAGS,
551 PropertyIdentifier::EVENT_STATE,
552 PropertyIdentifier::RELIABILITY,
553 PropertyIdentifier::OUT_OF_SERVICE,
554 ];
555 Cow::Borrowed(PROPS)
556 }
557
558 fn set_reliability_internal(&mut self, reliability: u32) -> Result<(), Error> {
559 if self.out_of_service {
563 return Err(common::write_access_denied_error());
564 }
565 if !common::is_reliability_value_valid(reliability) {
566 return Err(common::value_out_of_range_error());
567 }
568 self.reliability = reliability;
569 Ok(())
570 }
571
572 fn is_writable_property(&self, property: PropertyIdentifier) -> bool {
573 matches!(
576 property,
577 PropertyIdentifier::SCHEDULE_DEFAULT
578 | PropertyIdentifier::RELIABILITY
579 | PropertyIdentifier::OUT_OF_SERVICE
580 | PropertyIdentifier::DESCRIPTION
581 )
582 }
583
584 fn tick_schedule(
585 &mut self,
586 day_of_week: u8,
587 hour: u8,
588 minute: u8,
589 ) -> Option<(PropertyValue, Vec<(ObjectIdentifier, u32)>)> {
590 if self.out_of_service || self.list_of_object_property_references.is_empty() {
591 return None;
592 }
593
594 let new_value = self.evaluate(day_of_week, hour, minute);
595 if new_value == self.present_value {
596 return None;
597 }
598
599 self.present_value = new_value.clone();
600
601 let refs = self
602 .list_of_object_property_references
603 .iter()
604 .map(|r| (r.object_identifier, r.property_identifier))
605 .collect();
606
607 Some((new_value, refs))
608 }
609}
610
611#[cfg(test)]
612mod tests;