dw3000_ng/
time.rs

1//! Time-related types based on the DW3000's system time
2
3use core::ops::{Add, Sub};
4
5use serde::{Deserialize, Serialize};
6
7#[cfg(feature = "defmt")]
8use defmt::Format;
9
10/// The maximum value of 40-bit system time stamps.
11pub const TIME_MAX: u64 = 0xffffffffff;
12
13/// Represents an instant in time
14///
15/// You can get the current DW3000 system time by calling [`DW3000::sys_time`].
16///
17/// Internally uses the same 40-bit timestamps that the DW3000 uses.
18///
19/// [`DW3000::sys_time`]: ../hl/struct.DW3000.html#method.sys_time
20#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
21#[cfg_attr(feature = "defmt", derive(Format))]
22#[repr(C)]
23pub struct Instant(u64);
24
25impl Instant {
26    /// Creates a new instance of `Instant`
27    ///
28    /// The given value must fit in a 40-bit timestamp, so:
29    /// 0 <= `value` <= 2^40 - 1
30    ///
31    /// Returns `Some(...)`, if `value` is within the valid range, `None` if it
32    /// isn't.
33    ///
34    /// # Example
35    ///
36    /// ``` rust
37    /// use dw3000_ng::time::{
38    ///     TIME_MAX,
39    ///     Instant,
40    /// };
41    ///
42    /// let valid_instant   = Instant::new(TIME_MAX);
43    /// let invalid_instant = Instant::new(TIME_MAX + 1);
44    ///
45    /// assert!(valid_instant.is_some());
46    /// assert!(invalid_instant.is_none());
47    /// ```
48    pub fn new(value: u64) -> Option<Self> {
49        if value <= TIME_MAX {
50            Some(Instant(value))
51        } else {
52            None
53        }
54    }
55
56    /// Returns the raw 40-bit timestamp
57    ///
58    /// The returned value is guaranteed to be in the following range:
59    /// 0 <= `value` <= 2^40 - 1
60    pub fn value(&self) -> u64 {
61        self.0
62    }
63
64    /// Returns the amount of time passed between the two `Instant`s
65    ///
66    /// Assumes that `&self` represents a later time than the argument
67    /// `earlier`. Please make sure that this is the case, as this method has no
68    /// way of knowing (DW3000 timestamps can overflow, so comparing the
69    /// numerical value of the timestamp doesn't tell anything about order).
70    ///
71    /// # Example
72    ///
73    /// ``` rust
74    /// use dw3000_ng::time::{
75    ///     TIME_MAX,
76    ///     Instant,
77    /// };
78    ///
79    /// // `unwrap`ing here is okay, since we're passing constants that we know
80    /// // are in the valid range.
81    /// let instant_1 = Instant::new(TIME_MAX - 50).unwrap();
82    /// let instant_2 = Instant::new(TIME_MAX).unwrap();
83    /// let instant_3 = Instant::new(49).unwrap();
84    ///
85    /// // Works as expected, if the later timestamp is larger than the earlier
86    /// // one.
87    /// let duration = instant_2.duration_since(instant_1);
88    /// assert_eq!(duration.value(), 50);
89    ///
90    /// // Still works as expected, if the later timestamp is the numerically
91    /// // smaller value.
92    /// let duration = instant_3.duration_since(instant_2);
93    /// assert_eq!(duration.value(), 50);
94    /// ```
95    pub fn duration_since(&self, earlier: Instant) -> Duration {
96        if self.value() >= earlier.value() {
97            Duration(self.value() - earlier.value())
98        } else {
99            Duration(TIME_MAX - earlier.value() + self.value() + 1)
100        }
101    }
102}
103
104impl Add<Duration> for Instant {
105    type Output = Instant;
106
107    fn add(self, rhs: Duration) -> Self::Output {
108        // Both `Instant` and `Duration` are guaranteed to contain 40-bit
109        // numbers, so this addition will never overflow.
110        let value = (self.value() + rhs.value()) % (TIME_MAX + 1);
111
112        // We made sure to keep the result of the addition within `TIME_MAX`, so
113        // the following will never panic.
114        Instant::new(value).unwrap()
115    }
116}
117
118impl Sub<Duration> for Instant {
119    type Output = Instant;
120
121    fn sub(self, rhs: Duration) -> Self::Output {
122        // Both `Instant` and `Duration` are guaranteed to contain 40-bit
123        // numbers, so this addition will never overflow.
124        let value: u64 = if (self.value() as i64 - rhs.value() as i64) < 0 {
125            TIME_MAX + self.value() - rhs.value()
126        } else {
127            self.value() - rhs.value()
128        };
129
130        // We made sure to keep the result of the addition within `TIME_MAX`, so
131        // the following will never panic.
132        Instant::new(value).unwrap()
133    }
134}
135
136impl Sub<Instant> for Instant {
137    type Output = Instant;
138
139    fn sub(self, rhs: Instant) -> Self::Output {
140        // Both `Instant` and `Duration` are guaranteed to contain 40-bit
141        // numbers, so this addition will never overflow.
142        let value: u64 = if (self.value() as i64 - rhs.value() as i64) < 0 {
143            TIME_MAX + self.value() - rhs.value()
144        } else {
145            self.value() - rhs.value()
146        };
147
148        // We made sure to keep the result of the addition within `TIME_MAX`, so
149        // the following will never panic.
150        Instant::new(value).unwrap()
151    }
152}
153
154/// A duration between two instants in DW3000 system time
155///
156/// Internally uses the same 40-bit timestamps that the DW3000 uses.
157#[derive(Clone, Copy, Debug, Deserialize, Serialize, Eq, PartialEq)]
158#[repr(C)]
159pub struct Duration(u64);
160
161impl Duration {
162    /// Creates a new instance of `Duration`
163    ///
164    /// The given value must fit in a 40-bit timestamp, so:
165    /// 0 <= `value` <= 2^40 - 1
166    ///
167    /// Returns `Some(...)`, if `value` is within the valid range, `None` if it
168    /// isn't.
169    ///
170    /// # Example
171    ///
172    /// ``` rust
173    /// use dw3000_ng::time::{
174    ///     TIME_MAX,
175    ///     Duration,
176    /// };
177    ///
178    /// let valid_duration   = Duration::new(TIME_MAX);
179    /// let invalid_duration = Duration::new(TIME_MAX + 1);
180    ///
181    /// assert!(valid_duration.is_some());
182    /// assert!(invalid_duration.is_none());
183    /// ```
184    pub fn new(value: u64) -> Option<Self> {
185        if value <= TIME_MAX {
186            Some(Duration(value))
187        } else {
188            None
189        }
190    }
191
192    /// Creates an instance of `Duration` from a number of nanoseconds, rounding to the nearest
193    ///
194    /// On the DW3000, the TX_TIME/RX_TIME registers have a resolution of 40-bits
195    /// and a unit of 1/(128*499.2*10^6) seconds. This means that 1 nanosecond
196    /// is 63.8976 DW3000 time units.
197    ///
198    /// We do this with fixed point arithmetic, where
199    /// (dividend + (divisor / 2)) / divisor
200    pub fn from_nanos(nanos: u32) -> Self {
201        // `nanos` takes up at most 32 bits before it is cast to `u64`. That
202        // means the result of the multiplication fits within 38 bits, so the
203        // following should never panic.
204        Duration::new((nanos as u64 * 638976 + 5000) / 10000).unwrap()
205    }
206
207    /// Returns the raw 40-bit timestamp
208    ///
209    /// The returned value is guaranteed to be in the following range:
210    /// 0 <= `value` <= 2^40 - 1
211    pub fn value(&self) -> u64 {
212        self.0
213    }
214}
215
216#[cfg(test)]
217mod tests {
218    use super::*;
219
220    #[test]
221    fn instant_add_duration() {
222        let instant = Instant::new(0).unwrap();
223        let duration = Duration::new(1).unwrap();
224
225        let result = instant + duration;
226
227        assert_eq!(result.value(), 1);
228    }
229
230    #[test]
231    fn instant_sub_duration() {
232        let instant = Instant::new(1).unwrap();
233        let duration = Duration::new(1).unwrap();
234
235        let result = instant - duration;
236
237        assert_eq!(result.value(), 0);
238    }
239
240    #[test]
241    fn instant_sub_instant() {
242        let instant_1 = Instant::new(1).unwrap();
243        let instant_2 = Instant::new(0).unwrap();
244
245        let result = instant_1 - instant_2;
246
247        assert_eq!(result.value(), 1);
248    }
249
250    #[test]
251    fn instant_duration_since() {
252        let instant_1 = Instant::new(1).unwrap();
253        let instant_2 = Instant::new(0).unwrap();
254
255        let result = instant_1.duration_since(instant_2);
256
257        assert_eq!(result.value(), 1);
258    }
259
260    #[test]
261    fn duration_from_nanos() {
262        let duration = Duration::from_nanos(1);
263
264        assert_eq!(duration.value(), 64);
265
266        let duration = Duration::from_nanos(6);
267
268        assert_eq!(duration.value(), 383);
269    }
270}