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