1use crate::{bands::FrequencyBand, regions::Region};
13use core::{fmt::Display, str::FromStr};
14use rfham_core::{
15 error::CoreError,
16 frequencies::{Frequency, FrequencyRange, gigahertz, hertz, kilohertz, megahertz},
17};
18use rfham_markdown::error::MarkdownError;
19use rfham_markdown::{
20 Column, ColumnJustification, Table, blank_line, bulleted_list_item, header, link_to_string,
21 plain_text,
22};
23use serde_with::{DeserializeFromStr, SerializeDisplay};
24use std::io::Write;
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, DeserializeFromStr, SerializeDisplay)]
31pub enum FrequencyAllocation {
32 Band2200M,
33 Band630M,
34 Band160M,
35 Band80M,
36 Band60M,
37 Band40M,
38 Band30M,
39 Band20M,
40 Band17M,
41 Band15M,
42 Band12M,
43 Band10M,
44 Band8M,
45 Band6M,
46 Band5M,
47 Band4M,
48 Band2M,
49 Band1_25M,
50 Band70Cm,
51 Band33Cm,
52 Band23Cm,
53 Band13Cm,
54 Band9Cm,
55 Band5Cm,
56 Band3Cm,
57 Band1_2Cm,
58 Band6Mm,
59 Band4Mm,
60 Band2_5Mm,
61 Band2Mm,
62 Band1Mm,
63}
64
65impl Display for FrequencyAllocation {
70 #[allow(clippy::recursive_format_impl)]
72 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73 write!(
74 f,
75 "{}",
76 if f.alternate() {
77 match self {
78 Self::Band2200M => format!("{}/{}/135.7kHz", self, self.band()),
79 Self::Band630M => format!("{}/{}/472.0kHz", self, self.band()),
80 Self::Band160M => format!("{}/{}/1.81MHz", self, self.band()),
81 Self::Band80M => format!("{}/{}/3.5MHz", self, self.band()),
82 Self::Band60M => format!("{}/{}/5.3515MHz", self, self.band()),
83 Self::Band40M => format!("{}/{}/7.0MHz", self, self.band()),
84 Self::Band30M => format!("{}/{}/10.1MHz", self, self.band()),
85 Self::Band20M => format!("{}/{}/14.0MHz", self, self.band()),
86 Self::Band17M => format!("{}/{}/18.068MHz", self, self.band()),
87 Self::Band15M => format!("{}/{}/21.0MHz", self, self.band()),
88 Self::Band12M => format!("{}/{}/24.89MHz", self, self.band()),
89 Self::Band10M => format!("{}/{}/28.0MHz", self, self.band()),
90 Self::Band8M => format!("{}/{}/39.9MHz", self, self.band()),
91 Self::Band6M => format!("{}/{}/50.0MHz", self, self.band()),
92 Self::Band5M => format!("{}/{}/59.5.0MHz", self, self.band()),
93 Self::Band4M => format!("{}/{}/69.9MHz", self, self.band()),
94 Self::Band2M => format!("{}/{}/144.0MHz", self, self.band()),
95 Self::Band1_25M => format!("{}/{}/220.0MHz", self, self.band()),
96 Self::Band70Cm => format!("{}/{}/430.0MHz", self, self.band()),
97 Self::Band33Cm => format!("{}/{}/902.0MHz", self, self.band()),
98 Self::Band23Cm => format!("{}/{}/1.24GHz", self, self.band()),
99 Self::Band13Cm => format!("{}/{}/2.3GHz", self, self.band()),
100 Self::Band9Cm => format!("{}/{}/3.3GHz", self, self.band()),
101 Self::Band5Cm => format!("{}/{}/5.65GHz", self, self.band()),
102 Self::Band3Cm => format!("{}/{}/10.0GHz", self, self.band()),
103 Self::Band1_2Cm => format!("{}/{}/24.0GHz", self, self.band()),
104 Self::Band6Mm => format!("{}/{}/47.0GHz", self, self.band()),
105 Self::Band4Mm => format!("{}/{}/76.0GHz", self, self.band()),
106 Self::Band2_5Mm => format!("{}/{}/122.25GHz", self, self.band()),
107 Self::Band2Mm => format!("{}/{}/134.0GHz", self, self.band()),
108 Self::Band1Mm => format!("{}/{}/241.0GHz", self, self.band()),
109 }
110 } else {
111 match self {
112 Self::Band2200M => "2200m",
113 Self::Band630M => "630m",
114 Self::Band160M => "160m",
115 Self::Band80M => "80m",
116 Self::Band60M => "60m",
117 Self::Band40M => "40m",
118 Self::Band30M => "30m",
119 Self::Band20M => "20m",
120 Self::Band17M => "17m",
121 Self::Band15M => "15m",
122 Self::Band12M => "12m",
123 Self::Band10M => "10m",
124 Self::Band8M => "8m",
125 Self::Band6M => "6m",
126 Self::Band5M => "5m",
127 Self::Band4M => "4m",
128 Self::Band2M => "2m",
129 Self::Band1_25M => "1.25m",
130 Self::Band70Cm => "70cm",
131 Self::Band33Cm => "33cm",
132 Self::Band23Cm => "23cm",
133 Self::Band13Cm => "13cm",
134 Self::Band9Cm => "9cm",
135 Self::Band5Cm => "5cm",
136 Self::Band3Cm => "3cm",
137 Self::Band1_2Cm => "1.2cm",
138 Self::Band6Mm => "6mm",
139 Self::Band4Mm => "4mm",
140 Self::Band2_5Mm => "2.5mm",
141 Self::Band2Mm => "2mm",
142 Self::Band1Mm => "1mm",
143 }
144 .to_string()
145 }
146 )
147 }
148}
149
150impl FromStr for FrequencyAllocation {
151 type Err = CoreError;
152
153 fn from_str(s: &str) -> Result<Self, Self::Err> {
154 match s {
155 "2200m" => Ok(Self::Band2200M),
156 "630m" => Ok(Self::Band630M),
157 "160m" => Ok(Self::Band160M),
158 "80m" => Ok(Self::Band80M),
159 "60m" => Ok(Self::Band60M),
160 "40m" => Ok(Self::Band40M),
161 "30m" => Ok(Self::Band30M),
162 "20m" => Ok(Self::Band20M),
163 "17m" => Ok(Self::Band17M),
164 "15m" => Ok(Self::Band15M),
165 "12m" => Ok(Self::Band12M),
166 "10m" => Ok(Self::Band10M),
167 "8m" => Ok(Self::Band8M),
168 "6m" => Ok(Self::Band6M),
169 "5m" => Ok(Self::Band5M),
170 "4m" => Ok(Self::Band4M),
171 "2m" => Ok(Self::Band2M),
172 "1.25m" => Ok(Self::Band1_25M),
173 "70cm" => Ok(Self::Band70Cm),
174 "33cm" => Ok(Self::Band33Cm),
175 "23cm" => Ok(Self::Band23Cm),
176 "13cm" => Ok(Self::Band13Cm),
177 "9cm" => Ok(Self::Band9Cm),
178 "5cm" => Ok(Self::Band5Cm),
179 "3cm" => Ok(Self::Band3Cm),
180 "1.2cm" => Ok(Self::Band1_2Cm),
181 "6mm" => Ok(Self::Band6Mm),
182 "4mm" => Ok(Self::Band4Mm),
183 "2.5mm" => Ok(Self::Band2_5Mm),
184 "2mm" => Ok(Self::Band2Mm),
185 "1mm" => Ok(Self::Band1Mm),
186 _ => Err(CoreError::InvalidValueFromStr(
187 s.to_string(),
188 "FrequencyAllocation",
189 )),
190 }
191 }
192}
193
194impl FrequencyAllocation {
195 pub const fn band(&self) -> FrequencyBand {
196 match self {
197 Self::Band2200M => FrequencyBand::Low,
198 Self::Band630M | Self::Band160M => FrequencyBand::Medium,
199 Self::Band80M
200 | Self::Band60M
201 | Self::Band40M
202 | Self::Band30M
203 | Self::Band20M
204 | Self::Band17M
205 | Self::Band15M
206 | Self::Band12M
207 | Self::Band10M => FrequencyBand::High,
208 Self::Band8M
209 | Self::Band6M
210 | Self::Band5M
211 | Self::Band4M
212 | Self::Band2M
213 | Self::Band1_25M => FrequencyBand::VeryHigh,
214 Self::Band70Cm | Self::Band33Cm | Self::Band23Cm | Self::Band13Cm => {
215 FrequencyBand::UltraHigh
216 }
217 Self::Band9Cm | Self::Band5Cm | Self::Band3Cm | Self::Band1_2Cm => {
218 FrequencyBand::SuperHigh
219 }
220 Self::Band6Mm | Self::Band4Mm | Self::Band2_5Mm | Self::Band2Mm | Self::Band1Mm => {
221 FrequencyBand::ExtremelyHigh
222 }
223 }
224 }
225
226 pub fn range(&self, in_region: Region) -> Option<FrequencyRange> {
227 match (in_region, self) {
228 (_, Self::Band2200M) => Some(FrequencyRange::new(kilohertz(135.7), kilohertz(137.8))),
230 (_, Self::Band630M) => Some(FrequencyRange::new(kilohertz(472.0), kilohertz(479.0))),
231 (_, Self::Band60M) => Some(FrequencyRange::new(megahertz(5.3515), megahertz(5.3665))),
232 (_, Self::Band40M) => Some(FrequencyRange::new(megahertz(7.0), megahertz(7.3))),
233 (_, Self::Band30M) => Some(FrequencyRange::new(megahertz(10.1), megahertz(10.15))),
234 (_, Self::Band20M) => Some(FrequencyRange::new(megahertz(14.0), megahertz(14.35))),
235 (_, Self::Band17M) => Some(FrequencyRange::new(megahertz(18.068), megahertz(18.168))),
236 (_, Self::Band15M) => Some(FrequencyRange::new(megahertz(21.0), megahertz(21.45))),
237 (_, Self::Band12M) => Some(FrequencyRange::new(megahertz(24.89), megahertz(24.99))),
238 (_, Self::Band10M) => Some(FrequencyRange::new(megahertz(28.0), megahertz(29.7))),
239 (_, Self::Band2M) => Some(FrequencyRange::new(megahertz(144.0), megahertz(148.0))),
240 (_, Self::Band70Cm) => Some(FrequencyRange::new(megahertz(430.0), megahertz(440.0))),
241 (_, Self::Band23Cm) => Some(FrequencyRange::new(gigahertz(1.24), gigahertz(1.3))),
242 (_, Self::Band13Cm) => Some(FrequencyRange::new(gigahertz(2.3), gigahertz(2.45))),
243 (_, Self::Band3Cm) => Some(FrequencyRange::new(gigahertz(10.0), gigahertz(10.5))),
244 (_, Self::Band1_2Cm) => Some(FrequencyRange::new(gigahertz(24.0), gigahertz(24.25))),
245 (_, Self::Band6Mm) => Some(FrequencyRange::new(gigahertz(47.0), gigahertz(47.2))),
246 (_, Self::Band4Mm) => Some(FrequencyRange::new(gigahertz(76.0), gigahertz(81.5))),
247 (_, Self::Band2_5Mm) => Some(FrequencyRange::new(gigahertz(122.25), gigahertz(123.0))),
248 (_, Self::Band2Mm) => Some(FrequencyRange::new(gigahertz(134.0), gigahertz(141.0))),
249 (_, Self::Band1Mm) => Some(FrequencyRange::new(gigahertz(241.0), gigahertz(250.0))),
250 (Region::One, Self::Band160M) => {
252 Some(FrequencyRange::new(megahertz(1.81), megahertz(2.0)))
253 }
254 (Region::One, Self::Band80M) => {
255 Some(FrequencyRange::new(megahertz(3.5), megahertz(3.8)))
256 }
257 (Region::One, Self::Band8M) => {
258 Some(FrequencyRange::new(megahertz(39.9), megahertz(40.75)))
259 }
260 (Region::One, Self::Band5M) => {
261 Some(FrequencyRange::new(megahertz(59.5), megahertz(60.1)))
262 }
263 (Region::One, Self::Band4M) => {
264 Some(FrequencyRange::new(megahertz(69.9), megahertz(70.5)))
265 }
266 (Region::One, Self::Band5Cm) => {
267 Some(FrequencyRange::new(gigahertz(5.65), gigahertz(5.85)))
268 }
269 (_, Self::Band6M) => Some(FrequencyRange::new(megahertz(50.0), megahertz(54.0))),
271 (_, Self::Band9Cm) => Some(FrequencyRange::new(gigahertz(3.3), gigahertz(3.5))),
272 (Region::Two, Self::Band160M) => {
274 Some(FrequencyRange::new(megahertz(1.8), megahertz(2.0)))
275 }
276 (Region::Two, Self::Band80M) => {
277 Some(FrequencyRange::new(megahertz(3.5), megahertz(4.0)))
278 }
279 (Region::Two, Self::Band1_25M) => {
280 Some(FrequencyRange::new(megahertz(220.0), megahertz(230.0)))
281 }
282 (Region::Two, Self::Band33Cm) => {
283 Some(FrequencyRange::new(megahertz(902.0), megahertz(928.0)))
284 }
285 (Region::Two, Self::Band5Cm) => {
286 Some(FrequencyRange::new(gigahertz(5.65), gigahertz(5.925)))
287 }
288 (Region::Three, Self::Band160M) => {
290 Some(FrequencyRange::new(megahertz(1.8), megahertz(2.0)))
291 }
292 (Region::Three, Self::Band80M) => {
293 Some(FrequencyRange::new(megahertz(3.5), megahertz(3.9)))
294 }
295 (Region::Three, Self::Band5Cm) => {
296 Some(FrequencyRange::new(gigahertz(5.65), gigahertz(5.85)))
297 }
298 (_, _) => None,
299 }
300 }
301
302 pub fn total_range(&self) -> FrequencyRange {
303 let bounds = vec![
304 self.range(Region::One),
305 self.range(Region::Two),
306 self.range(Region::Three),
307 ]
308 .into_iter()
309 .flatten()
310 .map(|range| (range.start().value(), range.end().value()))
311 .collect::<Vec<_>>();
312
313 let start = bounds
315 .iter()
316 .map(|(start, _)| start)
317 .min_by(|l, r| l.total_cmp(r))
318 .unwrap();
319 let end = bounds
320 .iter()
321 .map(|(_, end)| end)
322 .max_by(|l, r| l.total_cmp(r))
323 .unwrap();
324 FrequencyRange::new(hertz(*start), hertz(*end))
325 }
326
327 pub fn classify(frequency: Frequency) -> Option<Self> {
328 match frequency.value() {
329 135700.0..137800.0 => Some(Self::Band2200M),
330 472000.0..479000.0 => Some(Self::Band630M),
331 1800000.0..2000000.0 => Some(Self::Band160M),
332 3500000.0..4000000.0 => Some(Self::Band80M),
333 5330000.0..5406000.0 => Some(Self::Band60M),
334 7000000.0..7300000.0 => Some(Self::Band40M),
335 10100000.0..10150000.0 => Some(Self::Band30M),
336 14000000.0..14350000.0 => Some(Self::Band20M),
337 18068000.0..18168000.0 => Some(Self::Band17M),
338 21000000.0..21450000.0 => Some(Self::Band15M),
339 24890000.0..24990000.0 => Some(Self::Band12M),
340 28000000.0..29700000.0 => Some(Self::Band10M),
341 39900000.0..40750000.0 => Some(Self::Band8M),
342 50000000.0..54000000.0 => Some(Self::Band6M),
343 144000000.0..148000000.0 => Some(Self::Band2M),
344 219000000.0..220000000.0 | 222000000.0..225000000.0 => Some(Self::Band1_25M),
345 420000000.0..450000000.0 => Some(Self::Band70Cm),
346 902000000.0..928000000.0 => Some(Self::Band33Cm),
347 1240000000.0..1300000000.0 => Some(Self::Band23Cm),
348 2300000000.0..2310000000.0 | 2390000000.0..2450000000.0 => Some(Self::Band13Cm),
349 3300000000.0..3500000000.0 => Some(Self::Band9Cm),
350 5650000000.0..5925000000.0 => Some(Self::Band5Cm),
351 10000000000.0..10500000000.0 => Some(Self::Band3Cm),
352 24000000000.0..24250000000.0 => Some(Self::Band1_2Cm),
353 47000000000.0..47200000000.0 => Some(Self::Band6Mm),
354 75500000000.0..81000000000.0 => Some(Self::Band4Mm),
355 122250000000.0..123000000000.0 => Some(Self::Band2_5Mm),
356 134000000000.0..141000000000.0 => Some(Self::Band2Mm),
357 241000000000.0..250000000000.0 => Some(Self::Band1Mm),
358 _ => None,
359 }
360 }
361
362 pub fn write_markdown<W: Write>(writer: &mut W) -> Result<(), MarkdownError> {
363 const NAME_COL_WIDTH: usize = 5;
364 const BAND_COL_WIDTH: usize = 5;
365 const START_COL_WIDTH: usize = 10;
366 const END_COL_WIDTH: usize = 10;
367
368 header(writer, 1, "IARU/ITU Frequency Allocations")?;
369 blank_line(writer)?;
370
371 let table = Table::new(vec![
372 ("Name", NAME_COL_WIDTH).into(),
373 ("Band", BAND_COL_WIDTH).into(),
374 Column::new("Start")
375 .with_width(START_COL_WIDTH)
376 .with_justification(ColumnJustification::Right),
377 Column::new("End")
378 .with_width(END_COL_WIDTH)
379 .with_justification(ColumnJustification::Right),
380 Column::new("Start")
381 .with_width(START_COL_WIDTH)
382 .with_justification(ColumnJustification::Right),
383 Column::new("End")
384 .with_width(END_COL_WIDTH)
385 .with_justification(ColumnJustification::Right),
386 Column::new("Start")
387 .with_width(START_COL_WIDTH)
388 .with_justification(ColumnJustification::Right),
389 Column::new("End")
390 .with_width(END_COL_WIDTH)
391 .with_justification(ColumnJustification::Right),
392 ])
393 .with_super_labels(vec![
394 "",
395 "",
396 "Region 1",
397 "",
398 "Region 2",
399 "",
400 "Region. 3",
401 "",
402 ]);
403
404 table.headers(writer)?;
405
406 for band in &[
407 Self::Band2200M,
408 Self::Band630M,
409 Self::Band160M,
410 Self::Band80M,
411 Self::Band60M,
412 Self::Band40M,
413 Self::Band30M,
414 Self::Band20M,
415 Self::Band17M,
416 Self::Band15M,
417 Self::Band12M,
418 Self::Band10M,
419 Self::Band6M,
420 Self::Band2M,
421 Self::Band1_25M,
422 Self::Band70Cm,
423 Self::Band33Cm,
424 Self::Band23Cm,
425 Self::Band13Cm,
426 Self::Band9Cm,
427 Self::Band5Cm,
428 Self::Band3Cm,
429 Self::Band1_2Cm,
430 Self::Band6Mm,
431 Self::Band4Mm,
432 Self::Band2_5Mm,
433 Self::Band2Mm,
434 Self::Band1Mm,
435 ] {
436 let region_1 = band.range(Region::One);
437 let region_2 = band.range(Region::Two);
438 let region_3 = band.range(Region::Three);
439
440 table.data_row(
441 writer,
442 &[
443 band.to_string(),
444 band.band().to_string(),
445 region_1
446 .as_ref()
447 .map(|r| r.start().to_string())
448 .unwrap_or("-".to_string()),
449 region_1
450 .as_ref()
451 .map(|r| r.end().to_string())
452 .unwrap_or("-".to_string()),
453 region_2
454 .as_ref()
455 .map(|r| r.start().to_string())
456 .unwrap_or("-".to_string()),
457 region_2
458 .as_ref()
459 .map(|r| r.end().to_string())
460 .unwrap_or("-".to_string()),
461 region_3
462 .as_ref()
463 .map(|r| r.start().to_string())
464 .unwrap_or("-".to_string()),
465 region_3
466 .as_ref()
467 .map(|r| r.end().to_string())
468 .unwrap_or("-".to_string()),
469 ],
470 )?;
471 }
472 blank_line(writer)?;
473
474 plain_text(writer, "For more information, see:")?;
475 blank_line(writer)?;
476
477 bulleted_list_item(
478 writer,
479 1,
480 format!(
481 "{}, IARU 2020.",
482 link_to_string(
483 "Amateur and Amateur-satellite Service Spectrum",
484 "https://www.iaru.org/wp-content/uploads/2020/01/Amateur-Services-Spectrum-2020_.pdf",
485 )
486 ),
487 )?;
488 bulleted_list_item(
489 writer,
490 1,
491 format!(
492 "{}, IARU.",
493 link_to_string(
494 "Regions",
495 "https://www.iaru.org/about-us/organisation-and-history/regions/",
496 )
497 ),
498 )?;
499 Ok(())
500 }
501}
502
503#[cfg(test)]
508mod test {
509 use super::FrequencyAllocation;
510 use rfham_core::frequencies::{kilohertz, megahertz};
511
512 #[test]
513 fn test_write_markdown_band_plan() {
514 FrequencyAllocation::write_markdown(&mut std::io::stdout()).unwrap();
515 }
516
517 #[test]
518 fn test_total_range() {
519 assert_eq!(
520 "3.5 MHz - 4 MHz".to_string(),
521 FrequencyAllocation::Band80M.total_range().to_string()
522 );
523 }
524
525 #[test]
526 fn test_frequency_classifier() {
527 assert_eq!(None, FrequencyAllocation::classify(kilohertz(130.0)));
528 assert_eq!(
529 Some(FrequencyAllocation::Band2200M),
530 FrequencyAllocation::classify(kilohertz(136.0))
531 );
532 assert_eq!(
533 Some(FrequencyAllocation::Band1_25M),
534 FrequencyAllocation::classify(megahertz(223.500))
536 );
537 }
538}