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}