lorawan_device/region/
mod.rs

1//! LoRaWAN device region definitions (eg: EU868, US915, etc).
2use lora_modulation::{Bandwidth, BaseBandModulationParams, CodingRate, SpreadingFactor};
3use lorawan::{maccommands::ChannelMask, parser::CfList};
4use rand_core::RngCore;
5
6use crate::mac::{Frame, Window};
7pub(crate) mod constants;
8pub(crate) use crate::radio::*;
9use constants::*;
10
11#[cfg(not(any(
12    feature = "region-as923-1",
13    feature = "region-as923-2",
14    feature = "region-as923-3",
15    feature = "region-as923-4",
16    feature = "region-eu433",
17    feature = "region-eu868",
18    feature = "region-in865",
19    feature = "region-au915",
20    feature = "region-us915"
21)))]
22compile_error!("You must enable at least one region! eg: `region-eu868`, `region-us915`...");
23
24#[cfg(any(
25    feature = "region-as923-1",
26    feature = "region-as923-2",
27    feature = "region-as923-3",
28    feature = "region-as923-4",
29    feature = "region-eu433",
30    feature = "region-eu868",
31    feature = "region-in865"
32))]
33mod dynamic_channel_plans;
34#[cfg(feature = "region-as923-1")]
35pub(crate) use dynamic_channel_plans::AS923_1;
36#[cfg(feature = "region-as923-2")]
37pub(crate) use dynamic_channel_plans::AS923_2;
38#[cfg(feature = "region-as923-3")]
39pub(crate) use dynamic_channel_plans::AS923_3;
40#[cfg(feature = "region-as923-4")]
41pub(crate) use dynamic_channel_plans::AS923_4;
42#[cfg(feature = "region-eu433")]
43pub(crate) use dynamic_channel_plans::EU433;
44#[cfg(feature = "region-eu868")]
45pub(crate) use dynamic_channel_plans::EU868;
46#[cfg(feature = "region-in865")]
47pub(crate) use dynamic_channel_plans::IN865;
48
49#[cfg(any(feature = "region-us915", feature = "region-au915"))]
50mod fixed_channel_plans;
51#[cfg(any(feature = "region-us915", feature = "region-au915"))]
52pub use fixed_channel_plans::Subband;
53#[cfg(feature = "region-au915")]
54pub use fixed_channel_plans::AU915;
55#[cfg(feature = "region-us915")]
56pub use fixed_channel_plans::US915;
57
58pub(crate) trait ChannelRegion<const D: usize> {
59    fn datarates() -> &'static [Option<Datarate>; D];
60
61    fn get_max_payload_length(datarate: DR, repeater_compatible: bool, dwell_time: bool) -> u8 {
62        let Some(Some(dr)) = Self::datarates().get(datarate as usize) else {
63            return 0;
64        };
65        let max_size = if dwell_time {
66            dr.max_mac_payload_size_with_dwell_time
67        } else {
68            dr.max_mac_payload_size
69        };
70        if repeater_compatible && max_size > 230 {
71            230
72        } else {
73            max_size
74        }
75    }
76}
77
78#[derive(Clone)]
79/// Contains LoRaWAN region-specific configuration; is required for creating a LoRaWAN Device.
80/// Generally constructed using the `Region` enum, unless you need to fine-tune US915 or AU915.
81pub struct Configuration {
82    state: State,
83}
84
85seq_macro::seq!(
86    N in 0..=15 {
87        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
88        #[cfg_attr(feature = "defmt", derive(defmt::Format))]
89        #[repr(u8)]
90        /// A restricted data rate type that exposes the number of variants to only what _may_ be
91        /// potentially be possible. Note that not all data rates are valid in all regions.
92        pub enum DR {
93            #(
94                _~N = N,
95            )*
96        }
97    }
98);
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
101/// Regions supported by this crate: AS923_1, AS923_2, AS923_3, AS923_4, AU915, EU868, EU433, IN865, US915.
102/// Each region is individually feature-gated (eg: `region-eu868`), however, by default, all regions are enabled.
103///
104pub enum Region {
105    #[cfg(feature = "region-as923-1")]
106    AS923_1,
107    #[cfg(feature = "region-as923-2")]
108    AS923_2,
109    #[cfg(feature = "region-as923-3")]
110    AS923_3,
111    #[cfg(feature = "region-as923-4")]
112    AS923_4,
113    #[cfg(feature = "region-au915")]
114    AU915,
115    #[cfg(feature = "region-eu868")]
116    EU868,
117    #[cfg(feature = "region-eu433")]
118    EU433,
119    #[cfg(feature = "region-in865")]
120    IN865,
121    #[cfg(feature = "region-us915")]
122    US915,
123}
124
125#[derive(Clone)]
126enum State {
127    #[cfg(feature = "region-as923-1")]
128    AS923_1(AS923_1),
129    #[cfg(feature = "region-as923-2")]
130    AS923_2(AS923_2),
131    #[cfg(feature = "region-as923-3")]
132    AS923_3(AS923_3),
133    #[cfg(feature = "region-as923-4")]
134    AS923_4(AS923_4),
135    #[cfg(feature = "region-au915")]
136    AU915(AU915),
137    #[cfg(feature = "region-eu868")]
138    EU868(EU868),
139    #[cfg(feature = "region-eu433")]
140    EU433(EU433),
141    #[cfg(feature = "region-in865")]
142    IN865(IN865),
143    #[cfg(feature = "region-us915")]
144    US915(US915),
145}
146
147impl State {
148    pub fn new(region: Region) -> State {
149        match region {
150            #[cfg(feature = "region-as923-1")]
151            Region::AS923_1 => State::AS923_1(AS923_1::default()),
152            #[cfg(feature = "region-as923-2")]
153            Region::AS923_2 => State::AS923_2(AS923_2::default()),
154            #[cfg(feature = "region-as923-3")]
155            Region::AS923_3 => State::AS923_3(AS923_3::default()),
156            #[cfg(feature = "region-as923-4")]
157            Region::AS923_4 => State::AS923_4(AS923_4::default()),
158            #[cfg(feature = "region-au915")]
159            Region::AU915 => State::AU915(AU915::default()),
160            #[cfg(feature = "region-eu868")]
161            Region::EU868 => State::EU868(EU868::default()),
162            #[cfg(feature = "region-eu433")]
163            Region::EU433 => State::EU433(EU433::default()),
164            #[cfg(feature = "region-in865")]
165            Region::IN865 => State::IN865(IN865::default()),
166            #[cfg(feature = "region-us915")]
167            Region::US915 => State::US915(US915::default()),
168        }
169    }
170
171    #[allow(dead_code)]
172    pub fn region(&self) -> Region {
173        match self {
174            #[cfg(feature = "region-as923-1")]
175            Self::AS923_1(_) => Region::AS923_1,
176            #[cfg(feature = "region-as923-2")]
177            Self::AS923_2(_) => Region::AS923_2,
178            #[cfg(feature = "region-as923-3")]
179            Self::AS923_3(_) => Region::AS923_3,
180            #[cfg(feature = "region-as923-4")]
181            Self::AS923_4(_) => Region::AS923_4,
182            #[cfg(feature = "region-au915")]
183            Self::AU915(_) => Region::AU915,
184            #[cfg(feature = "region-eu433")]
185            Self::EU433(_) => Region::EU433,
186            #[cfg(feature = "region-eu868")]
187            Self::EU868(_) => Region::EU868,
188            #[cfg(feature = "region-in865")]
189            Self::IN865(_) => Region::IN865,
190            #[cfg(feature = "region-us915")]
191            Self::US915(_) => Region::US915,
192        }
193    }
194}
195
196/// This datarate type is used internally for defining bandwidth/sf per region
197#[derive(Debug, Clone)]
198pub(crate) struct Datarate {
199    bandwidth: Bandwidth,
200    spreading_factor: SpreadingFactor,
201    max_mac_payload_size: u8,
202    max_mac_payload_size_with_dwell_time: u8,
203}
204macro_rules! mut_region_dispatch {
205  ($s:expr, $t:tt) => {
206      match &mut $s.state {
207        #[cfg(feature = "region-as923-1")]
208        State::AS923_1(state) => state.$t(),
209        #[cfg(feature = "region-as923-2")]
210        State::AS923_2(state) => state.$t(),
211        #[cfg(feature = "region-as923-3")]
212        State::AS923_3(state) => state.$t(),
213        #[cfg(feature = "region-as923-4")]
214        State::AS923_4(state) => state.$t(),
215        #[cfg(feature = "region-au915")]
216        State::AU915(state) => state.0.$t(),
217        #[cfg(feature = "region-eu868")]
218        State::EU868(state) => state.$t(),
219        #[cfg(feature = "region-eu433")]
220        State::EU433(state) => state.$t(),
221        #[cfg(feature = "region-in865")]
222        State::IN865(state) => state.$t(),
223        #[cfg(feature = "region-us915")]
224        State::US915(state) => state.0.$t(),
225    }
226  };
227  ($s:expr, $t:tt, $($arg:tt)*) => {
228      match &mut $s.state {
229        #[cfg(feature = "region-as923-1")]
230        State::AS923_1(state) => state.$t($($arg)*),
231        #[cfg(feature = "region-as923-2")]
232        State::AS923_2(state) => state.$t($($arg)*),
233        #[cfg(feature = "region-as923-3")]
234        State::AS923_3(state) => state.$t($($arg)*),
235        #[cfg(feature = "region-as923-4")]
236        State::AS923_4(state) => state.$t($($arg)*),
237        #[cfg(feature = "region-au915")]
238        State::AU915(state) => state.0.$t($($arg)*),
239        #[cfg(feature = "region-eu868")]
240        State::EU868(state) => state.$t($($arg)*),
241        #[cfg(feature = "region-eu433")]
242        State::EU433(state) => state.$t($($arg)*),
243        #[cfg(feature = "region-in865")]
244        State::IN865(state) => state.$t($($arg)*),
245        #[cfg(feature = "region-us915")]
246        State::US915(state) => state.0.$t($($arg)*),
247    }
248  };
249}
250
251macro_rules! region_dispatch {
252  ($s:expr, $t:tt) => {
253      match &$s.state {
254        #[cfg(feature = "region-as923-1")]
255        State::AS923_1(state) => state.$t(),
256        #[cfg(feature = "region-as923-2")]
257        State::AS923_2(state) => state.$t(),
258        #[cfg(feature = "region-as923-3")]
259        State::AS923_3(state) => state.$t(),
260        #[cfg(feature = "region-as923-4")]
261        State::AS923_4(state) => state.$t(),
262        #[cfg(feature = "region-au915")]
263        State::AU915(state) => state.0.$t(),
264        #[cfg(feature = "region-eu868")]
265        State::EU868(state) => state.$t(),
266        #[cfg(feature = "region-eu433")]
267        State::EU433(state) => state.$t(),
268        #[cfg(feature = "region-in865")]
269        State::IN865(state) => state.$t(),
270        #[cfg(feature = "region-us915")]
271        State::US915(state) => state.0.$t(),
272    }
273  };
274  ($s:expr, $t:tt, $($arg:tt)*) => {
275      match &$s.state {
276        #[cfg(feature = "region-as923-1")]
277        State::AS923_1(state) => state.$t($($arg)*),
278        #[cfg(feature = "region-as923-2")]
279        State::AS923_2(state) => state.$t($($arg)*),
280        #[cfg(feature = "region-as923-3")]
281        State::AS923_3(state) => state.$t($($arg)*),
282        #[cfg(feature = "region-as923-4")]
283        State::AS923_4(state) => state.$t($($arg)*),
284        #[cfg(feature = "region-au915")]
285        State::AU915(state) => state.0.$t($($arg)*),
286        #[cfg(feature = "region-eu868")]
287        State::EU868(state) => state.$t($($arg)*),
288        #[cfg(feature = "region-eu433")]
289        State::EU433(state) => state.$t($($arg)*),
290        #[cfg(feature = "region-in865")]
291        State::IN865(state) => state.$t($($arg)*),
292        #[cfg(feature = "region-us915")]
293        State::US915(state) => state.0.$t($($arg)*),
294    }
295  };
296}
297
298macro_rules! region_static_dispatch {
299  ($s:expr, $t:tt) => {
300      match &$s.state {
301        #[cfg(feature = "region-as923-1")]
302        State::AS923_1(_) => dynamic_channel_plans::AS923_1::$t(),
303        #[cfg(feature = "region-as923-2")]
304        State::AS923_2(_) => dynamic_channel_plans::AS923_2::$t(),
305        #[cfg(feature = "region-as923-3")]
306        State::AS923_3(_) => dynamic_channel_plans::AS923_3::$t(),
307        #[cfg(feature = "region-as923-4")]
308        State::AS923_4(_) => dynamic_channel_plans::AS923_4::$t(),
309        #[cfg(feature = "region-au915")]
310        State::AU915(_) => fixed_channel_plans::AU915::$t(),
311        #[cfg(feature = "region-eu868")]
312        State::EU868(_) => dynamic_channel_plans::EU868::$t(),
313        #[cfg(feature = "region-eu433")]
314        State::EU433(_) => dynamic_channel_plans::EU433::$t(),
315        #[cfg(feature = "region-in865")]
316        State::IN865(_) => dynamic_channel_plans::IN865::$t(),
317        #[cfg(feature = "region-us915")]
318        State::US915(_) => fixed_channel_plans::US915::$t(),
319    }
320  };
321  ($s:expr, $t:tt, $($arg:tt)*) => {
322      match &$s.state {
323        #[cfg(feature = "region-as923-1")]
324        State::AS923_1(_) => dynamic_channel_plans::AS923_1::$t($($arg)*),
325        #[cfg(feature = "region-as923-2")]
326        State::AS923_2(_) => dynamic_channel_plans::AS923_2::$t($($arg)*),
327        #[cfg(feature = "region-as923-3")]
328        State::AS923_3(_) => dynamic_channel_plans::AS923_3::$t($($arg)*),
329        #[cfg(feature = "region-as923-4")]
330        State::AS923_4(_) => dynamic_channel_plans::AS923_4::$t($($arg)*),
331        #[cfg(feature = "region-au915")]
332        State::AU915(_) => fixed_channel_plans::AU915::$t($($arg)*),
333        #[cfg(feature = "region-eu868")]
334        State::EU868(_) => dynamic_channel_plans::EU868::$t($($arg)*),
335        #[cfg(feature = "region-eu433")]
336        State::EU433(_) => dynamic_channel_plans::EU433::$t($($arg)*),
337        #[cfg(feature = "region-in865")]
338        State::IN865(_) => dynamic_channel_plans::IN865::$t($($arg)*),
339        #[cfg(feature = "region-us915")]
340        State::US915(_) => fixed_channel_plans::US915::$t($($arg)*),
341    }
342  };
343}
344
345impl Configuration {
346    pub fn new(region: Region) -> Configuration {
347        Configuration::with_state(State::new(region))
348    }
349
350    fn with_state(state: State) -> Configuration {
351        Configuration { state }
352    }
353
354    pub fn get_max_payload_length(
355        &self,
356        datarate: DR,
357        repeater_compatible: bool,
358        dwell_time: bool,
359    ) -> u8 {
360        region_static_dispatch!(
361            self,
362            get_max_payload_length,
363            datarate,
364            repeater_compatible,
365            dwell_time
366        )
367    }
368
369    pub(crate) fn create_tx_config<RNG: RngCore>(
370        &mut self,
371        rng: &mut RNG,
372        datarate: DR,
373        frame: &Frame,
374    ) -> TxConfig {
375        let (dr, frequency) = self.get_tx_dr_and_frequency(rng, datarate, frame);
376        TxConfig {
377            pw: self.get_dbm(),
378            rf: RfConfig {
379                frequency,
380                bb: BaseBandModulationParams::new(
381                    dr.spreading_factor,
382                    dr.bandwidth,
383                    self.get_coding_rate(),
384                ),
385            },
386        }
387    }
388
389    fn get_tx_dr_and_frequency<RNG: RngCore>(
390        &mut self,
391        rng: &mut RNG,
392        datarate: DR,
393        frame: &Frame,
394    ) -> (Datarate, u32) {
395        mut_region_dispatch!(self, get_tx_dr_and_frequency, rng, datarate, frame)
396    }
397
398    pub(crate) fn get_rx_config(&self, datarate: DR, frame: &Frame, window: &Window) -> RfConfig {
399        let dr = self.get_rx_datarate(datarate, frame, window);
400        RfConfig {
401            frequency: self.get_rx_frequency(frame, window),
402            bb: BaseBandModulationParams::new(
403                dr.spreading_factor,
404                dr.bandwidth,
405                self.get_coding_rate(),
406            ),
407        }
408    }
409
410    pub(crate) fn process_join_accept<T: AsRef<[u8]>, C>(
411        &mut self,
412        join_accept: &DecryptedJoinAcceptPayload<T, C>,
413    ) {
414        mut_region_dispatch!(self, process_join_accept, join_accept)
415    }
416
417    pub(crate) fn set_channel_mask(
418        &mut self,
419        channel_mask_control: u8,
420        channel_mask: ChannelMask<2>,
421    ) {
422        mut_region_dispatch!(self, handle_link_adr_channel_mask, channel_mask_control, channel_mask)
423    }
424
425    pub(crate) fn get_rx_frequency(&self, frame: &Frame, window: &Window) -> u32 {
426        region_dispatch!(self, get_rx_frequency, frame, window)
427    }
428
429    pub(crate) fn get_default_datarate(&self) -> DR {
430        region_dispatch!(self, get_default_datarate)
431    }
432
433    pub(crate) fn get_rx_datarate(&self, datarate: DR, frame: &Frame, window: &Window) -> Datarate {
434        region_dispatch!(self, get_rx_datarate, datarate, frame, window)
435    }
436
437    // Unicast: The RXC parameters are identical to the RX2 parameters, and they use the same
438    // channel and data rate. Modifying the RX2 parameters using the appropriate MAC
439    // commands also modifies the RXC parameters.
440    pub(crate) fn get_rxc_config(&self, datarate: DR) -> RfConfig {
441        let dr = self.get_rx_datarate(datarate, &Frame::Data, &Window::_2);
442        let frequency = self.get_rx_frequency(&Frame::Data, &Window::_2);
443        RfConfig {
444            frequency,
445            bb: BaseBandModulationParams::new(
446                dr.spreading_factor,
447                dr.bandwidth,
448                self.get_coding_rate(),
449            ),
450        }
451    }
452
453    pub(crate) fn get_dbm(&self) -> i8 {
454        region_dispatch!(self, get_dbm)
455    }
456
457    pub(crate) fn get_coding_rate(&self) -> CodingRate {
458        region_dispatch!(self, get_coding_rate)
459    }
460
461    #[allow(dead_code)]
462    pub(crate) fn get_current_region(&self) -> super::region::Region {
463        self.state.region()
464    }
465}
466
467macro_rules! from_region {
468    ($r:tt) => {
469        impl From<$r> for Configuration {
470            fn from(region: $r) -> Configuration {
471                Configuration::with_state(State::$r(region))
472            }
473        }
474    };
475}
476
477#[cfg(feature = "region-as923-1")]
478from_region!(AS923_1);
479#[cfg(feature = "region-as923-2")]
480from_region!(AS923_2);
481#[cfg(feature = "region-as923-3")]
482from_region!(AS923_3);
483#[cfg(feature = "region-as923-4")]
484from_region!(AS923_4);
485#[cfg(feature = "region-in865")]
486from_region!(IN865);
487#[cfg(feature = "region-au915")]
488from_region!(AU915);
489#[cfg(feature = "region-eu868")]
490from_region!(EU868);
491#[cfg(feature = "region-eu433")]
492from_region!(EU433);
493#[cfg(feature = "region-us915")]
494from_region!(US915);
495
496use lorawan::parser::DecryptedJoinAcceptPayload;
497
498pub(crate) trait RegionHandler {
499    fn process_join_accept<T: AsRef<[u8]>, C>(
500        &mut self,
501        join_accept: &DecryptedJoinAcceptPayload<T, C>,
502    );
503
504    fn handle_link_adr_channel_mask(
505        &mut self,
506        channel_mask_control: u8,
507        channel_mask: ChannelMask<2>,
508    );
509
510    fn get_default_datarate(&self) -> DR {
511        DR::_0
512    }
513    fn get_tx_dr_and_frequency<RNG: RngCore>(
514        &mut self,
515        rng: &mut RNG,
516        datarate: DR,
517        frame: &Frame,
518    ) -> (Datarate, u32);
519
520    fn get_rx_frequency(&self, frame: &Frame, window: &Window) -> u32;
521    fn get_rx_datarate(&self, datarate: DR, frame: &Frame, window: &Window) -> Datarate;
522    fn get_dbm(&self) -> i8 {
523        DEFAULT_DBM
524    }
525    fn get_coding_rate(&self) -> CodingRate {
526        DEFAULT_CODING_RATE
527    }
528}