1use crate::prelude::*;
4use alloc::borrow::Cow;
5use bacnet_encoding::constructed::{
6 encode_daily_schedule, encode_date_range, encode_special_event,
7};
8use bacnet_types::calendar::SpecificDate;
9use bacnet_types::constructed::{
10 BACnetDateRange, BACnetObjectPropertyReference, BACnetSpecialEvent, BACnetTimeValue,
11};
12use bacnet_types::enums::{
13 ErrorClass, ErrorCode, EventState, ObjectType, PropertyIdentifier, Reliability,
14};
15use bacnet_types::error::Error;
16use bacnet_types::primitives::{Date, ObjectIdentifier, PropertyValue, StatusFlags, Time};
17use bytes::BytesMut;
18
19use crate::common::{self, read_property_list_property};
20use crate::traits::BACnetObject;
21
22mod calendar;
23mod calendar_metadata;
24mod date_list;
25mod evaluation;
26mod metadata;
27mod out_of_service;
28mod reliability;
29mod targets;
30mod writes;
31
32pub use calendar::CalendarObject;
33pub use evaluation::{ScheduleTargetOutcome, ScheduleWrite};
34
35pub struct ScheduleObject {
74 oid: ObjectIdentifier,
75 name: String,
76 description: String,
77 present_value: PropertyValue,
78 schedule_default: PropertyValue,
79 out_of_service: bool,
80 reliability: Reliability,
81 reliability_before_out_of_service: Option<Reliability>,
84 owns_configuration_error: bool,
87 refusing_references: Vec<BACnetObjectPropertyReference>,
92 status_flags: StatusFlags,
93 weekly_schedule: [Vec<BACnetTimeValue>; 7],
95 exception_schedule: Vec<BACnetSpecialEvent>,
96 effective_period: BACnetDateRange,
97 list_of_object_property_references: Vec<BACnetObjectPropertyReference>,
98 priority_for_writing: u8,
100 in_effective_period: bool,
105 simulated_write: Option<PropertyValue>,
108 held: Option<targets::HeldCommand>,
110 relinquish_owed: Vec<ScheduleWrite>,
113 rewrite_owed: bool,
116}
117
118impl ScheduleObject {
119 pub fn new(
124 instance: u32,
125 name: impl Into<String>,
126 schedule_default: PropertyValue,
127 ) -> Result<Self, Error> {
128 let oid = ObjectIdentifier::new(ObjectType::SCHEDULE, instance)?;
129 if !schedule_default.is_primitive() {
130 return Err(common::invalid_data_type_error());
131 }
132 Ok(Self {
133 oid,
134 name: name.into(),
135 description: String::new(),
136 present_value: schedule_default.clone(),
137 schedule_default,
138 out_of_service: false,
139 reliability: Reliability::NO_FAULT_DETECTED,
140 reliability_before_out_of_service: None,
141 owns_configuration_error: false,
142 refusing_references: Vec::new(),
143 status_flags: StatusFlags::empty(),
144 weekly_schedule: [vec![], vec![], vec![], vec![], vec![], vec![], vec![]],
145 exception_schedule: Vec::new(),
146 effective_period: BACnetDateRange {
147 start_date: unspecified_date(),
148 end_date: unspecified_date(),
149 },
150 list_of_object_property_references: Vec::new(),
151 priority_for_writing: 16, in_effective_period: false,
153 simulated_write: None,
154 held: None,
155 relinquish_owed: Vec::new(),
156 rewrite_owed: false,
157 })
158 }
159
160 pub fn set_description(&mut self, desc: impl Into<String>) {
162 self.description = desc.into();
163 }
164
165 pub fn set_weekly_schedule(
172 &mut self,
173 day_index: usize,
174 entries: Vec<BACnetTimeValue>,
175 ) -> Result<(), Error> {
176 let Some(day) = self.weekly_schedule.get_mut(day_index) else {
177 return Err(common::value_out_of_range_error());
178 };
179 evaluation::check_time_values(&entries)?;
180 *day = entries;
181 self.contents_changed();
182 Ok(())
183 }
184
185 pub fn add_exception(&mut self, event: BACnetSpecialEvent) -> Result<(), Error> {
193 evaluation::check_special_event(&event)?;
194 if self.exception_schedule.len() >= writes::MAX_EXCEPTIONS {
195 return Err(writes::no_space_error());
196 }
197 self.exception_schedule.push(event);
198 self.contents_changed();
199 Ok(())
200 }
201
202 pub fn set_effective_period(&mut self, period: BACnetDateRange) -> Result<(), Error> {
207 if !period.is_valid() {
208 return Err(common::value_out_of_range_error());
209 }
210 self.effective_period = period;
211 Ok(())
212 }
213
214 pub fn set_priority_for_writing(&mut self, priority: u8) -> Result<(), Error> {
221 if !(1..=16).contains(&priority) {
222 return Err(common::value_out_of_range_error());
223 }
224 self.priority_for_writing = priority;
225 self.targets_changed();
226 Ok(())
227 }
228
229 pub fn add_object_property_reference(
236 &mut self,
237 r: BACnetObjectPropertyReference,
238 ) -> Result<(), Error> {
239 if self.list_of_object_property_references.len() >= targets::MAX_REFERENCES {
240 return Err(writes::no_space_error());
241 }
242 self.list_of_object_property_references.push(r);
243 self.targets_changed();
244 Ok(())
245 }
246
247 pub fn set_object_property_references(
261 &mut self,
262 references: Vec<BACnetObjectPropertyReference>,
263 ) -> Result<(), Error> {
264 if references.len() > targets::MAX_REFERENCES {
265 return Err(writes::no_space_error());
266 }
267 self.list_of_object_property_references = references;
268 self.targets_changed();
269 Ok(())
270 }
271
272 pub fn present_value(&self) -> &PropertyValue {
274 &self.present_value
275 }
276
277 fn contents_changed(&mut self) {
280 let _ = self.recompute_reliability();
281 }
282}
283
284fn unspecified_date() -> Date {
286 Date {
287 year: Date::UNSPECIFIED,
288 month: Date::UNSPECIFIED,
289 day: Date::UNSPECIFIED,
290 day_of_week: Date::UNSPECIFIED,
291 }
292}
293
294fn daily_schedule(time_values: &[BACnetTimeValue]) -> Result<PropertyValue, Error> {
296 let mut encoded = BytesMut::new();
297 encode_daily_schedule(&mut encoded, time_values)?;
298 Ok(PropertyValue::ApplicationData(encoded.to_vec()))
299}
300
301fn special_event(event: &BACnetSpecialEvent) -> Result<PropertyValue, Error> {
303 let mut encoded = BytesMut::new();
304 encode_special_event(&mut encoded, event)?;
305 Ok(PropertyValue::ApplicationData(encoded.to_vec()))
306}
307
308impl BACnetObject for ScheduleObject {
309 fn object_identifier(&self) -> ObjectIdentifier {
310 self.oid
311 }
312
313 fn object_name(&self) -> &str {
314 &self.name
315 }
316
317 fn read_property(
318 &self,
319 property: PropertyIdentifier,
320 array_index: Option<u32>,
321 ) -> Result<PropertyValue, Error> {
322 match property {
323 p if p == PropertyIdentifier::OBJECT_IDENTIFIER => {
324 Ok(PropertyValue::ObjectIdentifier(self.oid))
325 }
326 p if p == PropertyIdentifier::OBJECT_NAME => {
327 Ok(PropertyValue::CharacterString(self.name.clone()))
328 }
329 p if p == PropertyIdentifier::DESCRIPTION => {
330 Ok(PropertyValue::CharacterString(self.description.clone()))
331 }
332 p if p == PropertyIdentifier::OBJECT_TYPE => {
333 Ok(PropertyValue::Enumerated(ObjectType::SCHEDULE.to_raw()))
334 }
335 p if p == PropertyIdentifier::PRESENT_VALUE => Ok(self.present_value.clone()),
336 p if p == PropertyIdentifier::SCHEDULE_DEFAULT => Ok(self.schedule_default.clone()),
337 p if p == PropertyIdentifier::STATUS_FLAGS => Ok(common::compute_status_flags(
341 self.status_flags,
342 self.reliability,
343 self.out_of_service,
344 EventState::NORMAL,
345 )),
346 p if p == PropertyIdentifier::EVENT_STATE => {
347 Ok(PropertyValue::Enumerated(EventState::NORMAL.to_raw()))
348 }
349 p if p == PropertyIdentifier::RELIABILITY => {
350 Ok(PropertyValue::Enumerated(self.reliability.to_raw()))
351 }
352 p if p == PropertyIdentifier::OUT_OF_SERVICE => {
353 Ok(PropertyValue::Boolean(self.out_of_service))
354 }
355 p if p == PropertyIdentifier::WEEKLY_SCHEDULE => match array_index {
358 None => Ok(PropertyValue::List(
359 self.weekly_schedule
360 .iter()
361 .map(|day| daily_schedule(day))
362 .collect::<Result<_, _>>()?,
363 )),
364 Some(0) => Ok(PropertyValue::Unsigned(7)),
365 Some(idx) if (1..=7).contains(&idx) => {
366 daily_schedule(&self.weekly_schedule[(idx - 1) as usize])
367 }
368 _ => Err(common::invalid_array_index_error()),
369 },
370 p if p == PropertyIdentifier::EXCEPTION_SCHEDULE => match array_index {
371 None => Ok(PropertyValue::List(
372 self.exception_schedule
373 .iter()
374 .map(special_event)
375 .collect::<Result<_, _>>()?,
376 )),
377 Some(0) => Ok(PropertyValue::Unsigned(self.exception_schedule.len() as u64)),
378 Some(i) => (i as usize)
379 .checked_sub(1)
380 .and_then(|idx| self.exception_schedule.get(idx))
381 .ok_or_else(common::invalid_array_index_error)
382 .and_then(special_event),
383 },
384 p if p == PropertyIdentifier::EFFECTIVE_PERIOD => {
385 let mut encoded = BytesMut::new();
386 encode_date_range(&mut encoded, &self.effective_period);
387 Ok(PropertyValue::ApplicationData(encoded.to_vec()))
388 }
389 p if p == PropertyIdentifier::LIST_OF_OBJECT_PROPERTY_REFERENCES => {
392 Ok(crate::device_reference::reference_run(
393 &self
394 .list_of_object_property_references
395 .iter()
396 .map(crate::device_reference::local_property_reference)
397 .collect::<Vec<_>>(),
398 ))
399 }
400 p if p == PropertyIdentifier::PRIORITY_FOR_WRITING => {
401 Ok(PropertyValue::Unsigned(self.priority_for_writing as u64))
402 }
403 p if p == PropertyIdentifier::PROPERTY_LIST => {
404 read_property_list_property(&self.property_list(), array_index)
405 }
406 _ => Err(Error::protocol(
407 ErrorClass::PROPERTY,
408 ErrorCode::UNKNOWN_PROPERTY,
409 )),
410 }
411 }
412
413 fn write_property(
414 &mut self,
415 property: PropertyIdentifier,
416 array_index: Option<u32>,
417 value: PropertyValue,
418 _priority: Option<u8>,
419 ) -> Result<(), Error> {
420 if property == PropertyIdentifier::PRESENT_VALUE {
421 return self.write_present_value(array_index, value);
422 }
423 if property == PropertyIdentifier::WEEKLY_SCHEDULE {
424 return self.write_weekly_schedule(array_index, value);
425 }
426 if property == PropertyIdentifier::EXCEPTION_SCHEDULE {
427 return self.write_exception_schedule(array_index, value);
428 }
429 if property == PropertyIdentifier::EFFECTIVE_PERIOD {
430 if array_index.is_some() {
431 return Err(common::property_is_not_an_array_error());
432 }
433 return self.write_effective_period(value);
434 }
435 if property == PropertyIdentifier::LIST_OF_OBJECT_PROPERTY_REFERENCES {
436 if array_index.is_some() {
437 return Err(common::property_is_not_an_array_error());
438 }
439 return self.write_object_property_references(value);
440 }
441 if property == PropertyIdentifier::PRIORITY_FOR_WRITING {
442 if array_index.is_some() {
443 return Err(common::property_is_not_an_array_error());
444 }
445 return self.write_priority_for_writing(value);
446 }
447 if property == PropertyIdentifier::SCHEDULE_DEFAULT {
448 if array_index.is_some() {
449 return Err(common::property_is_not_an_array_error());
450 }
451 if !value.is_primitive() {
453 return Err(common::invalid_data_type_error());
454 }
455 self.schedule_default = value;
456 self.contents_changed();
457 return Ok(());
458 }
459 if property == PropertyIdentifier::RELIABILITY {
469 if !self.out_of_service {
470 return Err(common::write_access_denied_error());
471 }
472 if let PropertyValue::Enumerated(raw) = value {
473 let v = Reliability::from_raw(raw);
474 if !common::is_reliability_value_valid(v) {
475 return Err(common::value_out_of_range_error());
476 }
477 self.reliability = v;
478 return Ok(());
479 }
480 return Err(Error::protocol(
481 ErrorClass::PROPERTY,
482 ErrorCode::INVALID_DATA_TYPE,
483 ));
484 }
485 if let Some(result) = common::write_out_of_service_with_reliability_restore(
486 &mut self.out_of_service,
487 &mut self.reliability,
488 &mut self.reliability_before_out_of_service,
489 property,
490 &value,
491 ) {
492 result?;
493 let _ = self.recompute_reliability();
496 return Ok(());
497 }
498 if let Some(result) = common::write_description(&mut self.description, property, &value) {
499 return result;
500 }
501 Err(crate::common::unhandled_write_error(
502 self.property_metadata().as_ref(),
503 property,
504 array_index,
505 ))
506 }
507
508 fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
509 metadata::for_object(self)
510 }
511
512 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
513 crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
514 }
515
516 fn set_reliability_internal(&mut self, reliability: Reliability) -> Result<(), Error> {
517 if self.out_of_service {
521 return Err(common::write_access_denied_error());
522 }
523 if !common::is_reliability_value_valid(reliability) {
524 return Err(common::value_out_of_range_error());
525 }
526 self.reliability = reliability;
527 self.owns_configuration_error = false;
529 Ok(())
530 }
531
532 fn evaluate_reliability_internal(
533 &mut self,
534 ) -> Result<crate::traits::ReliabilityEvaluation, Error> {
535 Ok(self.recompute_reliability())
536 }
537
538 fn take_owed_schedule_writes(&mut self) -> Vec<ScheduleWrite> {
539 self.take_owed_writes()
540 }
541
542 fn complete_schedule_write(
543 &mut self,
544 write: &ScheduleWrite,
545 outcomes: &[ScheduleTargetOutcome],
546 ) -> bool {
547 self.complete_write(write, outcomes)
548 }
549
550 fn retry_refusals_naming(&self, target: ObjectIdentifier) -> Option<ScheduleWrite> {
551 self.retry_refused_naming(target)
552 }
553
554 fn tick_schedule(
555 &mut self,
556 today: SpecificDate,
557 time: Time,
558 calendar_active: &dyn Fn(ObjectIdentifier) -> bool,
559 ) -> Option<ScheduleWrite> {
560 if self.out_of_service {
563 return None;
564 }
565 let Some(value) = self.evaluate(today, time, calendar_active) else {
566 self.in_effective_period = false;
570 self.rewrite_owed = false;
571 self.held = None;
572 return None;
573 };
574 let entered = !core::mem::replace(&mut self.in_effective_period, true);
575 if !entered && !self.rewrite_owed && value == self.present_value {
576 return self.retry_refused();
579 }
580 self.present_value = value.clone();
581 self.command(value)
582 }
583}
584
585#[cfg(test)]
586mod tests;
587
588#[cfg(test)]
589mod calendar_tests;
590
591#[cfg(test)]
592mod evaluation_tests;
593
594#[cfg(test)]
595mod out_of_service_tests;
596
597#[cfg(test)]
598mod reliability_tests;
599
600#[cfg(test)]
601mod retry_tests;
602
603#[cfg(test)]
604mod targets_tests;
605
606#[cfg(test)]
607mod write_tests;