Skip to main content

pamoja_lora/region/
mod.rs

1//! Regional parameters: what a LoRaWAN radio may do, and where.
2//!
3//! A LoRa radio takes a spreading factor and a bandwidth. A *region* is what
4//! decides which of those are legal where the device is standing, what a data
5//! rate number means, how much payload fits, and which frequencies a gateway is
6//! listening on. Without it a caller has to already know their own channel plan,
7//! which is the difference between a stack that works on one continent and one
8//! that works anywhere.
9//!
10//! The tables come from the LoRa Alliance [`RP002-1.0.5` Regional Parameters]
11//! specification, and the tests assert the values the document prints rather
12//! than round-tripping the implementation against itself.
13//!
14//! [`RP002-1.0.5` Regional Parameters]: https://resources.lora-alliance.org/technical-specifications/rp002-1-0-5-lorawan-regional-parameters
15//!
16//! # These tables report; they never enforce
17//!
18//! Nothing here refuses to transmit, and no call gates on a duty cycle.
19//! [`ChannelPlan::duty_cycle_permille`] says what the region specifies and
20//! [`LinkSettings::min_off_time_us`](crate::LinkSettings::min_off_time_us) says
21//! what that costs; the decision stays with the caller.
22//!
23//! That is deliberate rather than squeamish. Most of what a regional plan
24//! encodes is physics and coordination rather than permission: the bands differ
25//! because each regulator left different spectrum unlicensed, a duty cycle is
26//! what stops an unlicensed band collapsing under everyone talking at once, and
27//! the plan doubles as a description of what a radio front end tuned for that
28//! band can physically do. But a node in a disaster zone may be operating under
29//! emergency spectrum provisions, or somewhere the question has stopped being
30//! meaningful, and a library that refused to transmit there would be harmful
31//! exactly where it is needed most. So the tables inform, the arithmetic costs
32//! it out, and the operator decides.
33//!
34//! # A named region is a convenience, not the only way in
35//!
36//! [`Region`] is a shortcut to a [`ChannelPlan`], which is an ordinary struct of
37//! borrowed tables. A private deployment holding licensed spectrum, or bespoke
38//! emergency work, builds its own plan from parts it owns and everything here
39//! still applies to it. The tables are borrowed rather than owned so the crate
40//! allocates nothing: the published plans point at constants, and a plan built
41//! at runtime points at whatever storage its caller chose.
42//!
43//! # Examples
44//!
45//! ```
46//! use pamoja_lora::region::{Modulation, Region};
47//!
48//! let plan = Region::Eu868.plan();
49//!
50//! // DR5 in Europe is SF7 at 125 kHz.
51//! let dr5 = plan.uplink_data_rate(5).expect("EU868 defines DR5");
52//! assert_eq!(
53//!     dr5.modulation,
54//!     Modulation::LoRa { spreading_factor: 7, bandwidth_hz: 125_000 }
55//! );
56//!
57//! // Talking straight to a gateway it carries 242 bytes of application payload,
58//! // and 222 if it may sit behind a repeater, which costs 20 bytes to encapsulate.
59//! assert_eq!(plan.max_payload(5, false).expect("DR5 carries payload").application, 242);
60//! assert_eq!(plan.max_payload(5, true).expect("DR5 carries payload").application, 222);
61//!
62//! // The airtime math already in this crate takes it from here.
63//! let settings = plan.link_settings(5).expect("DR5 is a LoRa data rate");
64//! assert!(settings.airtime_us(51) > 0);
65//! ```
66
67use crate::LinkSettings;
68
69mod channel_list;
70mod kind;
71#[cfg(feature = "alloc")]
72mod owned;
73mod plans;
74mod rules;
75
76#[cfg(test)]
77mod tests;
78
79pub use channel_list::FixedChannelList;
80pub use kind::PlanKind;
81#[cfg(feature = "alloc")]
82pub use owned::{ChannelPlanBuilder, OwnedChannelPlan, PayloadTable, PlanError};
83#[cfg(feature = "cn470")]
84pub use plans::Cn470Plan;
85pub use plans::Region;
86pub use rules::{JoinPlan, JoinSequence, MaskControl, PowerReference};
87
88/// How a data rate puts bits on the air.
89#[derive(Clone, Copy, Debug, PartialEq, Eq)]
90pub enum Modulation {
91    /// LoRa chirp spread spectrum, the modulation the rest of this crate models.
92    LoRa {
93        /// The spreading factor, 5 through 12.
94        spreading_factor: u8,
95        /// The channel bandwidth in hertz.
96        bandwidth_hz: u32,
97    },
98    /// Plain FSK, which one data rate in several regions uses.
99    Fsk {
100        /// The bit rate in bits per second.
101        bitrate_bps: u32,
102    },
103    /// Long-range frequency hopping spread spectrum.
104    LrFhss {
105        /// The numerator of the coding rate.
106        coding_rate_numerator: u8,
107        /// The denominator of the coding rate.
108        coding_rate_denominator: u8,
109        /// The occupied bandwidth in hertz.
110        bandwidth_hz: u32,
111    },
112}
113
114/// One data rate: how it is modulated and how fast it carries bits.
115#[derive(Clone, Copy, Debug, PartialEq, Eq)]
116pub struct DataRate {
117    /// How the data rate puts bits on the air.
118    pub modulation: Modulation,
119    /// The indicative physical bit rate the specification prints, in bits per
120    /// second.
121    pub bitrate_bps: u32,
122}
123
124impl DataRate {
125    /// Builds a LoRa data rate.
126    ///
127    /// # Arguments
128    ///
129    /// * `spreading_factor` - the spreading factor.
130    /// * `bandwidth_hz` - the channel bandwidth in hertz.
131    /// * `bitrate_bps` - the indicative bit rate the specification prints.
132    ///
133    /// # Returns
134    ///
135    /// The data rate.
136    pub const fn lora(spreading_factor: u8, bandwidth_hz: u32, bitrate_bps: u32) -> Self {
137        Self {
138            modulation: Modulation::LoRa {
139                spreading_factor,
140                bandwidth_hz,
141            },
142            bitrate_bps,
143        }
144    }
145
146    /// Builds an FSK data rate.
147    ///
148    /// # Arguments
149    ///
150    /// * `bitrate_bps` - the bit rate in bits per second.
151    ///
152    /// # Returns
153    ///
154    /// The data rate.
155    pub const fn fsk(bitrate_bps: u32) -> Self {
156        Self {
157            modulation: Modulation::Fsk { bitrate_bps },
158            bitrate_bps,
159        }
160    }
161
162    /// Builds an LR-FHSS data rate.
163    ///
164    /// # Arguments
165    ///
166    /// * `numerator` - the coding-rate numerator.
167    /// * `denominator` - the coding-rate denominator.
168    /// * `bandwidth_hz` - the occupied bandwidth in hertz.
169    /// * `bitrate_bps` - the indicative bit rate the specification prints.
170    ///
171    /// # Returns
172    ///
173    /// The data rate.
174    pub const fn lr_fhss(
175        numerator: u8,
176        denominator: u8,
177        bandwidth_hz: u32,
178        bitrate_bps: u32,
179    ) -> Self {
180        Self {
181            modulation: Modulation::LrFhss {
182                coding_rate_numerator: numerator,
183                coding_rate_denominator: denominator,
184                bandwidth_hz,
185            },
186            bitrate_bps,
187        }
188    }
189
190    /// Returns the link settings this data rate describes, for the airtime math.
191    ///
192    /// # Returns
193    ///
194    /// `Some(settings)` for a LoRa data rate, or `None` for FSK and LR-FHSS,
195    /// which this crate's chirp-based airtime model does not describe.
196    pub fn link_settings(&self) -> Option<LinkSettings> {
197        match self.modulation {
198            Modulation::LoRa {
199                spreading_factor,
200                bandwidth_hz,
201            } => Some(LinkSettings::new(spreading_factor, bandwidth_hz)),
202            _ => None,
203        }
204    }
205}
206
207/// The largest payload a data rate carries.
208///
209/// `M` is the MACPayload limit the physical layer imposes. `N` is the
210/// application payload that leaves room for the frame header, and shrinks
211/// further if the frame carries MAC commands in its `FOpts` field.
212#[derive(Clone, Copy, Debug, PartialEq, Eq)]
213pub struct MaxPayload {
214    /// The largest MACPayload, in bytes.
215    pub mac_payload: u16,
216    /// The largest application payload with an empty `FOpts` field, in bytes.
217    pub application: u16,
218}
219
220impl MaxPayload {
221    /// Builds a payload limit from the pair the specification tabulates.
222    ///
223    /// # Arguments
224    ///
225    /// * `mac_payload` - the `M` column.
226    /// * `application` - the `N` column.
227    ///
228    /// # Returns
229    ///
230    /// The limit.
231    pub const fn new(mac_payload: u16, application: u16) -> Self {
232        Self {
233            mac_payload,
234            application,
235        }
236    }
237}
238
239/// A run of evenly spaced channels, which is how the plans define them.
240///
241/// Every region lays its channels out as a start frequency and a fixed step, so
242/// a plan carries the arithmetic rather than 72 literal frequencies.
243#[derive(Clone, Copy, Debug, PartialEq, Eq)]
244pub struct ChannelBlock {
245    /// The frequency of the first channel in the block, in hertz.
246    pub start_hz: u32,
247    /// The spacing between channels, in hertz.
248    pub step_hz: u32,
249    /// How many channels the block holds.
250    pub count: u16,
251    /// The lowest data rate usable on these channels.
252    pub min_data_rate: u8,
253    /// The highest data rate usable on these channels.
254    pub max_data_rate: u8,
255}
256
257impl ChannelBlock {
258    /// Builds a block of evenly spaced channels.
259    ///
260    /// # Arguments
261    ///
262    /// * `start_hz` - the first channel frequency in hertz.
263    /// * `step_hz` - the spacing between channels in hertz.
264    /// * `count` - how many channels the block holds.
265    /// * `min_data_rate` - the lowest data rate usable on them.
266    /// * `max_data_rate` - the highest data rate usable on them.
267    ///
268    /// # Returns
269    ///
270    /// The block.
271    pub const fn new(
272        start_hz: u32,
273        step_hz: u32,
274        count: u16,
275        min_data_rate: u8,
276        max_data_rate: u8,
277    ) -> Self {
278        Self {
279            start_hz,
280            step_hz,
281            count,
282            min_data_rate,
283            max_data_rate,
284        }
285    }
286
287    /// Returns the frequency of one channel in the block.
288    ///
289    /// # Arguments
290    ///
291    /// * `index` - the channel's position within this block.
292    ///
293    /// # Returns
294    ///
295    /// `Some(hz)`, or `None` if `index` is past the end of the block.
296    pub const fn frequency_hz(&self, index: u16) -> Option<u32> {
297        if index >= self.count {
298            return None;
299        }
300        Some(self.start_hz + self.step_hz * index as u32)
301    }
302}
303
304/// A stretch of spectrum with its own transmit limits.
305///
306/// Europe divides its band into sub-bands whose duty cycles and power ceilings
307/// differ, so a plan reports them per frequency rather than once.
308#[derive(Clone, Copy, Debug, PartialEq, Eq)]
309pub struct SubBand {
310    /// The lowest frequency in the sub-band, in hertz, inclusive.
311    pub start_hz: u32,
312    /// The highest frequency in the sub-band, in hertz, inclusive.
313    pub end_hz: u32,
314    /// The share of time a transmitter may occupy the band, in parts per
315    /// thousand.
316    pub duty_cycle_permille: u32,
317    /// The power ceiling in the sub-band, in dBm EIRP.
318    pub max_eirp_dbm: i8,
319}
320
321impl SubBand {
322    /// Builds a sub-band.
323    ///
324    /// # Arguments
325    ///
326    /// * `start_hz` - the lowest frequency, inclusive.
327    /// * `end_hz` - the highest frequency, inclusive.
328    /// * `duty_cycle_permille` - the duty-cycle limit in parts per thousand.
329    /// * `max_eirp_dbm` - the power ceiling in dBm EIRP.
330    ///
331    /// # Returns
332    ///
333    /// The sub-band.
334    pub const fn new(
335        start_hz: u32,
336        end_hz: u32,
337        duty_cycle_permille: u32,
338        max_eirp_dbm: i8,
339    ) -> Self {
340        Self {
341            start_hz,
342            end_hz,
343            duty_cycle_permille,
344            max_eirp_dbm,
345        }
346    }
347
348    /// Reports whether a frequency falls inside this sub-band.
349    ///
350    /// # Arguments
351    ///
352    /// * `frequency_hz` - the frequency to test.
353    ///
354    /// # Returns
355    ///
356    /// `true` when the frequency is within the sub-band, inclusive of both ends.
357    pub const fn contains(&self, frequency_hz: u32) -> bool {
358        frequency_hz >= self.start_hz && frequency_hz <= self.end_hz
359    }
360}
361
362/// The Class B beacon settings a region broadcasts on.
363#[derive(Clone, Copy, Debug, PartialEq, Eq)]
364pub struct Beacon {
365    /// The data rate the beacon is sent at.
366    pub data_rate: u8,
367    /// The frequency the beacon is broadcast on, in hertz.
368    pub frequency_hz: u32,
369    /// The default ping-slot frequency, in hertz.
370    pub ping_slot_frequency_hz: u32,
371}
372
373/// A default channel of a LoRaWAN relay, TS011-1.0.1 section 3.2.2.
374///
375/// An end device under a relay sends its wake-on-radio frame on
376/// [`wor_frequency_hz`](Self::wor_frequency_hz) and hears the relay's acknowledgment on
377/// [`ack_frequency_hz`](Self::ack_frequency_hz), both at [`data_rate`](Self::data_rate).
378#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
379pub struct RelayChannel {
380    /// Where an end device sends its wake-on-radio frame, in hertz.
381    pub wor_frequency_hz: u32,
382    /// Where the relay acknowledges it, in hertz.
383    pub ack_frequency_hz: u32,
384    /// The data rate of both, numbered as the plan's downlink data rates, since the 900 MHz
385    /// plans define a 500 kHz rate at SF10 only in that table.
386    pub data_rate: u8,
387}
388
389impl RelayChannel {
390    /// Describes a relay channel.
391    ///
392    /// # Arguments
393    ///
394    /// * `wor_frequency_hz` - where the wake-on-radio frame goes, in hertz.
395    /// * `ack_frequency_hz` - where its acknowledgment comes back, in hertz.
396    /// * `data_rate` - the downlink data-rate number of both.
397    ///
398    /// # Returns
399    ///
400    /// The channel.
401    pub const fn new(wor_frequency_hz: u32, ack_frequency_hz: u32, data_rate: u8) -> Self {
402        Self {
403            wor_frequency_hz,
404            ack_frequency_hz,
405            data_rate,
406        }
407    }
408}
409
410/// A complete regional channel plan.
411///
412/// The named [`Region`] values are constants of this type. A deployment on
413/// licensed spectrum, or one doing something the published regions do not
414/// describe, builds its own from tables it owns, and every method here still
415/// applies.
416#[derive(Clone, Copy, Debug)]
417pub struct ChannelPlan<'a> {
418    /// The specification's name for the band, such as `"EU863-870"`.
419    pub name: &'a str,
420    /// The uplink data rates, indexed by data-rate number; `None` where the
421    /// number is reserved.
422    pub uplink_data_rates: &'a [Option<DataRate>],
423    /// The downlink data rates, indexed by data-rate number.
424    ///
425    /// Most regions use one table in both directions, and carry the same slice
426    /// here. The 900 MHz plans do not, which is why this is separate.
427    pub downlink_data_rates: &'a [Option<DataRate>],
428    /// The uplink payload limits when the device may be behind a repeater.
429    pub max_payload_repeater: &'a [Option<MaxPayload>],
430    /// The uplink payload limits when it will not be.
431    pub max_payload_direct: &'a [Option<MaxPayload>],
432    /// The downlink payload limits when the device may be behind a repeater.
433    ///
434    /// Most regions number their downlink data rates the same way as their
435    /// uplink ones and carry the same slice here. The 900 MHz plans do not.
436    pub downlink_max_payload_repeater: &'a [Option<MaxPayload>],
437    /// The downlink payload limits when it will not be.
438    pub downlink_max_payload_direct: &'a [Option<MaxPayload>],
439    /// The payload limits under a dwell-time limit, where the region has one.
440    pub max_payload_dwell_limited: Option<&'a [Option<MaxPayload>]>,
441    /// The channels a device must use to send a join request.
442    pub join_channels: &'a [ChannelBlock],
443    /// The channels a device starts with before a network adds any.
444    pub default_channels: &'a [ChannelBlock],
445    /// The sub-bands and their transmit limits.
446    pub sub_bands: &'a [SubBand],
447    /// The power ceiling assumed when no sub-band says otherwise, in dBm.
448    ///
449    /// It is radiated power unless [`power_reference`](Self::power_reference) says the plan
450    /// counts conducted power, as US902-928 does.
451    pub default_max_eirp_dbm: i8,
452    /// The step between transmit-power settings, in dB.
453    pub tx_power_step_db: u8,
454    /// The highest transmit-power index the region defines.
455    pub max_tx_power_index: u8,
456    /// The downlink data rate for each uplink data rate and RX1 offset, as
457    /// `[uplink data rate][offset]`.
458    pub rx1_data_rate_offsets: &'a [&'a [u8]],
459    /// The same mapping under a downlink dwell-time limit, where the region
460    /// publishes a second table for it.
461    pub rx1_data_rate_offsets_dwell_limited: Option<&'a [&'a [u8]]>,
462    /// The highest RX1 data-rate offset the region allows.
463    pub max_rx1_data_rate_offset: u8,
464    /// The fixed frequency the second receive window listens on, in hertz.
465    pub rx2_frequency_hz: u32,
466    /// The data rate the second receive window listens at.
467    pub rx2_data_rate: u8,
468    /// The next lower uplink data rate during adaptive back-off, indexed by the
469    /// current data rate; `None` where there is nothing lower.
470    pub data_rate_backoff: &'a [Option<u8>],
471    /// The Class B beacon settings.
472    pub beacon: Beacon,
473    /// The default channels of a LoRaWAN relay, by channel index, from the region's relay
474    /// parameters in RP002-1.0.5 sections 3.4.9 to 3.13.9; empty where a region defines
475    /// none.
476    pub relay_channels: &'a [RelayChannel],
477    /// Whether the region limits how long one transmission may occupy a channel.
478    pub has_dwell_time_limit: bool,
479    /// Whether a network may create and move channels, or only enable numbered ones.
480    pub kind: PlanKind,
481    /// Whether devices in the region answer `TXParamSetupReq`, which sets their
482    /// radiated power ceiling and dwell time.
483    ///
484    /// RP002-1.0.5 has AS923 and AU915-928 implement it and every other region
485    /// leave it out; a device in a region without it drops the command unanswered.
486    pub tx_param_setup: bool,
487    /// What each value of a `LinkADRReq` command's `ChMaskCntl` field does, indexed by the
488    /// value.
489    pub mask_controls: [MaskControl; 8],
490    /// The numbered downlink channels the first receive window answers on, for a plan that
491    /// has them, and empty for one that answers on the uplink's own frequency.
492    ///
493    /// The fixed plans number their downlink channels apart from their uplink ones, and
494    /// answer an uplink on channel `n` on downlink channel `n` modulo how many there are:
495    /// "RX1 Channel Number = Transmit Channel Number modulo NbChannel", RP002-1.0.5 section
496    /// 3.5.7.
497    pub downlink_channels: &'a [ChannelBlock],
498    /// The order a device tries the join channels in.
499    pub join_sequence: JoinSequence,
500    /// What the transmit power indexes count down from.
501    pub power_reference: PowerReference,
502    /// The plans a join selects between, for a region that puts a device on a plan by the
503    /// channel it joined on, and empty where the plan is the one a device joins on.
504    pub join_plans: &'a [JoinPlan<'a>],
505}
506
507impl<'a> ChannelPlan<'a> {
508    /// Returns the run of join channels a join channel number falls in.
509    ///
510    /// Join channels are numbered through [`join_plans`](Self::join_plans) in order, which
511    /// is how RP002-1.0.5 table 49 indexes the CN470-510 common join channels.
512    ///
513    /// # Arguments
514    ///
515    /// * `join_channel` - the join channel number.
516    ///
517    /// # Returns
518    ///
519    /// The run and the channel's position within it, or `None` for a plan with no join
520    /// plans or a number past the last run.
521    ///
522    /// # Examples
523    ///
524    /// ```
525    /// # #[cfg(feature = "cn470")] {
526    /// use pamoja_lora::region::{Cn470Plan, Region};
527    ///
528    /// // RP002-1.0.5 table 49: common join channel 9 goes out on 499.9 MHz, is answered
529    /// // there, and puts the device on the 20 MHz antenna's plan B.
530    /// let (run, offset) = Region::Cn470.plan().join_plan(9).expect("twenty join channels");
531    /// assert_eq!(run.channels.frequency_hz(offset), Some(499_900_000));
532    /// assert_eq!(run.accept_hz(offset), Some(499_900_000));
533    /// assert!(core::ptr::eq(run.plan, Cn470Plan::Antenna20MhzB.plan()));
534    /// # }
535    /// ```
536    pub fn join_plan(&self, join_channel: u16) -> Option<(JoinPlan<'a>, u16)> {
537        let mut remaining = join_channel;
538        for run in self.join_plans {
539            if remaining < run.channels.count {
540                return Some((*run, remaining));
541            }
542            remaining -= run.channels.count;
543        }
544        None
545    }
546}
547
548impl ChannelPlan<'_> {
549    /// Returns the frequency of a numbered downlink channel.
550    ///
551    /// # Arguments
552    ///
553    /// * `channel` - the downlink channel number, counted through
554    ///   [`downlink_channels`](Self::downlink_channels) in order.
555    ///
556    /// # Returns
557    ///
558    /// `Some(hz)`, or `None` if the plan numbers no such downlink channel.
559    pub fn downlink_channel_frequency_hz(&self, channel: u16) -> Option<u32> {
560        let mut remaining = channel;
561        for block in self.downlink_channels {
562            if remaining < block.count {
563                return block.frequency_hz(remaining);
564            }
565            remaining -= block.count;
566        }
567        None
568    }
569
570    /// Returns how many numbered downlink channels the plan has.
571    ///
572    /// # Returns
573    ///
574    /// The count, zero for a plan that answers on the uplink's own frequency.
575    pub fn downlink_channel_count(&self) -> u16 {
576        self.downlink_channels
577            .iter()
578            .map(|block| block.count)
579            .sum::<u16>()
580    }
581
582    /// Returns where the first receive window listens after an uplink.
583    ///
584    /// # Arguments
585    ///
586    /// * `uplink_channel` - the channel number the uplink went out on.
587    /// * `uplink_hz` - the frequency it went out on.
588    ///
589    /// # Returns
590    ///
591    /// The uplink's own frequency for a plan with no numbered downlink channels, and
592    /// otherwise the downlink channel the uplink channel maps to, or `None` if the plan's
593    /// downlink channels leave it undefined.
594    ///
595    /// # Examples
596    ///
597    /// ```
598    /// # #[cfg(feature = "us915")] {
599    /// use pamoja_lora::region::Region;
600    ///
601    /// // US902-928: an uplink on channel 65, the second 500 kHz channel at 904.6 MHz, is
602    /// // answered on downlink channel 1 at 923.9 MHz.
603    /// let plan = Region::Us915.plan();
604    /// assert_eq!(plan.rx1_frequency_hz(65, 904_600_000), Some(923_900_000));
605    /// # }
606    /// ```
607    pub fn rx1_frequency_hz(&self, uplink_channel: u16, uplink_hz: u32) -> Option<u32> {
608        let count = self.downlink_channel_count();
609        if count == 0 {
610            return Some(uplink_hz);
611        }
612        self.downlink_channel_frequency_hz(uplink_channel % count)
613    }
614
615    /// Returns the uplink data rate a number selects.
616    ///
617    /// # Arguments
618    ///
619    /// * `data_rate` - the data-rate number.
620    ///
621    /// # Returns
622    ///
623    /// `Some(rate)`, or `None` if the number is out of range or reserved in this
624    /// region.
625    pub fn uplink_data_rate(&self, data_rate: u8) -> Option<DataRate> {
626        *self.uplink_data_rates.get(usize::from(data_rate))?
627    }
628
629    /// Returns the downlink data rate a number selects.
630    ///
631    /// # Arguments
632    ///
633    /// * `data_rate` - the data-rate number.
634    ///
635    /// # Returns
636    ///
637    /// `Some(rate)`, or `None` if the number is out of range or reserved.
638    pub fn downlink_data_rate(&self, data_rate: u8) -> Option<DataRate> {
639        *self.downlink_data_rates.get(usize::from(data_rate))?
640    }
641
642    /// Returns the link settings an uplink data rate describes.
643    ///
644    /// This is the bridge into the airtime and duty-cycle math the rest of the
645    /// crate already provides.
646    ///
647    /// # Arguments
648    ///
649    /// * `data_rate` - the uplink data-rate number.
650    ///
651    /// # Returns
652    ///
653    /// `Some(settings)` for a LoRa data rate, or `None` if the number is not
654    /// defined here or names an FSK or LR-FHSS rate, which the chirp airtime
655    /// model does not describe.
656    pub fn link_settings(&self, data_rate: u8) -> Option<LinkSettings> {
657        self.uplink_data_rate(data_rate)?.link_settings()
658    }
659
660    /// Returns the largest payload an uplink data rate carries.
661    ///
662    /// # Arguments
663    ///
664    /// * `data_rate` - the uplink data-rate number.
665    /// * `behind_repeater` - whether the device may operate through a repeater,
666    ///   which costs a few bytes of encapsulation at the higher data rates.
667    ///
668    /// # Returns
669    ///
670    /// `Some(limit)`, or `None` if the data rate carries no payload here.
671    pub fn max_payload(&self, data_rate: u8, behind_repeater: bool) -> Option<MaxPayload> {
672        let table = if behind_repeater {
673            self.max_payload_repeater
674        } else {
675            self.max_payload_direct
676        };
677        *table.get(usize::from(data_rate))?
678    }
679
680    /// Returns the largest payload a downlink data rate carries.
681    ///
682    /// # Arguments
683    ///
684    /// * `data_rate` - the downlink data-rate number.
685    /// * `behind_repeater` - whether the device may operate through a repeater.
686    ///
687    /// # Returns
688    ///
689    /// `Some(limit)`, or `None` if the data rate carries no payload here.
690    pub fn downlink_max_payload(&self, data_rate: u8, behind_repeater: bool) -> Option<MaxPayload> {
691        let table = if behind_repeater {
692            self.downlink_max_payload_repeater
693        } else {
694            self.downlink_max_payload_direct
695        };
696        *table.get(usize::from(data_rate))?
697    }
698
699    /// Returns the largest payload an uplink data rate carries under a dwell-time
700    /// limit.
701    ///
702    /// # Arguments
703    ///
704    /// * `data_rate` - the uplink data-rate number.
705    ///
706    /// # Returns
707    ///
708    /// `Some(limit)`, or `None` if the region has no dwell-time limit or the
709    /// data rate carries nothing under one.
710    pub fn max_payload_dwell_limited(&self, data_rate: u8) -> Option<MaxPayload> {
711        *self
712            .max_payload_dwell_limited?
713            .get(usize::from(data_rate))?
714    }
715
716    /// Returns the duty-cycle limit that applies to a frequency.
717    ///
718    /// # Arguments
719    ///
720    /// * `frequency_hz` - the frequency to look up.
721    ///
722    /// # Returns
723    ///
724    /// `Some(permille)` for a frequency inside a sub-band this region limits, or
725    /// `None` where the region publishes no duty-cycle limit for it. `None` is
726    /// not permission; it means the constraint is elsewhere, typically a
727    /// dwell-time limit instead.
728    pub fn duty_cycle_permille(&self, frequency_hz: u32) -> Option<u32> {
729        self.sub_bands
730            .iter()
731            .find(|band| band.contains(frequency_hz))
732            .map(|band| band.duty_cycle_permille)
733    }
734
735    /// Returns the power ceiling that applies to a frequency, in dBm.
736    ///
737    /// # Arguments
738    ///
739    /// * `frequency_hz` - the frequency to look up.
740    ///
741    /// # Returns
742    ///
743    /// The sub-band's ceiling, or the region default where no sub-band covers
744    /// the frequency.
745    pub fn max_eirp_dbm(&self, frequency_hz: u32) -> i8 {
746        self.sub_bands
747            .iter()
748            .find(|band| band.contains(frequency_hz))
749            .map_or(self.default_max_eirp_dbm, |band| band.max_eirp_dbm)
750    }
751
752    /// Returns the power a transmit-power index selects, in dBm.
753    ///
754    /// The power is radiated or conducted as the plan's
755    /// [`power_reference`](Self::power_reference) says.
756    ///
757    /// # Arguments
758    ///
759    /// * `index` - the `TXPower` index from a `LinkADRReq`.
760    /// * `max_eirp_dbm` - the ceiling the index counts down from, usually
761    ///   [`max_eirp_dbm`](Self::max_eirp_dbm) for the frequency in use.
762    ///
763    /// # Returns
764    ///
765    /// `Some(dbm)`, or `None` if the index is above what the region defines.
766    pub fn tx_power_dbm(&self, index: u8, max_eirp_dbm: i8) -> Option<i8> {
767        if index > self.max_tx_power_index {
768            return None;
769        }
770        let step = i16::from(self.tx_power_step_db) * i16::from(index);
771        Some((i16::from(max_eirp_dbm) - step) as i8)
772    }
773
774    /// Returns the downlink data rate the first receive window uses.
775    ///
776    /// # Arguments
777    ///
778    /// * `uplink_data_rate` - the data rate the uplink was sent at.
779    /// * `offset` - the `RX1DROffset` in force.
780    ///
781    /// # Returns
782    ///
783    /// `Some(data_rate)`, or `None` if either argument is outside what the
784    /// region defines.
785    pub fn rx1_data_rate(&self, uplink_data_rate: u8, offset: u8) -> Option<u8> {
786        if offset > self.max_rx1_data_rate_offset {
787            return None;
788        }
789        self.rx1_data_rate_offsets
790            .get(usize::from(uplink_data_rate))?
791            .get(usize::from(offset))
792            .copied()
793    }
794
795    /// Returns the first receive window's data rate under a dwell-time limit.
796    ///
797    /// # Arguments
798    ///
799    /// * `uplink_data_rate` - the data rate the uplink was sent at.
800    /// * `offset` - the `RX1DROffset` in force.
801    ///
802    /// # Returns
803    ///
804    /// `Some(data_rate)`, or `None` if the region publishes no dwell-limited
805    /// mapping or either argument is outside what it defines.
806    pub fn rx1_data_rate_dwell_limited(&self, uplink_data_rate: u8, offset: u8) -> Option<u8> {
807        if offset > self.max_rx1_data_rate_offset {
808            return None;
809        }
810        self.rx1_data_rate_offsets_dwell_limited?
811            .get(usize::from(uplink_data_rate))?
812            .get(usize::from(offset))
813            .copied()
814    }
815
816    /// Returns a default relay channel.
817    ///
818    /// # Arguments
819    ///
820    /// * `index` - the channel index a relay configuration names, 0 or 1.
821    ///
822    /// # Returns
823    ///
824    /// The channel, or `None` where the region defines none at that index.
825    ///
826    /// # Examples
827    ///
828    /// ```
829    /// # #[cfg(feature = "eu868")] {
830    /// use pamoja_lora::region::{RelayChannel, Region};
831    ///
832    /// let plan = Region::Eu868.plan();
833    /// assert_eq!(
834    ///     plan.relay_channel(0),
835    ///     Some(RelayChannel::new(865_100_000, 865_300_000, 3))
836    /// );
837    /// assert_eq!(plan.relay_channel(2), None);
838    /// # }
839    /// ```
840    pub fn relay_channel(&self, index: u8) -> Option<RelayChannel> {
841        self.relay_channels.get(usize::from(index)).copied()
842    }
843
844    /// Returns the frequency and data rate of the second receive window.
845    ///
846    /// # Returns
847    ///
848    /// The frequency in hertz and the data-rate number.
849    pub fn rx2(&self) -> (u32, u8) {
850        (self.rx2_frequency_hz, self.rx2_data_rate)
851    }
852
853    /// Returns the next data rate down during adaptive back-off.
854    ///
855    /// # Arguments
856    ///
857    /// * `data_rate` - the data rate currently in use.
858    ///
859    /// # Returns
860    ///
861    /// `Some(next)`, or `None` when the device is already at the lowest rate the
862    /// region backs off to.
863    pub fn next_backoff_data_rate(&self, data_rate: u8) -> Option<u8> {
864        *self.data_rate_backoff.get(usize::from(data_rate))?
865    }
866
867    /// Returns the frequency of a channel by its number across the whole plan.
868    ///
869    /// Channel numbers run through the default blocks in order, which is the
870    /// numbering `LinkADRReq` channel masks use.
871    ///
872    /// # Arguments
873    ///
874    /// * `channel` - the channel number.
875    ///
876    /// # Returns
877    ///
878    /// `Some(hz)`, or `None` if the plan defines no such channel by default.
879    pub fn channel_frequency_hz(&self, channel: u16) -> Option<u32> {
880        let mut remaining = channel;
881        for block in self.default_channels {
882            if remaining < block.count {
883                return block.frequency_hz(remaining);
884            }
885            remaining -= block.count;
886        }
887        None
888    }
889
890    /// Returns how many channels the plan defines by default.
891    ///
892    /// # Returns
893    ///
894    /// The channel count.
895    pub fn default_channel_count(&self) -> u16 {
896        self.default_channels
897            .iter()
898            .map(|block| block.count)
899            .sum::<u16>()
900    }
901}