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
//! Types related to the LE scanning window.

use byteorder::{ByteOrder, LittleEndian};
use core::time::Duration;

/// Define a scanning window.
///
/// The controller runs LE scans every [`interval`](ScanWindow::interval), with scanning active
/// during the [`window`](ScanWindow::window) in every interval.
///
/// The minimum time range is 2.5 ms, and the maximum is 10.24 s. The window must be shorter than or
/// equal to the interval.
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ScanWindow {
    interval_width: Duration,
    window_width: Duration,
}

impl ScanWindow {
    /// Returns the interval for the scanning window. The controller starts an LE scan every
    /// interval.
    pub fn interval(&self) -> Duration {
        self.interval_width
    }

    /// Returns the amount of time the controller is scanning every interval.
    pub fn window(&self) -> Duration {
        self.window_width
    }

    /// Serializes the window into the given byte buffer.
    ///
    /// # Panics
    ///
    /// The buffer must be at least 4 bytes long.
    pub fn copy_into_slice(&self, bytes: &mut [u8]) {
        assert!(bytes.len() >= 4);

        LittleEndian::write_u16(&mut bytes[0..2], Self::duration_as_u16(self.interval_width));
        LittleEndian::write_u16(&mut bytes[2..4], Self::duration_as_u16(self.window_width));
    }

    /// Begins building a [ScanWindow]. The scan window has the given interval. Returns a
    /// [builder](ScanWindowBuilder) that can be used to set the window duration.
    ///
    /// # Errors
    ///
    /// - [ScanWindowError::TooShort] if the provided interval is too short. It must be at least 2.5
    ///   ms.
    /// - [ScanWindowError::TooLong] if the provided interval is too long. It must be 10.24 seconds
    ///   or less.
    pub fn start_every(interval: Duration) -> Result<ScanWindowBuilder, ScanWindowError> {
        Ok(ScanWindowBuilder {
            interval: ScanWindow::validate(interval)?,
        })
    }

    fn validate(d: Duration) -> Result<Duration, ScanWindowError> {
        const MIN: Duration = Duration::from_micros(2500);
        if d < MIN {
            return Err(ScanWindowError::TooShort(d));
        }

        const MAX: Duration = Duration::from_millis(10240);
        if d > MAX {
            return Err(ScanWindowError::TooLong(d));
        }

        Ok(d)
    }

    fn duration_as_u16(d: Duration) -> u16 {
        // T = 0.625 ms * N
        // so N = T / 0.625 ms
        //      = T / 625 us
        //
        // Note: 1600 = 1_000_000 / 625
        (1600 * d.as_secs() as u32 + (d.subsec_micros() / 625)) as u16
    }
}

/// Intermediate builder for the [`ScanWindow`].
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ScanWindowBuilder {
    interval: Duration,
}

impl ScanWindowBuilder {
    /// Completes building a [ScanWindow]. The scan window has the given window.
    ///
    /// # Errors
    ///
    /// - [ScanWindowError::TooShort] if the provided interval is too short. It must be at least 2.5
    ///   ms.
    /// - [ScanWindowError::TooLong] if the provided interval is too long. It must be 10.24 seconds
    ///   or less.
    /// - [ScanWindowError::Inverted] if the window is longer than the interval.
    pub fn open_for(&self, window: Duration) -> Result<ScanWindow, ScanWindowError> {
        if window > self.interval {
            return Err(ScanWindowError::Inverted {
                interval: self.interval,
                window,
            });
        }

        Ok(ScanWindow {
            interval_width: self.interval,
            window_width: ScanWindow::validate(window)?,
        })
    }
}

/// Types of errors that can occure when creating a [`ScanWindow`].
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ScanWindowError {
    /// The duration is too short. Both the interval and duration must be at least 2.5 ms. Includes
    /// the invalid duration.
    TooShort(Duration),
    /// The duration is too long. Both the interval and duration must be no more than 10.24
    /// seconds. Includes the invalid duration.
    TooLong(Duration),
    /// The interval and window are inverted. That is, the interval is shorter than the window.
    Inverted {
        /// The provided interval, which is shorter than the window.
        interval: Duration,
        /// The provided window, which is longer than the interval.
        window: Duration,
    },
}