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
69#[cfg(feature = "alloc")]
70mod owned;
71mod plans;
72
73#[cfg(test)]
74mod tests;
75
76#[cfg(feature = "alloc")]
77pub use owned::{ChannelPlanBuilder, OwnedChannelPlan, PayloadTable, PlanError};
78pub use plans::Region;
79
80/// How a data rate puts bits on the air.
81#[derive(Clone, Copy, Debug, PartialEq, Eq)]
82pub enum Modulation {
83    /// LoRa chirp spread spectrum, the modulation the rest of this crate models.
84    LoRa {
85        /// The spreading factor, 5 through 12.
86        spreading_factor: u8,
87        /// The channel bandwidth in hertz.
88        bandwidth_hz: u32,
89    },
90    /// Plain FSK, which one data rate in several regions uses.
91    Fsk {
92        /// The bit rate in bits per second.
93        bitrate_bps: u32,
94    },
95    /// Long-range frequency hopping spread spectrum.
96    LrFhss {
97        /// The numerator of the coding rate.
98        coding_rate_numerator: u8,
99        /// The denominator of the coding rate.
100        coding_rate_denominator: u8,
101        /// The occupied bandwidth in hertz.
102        bandwidth_hz: u32,
103    },
104}
105
106/// One data rate: how it is modulated and how fast it carries bits.
107#[derive(Clone, Copy, Debug, PartialEq, Eq)]
108pub struct DataRate {
109    /// How the data rate puts bits on the air.
110    pub modulation: Modulation,
111    /// The indicative physical bit rate the specification prints, in bits per
112    /// second.
113    pub bitrate_bps: u32,
114}
115
116impl DataRate {
117    /// Builds a LoRa data rate.
118    ///
119    /// # Arguments
120    ///
121    /// * `spreading_factor` - the spreading factor.
122    /// * `bandwidth_hz` - the channel bandwidth in hertz.
123    /// * `bitrate_bps` - the indicative bit rate the specification prints.
124    ///
125    /// # Returns
126    ///
127    /// The data rate.
128    pub const fn lora(spreading_factor: u8, bandwidth_hz: u32, bitrate_bps: u32) -> Self {
129        Self {
130            modulation: Modulation::LoRa {
131                spreading_factor,
132                bandwidth_hz,
133            },
134            bitrate_bps,
135        }
136    }
137
138    /// Builds an FSK data rate.
139    ///
140    /// # Arguments
141    ///
142    /// * `bitrate_bps` - the bit rate in bits per second.
143    ///
144    /// # Returns
145    ///
146    /// The data rate.
147    pub const fn fsk(bitrate_bps: u32) -> Self {
148        Self {
149            modulation: Modulation::Fsk { bitrate_bps },
150            bitrate_bps,
151        }
152    }
153
154    /// Builds an LR-FHSS data rate.
155    ///
156    /// # Arguments
157    ///
158    /// * `numerator` - the coding-rate numerator.
159    /// * `denominator` - the coding-rate denominator.
160    /// * `bandwidth_hz` - the occupied bandwidth in hertz.
161    /// * `bitrate_bps` - the indicative bit rate the specification prints.
162    ///
163    /// # Returns
164    ///
165    /// The data rate.
166    pub const fn lr_fhss(
167        numerator: u8,
168        denominator: u8,
169        bandwidth_hz: u32,
170        bitrate_bps: u32,
171    ) -> Self {
172        Self {
173            modulation: Modulation::LrFhss {
174                coding_rate_numerator: numerator,
175                coding_rate_denominator: denominator,
176                bandwidth_hz,
177            },
178            bitrate_bps,
179        }
180    }
181
182    /// Returns the link settings this data rate describes, for the airtime math.
183    ///
184    /// # Returns
185    ///
186    /// `Some(settings)` for a LoRa data rate, or `None` for FSK and LR-FHSS,
187    /// which this crate's chirp-based airtime model does not describe.
188    pub fn link_settings(&self) -> Option<LinkSettings> {
189        match self.modulation {
190            Modulation::LoRa {
191                spreading_factor,
192                bandwidth_hz,
193            } => Some(LinkSettings::new(spreading_factor, bandwidth_hz)),
194            _ => None,
195        }
196    }
197}
198
199/// The largest payload a data rate carries.
200///
201/// `M` is the MACPayload limit the physical layer imposes. `N` is the
202/// application payload that leaves room for the frame header, and shrinks
203/// further if the frame carries MAC commands in its `FOpts` field.
204#[derive(Clone, Copy, Debug, PartialEq, Eq)]
205pub struct MaxPayload {
206    /// The largest MACPayload, in bytes.
207    pub mac_payload: u16,
208    /// The largest application payload with an empty `FOpts` field, in bytes.
209    pub application: u16,
210}
211
212impl MaxPayload {
213    /// Builds a payload limit from the pair the specification tabulates.
214    ///
215    /// # Arguments
216    ///
217    /// * `mac_payload` - the `M` column.
218    /// * `application` - the `N` column.
219    ///
220    /// # Returns
221    ///
222    /// The limit.
223    pub const fn new(mac_payload: u16, application: u16) -> Self {
224        Self {
225            mac_payload,
226            application,
227        }
228    }
229}
230
231/// A run of evenly spaced channels, which is how the plans define them.
232///
233/// Every region lays its channels out as a start frequency and a fixed step, so
234/// a plan carries the arithmetic rather than 72 literal frequencies.
235#[derive(Clone, Copy, Debug, PartialEq, Eq)]
236pub struct ChannelBlock {
237    /// The frequency of the first channel in the block, in hertz.
238    pub start_hz: u32,
239    /// The spacing between channels, in hertz.
240    pub step_hz: u32,
241    /// How many channels the block holds.
242    pub count: u16,
243    /// The lowest data rate usable on these channels.
244    pub min_data_rate: u8,
245    /// The highest data rate usable on these channels.
246    pub max_data_rate: u8,
247}
248
249impl ChannelBlock {
250    /// Builds a block of evenly spaced channels.
251    ///
252    /// # Arguments
253    ///
254    /// * `start_hz` - the first channel frequency in hertz.
255    /// * `step_hz` - the spacing between channels in hertz.
256    /// * `count` - how many channels the block holds.
257    /// * `min_data_rate` - the lowest data rate usable on them.
258    /// * `max_data_rate` - the highest data rate usable on them.
259    ///
260    /// # Returns
261    ///
262    /// The block.
263    pub const fn new(
264        start_hz: u32,
265        step_hz: u32,
266        count: u16,
267        min_data_rate: u8,
268        max_data_rate: u8,
269    ) -> Self {
270        Self {
271            start_hz,
272            step_hz,
273            count,
274            min_data_rate,
275            max_data_rate,
276        }
277    }
278
279    /// Returns the frequency of one channel in the block.
280    ///
281    /// # Arguments
282    ///
283    /// * `index` - the channel's position within this block.
284    ///
285    /// # Returns
286    ///
287    /// `Some(hz)`, or `None` if `index` is past the end of the block.
288    pub const fn frequency_hz(&self, index: u16) -> Option<u32> {
289        if index >= self.count {
290            return None;
291        }
292        Some(self.start_hz + self.step_hz * index as u32)
293    }
294}
295
296/// A stretch of spectrum with its own transmit limits.
297///
298/// Europe divides its band into sub-bands whose duty cycles and power ceilings
299/// differ, so a plan reports them per frequency rather than once.
300#[derive(Clone, Copy, Debug, PartialEq, Eq)]
301pub struct SubBand {
302    /// The lowest frequency in the sub-band, in hertz, inclusive.
303    pub start_hz: u32,
304    /// The highest frequency in the sub-band, in hertz, inclusive.
305    pub end_hz: u32,
306    /// The share of time a transmitter may occupy the band, in parts per
307    /// thousand.
308    pub duty_cycle_permille: u32,
309    /// The power ceiling in the sub-band, in dBm EIRP.
310    pub max_eirp_dbm: i8,
311}
312
313impl SubBand {
314    /// Builds a sub-band.
315    ///
316    /// # Arguments
317    ///
318    /// * `start_hz` - the lowest frequency, inclusive.
319    /// * `end_hz` - the highest frequency, inclusive.
320    /// * `duty_cycle_permille` - the duty-cycle limit in parts per thousand.
321    /// * `max_eirp_dbm` - the power ceiling in dBm EIRP.
322    ///
323    /// # Returns
324    ///
325    /// The sub-band.
326    pub const fn new(
327        start_hz: u32,
328        end_hz: u32,
329        duty_cycle_permille: u32,
330        max_eirp_dbm: i8,
331    ) -> Self {
332        Self {
333            start_hz,
334            end_hz,
335            duty_cycle_permille,
336            max_eirp_dbm,
337        }
338    }
339
340    /// Reports whether a frequency falls inside this sub-band.
341    ///
342    /// # Arguments
343    ///
344    /// * `frequency_hz` - the frequency to test.
345    ///
346    /// # Returns
347    ///
348    /// `true` when the frequency is within the sub-band, inclusive of both ends.
349    pub const fn contains(&self, frequency_hz: u32) -> bool {
350        frequency_hz >= self.start_hz && frequency_hz <= self.end_hz
351    }
352}
353
354/// The Class B beacon settings a region broadcasts on.
355#[derive(Clone, Copy, Debug, PartialEq, Eq)]
356pub struct Beacon {
357    /// The data rate the beacon is sent at.
358    pub data_rate: u8,
359    /// The frequency the beacon is broadcast on, in hertz.
360    pub frequency_hz: u32,
361    /// The default ping-slot frequency, in hertz.
362    pub ping_slot_frequency_hz: u32,
363}
364
365/// A complete regional channel plan.
366///
367/// The named [`Region`] values are constants of this type. A deployment on
368/// licensed spectrum, or one doing something the published regions do not
369/// describe, builds its own from tables it owns, and every method here still
370/// applies.
371#[derive(Clone, Copy, Debug)]
372pub struct ChannelPlan<'a> {
373    /// The specification's name for the band, such as `"EU863-870"`.
374    pub name: &'a str,
375    /// The uplink data rates, indexed by data-rate number; `None` where the
376    /// number is reserved.
377    pub uplink_data_rates: &'a [Option<DataRate>],
378    /// The downlink data rates, indexed by data-rate number.
379    ///
380    /// Most regions use one table in both directions, and carry the same slice
381    /// here. The 900 MHz plans do not, which is why this is separate.
382    pub downlink_data_rates: &'a [Option<DataRate>],
383    /// The uplink payload limits when the device may be behind a repeater.
384    pub max_payload_repeater: &'a [Option<MaxPayload>],
385    /// The uplink payload limits when it will not be.
386    pub max_payload_direct: &'a [Option<MaxPayload>],
387    /// The downlink payload limits when the device may be behind a repeater.
388    ///
389    /// Most regions number their downlink data rates the same way as their
390    /// uplink ones and carry the same slice here. The 900 MHz plans do not.
391    pub downlink_max_payload_repeater: &'a [Option<MaxPayload>],
392    /// The downlink payload limits when it will not be.
393    pub downlink_max_payload_direct: &'a [Option<MaxPayload>],
394    /// The payload limits under a dwell-time limit, where the region has one.
395    pub max_payload_dwell_limited: Option<&'a [Option<MaxPayload>]>,
396    /// The channels a device must use to send a join request.
397    pub join_channels: &'a [ChannelBlock],
398    /// The channels a device starts with before a network adds any.
399    pub default_channels: &'a [ChannelBlock],
400    /// The sub-bands and their transmit limits.
401    pub sub_bands: &'a [SubBand],
402    /// The power ceiling assumed when no sub-band says otherwise, in dBm.
403    pub default_max_eirp_dbm: i8,
404    /// The step between transmit-power settings, in dB.
405    pub tx_power_step_db: u8,
406    /// The highest transmit-power index the region defines.
407    pub max_tx_power_index: u8,
408    /// The downlink data rate for each uplink data rate and RX1 offset, as
409    /// `[uplink data rate][offset]`.
410    pub rx1_data_rate_offsets: &'a [&'a [u8]],
411    /// The same mapping under a downlink dwell-time limit, where the region
412    /// publishes a second table for it.
413    pub rx1_data_rate_offsets_dwell_limited: Option<&'a [&'a [u8]]>,
414    /// The highest RX1 data-rate offset the region allows.
415    pub max_rx1_data_rate_offset: u8,
416    /// The fixed frequency the second receive window listens on, in hertz.
417    pub rx2_frequency_hz: u32,
418    /// The data rate the second receive window listens at.
419    pub rx2_data_rate: u8,
420    /// The next lower uplink data rate during adaptive back-off, indexed by the
421    /// current data rate; `None` where there is nothing lower.
422    pub data_rate_backoff: &'a [Option<u8>],
423    /// The Class B beacon settings.
424    pub beacon: Beacon,
425    /// Whether the region limits how long one transmission may occupy a channel.
426    pub has_dwell_time_limit: bool,
427}
428
429impl ChannelPlan<'_> {
430    /// Returns the uplink data rate a number selects.
431    ///
432    /// # Arguments
433    ///
434    /// * `data_rate` - the data-rate number.
435    ///
436    /// # Returns
437    ///
438    /// `Some(rate)`, or `None` if the number is out of range or reserved in this
439    /// region.
440    pub fn uplink_data_rate(&self, data_rate: u8) -> Option<DataRate> {
441        *self.uplink_data_rates.get(usize::from(data_rate))?
442    }
443
444    /// Returns the downlink data rate a number selects.
445    ///
446    /// # Arguments
447    ///
448    /// * `data_rate` - the data-rate number.
449    ///
450    /// # Returns
451    ///
452    /// `Some(rate)`, or `None` if the number is out of range or reserved.
453    pub fn downlink_data_rate(&self, data_rate: u8) -> Option<DataRate> {
454        *self.downlink_data_rates.get(usize::from(data_rate))?
455    }
456
457    /// Returns the link settings an uplink data rate describes.
458    ///
459    /// This is the bridge into the airtime and duty-cycle math the rest of the
460    /// crate already provides.
461    ///
462    /// # Arguments
463    ///
464    /// * `data_rate` - the uplink data-rate number.
465    ///
466    /// # Returns
467    ///
468    /// `Some(settings)` for a LoRa data rate, or `None` if the number is not
469    /// defined here or names an FSK or LR-FHSS rate, which the chirp airtime
470    /// model does not describe.
471    pub fn link_settings(&self, data_rate: u8) -> Option<LinkSettings> {
472        self.uplink_data_rate(data_rate)?.link_settings()
473    }
474
475    /// Returns the largest payload an uplink data rate carries.
476    ///
477    /// # Arguments
478    ///
479    /// * `data_rate` - the uplink data-rate number.
480    /// * `behind_repeater` - whether the device may operate through a repeater,
481    ///   which costs a few bytes of encapsulation at the higher data rates.
482    ///
483    /// # Returns
484    ///
485    /// `Some(limit)`, or `None` if the data rate carries no payload here.
486    pub fn max_payload(&self, data_rate: u8, behind_repeater: bool) -> Option<MaxPayload> {
487        let table = if behind_repeater {
488            self.max_payload_repeater
489        } else {
490            self.max_payload_direct
491        };
492        *table.get(usize::from(data_rate))?
493    }
494
495    /// Returns the largest payload a downlink data rate carries.
496    ///
497    /// # Arguments
498    ///
499    /// * `data_rate` - the downlink data-rate number.
500    /// * `behind_repeater` - whether the device may operate through a repeater.
501    ///
502    /// # Returns
503    ///
504    /// `Some(limit)`, or `None` if the data rate carries no payload here.
505    pub fn downlink_max_payload(&self, data_rate: u8, behind_repeater: bool) -> Option<MaxPayload> {
506        let table = if behind_repeater {
507            self.downlink_max_payload_repeater
508        } else {
509            self.downlink_max_payload_direct
510        };
511        *table.get(usize::from(data_rate))?
512    }
513
514    /// Returns the largest payload an uplink data rate carries under a dwell-time
515    /// limit.
516    ///
517    /// # Arguments
518    ///
519    /// * `data_rate` - the uplink data-rate number.
520    ///
521    /// # Returns
522    ///
523    /// `Some(limit)`, or `None` if the region has no dwell-time limit or the
524    /// data rate carries nothing under one.
525    pub fn max_payload_dwell_limited(&self, data_rate: u8) -> Option<MaxPayload> {
526        *self
527            .max_payload_dwell_limited?
528            .get(usize::from(data_rate))?
529    }
530
531    /// Returns the duty-cycle limit that applies to a frequency.
532    ///
533    /// # Arguments
534    ///
535    /// * `frequency_hz` - the frequency to look up.
536    ///
537    /// # Returns
538    ///
539    /// `Some(permille)` for a frequency inside a sub-band this region limits, or
540    /// `None` where the region publishes no duty-cycle limit for it. `None` is
541    /// not permission; it means the constraint is elsewhere, typically a
542    /// dwell-time limit instead.
543    pub fn duty_cycle_permille(&self, frequency_hz: u32) -> Option<u32> {
544        self.sub_bands
545            .iter()
546            .find(|band| band.contains(frequency_hz))
547            .map(|band| band.duty_cycle_permille)
548    }
549
550    /// Returns the power ceiling that applies to a frequency, in dBm.
551    ///
552    /// # Arguments
553    ///
554    /// * `frequency_hz` - the frequency to look up.
555    ///
556    /// # Returns
557    ///
558    /// The sub-band's ceiling, or the region default where no sub-band covers
559    /// the frequency.
560    pub fn max_eirp_dbm(&self, frequency_hz: u32) -> i8 {
561        self.sub_bands
562            .iter()
563            .find(|band| band.contains(frequency_hz))
564            .map_or(self.default_max_eirp_dbm, |band| band.max_eirp_dbm)
565    }
566
567    /// Returns the radiated power a transmit-power index selects, in dBm.
568    ///
569    /// # Arguments
570    ///
571    /// * `index` - the `TXPower` index from a `LinkADRReq`.
572    /// * `max_eirp_dbm` - the ceiling the index counts down from, usually
573    ///   [`max_eirp_dbm`](Self::max_eirp_dbm) for the frequency in use.
574    ///
575    /// # Returns
576    ///
577    /// `Some(dbm)`, or `None` if the index is above what the region defines.
578    pub fn tx_power_dbm(&self, index: u8, max_eirp_dbm: i8) -> Option<i8> {
579        if index > self.max_tx_power_index {
580            return None;
581        }
582        let step = i16::from(self.tx_power_step_db) * i16::from(index);
583        Some((i16::from(max_eirp_dbm) - step) as i8)
584    }
585
586    /// Returns the downlink data rate the first receive window uses.
587    ///
588    /// # Arguments
589    ///
590    /// * `uplink_data_rate` - the data rate the uplink was sent at.
591    /// * `offset` - the `RX1DROffset` in force.
592    ///
593    /// # Returns
594    ///
595    /// `Some(data_rate)`, or `None` if either argument is outside what the
596    /// region defines.
597    pub fn rx1_data_rate(&self, uplink_data_rate: u8, offset: u8) -> Option<u8> {
598        if offset > self.max_rx1_data_rate_offset {
599            return None;
600        }
601        self.rx1_data_rate_offsets
602            .get(usize::from(uplink_data_rate))?
603            .get(usize::from(offset))
604            .copied()
605    }
606
607    /// Returns the first receive window's data rate under a dwell-time limit.
608    ///
609    /// # Arguments
610    ///
611    /// * `uplink_data_rate` - the data rate the uplink was sent at.
612    /// * `offset` - the `RX1DROffset` in force.
613    ///
614    /// # Returns
615    ///
616    /// `Some(data_rate)`, or `None` if the region publishes no dwell-limited
617    /// mapping or either argument is outside what it defines.
618    pub fn rx1_data_rate_dwell_limited(&self, uplink_data_rate: u8, offset: u8) -> Option<u8> {
619        if offset > self.max_rx1_data_rate_offset {
620            return None;
621        }
622        self.rx1_data_rate_offsets_dwell_limited?
623            .get(usize::from(uplink_data_rate))?
624            .get(usize::from(offset))
625            .copied()
626    }
627
628    /// Returns the frequency and data rate of the second receive window.
629    ///
630    /// # Returns
631    ///
632    /// The frequency in hertz and the data-rate number.
633    pub fn rx2(&self) -> (u32, u8) {
634        (self.rx2_frequency_hz, self.rx2_data_rate)
635    }
636
637    /// Returns the next data rate down during adaptive back-off.
638    ///
639    /// # Arguments
640    ///
641    /// * `data_rate` - the data rate currently in use.
642    ///
643    /// # Returns
644    ///
645    /// `Some(next)`, or `None` when the device is already at the lowest rate the
646    /// region backs off to.
647    pub fn next_backoff_data_rate(&self, data_rate: u8) -> Option<u8> {
648        *self.data_rate_backoff.get(usize::from(data_rate))?
649    }
650
651    /// Returns the frequency of a channel by its number across the whole plan.
652    ///
653    /// Channel numbers run through the default blocks in order, which is the
654    /// numbering `LinkADRReq` channel masks use.
655    ///
656    /// # Arguments
657    ///
658    /// * `channel` - the channel number.
659    ///
660    /// # Returns
661    ///
662    /// `Some(hz)`, or `None` if the plan defines no such channel by default.
663    pub fn channel_frequency_hz(&self, channel: u16) -> Option<u32> {
664        let mut remaining = channel;
665        for block in self.default_channels {
666            if remaining < block.count {
667                return block.frequency_hz(remaining);
668            }
669            remaining -= block.count;
670        }
671        None
672    }
673
674    /// Returns how many channels the plan defines by default.
675    ///
676    /// # Returns
677    ///
678    /// The channel count.
679    pub fn default_channel_count(&self) -> u16 {
680        self.default_channels
681            .iter()
682            .map(|block| block.count)
683            .sum::<u16>()
684    }
685}