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}