1use pamoja_lora::region::{ChannelPlan, MaskControl, PlanKind};
11
12use crate::cflist::{CfList, CfListKind};
13
14pub const MAX_CHANNELS: usize = 96;
16
17pub(crate) const MASK_GROUPS: usize = MAX_CHANNELS / 16;
19
20pub(crate) const DYNAMIC_MAX_CHANNELS: usize = 80;
23
24const CREATED_DATA_RATES: (u8, u8) = (0, 5);
27
28#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
30pub struct Channel {
31 pub uplink_hz: u32,
33 pub downlink_hz: u32,
37 pub min_data_rate: u8,
39 pub max_data_rate: u8,
41}
42
43impl Channel {
44 pub const fn new(hz: u32, min_data_rate: u8, max_data_rate: u8) -> Channel {
56 Channel {
57 uplink_hz: hz,
58 downlink_hz: hz,
59 min_data_rate,
60 max_data_rate,
61 }
62 }
63
64 pub const fn carries(&self, data_rate: u8) -> bool {
74 data_rate >= self.min_data_rate && data_rate <= self.max_data_rate
75 }
76}
77
78#[derive(Clone, Copy, Debug, PartialEq, Eq)]
80pub(crate) struct Channels {
81 slots: [Option<Channel>; MAX_CHANNELS],
82 enabled: [u16; MASK_GROUPS],
83 defaults: usize,
84}
85
86impl Channels {
87 pub(crate) fn defaults(plan: &ChannelPlan) -> Channels {
89 let mut channels = Channels {
90 slots: [None; MAX_CHANNELS],
91 enabled: [0; MASK_GROUPS],
92 defaults: 0,
93 };
94 let mut index = 0;
95 for block in plan.default_channels {
96 for offset in 0..block.count {
97 if index >= MAX_CHANNELS {
98 break;
99 }
100 if let Some(hz) = block.frequency_hz(offset) {
101 let mut channel = Channel::new(hz, block.min_data_rate, block.max_data_rate);
102 channel.downlink_hz = plan.rx1_frequency_hz(index as u16, hz).unwrap_or(hz);
103 channels.slots[index] = Some(channel);
104 channels.set_bit(index, true);
105 }
106 index += 1;
107 }
108 }
109 channels.defaults = index;
110 channels
111 }
112
113 pub(crate) const fn from_parts(
115 slots: [Option<Channel>; MAX_CHANNELS],
116 enabled: [u16; MASK_GROUPS],
117 defaults: usize,
118 ) -> Channels {
119 Channels {
120 slots,
121 enabled,
122 defaults,
123 }
124 }
125
126 pub(crate) const fn slots(&self) -> &[Option<Channel>; MAX_CHANNELS] {
128 &self.slots
129 }
130
131 pub(crate) const fn default_count(&self) -> usize {
133 self.defaults
134 }
135
136 pub(crate) fn get(&self, index: usize) -> Option<Channel> {
137 self.slots.get(index).copied().flatten()
138 }
139
140 pub(crate) fn is_enabled(&self, index: usize) -> bool {
141 index < MAX_CHANNELS && self.enabled[index / 16] & (1 << (index % 16)) != 0
142 }
143
144 pub(crate) fn enabled(&self) -> impl Iterator<Item = (usize, Channel)> + '_ {
146 (0..MAX_CHANNELS).filter_map(move |index| {
147 self.get(index)
148 .filter(|_| self.is_enabled(index))
149 .map(|channel| (index, channel))
150 })
151 }
152
153 pub(crate) const fn mask(&self) -> [u16; MASK_GROUPS] {
155 self.enabled
156 }
157
158 pub(crate) fn carries(&self, mask: &[u16; MASK_GROUPS], data_rate: u8) -> bool {
160 (0..MAX_CHANNELS).any(|index| {
161 mask[index / 16] & (1 << (index % 16)) != 0
162 && self
163 .get(index)
164 .is_some_and(|channel| channel.carries(data_rate))
165 })
166 }
167
168 pub(crate) fn enable_defaults(&mut self) {
175 for index in 0..self.defaults {
176 if self.get(index).is_some() {
177 self.set_bit(index, true);
178 }
179 }
180 }
181
182 pub(crate) fn apply_cflist(
194 &mut self,
195 plan: &ChannelPlan,
196 list: CfList,
197 usable: impl Fn(u32) -> bool,
198 ) {
199 if plan.kind == PlanKind::Fixed {
200 if let Some(groups) = list.channel_mask_groups() {
201 for index in 0..MAX_CHANNELS {
202 let on = groups[index / 16] & (1 << (index % 16)) != 0;
203 self.set_bit(index, on && self.get(index).is_some());
204 }
205 }
206 return;
207 }
208
209 for index in self.defaults..MAX_CHANNELS {
210 self.slots[index] = None;
211 self.set_bit(index, false);
212 }
213
214 let (low, high) = CREATED_DATA_RATES;
215 match list.kind() {
216 CfListKind::Frequencies => {
217 let Some(frequencies) = list.frequencies_hz() else {
218 return;
219 };
220 for (slot, hz) in frequencies.into_iter().enumerate() {
221 let index = self.defaults + slot;
222 if hz != 0 && index < DYNAMIC_MAX_CHANNELS && usable(hz) {
223 self.slots[index] = Some(Channel::new(hz, low, high));
224 self.set_bit(index, true);
225 }
226 }
227 }
228 CfListKind::ChannelMasks => {
229 let PlanKind::Dynamic {
230 channel_list: Some(numbering),
231 } = plan.kind
232 else {
233 return;
234 };
235 let mut index = self.defaults;
236 for number in list.enabled_channels() {
237 if index >= DYNAMIC_MAX_CHANNELS {
238 break;
239 }
240 if let Some(hz) = numbering.frequency_hz(number).filter(|hz| usable(*hz)) {
241 self.slots[index] = Some(Channel::new(hz, low, high));
242 self.set_bit(index, true);
243 index += 1;
244 }
245 }
246 }
247 CfListKind::Reserved(_) => {}
248 }
249 }
250
251 pub(crate) fn mask_after(
262 &self,
263 block: &[(u16, u8)],
264 controls: &[MaskControl; 8],
265 ) -> Option<[u16; MASK_GROUPS]> {
266 let mut mask = self.enabled;
267 for &(bits, control) in block {
268 match controls[usize::from(control & 0x07)] {
269 MaskControl::Group(group) => self.set_group(&mut mask, group, bits)?,
270 MaskControl::Banks => {
271 for bank in 0..10 {
272 self.switch_bank(&mut mask, bank, bits & (1 << bank) != 0);
273 }
274 }
275 MaskControl::PairedBanks => {
276 for bank in 0..8 {
277 let on = bits & (1 << bank) != 0;
278 self.switch_bank(&mut mask, bank, on);
279 self.switch(&mut mask, 64 + bank, on);
280 }
281 self.switch_bank(&mut mask, 9, bits & (1 << 9) != 0);
282 }
283 MaskControl::All { on, then_group } => {
284 for index in 0..MAX_CHANNELS {
285 self.switch(&mut mask, index, on);
286 }
287 if let Some(group) = then_group {
288 self.set_group(&mut mask, group, bits)?;
289 }
290 }
291 MaskControl::Reserved => return None,
292 }
293 }
294 if mask.iter().all(|group| *group == 0) {
295 return None;
296 }
297 Some(mask)
298 }
299
300 fn set_group(&self, mask: &mut [u16; MASK_GROUPS], group: u8, bits: u16) -> Option<()> {
302 let group = usize::from(group);
303 if group >= MASK_GROUPS {
304 return None;
305 }
306 for bit in 0..16 {
307 if bits & (1 << bit) != 0 && self.get(group * 16 + bit).is_none() {
308 return None;
309 }
310 }
311 mask[group] = bits;
312 Some(())
313 }
314
315 fn switch_bank(&self, mask: &mut [u16; MASK_GROUPS], bank: usize, on: bool) {
317 for index in bank * 8..bank * 8 + 8 {
318 self.switch(mask, index, on);
319 }
320 }
321
322 fn switch(&self, mask: &mut [u16; MASK_GROUPS], index: usize, on: bool) {
324 if index >= MAX_CHANNELS {
325 return;
326 }
327 let bit = 1 << (index % 16);
328 if on && self.get(index).is_some() {
329 mask[index / 16] |= bit;
330 } else {
331 mask[index / 16] &= !bit;
332 }
333 }
334
335 pub(crate) fn set_mask(&mut self, mask: [u16; MASK_GROUPS]) {
336 self.enabled = mask;
337 }
338
339 pub(crate) fn create(&mut self, index: usize, channel: Channel) {
341 if index < MAX_CHANNELS {
342 self.slots[index] = Some(channel);
343 self.set_bit(index, true);
344 }
345 }
346
347 pub(crate) fn remove(&mut self, index: usize) {
349 if index < MAX_CHANNELS {
350 self.slots[index] = None;
351 self.set_bit(index, false);
352 }
353 }
354
355 pub(crate) fn set_downlink(&mut self, index: usize, hz: u32) {
357 if let Some(Some(channel)) = self.slots.get_mut(index) {
358 channel.downlink_hz = hz;
359 }
360 }
361
362 fn set_bit(&mut self, index: usize, on: bool) {
363 let bit = 1 << (index % 16);
364 if on {
365 self.enabled[index / 16] |= bit;
366 } else {
367 self.enabled[index / 16] &= !bit;
368 }
369 }
370}
371
372#[cfg(test)]
373mod tests {
374 use super::*;
375 use pamoja_lora::region::{Cn470Plan, Region};
376
377 fn european() -> Channels {
378 Channels::defaults(Region::Eu868.plan())
379 }
380
381 fn american() -> Channels {
382 Channels::defaults(Region::Us915.plan())
383 }
384
385 fn enabled_numbers(channels: &Channels) -> Vec<usize> {
386 channels.enabled().map(|(index, _)| index).collect()
387 }
388
389 fn numbers(mask: [u16; MASK_GROUPS]) -> Vec<usize> {
390 (0..MAX_CHANNELS)
391 .filter(|index| mask[index / 16] & (1 << (index % 16)) != 0)
392 .collect()
393 }
394
395 #[test]
396 fn a_european_device_starts_on_its_three_default_channels() {
397 let channels = european();
398 let enabled: Vec<u32> = channels.enabled().map(|(_, c)| c.uplink_hz).collect();
399 assert_eq!(enabled, [868_100_000, 868_300_000, 868_500_000]);
400 assert_eq!(channels.default_count(), 3);
401 assert_eq!(
402 channels.get(0).map(|c| (c.min_data_rate, c.max_data_rate)),
403 Some((0, 5))
404 );
405 assert_eq!(channels.get(2).map(|c| c.downlink_hz), Some(868_500_000));
406 }
407
408 #[test]
409 fn an_american_device_starts_on_all_seventy_two_answered_on_eight() {
410 let channels = american();
412 assert_eq!(channels.enabled().count(), 72);
413 assert_eq!(channels.default_count(), 72);
414 let channel = |index| channels.get(index).expect("defined");
415 assert_eq!(
416 (channel(0).uplink_hz, channel(0).downlink_hz),
417 (902_300_000, 923_300_000)
418 );
419 assert_eq!(
420 (channel(63).uplink_hz, channel(63).downlink_hz),
421 (914_900_000, 927_500_000)
422 );
423 assert_eq!(
424 (channel(64).uplink_hz, channel(64).downlink_hz),
425 (903_000_000, 923_300_000)
426 );
427 assert_eq!(
428 (channel(64).min_data_rate, channel(64).max_data_rate),
429 (4, 4)
430 );
431 assert_eq!(channels.get(72), None);
432 }
433
434 #[test]
435 fn the_ninety_six_channel_plan_fills_the_table() {
436 let channels = Channels::defaults(Cn470Plan::Channels96.plan());
437 assert_eq!(channels.enabled().count(), 96);
438 assert_eq!(
439 channels.get(49).map(|c| c.downlink_hz),
440 Some(500_500_000),
441 "channel 49 is answered on downlink channel 1"
442 );
443 }
444
445 #[test]
446 fn a_frequency_list_defines_the_five_channels_after_the_defaults() {
447 let mut channels = european();
448 let list = CfList::frequencies([867_100_000, 867_300_000, 0, 867_700_000, 867_900_000])
449 .expect("valid");
450 channels.apply_cflist(Region::Eu868.plan(), list, |_| true);
451
452 let enabled: Vec<(usize, u32)> =
453 channels.enabled().map(|(i, c)| (i, c.uplink_hz)).collect();
454 assert_eq!(
455 enabled,
456 [
457 (0, 868_100_000),
458 (1, 868_300_000),
459 (2, 868_500_000),
460 (3, 867_100_000),
461 (4, 867_300_000),
462 (6, 867_700_000),
463 (7, 867_900_000),
464 ],
465 "an unused slot leaves its channel undefined"
466 );
467 assert_eq!(
468 channels.get(3).map(|c| (c.min_data_rate, c.max_data_rate)),
469 Some((0, 5))
470 );
471 }
472
473 #[test]
474 fn a_mask_list_creates_channels_in_order_of_their_numbers() {
475 let mut channels = european();
478 let list = CfList::channel_masks([0x0001, 0x10A0, 0, 0, 0, 0]);
479 channels.apply_cflist(Region::Eu868.plan(), list, |_| true);
480
481 let created: Vec<(usize, u32)> = channels
482 .enabled()
483 .skip(3)
484 .map(|(i, c)| (i, c.uplink_hz))
485 .collect();
486 assert_eq!(
487 created,
488 [
489 (3, 863_100_000),
490 (4, 867_300_000),
491 (5, 867_700_000),
492 (6, 868_700_000)
493 ]
494 );
495 }
496
497 #[test]
498 fn a_fixed_plan_takes_a_mask_list_as_which_channels_are_on() {
499 let mut channels = american();
502 let list = CfList::channel_masks([0xFF00, 0, 0, 0, 0x0102, 0]);
503 channels.apply_cflist(Region::Us915.plan(), list, |_| true);
504 assert_eq!(
505 enabled_numbers(&channels),
506 [8, 9, 10, 11, 12, 13, 14, 15, 65]
507 );
508
509 let mut channels = american();
510 let frequencies = CfList::frequencies([903_100_000, 0, 0, 0, 0]).expect("valid");
511 channels.apply_cflist(Region::Us915.plan(), frequencies, |_| true);
512 assert_eq!(
513 channels.enabled().count(),
514 72,
515 "a fixed plan ignores a list of frequencies"
516 );
517 }
518
519 #[test]
520 fn a_region_with_no_numbering_ignores_a_mask_list() {
521 let plan = Region::Eu433.plan();
522 let mut channels = Channels::defaults(plan);
523 channels.apply_cflist(plan, CfList::channel_masks([0xFFFF, 0, 0, 0, 0, 0]), |_| {
524 true
525 });
526 assert_eq!(channels.enabled().count(), 3);
527 }
528
529 #[test]
530 fn a_frequency_the_radio_cannot_use_is_left_out() {
531 let mut channels = european();
532 let list = CfList::frequencies([867_100_000, 902_300_000, 0, 0, 0]).expect("valid");
533 channels.apply_cflist(Region::Eu868.plan(), list, |hz| hz < 870_000_000);
534 assert!(channels.get(3).is_some());
535 assert!(channels.get(4).is_none());
536 }
537
538 #[test]
539 fn channel_controls_set_groups_banks_and_everything() {
540 let mut channels = european();
541 channels.apply_cflist(
542 Region::Eu868.plan(),
543 CfList::frequencies([
544 867_100_000,
545 867_300_000,
546 867_500_000,
547 867_700_000,
548 867_900_000,
549 ])
550 .expect("valid"),
551 |_| true,
552 );
553 let controls = &MaskControl::DYNAMIC;
554
555 assert_eq!(
556 channels.mask_after(&[(0b0000_0101, 0)], controls),
557 Some([0b0101, 0, 0, 0, 0, 0])
558 );
559 assert_eq!(
560 channels.mask_after(&[(0, 5)], controls),
561 None,
562 "switching every bank off leaves nothing enabled"
563 );
564 assert_eq!(
565 channels.mask_after(&[(1, 5)], controls),
566 Some([0xFF, 0, 0, 0, 0, 0])
567 );
568 assert_eq!(
569 channels.mask_after(&[(0b0001, 0), (0xFFFF, 6)], controls),
570 Some([0xFF, 0, 0, 0, 0, 0]),
571 "6 enables every defined channel, whatever came before it in the block"
572 );
573 assert_eq!(
574 channels.mask_after(&[(0xFFFF, 7)], controls),
575 None,
576 "7 is reserved"
577 );
578 assert_eq!(
579 channels.mask_after(&[(1 << 9, 0)], controls),
580 None,
581 "a bit for an undefined channel refuses the block"
582 );
583 assert_eq!(
584 channels.mask_after(&[(0, 0), (0b10, 0)], controls),
585 Some([0b10, 0, 0, 0, 0, 0]),
586 "the block is taken as a whole, so an empty group partway is fine"
587 );
588 }
589
590 #[test]
591 fn the_american_controls_narrow_a_device_to_one_sub_band() {
592 let channels = american();
595 let controls = &Region::Us915.plan().mask_controls;
596
597 let two = channels
598 .mask_after(&[(0x0000, 7), (0x00FF, 0)], controls)
599 .expect("accepted");
600 assert_eq!(numbers(two), [0, 1, 2, 3, 4, 5, 6, 7]);
601
602 let one = channels
603 .mask_after(&[(0x0001, 5)], controls)
604 .expect("accepted");
605 assert_eq!(numbers(one), [0, 1, 2, 3, 4, 5, 6, 7, 64]);
606
607 let second = channels
608 .mask_after(&[(0x0002, 5)], controls)
609 .expect("accepted");
610 assert_eq!(
611 numbers(second),
612 [8, 9, 10, 11, 12, 13, 14, 15, 65],
613 "the second sub-band that many networks run"
614 );
615
616 let wide_only = channels
617 .mask_after(&[(0x00F0, 7)], controls)
618 .expect("accepted");
619 assert_eq!(numbers(wide_only), [68, 69, 70, 71]);
620
621 let everything = channels
622 .mask_after(&[(0x0000, 7), (0x00FF, 6)], controls)
623 .expect("accepted");
624 assert_eq!(numbers(everything).len(), 72);
625
626 assert_eq!(
627 channels.mask_after(&[(0x0100, 6)], controls),
628 None,
629 "channel 72 is not defined"
630 );
631 assert_eq!(
632 channels.mask_after(&[(0x0000, 7)], controls),
633 None,
634 "7 with an empty mask leaves nothing on"
635 );
636 }
637
638 #[test]
639 fn the_chinese_antenna_plans_switch_everything_and_reserve_the_rest() {
640 let twenty = Cn470Plan::Antenna20MhzA.plan();
642 let channels = Channels::defaults(twenty);
643 let controls = &twenty.mask_controls;
644 assert_eq!(
645 channels.mask_after(&[(0, 7), (0x000F, 3)], controls),
646 Some([0, 0, 0, 0x000F, 0, 0])
647 );
648 assert_eq!(channels.mask_after(&[(0xFFFF, 4)], controls), None);
649 assert_eq!(channels.mask_after(&[(0, 7)], controls), None);
650
651 let twenty_six = Cn470Plan::Antenna26MhzB.plan();
652 let channels = Channels::defaults(twenty_six);
653 let controls = &twenty_six.mask_controls;
654 assert_eq!(
655 channels.mask_after(&[(0, 4), (0x8000, 2)], controls),
656 Some([0, 0, 0x8000, 0, 0, 0])
657 );
658 assert_eq!(
659 channels.mask_after(&[(0x0001, 3)], controls),
660 Some([0xFFFF, 0xFFFF, 0xFFFF, 0, 0, 0])
661 );
662 assert_eq!(
663 channels.mask_after(&[(0x0001, 3), (0x0001, 3)], controls),
664 Some([0xFFFF, 0xFFFF, 0xFFFF, 0, 0, 0])
665 );
666 assert_eq!(channels.mask_after(&[(0x0001, 5)], controls), None);
667 assert_eq!(
668 channels.mask_after(&[(0x0001, 2), (0x0001, 3)], controls),
669 Some([0xFFFF, 0xFFFF, 0xFFFF, 0, 0, 0])
670 );
671 }
672
673 #[test]
674 fn the_ninety_six_channel_plan_reaches_its_sixth_group() {
675 let plan = Cn470Plan::Channels96.plan();
676 let channels = Channels::defaults(plan);
677 assert_eq!(
678 channels.mask_after(
679 &[(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (0x8000, 5)],
680 &plan.mask_controls
681 ),
682 Some([0, 0, 0, 0, 0, 0x8000])
683 );
684 }
685
686 #[test]
687 fn restoring_defaults_leaves_created_channels_as_they_were() {
688 let mut channels = european();
689 channels.create(3, Channel::new(867_100_000, 0, 5));
690 channels.set_mask([0b1000, 0, 0, 0, 0, 0]);
691 channels.enable_defaults();
692 assert_eq!(channels.mask(), [0b1111, 0, 0, 0, 0, 0]);
693
694 let mut channels = american();
695 channels.set_mask([0, 0x0100, 0, 0, 0, 0]);
696 channels.enable_defaults();
697 assert_eq!(
698 channels.enabled().count(),
699 72,
700 "a fixed plan turns every channel back on"
701 );
702 }
703
704 #[test]
705 fn a_channel_carries_the_rates_in_its_range() {
706 let channel = Channel::new(868_100_000, 0, 5);
707 assert!(channel.carries(0) && channel.carries(5));
708 assert!(!channel.carries(6));
709 let channels = european();
710 assert!(channels.carries(&channels.mask(), 5));
711 assert!(!channels.carries(&channels.mask(), 6));
712 }
713}