1use colored::Colorize;
13use rfham_core::{
14 agency::Agency,
15 country::CountryCode,
16 error::CoreError,
17 frequency::{Frequency, FrequencyRange},
18 power::Power,
19};
20use rfham_itu::{allocations::FrequencyAllocation, regions::Region};
21use rfham_markdown::{
22 Table, ToMarkdown, ToMarkdownWith, blank_line, bulleted_list, bulleted_list_item, header,
23 link_to_string, numbered_list_item, plain_text,
24};
25use serde::{Deserialize, Serialize};
26use serde_with::{DeserializeFromStr, SerializeDisplay};
27use std::{cmp::Ordering, collections::HashMap, fmt::Display, str::FromStr};
28
29pub type DisplayName = String;
38pub type LicenseKey = String;
39
40#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
41pub struct BandPlan {
42 name: DisplayName,
43 maintainer: Agency,
44 #[serde(skip_serializing_if = "Option::is_none")]
45 regulator: Option<Agency>,
46 region: Region,
47 #[serde(skip_serializing_if = "Vec::is_empty")]
48 countries: Vec<CountryCode>,
49 #[serde(skip_serializing_if = "HashMap::is_empty")]
50 licenses: HashMap<LicenseKey, LicenseClass>,
51 #[serde(skip_serializing_if = "HashMap::is_empty")]
52 bands: HashMap<FrequencyAllocation, PlanBand>,
53 #[serde(skip_serializing_if = "Option::is_none")]
54 default_max_power: Option<Power>,
55 #[serde(skip_serializing_if = "Vec::is_empty")]
56 notes: Vec<String>,
57}
58
59#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
60pub struct Band {
61 allocation: FrequencyAllocation,
62 #[serde(flatten)]
63 range: FrequencyRange,
64}
65
66#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
67pub struct PlanBand {
68 band: Band,
69 is_primary_user: bool,
70 #[serde(skip_serializing_if = "BandRestrictions::is_unrestricted")]
71 restrictions: BandRestrictions,
72 #[serde(skip_serializing_if = "Vec::is_empty")]
73 segments: Vec<Segment>,
74 #[serde(skip_serializing_if = "Vec::is_empty")]
75 calling_frequencies: Vec<CallingFrequency>,
76 #[serde(skip_serializing_if = "Vec::is_empty")]
77 notes: Vec<String>,
78}
79
80#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
81pub struct Segment {
82 #[serde(flatten)]
83 range: FrequencyRange,
84 #[serde(skip_serializing_if = "BandRestrictions::is_unrestricted")]
85 restrictions: BandRestrictions,
86 #[serde(skip_serializing_if = "Vec::is_empty")]
87 notes: Vec<String>,
88}
89
90#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
91pub struct BandRestrictions {
92 #[serde(skip_serializing_if = "Vec::is_empty")]
93 license_restrictions: Vec<LicenseKey>,
94 #[serde(skip_serializing_if = "Vec::is_empty")]
95 usage_restrictions: Vec<UsageRestriction>,
96 #[serde(skip_serializing_if = "Option::is_none")]
97 power_restriction: Option<PowerRestriction>,
98 #[serde(skip_serializing_if = "Option::is_none")]
99 bandwidth_restriction: Option<BandwidthRestriction>,
100}
101
102#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
103pub struct CallingFrequency {
104 frequency: Frequency,
105 name: Option<DisplayName>,
106 #[serde(skip_serializing_if = "Vec::is_empty")]
107 usage_restrictions: Vec<UsageRestriction>,
108}
109
110#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
111pub struct LicenseClass {
112 ordinal: u32,
113 name: DisplayName,
114 is_active: bool,
115}
116
117#[derive(Clone, Copy, Debug, PartialEq, DeserializeFromStr, SerializeDisplay)]
118pub enum UsageRestriction {
119 Beacon,
120 CW,
121 Data,
122 Digital,
123 Dx,
124 EarthMoonEarth,
125 Phone,
126 Qrp,
127 RemoteControl,
128 Repeater(RepeaterUsage),
129 Rtty,
130 Satellite(SatelliteUsage),
131 Simplex,
132 SlowScanTv,
133 Test(RepeaterUsage),
134}
135
136#[derive(Clone, Copy, Debug, PartialEq, DeserializeFromStr, SerializeDisplay)]
137pub enum PhoneMode {
138 AmplitudeModulated,
139 FrequencyModulated,
140 SingleSideband,
141 SingleSidebandLower,
142 SingleSidebandUpper,
143}
144
145#[derive(Clone, Copy, Debug, Default, PartialEq, DeserializeFromStr, SerializeDisplay)]
146pub enum PowerMeasure {
147 #[default]
148 PeakEnvelopePower,
149 EffectiveRadiatedPower,
150 EffectiveIsotropicRadiatedPower,
151}
152
153#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
154pub struct PowerRestriction {
155 max_power: Power,
156 measure: PowerMeasure,
157}
158
159#[derive(Clone, Copy, Debug, PartialEq, DeserializeFromStr, SerializeDisplay)]
160pub enum RepeaterUsage {
161 Input,
162 Output,
163}
164
165#[derive(Clone, Copy, Debug, PartialEq, DeserializeFromStr, SerializeDisplay)]
166pub enum SatelliteUsage {
167 Downlink,
168 Uplink,
169}
170
171#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
172pub struct BandwidthRestriction {
173 max_bandwidth: Frequency,
174}
175
176const START_COL_WIDTH: usize = 10;
189const END_COL_WIDTH: usize = 10;
190const CLASS_COL_WIDTH: usize = 14;
191const USAGE_COL_WIDTH: usize = 20;
192const POWER_COL_WIDTH: usize = 14;
193const BANDWIDTH_COL_WIDTH: usize = 14;
194const CLASS_OR_USAGE_DEFAULT: &str = "any";
195const POWER_DEFAULT: &str = "full power";
196const BANDWIDTH_DEFAULT: &str = "-";
197
198impl ToMarkdownWith for BandPlan {
203 type Context = Vec<FrequencyAllocation>;
204
205 fn write_markdown_with<W: std::io::Write>(
206 &self,
207 writer: &mut W,
208 context: Self::Context,
209 ) -> Result<(), CoreError> {
210 header(writer, 1, &self.name)?;
211 blank_line(writer)?;
212
213 if let Some(regulator) = self.regulator.as_ref() {
214 bulleted_list_item(
215 writer,
216 1,
217 &if let Some(url) = ®ulator.url() {
218 format!(
219 "Regulatory Agency: {}",
220 link_to_string(regulator.to_string(), url)
221 )
222 } else {
223 format!("Regulatory Agency: {}", regulator)
224 },
225 )?;
226 }
227 bulleted_list_item(
228 writer,
229 1,
230 &if let Some(url) = &self.maintainer.url() {
231 format!(
232 "Maintaining Agency: {}",
233 link_to_string(self.maintainer.to_string(), url)
234 )
235 } else {
236 format!("Maintaining Agency: {}", self.maintainer)
237 },
238 )?;
239 bulleted_list_item(writer, 1, format!("Region: {:#}", self.region))?;
240 if !self.countries.is_empty() {
241 bulleted_list_item(
242 writer,
243 1,
244 format!(
245 "Countries: {}",
246 self.countries
247 .iter()
248 .map(|c| c.as_str())
249 .collect::<Vec<_>>()
250 .join(", ")
251 ),
252 )?;
253 }
254 if let Some(default_max_power) = &self.default_max_power {
255 bulleted_list_item(
256 writer,
257 1,
258 format!("Default maximum power: {default_max_power}"),
259 )?;
260 }
261 blank_line(writer)?;
262
263 if !self.notes.is_empty() {
264 plain_text(writer, "Notes:")?;
265 blank_line(writer)?;
266 bulleted_list(writer, 1, &self.notes)?;
267 blank_line(writer)?;
268 }
269
270 header(writer, 2, "License Classes")?;
271 blank_line(writer)?;
272
273 let mut ordered_licenses = self.licenses.iter().collect::<Vec<_>>();
274 ordered_licenses.sort_by(|a, b| a.1.ordinal.cmp(&b.1.ordinal));
275
276 for (number, (key, license)) in ordered_licenses.iter().enumerate() {
277 numbered_list_item(
278 writer,
279 1,
280 number,
281 format!(
282 "{} ({}){}",
283 license.name,
284 key,
285 if !license.is_active {
286 format!(" {}", "inactive class".italic())
287 } else {
288 String::default()
289 }
290 ),
291 )?;
292 }
293 blank_line(writer)?;
294
295 let mut ordered_bands = self.bands.values().collect::<Vec<_>>();
296 ordered_bands.sort_by(|a, b| {
297 a.band
298 .range
299 .start()
300 .value()
301 .partial_cmp(&b.band.range.start().value())
302 .unwrap_or(Ordering::Equal)
303 });
304
305 for band in ordered_bands {
306 if context.is_empty() || context.contains(&band.band.allocation) {
307 band.write_markdown(writer)?;
308 }
309 }
310
311 Ok(())
312 }
313}
314
315impl BandPlan {
316 #[allow(clippy::too_many_arguments)]
318 const fn inner_new(
319 maintaining_agency: Agency,
320 name: String,
321 region: Region,
322 countries: Vec<CountryCode>,
323 licenses: HashMap<String, LicenseClass>,
324 bands: HashMap<FrequencyAllocation, PlanBand>,
325 default_max_power: Option<Power>,
326 notes: Vec<String>,
327 ) -> Self {
328 Self {
329 maintainer: maintaining_agency,
330 regulator: None,
331 name,
332 region,
333 countries,
334 licenses,
335 bands,
336 default_max_power,
337 notes,
338 }
339 }
340
341 pub fn new<S: Into<String>>(maintainer: Agency, region: Region, name: S) -> Self {
342 Self::inner_new(
343 maintainer,
344 name.into(),
345 region,
346 Vec::default(),
347 HashMap::default(),
348 HashMap::default(),
349 Option::default(),
350 Vec::default(),
351 )
352 }
353
354 pub fn with_regulator(mut self, regulator: Agency) -> Self {
355 self.regulator = Some(regulator);
356 self
357 }
358
359 pub fn in_country(mut self, country: CountryCode) -> Self {
360 self.countries.push(country);
361 self
362 }
363
364 pub fn in_countries(mut self, countries: &[CountryCode]) -> Self {
365 self.countries.extend(countries.to_vec());
366 self
367 }
368
369 pub fn with_licenses(mut self, licenses: HashMap<LicenseKey, LicenseClass>) -> Self {
370 self.licenses = licenses;
371 self
372 }
373
374 pub fn with_license_list(mut self, licenses: Vec<(DisplayName, bool)>) -> Self {
375 let licenses: HashMap<LicenseKey, LicenseClass> = licenses
376 .into_iter()
377 .enumerate()
378 .map(|(ordinal, (name, is_active))| {
379 (
380 name.clone(),
381 LicenseClass::new(ordinal as u32, &name, is_active),
382 )
383 })
384 .collect();
385 self.licenses = licenses;
386 self
387 }
388
389 pub fn with_bands(mut self, bands: HashMap<FrequencyAllocation, PlanBand>) -> Self {
390 self.bands = bands;
391 self
392 }
393
394 pub fn with_bands_list(self, bands: Vec<PlanBand>) -> Self {
395 self.with_bands(
396 bands
397 .into_iter()
398 .map(|band| (band.band.allocation, band))
399 .collect(),
400 )
401 }
402
403 pub fn with_default_max_power(mut self, default_max_power: Power) -> Self {
404 self.default_max_power = Some(default_max_power);
405 self
406 }
407
408 pub fn with_notes(mut self, notes: Vec<String>) -> Self {
409 self.notes = notes;
410 self
411 }
412
413 pub fn name(&self) -> &str {
414 self.name.as_str()
415 }
416
417 pub fn band(&self, itu: &FrequencyAllocation) -> Option<&PlanBand> {
418 self.bands.get(itu)
419 }
420
421 pub fn maintaining_agency(&self) -> &Agency {
422 &self.maintainer
423 }
424}
425
426impl Display for Band {
429 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
430 write!(f, "{} ({})", self.allocation, self.range)
431 }
432}
433
434impl Band {
435 pub fn new_default(allocation: FrequencyAllocation, region: Region) -> Self {
436 let range = allocation
437 .range(region)
438 .expect("Could not create band from allocation missing in {region:#}.");
439 Self::new(allocation, range.start(), range.end())
440 }
441
442 pub fn new(allocation: FrequencyAllocation, start: Frequency, end: Frequency) -> Self {
443 assert!(start < end);
444 Self {
445 allocation,
446 range: FrequencyRange::new(start, end),
447 }
448 }
449
450 pub fn allocation(&self) -> &FrequencyAllocation {
451 &self.allocation
452 }
453
454 pub fn range(&self) -> &FrequencyRange {
455 &self.range
456 }
457}
458
459impl ToMarkdown for PlanBand {
462 fn write_markdown<W: std::io::Write>(&self, writer: &mut W) -> Result<(), CoreError> {
463 let actual = &self.band;
464 header(
465 writer,
466 2,
467 format!("{} Band ({})", actual.allocation, actual.range),
468 )?;
469 blank_line(writer)?;
470
471 plain_text(
472 writer,
473 format!(
474 "Note: amateur operators are the {} users on this band.",
475 if self.is_primary_user {
476 "primary"
477 } else {
478 "secondary"
479 }
480 .bold()
481 ),
482 )?;
483 blank_line(writer)?;
484
485 if !self.calling_frequencies.is_empty() {
486 plain_text(writer, "Calling Frequencies:")?;
487 blank_line(writer)?;
488
489 let mut ordered_calling = self.calling_frequencies.iter().collect::<Vec<_>>();
490 ordered_calling.sort_by(|a, b| {
491 a.frequency
492 .value()
493 .partial_cmp(&b.frequency.value())
494 .unwrap_or(Ordering::Equal)
495 });
496
497 for (i, calling) in ordered_calling.iter().enumerate() {
498 numbered_list_item(
499 writer,
500 1,
501 i,
502 format!(
503 "{}{}{}",
504 calling.frequency,
505 if let Some(label) = &calling.name {
506 format!(" {label}")
507 } else {
508 String::default()
509 },
510 if !calling.usage_restrictions.is_empty() {
511 format!(
512 " ({})",
513 calling
514 .usage_restrictions
515 .iter()
516 .map(|r| r.to_string())
517 .collect::<Vec<_>>()
518 .join(", ")
519 )
520 } else {
521 String::default()
522 }
523 ),
524 )?;
525 }
526 }
527
528 if !self.restrictions.is_unrestricted() || !self.segments.is_empty() {
529 plain_text(writer, "Band restrictions:")?;
530 blank_line(writer)?;
531
532 let table = Table::new(vec![
533 ("Start", START_COL_WIDTH).into(),
534 ("End", END_COL_WIDTH).into(),
535 ("License Class", CLASS_COL_WIDTH).into(),
536 ("Usage/Mode", USAGE_COL_WIDTH).into(),
537 ("Power", POWER_COL_WIDTH).into(),
538 ("Max Bandwidth", BANDWIDTH_COL_WIDTH).into(),
539 ]);
540 table.headers(writer)?;
541
542 if !self.restrictions.is_unrestricted() {
543 self.restrictions
544 .write_markdown_with(writer, (actual.range.clone(), table.clone()))?;
545 }
546
547 if !self.segments.is_empty() {
548 let mut ordered_segments = self.segments.iter().collect::<Vec<_>>();
549 ordered_segments.sort_by(|a, b| {
550 a.range
551 .start()
552 .value()
553 .partial_cmp(&b.range.start().value())
554 .unwrap_or(Ordering::Equal)
555 });
556
557 for segment in ordered_segments {
558 segment.write_markdown_with(writer, (segment.range.clone(), table.clone()))?;
559 }
560 blank_line(writer)?;
561 }
562 }
563
564 if !self.notes.is_empty() {
565 plain_text(writer, "Notes:")?;
566 blank_line(writer)?;
567
568 bulleted_list(writer, 1, &self.notes)?;
569 }
570 blank_line(writer)?;
571 Ok(())
572 }
573}
574
575impl PlanBand {
576 fn inner_new(
577 band: Band,
578 primary_user: bool,
579 restrictions: BandRestrictions,
580 segments: Vec<Segment>,
581 calling_frequencies: Vec<CallingFrequency>,
582 notes: Vec<String>,
583 ) -> Self {
584 Self {
585 band,
586 is_primary_user: primary_user,
587 restrictions,
588 segments,
589 calling_frequencies,
590 notes,
591 }
592 }
593 pub fn new(band: Band) -> Self {
594 Self::inner_new(
595 band,
596 true,
597 BandRestrictions::none(),
598 Vec::default(),
599 Vec::default(),
600 Vec::default(),
601 )
602 }
603 pub fn with_is_primary_user(mut self, is_primary_user: bool) -> Self {
604 self.is_primary_user = is_primary_user;
605 self
606 }
607 pub fn with_restrictions(mut self, restrictions: BandRestrictions) -> Self {
608 self.restrictions = restrictions;
609 self
610 }
611 pub fn with_segments(mut self, segments: Vec<Segment>) -> Self {
612 self.segments = segments;
613 self
614 }
615 pub fn with_calling_frequencies(mut self, calling_frequencies: Vec<CallingFrequency>) -> Self {
616 self.calling_frequencies = calling_frequencies;
617 self
618 }
619 pub fn with_notes(mut self, notes: Vec<String>) -> Self {
620 self.notes = notes;
621 self
622 }
623
624 pub fn band(&self) -> &Band {
625 &self.band
626 }
627}
628
629impl ToMarkdownWith for Segment {
632 type Context = (FrequencyRange, Table);
633
634 fn write_markdown_with<W: std::io::Write>(
635 &self,
636 writer: &mut W,
637 context: Self::Context,
638 ) -> Result<(), CoreError> {
639 let (range, table) = context;
640 if self.restrictions.is_unrestricted() {
641 table.data_row(
642 writer,
643 &[
644 range.start().to_string(),
645 range.end().to_string(),
646 CLASS_OR_USAGE_DEFAULT.italic().to_string(),
647 CLASS_OR_USAGE_DEFAULT.italic().to_string(),
648 POWER_DEFAULT.italic().to_string(),
649 BANDWIDTH_DEFAULT.to_string(),
650 ],
651 )?;
652 } else {
653 table.data_row(
654 writer,
655 &[
656 range.start().to_string(),
657 range.end().to_string(),
658 if self.restrictions.license_restrictions.is_empty() {
659 CLASS_OR_USAGE_DEFAULT.to_string()
660 } else {
661 #[allow(clippy::iter_cloned_collect)] self.restrictions
663 .license_restrictions
664 .iter()
665 .cloned()
666 .collect::<Vec<_>>()
667 .join(", ")
668 },
669 if self.restrictions.usage_restrictions.is_empty() {
670 CLASS_OR_USAGE_DEFAULT.to_string()
671 } else if self.restrictions.usage_restrictions.len() == 1 {
672 format!(
673 "{} only",
674 self.restrictions.usage_restrictions.first().unwrap()
675 )
676 } else {
677 #[allow(clippy::iter_cloned_collect)] self.restrictions
679 .usage_restrictions
680 .iter()
681 .map(|r| r.to_string())
682 .collect::<Vec<_>>()
683 .join(", ")
684 },
685 if let Some(power_restriction) = self.restrictions.power_restriction() {
686 power_restriction.to_string()
687 } else {
688 POWER_DEFAULT.to_string()
689 },
690 if let Some(bandwidth_restriction) = self.restrictions.bandwidth_restriction() {
691 bandwidth_restriction.to_string()
692 } else {
693 BANDWIDTH_DEFAULT.to_string()
694 },
695 ],
696 )?;
697 }
698 Ok(())
699 }
700}
701
702impl Segment {
703 pub fn new(frequency_start: Frequency, frequency_end: Frequency) -> Self {
704 Self {
705 range: FrequencyRange::new(frequency_start, frequency_end),
706 restrictions: Default::default(),
707 notes: Vec::default(),
708 }
709 }
710 pub fn with_restrictions(mut self, restrictions: BandRestrictions) -> Self {
711 self.restrictions = restrictions;
712 self
713 }
714 pub fn with_notes(mut self, notes: Vec<String>) -> Self {
715 self.notes = notes;
716 self
717 }
718}
719
720impl ToMarkdownWith for BandRestrictions {
723 type Context = (FrequencyRange, Table);
724
725 fn write_markdown_with<W: std::io::Write>(
726 &self,
727 writer: &mut W,
728 context: Self::Context,
729 ) -> Result<(), CoreError> {
730 let (range, table) = context;
731 table.data_row(
732 writer,
733 &[
734 range.start().to_string(),
735 range.end().to_string(),
736 if self.license_restrictions.is_empty() {
737 CLASS_OR_USAGE_DEFAULT.to_string()
738 } else {
739 #[allow(clippy::iter_cloned_collect)] self.license_restrictions
741 .iter()
742 .cloned()
743 .collect::<Vec<_>>()
744 .join(", ")
745 },
746 if self.usage_restrictions.is_empty() {
747 CLASS_OR_USAGE_DEFAULT.to_string()
748 } else if self.usage_restrictions.len() == 1 {
749 format!("{} only", self.usage_restrictions.first().unwrap())
750 } else {
751 #[allow(clippy::iter_cloned_collect)] self.usage_restrictions
753 .iter()
754 .map(|r| r.to_string())
755 .collect::<Vec<_>>()
756 .join(", ")
757 },
758 if let Some(power_restriction) = self.power_restriction {
759 power_restriction.to_string()
760 } else {
761 POWER_DEFAULT.to_string()
762 },
763 if let Some(bandwidth_restriction) = self.bandwidth_restriction() {
764 bandwidth_restriction.to_string()
765 } else {
766 BANDWIDTH_DEFAULT.to_string()
767 },
768 ],
769 )?;
770 blank_line(writer)?;
771 Ok(())
772 }
773}
774
775impl BandRestrictions {
776 pub fn none() -> Self {
777 Self::default()
778 }
779 pub fn with_license_restrictions(mut self, license_restrictions: Vec<LicenseKey>) -> Self {
780 self.license_restrictions = license_restrictions;
781 self
782 }
783 pub fn with_usage_restrictions(mut self, usage_restrictions: Vec<UsageRestriction>) -> Self {
784 self.usage_restrictions = usage_restrictions;
785 self
786 }
787 pub fn with_power_restriction(mut self, power_restriction: PowerRestriction) -> Self {
788 self.power_restriction = Some(power_restriction);
789 self
790 }
791 pub fn with_bandwidth_restriction(mut self, max_bandwidth: BandwidthRestriction) -> Self {
792 self.bandwidth_restriction = Some(max_bandwidth);
793 self
794 }
795 pub fn is_unrestricted(&self) -> bool {
796 self.license_restrictions.is_empty()
797 && self.usage_restrictions.is_empty()
798 && self.power_restriction.is_none()
799 }
800 pub fn license_restrictions(&self) -> impl Iterator<Item = &LicenseKey> {
801 self.license_restrictions.iter()
802 }
803 pub fn usage_restrictions(&self) -> impl Iterator<Item = &UsageRestriction> {
804 self.usage_restrictions.iter()
805 }
806 pub fn power_restriction(&self) -> Option<&PowerRestriction> {
807 self.power_restriction.as_ref()
808 }
809 pub fn bandwidth_restriction(&self) -> Option<&BandwidthRestriction> {
810 self.bandwidth_restriction.as_ref()
811 }
812}
813
814impl Display for CallingFrequency {
817 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
818 write!(
819 f,
820 "{:.3}: Calling{}",
821 self.frequency,
822 if self.usage_restrictions.is_empty() {
823 String::default()
824 } else {
825 self.usage_restrictions
826 .iter()
827 .map(|v| v.to_string())
828 .collect::<Vec<_>>()
829 .join(" ")
830 }
831 )
832 }
833}
834
835impl CallingFrequency {
836 pub const fn new(frequency: Frequency, usage_restrictions: Vec<UsageRestriction>) -> Self {
837 Self {
838 frequency,
839 name: None,
840 usage_restrictions,
841 }
842 }
843 pub const fn unrestricted(frequency: Frequency) -> Self {
844 Self {
845 frequency,
846 name: None,
847 usage_restrictions: Vec::new(),
848 }
849 }
850 pub fn with_usage_restrictions(mut self, usage_restrictions: Vec<UsageRestriction>) -> Self {
851 self.usage_restrictions = usage_restrictions;
852 self
853 }
854 pub fn with_label<S: Into<String>>(mut self, label: S) -> Self {
855 self.name = Some(label.into());
856 self
857 }
858 pub const fn frequency(&self) -> Frequency {
859 self.frequency
860 }
861 pub fn usage_restrictions(&self) -> impl Iterator<Item = &UsageRestriction> {
862 self.usage_restrictions.iter()
863 }
864}
865
866impl Display for LicenseClass {
869 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
870 write!(
871 f,
872 "{}",
873 if f.alternate() {
874 format!(
875 "{:02}: {} ({})",
876 self.ordinal,
877 self.name,
878 if self.is_active { "active" } else { "inactive" }
879 )
880 } else {
881 format!(
882 "{}{}",
883 self.name,
884 if self.is_active { "" } else { " (inactive)" }
885 )
886 }
887 )
888 }
889}
890
891impl LicenseClass {
892 pub fn new(ordinal: u32, name: &str, is_active: bool) -> Self {
893 Self {
894 name: name.to_string(),
895 ordinal,
896 is_active,
897 }
898 }
899 pub fn active(ordinal: u32, name: &str) -> Self {
900 Self {
901 name: name.to_string(),
902 ordinal,
903 is_active: true,
904 }
905 }
906 pub fn inactive(ordinal: u32, name: &str) -> Self {
907 Self {
908 name: name.to_string(),
909 ordinal,
910 is_active: false,
911 }
912 }
913
914 pub fn name(&self) -> &DisplayName {
915 &self.name
916 }
917
918 pub fn ordinal(&self) -> u32 {
919 self.ordinal
920 }
921
922 pub fn is_active(&self) -> bool {
923 self.is_active
924 }
925}
926
927impl Display for UsageRestriction {
930 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
931 write!(
932 f,
933 "{}",
934 match self {
935 UsageRestriction::Beacon => "beacon".to_string(),
936 UsageRestriction::CW => "CW".to_string(),
937 UsageRestriction::Data => "data".to_string(),
938 UsageRestriction::Digital => "digital".to_string(),
939 UsageRestriction::Dx => "DX".to_string(),
940 UsageRestriction::EarthMoonEarth => "EME".to_string(),
941 UsageRestriction::Phone => "phone".to_string(),
942 UsageRestriction::Qrp => "QRP".to_string(),
943 UsageRestriction::RemoteControl => "remote-control".to_string(),
944 UsageRestriction::Repeater(repeater_usage) => format!("repeater {repeater_usage}"),
945 UsageRestriction::Rtty => "RTTY".to_string(),
946 UsageRestriction::Satellite(satellite_usage) =>
947 format!("satellite {satellite_usage}"),
948 UsageRestriction::Simplex => "simplex".to_string(),
949 UsageRestriction::SlowScanTv => "SSTV".to_string(),
950 UsageRestriction::Test(repeater_usage) => format!("test-pair {repeater_usage}"),
951 }
952 )
953 }
954}
955
956impl FromStr for UsageRestriction {
957 type Err = CoreError;
958
959 fn from_str(s: &str) -> Result<Self, Self::Err> {
960 match s {
961 "beacon" => Ok(Self::Beacon),
962 "CW" => Ok(Self::CW),
963 "data" => Ok(Self::Data),
964 "digital" => Ok(Self::Digital),
965 "DX" => Ok(Self::Dx),
966 "EQE" => Ok(Self::EarthMoonEarth),
967 "QRP" => Ok(Self::Qrp),
968 "remote-control" => Ok(Self::RemoteControl),
969 "RTTY" => Ok(Self::Rtty),
970 "simplex" => Ok(Self::Simplex),
971 "SSTV" => Ok(Self::SlowScanTv),
972 _ => {
973 if let Some(repeater) = s.strip_prefix("repeater") {
974 Ok(Self::Repeater(RepeaterUsage::from_str(repeater.trim())?))
975 } else if let Some(satellite) = s.strip_prefix("satellite") {
976 Ok(Self::Satellite(SatelliteUsage::from_str(satellite.trim())?))
977 } else if let Some(test_pair) = s.strip_prefix("test-pair") {
978 Ok(Self::Test(RepeaterUsage::from_str(test_pair.trim())?))
979 } else {
980 Err(CoreError::InvalidValueFromStr(
981 s.to_string(),
982 "UsageRestriction",
983 ))
984 }
985 }
986 }
987 }
988}
989
990impl Display for PhoneMode {
993 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
994 write!(
995 f,
996 "{}",
997 match (self, f.alternate()) {
998 (PhoneMode::AmplitudeModulated, true) => "Amplitude Modulation (AM)",
999 (PhoneMode::AmplitudeModulated, false) => "AM",
1000 (PhoneMode::FrequencyModulated, true) => "FrequencyModulation (FM)",
1001 (PhoneMode::FrequencyModulated, false) => "FM",
1002 (PhoneMode::SingleSideband, true) => "Single Sideband (SSB)",
1003 (PhoneMode::SingleSideband, false) => "SSB",
1004 (PhoneMode::SingleSidebandLower, true) => "Lower Sideband (LSB)",
1005 (PhoneMode::SingleSidebandLower, false) => "LSB",
1006 (PhoneMode::SingleSidebandUpper, true) => "Upper Sideband (USB)",
1007 (PhoneMode::SingleSidebandUpper, false) => "USB",
1008 }
1009 )
1010 }
1011}
1012
1013impl FromStr for PhoneMode {
1014 type Err = CoreError;
1015
1016 fn from_str(s: &str) -> Result<Self, Self::Err> {
1017 match s {
1018 "AM" => Ok(Self::AmplitudeModulated),
1019 "FM" => Ok(Self::FrequencyModulated),
1020 "SSB" => Ok(Self::SingleSideband),
1021 "LSB" => Ok(Self::SingleSidebandLower),
1022 "USB" => Ok(Self::SingleSidebandUpper),
1023 _ => Err(CoreError::InvalidValueFromStr(s.to_string(), "PhoneMode")),
1024 }
1025 }
1026}
1027
1028impl Display for PowerMeasure {
1031 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1032 write!(
1033 f,
1034 "{}",
1035 match (self, f.alternate()) {
1036 (PowerMeasure::PeakEnvelopePower, true) => "Peak Envelope Power (PEP)",
1037 (PowerMeasure::PeakEnvelopePower, false) => "PEP",
1038 (PowerMeasure::EffectiveRadiatedPower, true) => "Effective Radiated Power (ERP)",
1039 (PowerMeasure::EffectiveRadiatedPower, false) => "ERP",
1040 (PowerMeasure::EffectiveIsotropicRadiatedPower, true) =>
1041 "Effective Isotropic Radiated Power (EIRP)",
1042 (PowerMeasure::EffectiveIsotropicRadiatedPower, false) => "EIRP",
1043 }
1044 )
1045 }
1046}
1047
1048impl FromStr for PowerMeasure {
1049 type Err = CoreError;
1050
1051 fn from_str(s: &str) -> Result<Self, Self::Err> {
1052 match s {
1053 "PEP" => Ok(Self::PeakEnvelopePower),
1054 "ERP" => Ok(Self::EffectiveRadiatedPower),
1055 "EIRP" => Ok(Self::EffectiveIsotropicRadiatedPower),
1056 _ => Err(CoreError::InvalidValueFromStr(
1057 s.to_string(),
1058 "PowerMeasure",
1059 )),
1060 }
1061 }
1062}
1063
1064impl Display for PowerRestriction {
1067 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1068 write!(f, "{} {}", self.max_power, self.measure)
1069 }
1070}
1071
1072impl<T> From<T> for PowerRestriction
1073where
1074 T: Into<Power>,
1075{
1076 fn from(value: T) -> Self {
1077 Self::pep(value.into())
1078 }
1079}
1080
1081impl PowerRestriction {
1082 pub fn new(max_power: Power, measure: PowerMeasure) -> Self {
1083 Self { max_power, measure }
1084 }
1085 pub fn pep(max_power: Power) -> Self {
1086 Self {
1087 max_power,
1088 measure: PowerMeasure::PeakEnvelopePower,
1089 }
1090 }
1091 pub fn erp(max_power: Power) -> Self {
1092 Self {
1093 max_power,
1094 measure: PowerMeasure::EffectiveRadiatedPower,
1095 }
1096 }
1097 pub fn eirp(max_power: Power) -> Self {
1098 Self {
1099 max_power,
1100 measure: PowerMeasure::EffectiveIsotropicRadiatedPower,
1101 }
1102 }
1103 pub fn max_power(&self) -> Power {
1104 self.max_power
1105 }
1106 pub fn measure(&self) -> PowerMeasure {
1107 self.measure
1108 }
1109}
1110
1111impl Display for RepeaterUsage {
1114 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1115 write!(
1116 f,
1117 "{}",
1118 match self {
1119 RepeaterUsage::Input => "input",
1120 RepeaterUsage::Output => "output",
1121 }
1122 )
1123 }
1124}
1125
1126impl FromStr for RepeaterUsage {
1127 type Err = CoreError;
1128
1129 fn from_str(s: &str) -> Result<Self, Self::Err> {
1130 match s {
1131 "input" => Ok(Self::Input),
1132 "output" => Ok(Self::Output),
1133 _ => Err(CoreError::InvalidValueFromStr(
1134 s.to_string(),
1135 "RepeaterUsage",
1136 )),
1137 }
1138 }
1139}
1140
1141impl Display for SatelliteUsage {
1144 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1145 write!(
1146 f,
1147 "{}",
1148 match self {
1149 SatelliteUsage::Downlink => "downlink",
1150 SatelliteUsage::Uplink => "uplink",
1151 }
1152 )
1153 }
1154}
1155
1156impl FromStr for SatelliteUsage {
1157 type Err = CoreError;
1158
1159 fn from_str(s: &str) -> Result<Self, Self::Err> {
1160 match s {
1161 "downlink" => Ok(Self::Downlink),
1162 "uplink" => Ok(Self::Uplink),
1163 _ => Err(CoreError::InvalidValueFromStr(
1164 s.to_string(),
1165 "SatelliteUsage",
1166 )),
1167 }
1168 }
1169}
1170
1171impl Display for BandwidthRestriction {
1174 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1175 write!(f, "{}", self.max_bandwidth)
1176 }
1177}
1178
1179impl From<Frequency> for BandwidthRestriction {
1180 fn from(value: Frequency) -> Self {
1181 Self::new(value)
1182 }
1183}
1184
1185impl From<f64> for BandwidthRestriction {
1186 fn from(value: f64) -> Self {
1187 Self::new(value)
1188 }
1189}
1190
1191impl BandwidthRestriction {
1192 pub fn new<F: Into<Frequency>>(max_bandwidth: F) -> Self {
1193 Self {
1194 max_bandwidth: max_bandwidth.into(),
1195 }
1196 }
1197
1198 pub fn max_bandwidth(&self) -> Frequency {
1199 self.max_bandwidth
1200 }
1201}
1202
1203pub mod uk_rsgb;
1207pub mod us_fcc;