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