Skip to main content

pamoja_lora/region/
rules.rs

1//! The rules a device follows on a plan beyond its tables: what a channel mask control does,
2//! the order join channels are tried in, what a power index counts down from, and which plan
3//! a join lands on where a region has several.
4
5use super::{ChannelBlock, ChannelPlan};
6
7/// What one value of a `LinkADRReq` command's `ChMaskCntl` field does to a device's channels.
8///
9/// TS001-1.0.4 section 5.3 leaves the meaning to the regional parameters, and RP002-1.0.5
10/// gives each region a table of eight. A plan carries its table as
11/// [`ChannelPlan::mask_controls`], indexed by the field's value.
12///
13/// # Examples
14///
15/// ```
16/// # #[cfg(feature = "us915")] {
17/// use pamoja_lora::region::{MaskControl, Region};
18///
19/// // RP002-1.0.5 table 23: in US902-928, 7 turns every 125 kHz channel off and sets the
20/// // 500 kHz channels from the mask.
21/// assert_eq!(
22///     Region::Us915.plan().mask_controls[7],
23///     MaskControl::All { on: false, then_group: Some(4) }
24/// );
25/// # }
26/// ```
27#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
28pub enum MaskControl {
29    /// The mask sets the sixteen channels of one group, channels `16 * group` to
30    /// `16 * group + 15`.
31    Group(u8),
32    /// Each of the mask's ten low bits switches a bank of eight channels, bank `b` being
33    /// channels `8 * b` to `8 * b + 7`. Channels a bank names but the device has not defined
34    /// stay off.
35    Banks,
36    /// Each of the mask's eight low bits switches a bank of eight channels together with the
37    /// channel numbered 64 plus the bit, and bit 9 switches channels 72 to 79 where they are
38    /// defined. This is the 900 MHz plans' pairing of eight 125 kHz channels with one 500 kHz
39    /// channel.
40    PairedBanks,
41    /// Every defined channel on, or every channel off, and then, where a group is named, the
42    /// mask sets that group.
43    All {
44        /// Whether the channels go on or off.
45        on: bool,
46        /// The group the mask then sets, if any.
47        then_group: Option<u8>,
48    },
49    /// A value the region reserves, which a device refuses.
50    Reserved,
51}
52
53impl MaskControl {
54    /// The table every dynamic plan shares, RP002-1.0.5 table 14 for EU863-870 and its
55    /// counterparts: groups 0 to 4, banks of eight, every defined channel on, and a reserved 7.
56    pub const DYNAMIC: [MaskControl; 8] = [
57        MaskControl::Group(0),
58        MaskControl::Group(1),
59        MaskControl::Group(2),
60        MaskControl::Group(3),
61        MaskControl::Group(4),
62        MaskControl::Banks,
63        MaskControl::All {
64            on: true,
65            then_group: None,
66        },
67        MaskControl::Reserved,
68    ];
69}
70
71/// The order a device tries a plan's join channels in.
72#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
73pub enum JoinSequence {
74    /// A join channel at random for each attempt, with the data rate stepping from the
75    /// fastest the join channels carry down to the slowest and round again.
76    #[default]
77    Random,
78    /// Passes over the join channels that cover every one before any repeats, RP002-1.0.5
79    /// sections 3.5.2 and 3.8.2: each pass takes one channel at random from each group of
80    /// eight in the first join block, in order, and then one channel of the blocks after it.
81    /// Each attempt goes out at the lowest data rate its channel's block carries.
82    OctetPasses,
83}
84
85/// What a plan's transmit power indexes count down from.
86#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
87pub enum PowerReference {
88    /// A radiated ceiling, so a device takes its antenna's gain off the power it asks of its
89    /// radio.
90    #[default]
91    Eirp,
92    /// A conducted ceiling, as RP002-1.0.5 table 22 sets for US902-928, so the power index
93    /// names the radio's own output.
94    Conducted {
95        /// The antenna gain the ceiling already allows for, in dB. An antenna with more takes
96        /// the excess off the output: RP002-1.0.5 section 3.5.2 summarizes 47 CFR 15.247(b)(4)
97        /// as reducing the conducted power by every dB of gain above 6 dBi.
98        gain_allowance_db: u8,
99    },
100}
101
102/// A run of join channels that puts a device on one plan, for a region that picks the plan
103/// by the channel a device joined on.
104///
105/// RP002-1.0.5 section 3.9.2 has CN470-510 devices scan twenty common join channels, each
106/// tied to one of four plans. The channel a join accept answers decides the plan, where the
107/// accept arrives, and where the second receive window listens afterward.
108#[derive(Clone, Copy, Debug)]
109pub struct JoinPlan<'a> {
110    /// The join channels this run covers, with the data rates a request may use on them.
111    pub channels: ChannelBlock,
112    /// Where the join accept answering the first channel of the run arrives, in hertz.
113    pub accept_start_hz: u32,
114    /// How far the accept frequency moves for each next channel, in hertz.
115    pub accept_step_hz: u32,
116    /// The second receive window's frequency after joining on the first channel, in hertz.
117    pub rx2_start_hz: u32,
118    /// How far that frequency moves for each next channel, in hertz.
119    pub rx2_step_hz: u32,
120    /// The plan a device joined on these channels follows.
121    pub plan: &'a ChannelPlan<'a>,
122}
123
124impl<'a> JoinPlan<'a> {
125    /// Builds a run of join channels.
126    ///
127    /// # Arguments
128    ///
129    /// * `channels` - the join channels and their data rates.
130    /// * `accept` - the accept frequency for the first channel and its step, in hertz.
131    /// * `rx2` - the second window's frequency for the first channel and its step, in hertz.
132    /// * `plan` - the plan a join on these channels puts a device on.
133    ///
134    /// # Returns
135    ///
136    /// The run.
137    pub const fn new(
138        channels: ChannelBlock,
139        accept: (u32, u32),
140        rx2: (u32, u32),
141        plan: &'a ChannelPlan<'a>,
142    ) -> JoinPlan<'a> {
143        JoinPlan {
144            channels,
145            accept_start_hz: accept.0,
146            accept_step_hz: accept.1,
147            rx2_start_hz: rx2.0,
148            rx2_step_hz: rx2.1,
149            plan,
150        }
151    }
152
153    /// Returns where the join accept answering one channel of the run arrives.
154    ///
155    /// # Arguments
156    ///
157    /// * `offset` - the channel's position in the run.
158    ///
159    /// # Returns
160    ///
161    /// The frequency in hertz, or `None` past the end of the run.
162    pub const fn accept_hz(&self, offset: u16) -> Option<u32> {
163        if offset >= self.channels.count {
164            return None;
165        }
166        Some(self.accept_start_hz + self.accept_step_hz * offset as u32)
167    }
168
169    /// Returns where the second receive window listens after joining on one channel of the run.
170    ///
171    /// # Arguments
172    ///
173    /// * `offset` - the channel's position in the run.
174    ///
175    /// # Returns
176    ///
177    /// The frequency in hertz, or `None` past the end of the run.
178    pub const fn rx2_hz(&self, offset: u16) -> Option<u32> {
179        if offset >= self.channels.count {
180            return None;
181        }
182        Some(self.rx2_start_hz + self.rx2_step_hz * offset as u32)
183    }
184}