1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
//! The Access_Event_Time an Access Point stamps on an event it records
//! itself: an Out_Of_Service edge, or a reported event that brings no time
//! of its own (#1248, #1132).
//!
//! Access_Event_Time moves with every update of Access_Event (Clause
//! 12.31.29) and is the point's Table 13-1 trigger, so two events in a row
//! must never share a time, or the second sends no COV report. Several events
//! of one access transaction share a tag (Clause 12.31.27.1), so the time
//! can't be derived from the tag. The point stamps each event strictly after
//! the time it served before:
//!
//! - With a usable Device clock, the clock's date and time, unless that
//! isn't later than the time served, as when two events land in the same
//! hundredth or the clock was set back. The stamp is then one hundredth of
//! a second past the time served, carried into the seconds, minutes, hours
//! and the next day as needed.
//! - Without one, the sequence-number form (Clause 21.6): one past the
//! sequence number served, counting from 1 when the time served is in
//! another form, and wrapping from 65535 back to 1. It never takes 0, the
//! value of an update time with no update yet.
//!
//! A time the application passes with an event is served as given.
use bacnet_types::calendar::SpecificDate;
use bacnet_types::primitives::{BACnetTimeStamp, Date, Time};
use super::next_sequence;
use crate::clock::{ClockFrame, ClockReader};
/// A date and time with every field given, ordered from the year down to
/// the hundredths.
type Instant = (SpecificDate, [u8; 4]);
/// The Access_Event_Time for an event recorded now, after `previous`.
pub(super) fn next_event_time(
clock: Option<&dyn ClockReader>,
previous: &BACnetTimeStamp,
) -> BACnetTimeStamp {
next_event_time_from_frame(clock.and_then(ClockReader::read_clock), previous)
}
/// Stamp from the same coherent clock observation retained for timed capture.
pub(super) fn next_event_time_from_frame(
frame: Option<ClockFrame>,
previous: &BACnetTimeStamp,
) -> BACnetTimeStamp {
let Some(ClockFrame {
local_date: date,
local_time: time,
..
}) = frame.filter(|frame| frame.is_valid_actual_datetime())
else {
let last = match previous {
BACnetTimeStamp::SequenceNumber(number) => *number,
_ => 0,
};
return BACnetTimeStamp::SequenceNumber(next_sequence(last));
};
let now = BACnetTimeStamp::DateTime { date, time };
let BACnetTimeStamp::DateTime {
date: last_date,
time: last_time,
} = previous
else {
return now;
};
match (instant(&date, &time), instant(last_date, last_time)) {
(Some(current), Some(last)) if current <= last => one_hundredth_after(last).unwrap_or(now),
_ => now,
}
}
/// `date` and `time` as an [`Instant`], or `None` unless both are fully
/// specified and the date names a real day.
fn instant(date: &Date, time: &Time) -> Option<Instant> {
let day = SpecificDate::from_date(date)?;
time.is_specific()
.then_some((day, [time.hour, time.minute, time.second, time.hundredths]))
}
/// The date and time one hundredth of a second after `instant`, or `None`
/// past the last day a BACnet Date holds.
fn one_hundredth_after(
(day, [hour, minute, second, hundredths]): Instant,
) -> Option<BACnetTimeStamp> {
let mut time = [hour, minute, second, hundredths.saturating_add(1)];
// Carry each field that reaches its limit into the one above it.
for (field, limit) in [(3, 100), (2, 60), (1, 60)] {
if time[field] >= limit {
time[field] = 0;
time[field - 1] = time[field - 1].saturating_add(1);
}
}
let day = if time[0] >= 24 {
time[0] = 0;
next_day(day)?
} else {
day
};
let [hour, minute, second, hundredths] = time;
Some(BACnetTimeStamp::DateTime {
date: day.to_date(),
time: Time {
hour,
minute,
second,
hundredths,
},
})
}
/// The day after `day`.
fn next_day(day: SpecificDate) -> Option<SpecificDate> {
SpecificDate::new(day.year(), day.month(), day.day() + 1)
.or_else(|| SpecificDate::new(day.year(), day.month() + 1, 1))
.or_else(|| SpecificDate::new(day.year() + 1, 1, 1))
}
/// Automatically stamped DateTimes must advance; sequences may wrap but differ.
/// A nonspecific initial DateTime has no previous instant to order against.
pub(super) fn stamped_time_advances(previous: &BACnetTimeStamp, next: &BACnetTimeStamp) -> bool {
if previous == next {
return false;
}
match (previous, next) {
(
BACnetTimeStamp::DateTime {
date: old_date,
time: old_time,
},
BACnetTimeStamp::DateTime { date, time },
) => match (instant(old_date, old_time), instant(date, time)) {
(Some(old), Some(new)) => new > old,
_ => true,
},
_ => true,
}
}