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}