1use bacnet_types::enums::{ObjectType, PropertyIdentifier};
7use bacnet_types::error::Error;
8use bacnet_types::primitives::{Date, ObjectIdentifier, PropertyValue, StatusFlags, Time};
9use std::borrow::Cow;
10
11use crate::common::{self, read_common_properties};
12use crate::traits::BACnetObject;
13
14const TIMER_STATE_IDLE: u32 = 0;
16const TIMER_STATE_RUNNING: u32 = 1;
17const TIMER_STATE_EXPIRED: u32 = 2;
18
19pub struct TimerObject {
21 oid: ObjectIdentifier,
22 name: String,
23 description: String,
24 present_value: u32,
25 timer_running: bool,
26 initial_timeout: u64,
27 update_time: (Date, Time),
28 expiration_time: (Date, Time),
29 status_flags: StatusFlags,
30 event_state: u32,
32 out_of_service: bool,
33 reliability: u32,
34}
35
36impl TimerObject {
37 pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
39 let oid = ObjectIdentifier::new(ObjectType::TIMER, instance)?;
40 Ok(Self {
41 oid,
42 name: name.into(),
43 description: String::new(),
44 present_value: TIMER_STATE_IDLE,
45 timer_running: false,
46 initial_timeout: 0,
47 update_time: (
48 Date {
49 year: 0xFF,
50 month: 0xFF,
51 day: 0xFF,
52 day_of_week: 0xFF,
53 },
54 Time {
55 hour: 0xFF,
56 minute: 0xFF,
57 second: 0xFF,
58 hundredths: 0xFF,
59 },
60 ),
61 expiration_time: (
62 Date {
63 year: 0xFF,
64 month: 0xFF,
65 day: 0xFF,
66 day_of_week: 0xFF,
67 },
68 Time {
69 hour: 0xFF,
70 minute: 0xFF,
71 second: 0xFF,
72 hundredths: 0xFF,
73 },
74 ),
75 status_flags: StatusFlags::empty(),
76 event_state: 0, out_of_service: false,
78 reliability: 0,
79 })
80 }
81
82 pub fn start(&mut self) {
84 self.present_value = TIMER_STATE_RUNNING;
85 self.timer_running = true;
86 }
87
88 pub fn stop(&mut self) {
90 self.present_value = TIMER_STATE_IDLE;
91 self.timer_running = false;
92 }
93
94 pub fn set_initial_timeout(&mut self, timeout_ms: u64) {
96 self.initial_timeout = timeout_ms;
97 }
98
99 pub fn set_update_time(&mut self, date: Date, time: Time) {
101 self.update_time = (date, time);
102 }
103
104 pub fn set_expiration_time(&mut self, date: Date, time: Time) {
106 self.expiration_time = (date, time);
107 }
108}
109
110impl BACnetObject for TimerObject {
111 fn object_identifier(&self) -> ObjectIdentifier {
112 self.oid
113 }
114
115 fn object_name(&self) -> &str {
116 &self.name
117 }
118
119 fn read_property(
120 &self,
121 property: PropertyIdentifier,
122 array_index: Option<u32>,
123 ) -> Result<PropertyValue, Error> {
124 if let Some(result) = read_common_properties!(self, property, array_index) {
125 return result;
126 }
127 match property {
128 p if p == PropertyIdentifier::OBJECT_TYPE => {
129 Ok(PropertyValue::Enumerated(ObjectType::TIMER.to_raw()))
130 }
131 p if p == PropertyIdentifier::PRESENT_VALUE => {
132 Ok(PropertyValue::Enumerated(self.present_value))
133 }
134 p if p == PropertyIdentifier::TIMER_STATE => {
135 Ok(PropertyValue::Enumerated(self.present_value))
136 }
137 p if p == PropertyIdentifier::TIMER_RUNNING => {
138 Ok(PropertyValue::Boolean(self.timer_running))
139 }
140 p if p == PropertyIdentifier::INITIAL_TIMEOUT => {
141 Ok(PropertyValue::Unsigned(self.initial_timeout))
142 }
143 p if p == PropertyIdentifier::UPDATE_TIME => Ok(PropertyValue::List(vec![
144 PropertyValue::Date(self.update_time.0),
145 PropertyValue::Time(self.update_time.1),
146 ])),
147 p if p == PropertyIdentifier::EXPIRATION_TIME => Ok(PropertyValue::List(vec![
148 PropertyValue::Date(self.expiration_time.0),
149 PropertyValue::Time(self.expiration_time.1),
150 ])),
151 p if p == PropertyIdentifier::EVENT_STATE => {
152 Ok(PropertyValue::Enumerated(self.event_state))
153 }
154 _ => Err(common::unknown_property_error()),
155 }
156 }
157
158 fn write_property(
159 &mut self,
160 property: PropertyIdentifier,
161 _array_index: Option<u32>,
162 value: PropertyValue,
163 _priority: Option<u8>,
164 ) -> Result<(), Error> {
165 if let Some(result) =
166 common::write_out_of_service(&mut self.out_of_service, property, &value)
167 {
168 return result;
169 }
170 if let Some(result) = common::write_description(&mut self.description, property, &value) {
171 return result;
172 }
173 match property {
174 p if p == PropertyIdentifier::PRESENT_VALUE => {
175 if let PropertyValue::Enumerated(v) = value {
176 if v > TIMER_STATE_EXPIRED {
177 return Err(common::value_out_of_range_error());
178 }
179 self.present_value = v;
180 self.timer_running = v == TIMER_STATE_RUNNING;
181 Ok(())
182 } else {
183 Err(common::invalid_data_type_error())
184 }
185 }
186 p if p == PropertyIdentifier::INITIAL_TIMEOUT => {
187 if let PropertyValue::Unsigned(v) = value {
188 self.initial_timeout = v;
189 Ok(())
190 } else {
191 Err(common::invalid_data_type_error())
192 }
193 }
194 _ => Err(common::write_access_denied_error()),
195 }
196 }
197
198 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
199 static PROPS: &[PropertyIdentifier] = &[
200 PropertyIdentifier::OBJECT_IDENTIFIER,
201 PropertyIdentifier::OBJECT_NAME,
202 PropertyIdentifier::DESCRIPTION,
203 PropertyIdentifier::OBJECT_TYPE,
204 PropertyIdentifier::PRESENT_VALUE,
205 PropertyIdentifier::TIMER_STATE,
206 PropertyIdentifier::TIMER_RUNNING,
207 PropertyIdentifier::INITIAL_TIMEOUT,
208 PropertyIdentifier::UPDATE_TIME,
209 PropertyIdentifier::EXPIRATION_TIME,
210 PropertyIdentifier::STATUS_FLAGS,
211 PropertyIdentifier::OUT_OF_SERVICE,
212 PropertyIdentifier::RELIABILITY,
213 ];
214 Cow::Borrowed(PROPS)
215 }
216}
217
218#[cfg(test)]
223mod tests {
224 use super::*;
225
226 #[test]
227 fn timer_create_and_read_defaults() {
228 let timer = TimerObject::new(1, "TMR-1").unwrap();
229 assert_eq!(timer.object_name(), "TMR-1");
230 assert_eq!(
231 timer
232 .read_property(PropertyIdentifier::PRESENT_VALUE, None)
233 .unwrap(),
234 PropertyValue::Enumerated(TIMER_STATE_IDLE)
235 );
236 assert_eq!(
237 timer
238 .read_property(PropertyIdentifier::TIMER_RUNNING, None)
239 .unwrap(),
240 PropertyValue::Boolean(false)
241 );
242 }
243
244 #[test]
245 fn timer_object_type() {
246 let timer = TimerObject::new(1, "TMR-1").unwrap();
247 assert_eq!(
248 timer
249 .read_property(PropertyIdentifier::OBJECT_TYPE, None)
250 .unwrap(),
251 PropertyValue::Enumerated(ObjectType::TIMER.to_raw())
252 );
253 }
254
255 #[test]
256 fn timer_start_stop() {
257 let mut timer = TimerObject::new(1, "TMR-1").unwrap();
258 timer.start();
259 assert_eq!(
260 timer
261 .read_property(PropertyIdentifier::PRESENT_VALUE, None)
262 .unwrap(),
263 PropertyValue::Enumerated(TIMER_STATE_RUNNING)
264 );
265 assert_eq!(
266 timer
267 .read_property(PropertyIdentifier::TIMER_RUNNING, None)
268 .unwrap(),
269 PropertyValue::Boolean(true)
270 );
271
272 timer.stop();
273 assert_eq!(
274 timer
275 .read_property(PropertyIdentifier::PRESENT_VALUE, None)
276 .unwrap(),
277 PropertyValue::Enumerated(TIMER_STATE_IDLE)
278 );
279 assert_eq!(
280 timer
281 .read_property(PropertyIdentifier::TIMER_RUNNING, None)
282 .unwrap(),
283 PropertyValue::Boolean(false)
284 );
285 }
286
287 #[test]
288 fn timer_write_present_value() {
289 let mut timer = TimerObject::new(1, "TMR-1").unwrap();
290 timer
291 .write_property(
292 PropertyIdentifier::PRESENT_VALUE,
293 None,
294 PropertyValue::Enumerated(TIMER_STATE_RUNNING),
295 None,
296 )
297 .unwrap();
298 assert_eq!(
299 timer
300 .read_property(PropertyIdentifier::PRESENT_VALUE, None)
301 .unwrap(),
302 PropertyValue::Enumerated(TIMER_STATE_RUNNING)
303 );
304 assert_eq!(
305 timer
306 .read_property(PropertyIdentifier::TIMER_RUNNING, None)
307 .unwrap(),
308 PropertyValue::Boolean(true)
309 );
310 }
311
312 #[test]
313 fn timer_write_present_value_out_of_range() {
314 let mut timer = TimerObject::new(1, "TMR-1").unwrap();
315 let result = timer.write_property(
316 PropertyIdentifier::PRESENT_VALUE,
317 None,
318 PropertyValue::Enumerated(99),
319 None,
320 );
321 assert!(result.is_err());
322 }
323
324 #[test]
325 fn timer_write_present_value_wrong_type() {
326 let mut timer = TimerObject::new(1, "TMR-1").unwrap();
327 let result = timer.write_property(
328 PropertyIdentifier::PRESENT_VALUE,
329 None,
330 PropertyValue::Unsigned(1),
331 None,
332 );
333 assert!(result.is_err());
334 }
335
336 #[test]
337 fn timer_read_initial_timeout() {
338 let mut timer = TimerObject::new(1, "TMR-1").unwrap();
339 timer.set_initial_timeout(5000);
340 assert_eq!(
341 timer
342 .read_property(PropertyIdentifier::INITIAL_TIMEOUT, None)
343 .unwrap(),
344 PropertyValue::Unsigned(5000)
345 );
346 }
347
348 #[test]
349 fn timer_write_initial_timeout() {
350 let mut timer = TimerObject::new(1, "TMR-1").unwrap();
351 timer
352 .write_property(
353 PropertyIdentifier::INITIAL_TIMEOUT,
354 None,
355 PropertyValue::Unsigned(10000),
356 None,
357 )
358 .unwrap();
359 assert_eq!(
360 timer
361 .read_property(PropertyIdentifier::INITIAL_TIMEOUT, None)
362 .unwrap(),
363 PropertyValue::Unsigned(10000)
364 );
365 }
366
367 #[test]
368 fn timer_read_update_time() {
369 let timer = TimerObject::new(1, "TMR-1").unwrap();
370 let val = timer
371 .read_property(PropertyIdentifier::UPDATE_TIME, None)
372 .unwrap();
373 let unspec_date = Date {
374 year: 0xFF,
375 month: 0xFF,
376 day: 0xFF,
377 day_of_week: 0xFF,
378 };
379 let unspec_time = Time {
380 hour: 0xFF,
381 minute: 0xFF,
382 second: 0xFF,
383 hundredths: 0xFF,
384 };
385 assert_eq!(
386 val,
387 PropertyValue::List(vec![
388 PropertyValue::Date(unspec_date),
389 PropertyValue::Time(unspec_time),
390 ])
391 );
392 }
393
394 #[test]
395 fn timer_read_timer_state_matches_pv() {
396 let mut timer = TimerObject::new(1, "TMR-1").unwrap();
397 timer.start();
398 let pv = timer
399 .read_property(PropertyIdentifier::PRESENT_VALUE, None)
400 .unwrap();
401 let ts = timer
402 .read_property(PropertyIdentifier::TIMER_STATE, None)
403 .unwrap();
404 assert_eq!(pv, ts);
405 }
406
407 #[test]
408 fn timer_property_list() {
409 let timer = TimerObject::new(1, "TMR-1").unwrap();
410 let list = timer.property_list();
411 assert!(list.contains(&PropertyIdentifier::PRESENT_VALUE));
412 assert!(list.contains(&PropertyIdentifier::TIMER_STATE));
413 assert!(list.contains(&PropertyIdentifier::TIMER_RUNNING));
414 assert!(list.contains(&PropertyIdentifier::INITIAL_TIMEOUT));
415 assert!(list.contains(&PropertyIdentifier::UPDATE_TIME));
416 assert!(list.contains(&PropertyIdentifier::EXPIRATION_TIME));
417 }
418}