bacnet_objects/
load_control.rs1use bacnet_encoding::constructed::{decode_shed_level, encode_shed_level};
7use bacnet_types::constructed::BACnetShedLevel;
8use bacnet_types::enums::{EventState, ObjectType, PropertyIdentifier, Reliability};
9use bacnet_types::error::Error;
10use bacnet_types::primitives::{Date, ObjectIdentifier, PropertyValue, StatusFlags, Time};
11use bytes::BytesMut;
12use std::borrow::Cow;
13
14use crate::common::{self, read_common_properties};
15use crate::traits::BACnetObject;
16
17mod metadata;
18#[cfg(test)]
19mod tests;
20
21pub struct LoadControlObject {
30 oid: ObjectIdentifier,
31 name: String,
32 description: String,
33 present_value: u32,
36 requested_shed_level: BACnetShedLevel,
37 expected_shed_level: BACnetShedLevel,
38 actual_shed_level: BACnetShedLevel,
39 shed_duration: u64,
40 start_time: (Date, Time),
41 status_flags: StatusFlags,
42 event_state: EventState,
44 reliability: Reliability,
45}
46
47impl LoadControlObject {
48 pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
53 let oid = ObjectIdentifier::new(ObjectType::LOAD_CONTROL, instance)?;
54 Ok(Self {
55 oid,
56 name: name.into(),
57 description: String::new(),
58 present_value: 0,
59 requested_shed_level: BACnetShedLevel::Level(0),
60 expected_shed_level: BACnetShedLevel::Level(0),
61 actual_shed_level: BACnetShedLevel::Level(0),
62 shed_duration: 0,
63 start_time: (
64 Date {
65 year: 0xFF,
66 month: 0xFF,
67 day: 0xFF,
68 day_of_week: 0xFF,
69 },
70 Time {
71 hour: 0xFF,
72 minute: 0xFF,
73 second: 0xFF,
74 hundredths: 0xFF,
75 },
76 ),
77 status_flags: StatusFlags::empty(),
78 event_state: EventState::NORMAL,
79 reliability: Reliability::NO_FAULT_DETECTED,
80 })
81 }
82
83 pub fn set_requested_shed_level(&mut self, level: BACnetShedLevel) -> Result<(), Error> {
93 match level {
94 BACnetShedLevel::Percent(percent) if percent > 100 => {
95 return Err(common::value_out_of_range_error());
96 }
97 BACnetShedLevel::Amount(amount) => {
98 common::reject_non_finite(amount)?;
99 if amount < 0.0 {
100 return Err(common::value_out_of_range_error());
101 }
102 }
103 _ => {}
104 }
105 let default = choice_default(&level);
106 self.expected_shed_level = default.clone();
107 self.actual_shed_level = default;
108 self.requested_shed_level = level;
109 Ok(())
110 }
111
112 pub fn set_actual_shed_level(&mut self, level: BACnetShedLevel) -> Result<(), Error> {
118 if std::mem::discriminant(&level) != std::mem::discriminant(&self.requested_shed_level) {
119 return Err(common::value_out_of_range_error());
120 }
121 if let BACnetShedLevel::Amount(amount) = level {
122 common::reject_non_finite(amount)?;
123 }
124 self.actual_shed_level = level;
125 Ok(())
126 }
127}
128
129fn choice_default(level: &BACnetShedLevel) -> BACnetShedLevel {
132 match level {
133 BACnetShedLevel::Percent(_) => BACnetShedLevel::Percent(100),
134 BACnetShedLevel::Level(_) => BACnetShedLevel::Level(0),
135 BACnetShedLevel::Amount(_) => BACnetShedLevel::Amount(0.0),
136 }
137}
138
139fn shed_level_value(level: &BACnetShedLevel) -> PropertyValue {
141 let mut buf = BytesMut::new();
142 encode_shed_level(&mut buf, level);
143 PropertyValue::ApplicationData(buf.to_vec())
144}
145
146fn decode_shed_level_write(value: PropertyValue) -> Result<BACnetShedLevel, Error> {
151 let PropertyValue::ApplicationData(bytes) = value else {
152 return Err(common::invalid_data_type_error());
153 };
154 match decode_shed_level(&bytes, 0) {
155 Ok((level, end)) if end == bytes.len() => Ok(level),
156 _ => Err(common::invalid_data_type_error()),
157 }
158}
159
160impl BACnetObject for LoadControlObject {
161 fn object_identifier(&self) -> ObjectIdentifier {
162 self.oid
163 }
164
165 fn object_name(&self) -> &str {
166 &self.name
167 }
168
169 fn read_property(
170 &self,
171 property: PropertyIdentifier,
172 array_index: Option<u32>,
173 ) -> Result<PropertyValue, Error> {
174 if let Some(result) =
177 read_common_properties!(self, property, array_index, no_out_of_service)
178 {
179 return result;
180 }
181 match property {
182 p if p == PropertyIdentifier::OBJECT_TYPE => {
183 Ok(PropertyValue::Enumerated(ObjectType::LOAD_CONTROL.to_raw()))
184 }
185 p if p == PropertyIdentifier::PRESENT_VALUE => {
186 Ok(PropertyValue::Enumerated(self.present_value))
187 }
188 p if p == PropertyIdentifier::REQUESTED_SHED_LEVEL => {
189 Ok(shed_level_value(&self.requested_shed_level))
190 }
191 p if p == PropertyIdentifier::EXPECTED_SHED_LEVEL => {
192 Ok(shed_level_value(&self.expected_shed_level))
193 }
194 p if p == PropertyIdentifier::ACTUAL_SHED_LEVEL => {
195 Ok(shed_level_value(&self.actual_shed_level))
196 }
197 p if p == PropertyIdentifier::SHED_DURATION => {
198 Ok(PropertyValue::Unsigned(self.shed_duration))
199 }
200 p if p == PropertyIdentifier::START_TIME => Ok(PropertyValue::List(vec![
201 PropertyValue::Date(self.start_time.0),
202 PropertyValue::Time(self.start_time.1),
203 ])),
204 p if p == PropertyIdentifier::EVENT_STATE => {
205 Ok(PropertyValue::Enumerated(self.event_state.to_raw()))
206 }
207 _ => Err(common::unknown_property_error()),
208 }
209 }
210
211 fn write_property(
212 &mut self,
213 property: PropertyIdentifier,
214 _array_index: Option<u32>,
215 value: PropertyValue,
216 _priority: Option<u8>,
217 ) -> Result<(), Error> {
218 if let Some(result) = common::write_description(&mut self.description, property, &value) {
219 return result;
220 }
221 match property {
222 p if p == PropertyIdentifier::SHED_DURATION => {
223 if let PropertyValue::Unsigned(v) = value {
224 self.shed_duration = v;
225 Ok(())
226 } else {
227 Err(common::invalid_data_type_error())
228 }
229 }
230 p if p == PropertyIdentifier::REQUESTED_SHED_LEVEL => {
231 self.set_requested_shed_level(decode_shed_level_write(value)?)
232 }
233 _ => Err(crate::common::unhandled_write_error(
234 self.property_metadata().as_ref(),
235 property,
236 _array_index,
237 )),
238 }
239 }
240
241 fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
242 metadata::for_object(self)
243 }
244
245 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
246 crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
247 }
248
249 fn supports_cov(&self) -> bool {
252 true
253 }
254}