1use bacnet_types::constructed::BACnetShedLevel;
7use bacnet_types::enums::{ObjectType, PropertyIdentifier};
8use bacnet_types::error::Error;
9use bacnet_types::primitives::{Date, ObjectIdentifier, PropertyValue, StatusFlags, Time};
10use std::borrow::Cow;
11
12use crate::common::{self, read_common_properties};
13use crate::traits::BACnetObject;
14
15pub struct LoadControlObject {
17 oid: ObjectIdentifier,
18 name: String,
19 description: String,
20 present_value: u32,
23 requested_shed_level: BACnetShedLevel,
24 expected_shed_level: BACnetShedLevel,
25 actual_shed_level: BACnetShedLevel,
26 shed_duration: u64,
27 start_time: (Date, Time),
28 status_flags: StatusFlags,
29 event_state: u32,
31 out_of_service: bool,
32 reliability: u32,
33}
34
35impl LoadControlObject {
36 pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
38 let oid = ObjectIdentifier::new(ObjectType::LOAD_CONTROL, instance)?;
39 Ok(Self {
40 oid,
41 name: name.into(),
42 description: String::new(),
43 present_value: 0,
44 requested_shed_level: BACnetShedLevel::Percent(0),
45 expected_shed_level: BACnetShedLevel::Percent(0),
46 actual_shed_level: BACnetShedLevel::Percent(0),
47 shed_duration: 0,
48 start_time: (
49 Date {
50 year: 0xFF,
51 month: 0xFF,
52 day: 0xFF,
53 day_of_week: 0xFF,
54 },
55 Time {
56 hour: 0xFF,
57 minute: 0xFF,
58 second: 0xFF,
59 hundredths: 0xFF,
60 },
61 ),
62 status_flags: StatusFlags::empty(),
63 event_state: 0, out_of_service: false,
65 reliability: 0,
66 })
67 }
68
69 pub fn set_requested_shed_level(&mut self, level: BACnetShedLevel) {
71 self.requested_shed_level = level;
72 }
73
74 pub fn set_actual_shed_level(&mut self, level: BACnetShedLevel) {
76 self.actual_shed_level = level;
77 }
78
79 fn shed_level_to_property(level: &BACnetShedLevel) -> PropertyValue {
81 match level {
82 BACnetShedLevel::Percent(v) => {
83 PropertyValue::List(vec![PropertyValue::Unsigned(*v as u64)])
84 }
85 BACnetShedLevel::Level(v) => {
86 PropertyValue::List(vec![PropertyValue::Unsigned(*v as u64)])
87 }
88 BACnetShedLevel::Amount(v) => PropertyValue::List(vec![PropertyValue::Real(*v)]),
89 }
90 }
91}
92
93impl BACnetObject for LoadControlObject {
94 fn object_identifier(&self) -> ObjectIdentifier {
95 self.oid
96 }
97
98 fn object_name(&self) -> &str {
99 &self.name
100 }
101
102 fn read_property(
103 &self,
104 property: PropertyIdentifier,
105 array_index: Option<u32>,
106 ) -> Result<PropertyValue, Error> {
107 if let Some(result) = read_common_properties!(self, property, array_index) {
108 return result;
109 }
110 match property {
111 p if p == PropertyIdentifier::OBJECT_TYPE => {
112 Ok(PropertyValue::Enumerated(ObjectType::LOAD_CONTROL.to_raw()))
113 }
114 p if p == PropertyIdentifier::PRESENT_VALUE => {
115 Ok(PropertyValue::Enumerated(self.present_value))
116 }
117 p if p == PropertyIdentifier::REQUESTED_SHED_LEVEL => {
118 Ok(Self::shed_level_to_property(&self.requested_shed_level))
119 }
120 p if p == PropertyIdentifier::EXPECTED_SHED_LEVEL => {
121 Ok(Self::shed_level_to_property(&self.expected_shed_level))
122 }
123 p if p == PropertyIdentifier::ACTUAL_SHED_LEVEL => {
124 Ok(Self::shed_level_to_property(&self.actual_shed_level))
125 }
126 p if p == PropertyIdentifier::SHED_DURATION => {
127 Ok(PropertyValue::Unsigned(self.shed_duration))
128 }
129 p if p == PropertyIdentifier::START_TIME => Ok(PropertyValue::List(vec![
130 PropertyValue::Date(self.start_time.0),
131 PropertyValue::Time(self.start_time.1),
132 ])),
133 p if p == PropertyIdentifier::EVENT_STATE => {
134 Ok(PropertyValue::Enumerated(self.event_state))
135 }
136 _ => Err(common::unknown_property_error()),
137 }
138 }
139
140 fn write_property(
141 &mut self,
142 property: PropertyIdentifier,
143 _array_index: Option<u32>,
144 value: PropertyValue,
145 _priority: Option<u8>,
146 ) -> Result<(), Error> {
147 if let Some(result) =
148 common::write_out_of_service(&mut self.out_of_service, property, &value)
149 {
150 return result;
151 }
152 if let Some(result) = common::write_description(&mut self.description, property, &value) {
153 return result;
154 }
155 match property {
156 p if p == PropertyIdentifier::SHED_DURATION => {
157 if let PropertyValue::Unsigned(v) = value {
158 self.shed_duration = v;
159 Ok(())
160 } else {
161 Err(common::invalid_data_type_error())
162 }
163 }
164 p if p == PropertyIdentifier::REQUESTED_SHED_LEVEL => {
165 if let PropertyValue::List(ref items) = value {
167 if items.len() == 1 {
168 match &items[0] {
169 PropertyValue::Unsigned(v) => {
170 self.requested_shed_level =
171 BACnetShedLevel::Percent(common::u64_to_u32(*v)?);
172 Ok(())
173 }
174 PropertyValue::Real(v) => {
175 common::reject_non_finite(*v)?;
176 self.requested_shed_level = BACnetShedLevel::Amount(*v);
177 Ok(())
178 }
179 _ => Err(common::invalid_data_type_error()),
180 }
181 } else {
182 Err(common::invalid_data_type_error())
183 }
184 } else {
185 Err(common::invalid_data_type_error())
186 }
187 }
188 _ => Err(common::write_access_denied_error()),
189 }
190 }
191
192 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
193 static PROPS: &[PropertyIdentifier] = &[
194 PropertyIdentifier::OBJECT_IDENTIFIER,
195 PropertyIdentifier::OBJECT_NAME,
196 PropertyIdentifier::DESCRIPTION,
197 PropertyIdentifier::OBJECT_TYPE,
198 PropertyIdentifier::PRESENT_VALUE,
199 PropertyIdentifier::REQUESTED_SHED_LEVEL,
200 PropertyIdentifier::EXPECTED_SHED_LEVEL,
201 PropertyIdentifier::ACTUAL_SHED_LEVEL,
202 PropertyIdentifier::SHED_DURATION,
203 PropertyIdentifier::START_TIME,
204 PropertyIdentifier::STATUS_FLAGS,
205 PropertyIdentifier::OUT_OF_SERVICE,
206 PropertyIdentifier::RELIABILITY,
207 ];
208 Cow::Borrowed(PROPS)
209 }
210}
211
212#[cfg(test)]
217mod tests {
218 use super::*;
219
220 #[test]
221 fn load_control_create_and_read_defaults() {
222 let lc = LoadControlObject::new(1, "LC-1").unwrap();
223 assert_eq!(lc.object_name(), "LC-1");
224 assert_eq!(
225 lc.read_property(PropertyIdentifier::PRESENT_VALUE, None)
226 .unwrap(),
227 PropertyValue::Enumerated(0)
228 );
229 }
230
231 #[test]
232 fn load_control_object_type() {
233 let lc = LoadControlObject::new(1, "LC-1").unwrap();
234 assert_eq!(
235 lc.read_property(PropertyIdentifier::OBJECT_TYPE, None)
236 .unwrap(),
237 PropertyValue::Enumerated(ObjectType::LOAD_CONTROL.to_raw())
238 );
239 }
240
241 #[test]
242 fn load_control_read_shed_levels() {
243 let lc = LoadControlObject::new(1, "LC-1").unwrap();
244 assert_eq!(
246 lc.read_property(PropertyIdentifier::REQUESTED_SHED_LEVEL, None)
247 .unwrap(),
248 PropertyValue::List(vec![PropertyValue::Unsigned(0)])
249 );
250 assert_eq!(
251 lc.read_property(PropertyIdentifier::EXPECTED_SHED_LEVEL, None)
252 .unwrap(),
253 PropertyValue::List(vec![PropertyValue::Unsigned(0)])
254 );
255 assert_eq!(
256 lc.read_property(PropertyIdentifier::ACTUAL_SHED_LEVEL, None)
257 .unwrap(),
258 PropertyValue::List(vec![PropertyValue::Unsigned(0)])
259 );
260 }
261
262 #[test]
263 fn load_control_set_requested_shed_level_amount() {
264 let mut lc = LoadControlObject::new(1, "LC-1").unwrap();
265 lc.set_requested_shed_level(BACnetShedLevel::Amount(42.5));
266 assert_eq!(
267 lc.read_property(PropertyIdentifier::REQUESTED_SHED_LEVEL, None)
268 .unwrap(),
269 PropertyValue::List(vec![PropertyValue::Real(42.5)])
270 );
271 }
272
273 #[test]
274 fn load_control_write_shed_duration() {
275 let mut lc = LoadControlObject::new(1, "LC-1").unwrap();
276 lc.write_property(
277 PropertyIdentifier::SHED_DURATION,
278 None,
279 PropertyValue::Unsigned(3600),
280 None,
281 )
282 .unwrap();
283 assert_eq!(
284 lc.read_property(PropertyIdentifier::SHED_DURATION, None)
285 .unwrap(),
286 PropertyValue::Unsigned(3600)
287 );
288 }
289
290 #[test]
291 fn load_control_write_requested_shed_level() {
292 let mut lc = LoadControlObject::new(1, "LC-1").unwrap();
293 lc.write_property(
294 PropertyIdentifier::REQUESTED_SHED_LEVEL,
295 None,
296 PropertyValue::List(vec![PropertyValue::Unsigned(50)]),
297 None,
298 )
299 .unwrap();
300 assert_eq!(
301 lc.read_property(PropertyIdentifier::REQUESTED_SHED_LEVEL, None)
302 .unwrap(),
303 PropertyValue::List(vec![PropertyValue::Unsigned(50)])
304 );
305 }
306
307 #[test]
308 fn load_control_write_requested_shed_level_amount() {
309 let mut lc = LoadControlObject::new(1, "LC-1").unwrap();
310 lc.write_property(
311 PropertyIdentifier::REQUESTED_SHED_LEVEL,
312 None,
313 PropertyValue::List(vec![PropertyValue::Real(25.5)]),
314 None,
315 )
316 .unwrap();
317 assert_eq!(
318 lc.read_property(PropertyIdentifier::REQUESTED_SHED_LEVEL, None)
319 .unwrap(),
320 PropertyValue::List(vec![PropertyValue::Real(25.5)])
321 );
322 }
323
324 #[test]
325 fn load_control_write_requested_shed_level_wrong_type() {
326 let mut lc = LoadControlObject::new(1, "LC-1").unwrap();
327 let result = lc.write_property(
328 PropertyIdentifier::REQUESTED_SHED_LEVEL,
329 None,
330 PropertyValue::Unsigned(50),
331 None,
332 );
333 assert!(result.is_err());
334 }
335
336 #[test]
337 fn load_control_read_start_time() {
338 let lc = LoadControlObject::new(1, "LC-1").unwrap();
339 let val = lc
340 .read_property(PropertyIdentifier::START_TIME, None)
341 .unwrap();
342 let unspec_date = Date {
343 year: 0xFF,
344 month: 0xFF,
345 day: 0xFF,
346 day_of_week: 0xFF,
347 };
348 let unspec_time = Time {
349 hour: 0xFF,
350 minute: 0xFF,
351 second: 0xFF,
352 hundredths: 0xFF,
353 };
354 assert_eq!(
355 val,
356 PropertyValue::List(vec![
357 PropertyValue::Date(unspec_date),
358 PropertyValue::Time(unspec_time),
359 ])
360 );
361 }
362
363 #[test]
364 fn load_control_property_list() {
365 let lc = LoadControlObject::new(1, "LC-1").unwrap();
366 let list = lc.property_list();
367 assert!(list.contains(&PropertyIdentifier::PRESENT_VALUE));
368 assert!(list.contains(&PropertyIdentifier::REQUESTED_SHED_LEVEL));
369 assert!(list.contains(&PropertyIdentifier::EXPECTED_SHED_LEVEL));
370 assert!(list.contains(&PropertyIdentifier::ACTUAL_SHED_LEVEL));
371 assert!(list.contains(&PropertyIdentifier::SHED_DURATION));
372 assert!(list.contains(&PropertyIdentifier::START_TIME));
373 }
374}