use colored::Colorize;
use rfham_bands::BandPlan;
use rfham_core::frequencies::{FrequencyRange, Wavelength, meters};
use rfham_itu::allocations::FrequencyAllocation;
use rfham_markdown::{
MarkdownError, ToMarkdown, blank_line, fenced_code_block_end, fenced_code_block_start, header,
italic_to_string, numbered_list_item, plain_text,
};
use std::fmt::Display;
#[derive(Clone, Debug, PartialEq)]
pub struct SimpleDipole {
band: FrequencyAllocation,
band_plan: Option<BandPlan>,
}
impl Display for SimpleDipole {
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
todo!()
}
}
impl ToMarkdown for SimpleDipole {
fn write_markdown<W: std::io::Write>(&self, writer: &mut W) -> Result<(), MarkdownError> {
if let Some(quarter_wavelength) = self.pole_length() {
const QUARTER_WAVE_PADDING: usize = "|<--- ".len() + " --->|".len();
const HALF_WAVE_PADDING: usize = "|< ".len() + " >|".len();
let wl_4 = format!("λ/4 = {quarter_wavelength:#.3}");
let wl_4_len: usize = wl_4.len() - 1;
let wl_4_padded_len = wl_4_len + QUARTER_WAVE_PADDING;
let wl_2 = meters(quarter_wavelength.value() * 2.0);
let wl_2 = format!("λ/2 = {wl_2:#.3}");
let wl_2_len = wl_2.len() - 1;
let width = wl_4_padded_len * 2 + 1;
let pad_width = (width - (wl_2_len + HALF_WAVE_PADDING)) / 2;
let pad_str = "─".repeat(pad_width);
header(
writer,
1,
format!("Classical half-wave dipole antenna for {} band.", self.band),
)?;
blank_line(writer)?;
fenced_code_block_start(writer)?;
let left_pad = format!("|<{pad_str}").blue().dimmed();
let right_pad = format!("{pad_str}{}>|", if wl_2_len % 2 == 1 { "" } else { "─" },)
.blue()
.dimmed();
writeln!(writer, "{} {} {}", left_pad, wl_2.bold(), right_pad)?;
let quarter_measure = format!(
"{} {} {}",
"|<───".blue().dimmed(),
wl_4.bold(),
"───>|".blue().dimmed(),
);
writeln!(writer, "{quarter_measure} {quarter_measure}",)?;
plain_text(
writer,
format!(
"{}┳{}",
"─".repeat(wl_4_padded_len),
"─".repeat(wl_4_padded_len)
),
)?;
writeln!(
writer,
"{}│ {}",
" ".repeat(wl_4_padded_len),
"∧".blue().dimmed()
)?;
writeln!(
writer,
"{}│ {}",
" ".repeat(wl_4_padded_len),
"│".blue().dimmed()
)?;
writeln!(
writer,
"{}│ {} {}",
" ".repeat(wl_4_padded_len),
"│".blue().dimmed(),
wl_2.bold()
)?;
writeln!(
writer,
"{}│ {}",
" ".repeat(wl_4_padded_len),
"│".blue().dimmed()
)?;
writeln!(
writer,
"{}│ {}",
" ".repeat(wl_4_padded_len),
"∨".blue().dimmed()
)?;
fenced_code_block_end(writer)?;
blank_line(writer)?;
plain_text(writer, "Notes:")?;
blank_line(writer)?;
let range = self.band_range().unwrap();
numbered_list_item(
writer,
1,
1,
format!("Frequency range for {} band is {:.3}.", self.band, range,),
)?;
numbered_list_item(
writer,
2,
1,
format!(
"From the {}.",
if let Some(band_plan) = &self.band_plan {
format!(
"{} by {}",
italic_to_string(band_plan.name()),
band_plan.maintaining_agency()
)
} else {
"ITU frequency allocation".to_string()
}
),
)?;
numbered_list_item(
writer,
1,
2,
format!("Mid-point of band is {:.3}.", range.mid_band()),
)?;
numbered_list_item(
writer,
1,
3,
format!(
"Wavelength of mid-point is {:.3}.",
range.mid_band().to_wavelength()
),
)?;
numbered_list_item(
writer,
1,
4,
format!("Half-wave length is {wl_2} for overall antenna."),
)?;
numbered_list_item(
writer,
1,
5,
format!("Quarter-wave length is {wl_4} for each antenna pole."),
)?;
} else {
println!(
"{}",
"Error: could not determine wavelength for antenna".red()
);
}
Ok(())
}
}
impl SimpleDipole {
pub fn new(band: FrequencyAllocation) -> Self {
Self {
band,
band_plan: None,
}
}
pub fn new_in_plan(band: FrequencyAllocation, band_plan: BandPlan) -> Self {
Self {
band,
band_plan: Some(band_plan),
}
}
fn band_range(&self) -> Option<FrequencyRange> {
if let Some(band_plan) = &self.band_plan {
band_plan
.band(&self.band)
.map(|band| band.band().range())
.cloned()
} else {
Some(self.band.total_range())
}
}
pub fn antenna_length(&self) -> Option<Wavelength> {
if let Some(range) = self.band_range() {
let mid_band = range.mid_band();
let wavelength = mid_band.to_wavelength();
Some(meters(wavelength.value() / 2.0))
} else {
None
}
}
pub fn pole_length(&self) -> Option<Wavelength> {
self.antenna_length().map(|v| meters(v.value() / 2.0))
}
}
#[cfg(test)]
mod tests {
use super::SimpleDipole;
use rfham_itu::allocations::FrequencyAllocation;
#[test]
fn test_antenna_length_2m() {
let dipole = SimpleDipole::new(FrequencyAllocation::Band2M);
let length = dipole.antenna_length().unwrap();
assert!(length.value() > 1.0 && length.value() < 1.1);
}
#[test]
fn test_pole_length_is_half_antenna() {
let dipole = SimpleDipole::new(FrequencyAllocation::Band2M);
let antenna = dipole.antenna_length().unwrap().value();
let pole = dipole.pole_length().unwrap().value();
assert!((antenna / 2.0 - pole).abs() < 1e-9);
}
#[test]
fn test_antenna_length_40m() {
let dipole = SimpleDipole::new(FrequencyAllocation::Band40M);
let length = dipole.antenna_length().unwrap();
assert!(length.value() > 20.0 && length.value() < 22.0);
}
}