use colored::Colorize;
use rfham_bands::BandPlan;
use rfham_core::{
error::CoreError,
frequency::{FrequencyRange, Wavelength, meters},
};
use rfham_itu::allocations::FrequencyAllocation;
use rfham_markdown::{
ToMarkdown, blank_line, fenced_code_block_end, fenced_code_block_start, header,
italic_to_string, numbered_list_item, plain_text,
};
use std::{fmt::Display, str::FromStr};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AntennaForm {
Dipole,
Dish,
Vertical,
EndFed,
RandomWire,
Yagi,
Other,
}
#[derive(Clone, Debug, PartialEq)]
pub struct SimpleDipole {
band: FrequencyAllocation,
band_plan: Option<BandPlan>,
}
impl Display for AntennaForm {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Dipole => "dipole",
Self::Dish => "dish",
Self::Vertical => "vertical",
Self::EndFed => "efhw",
Self::RandomWire => "random",
Self::Yagi => "yagi",
Self::Other => "other",
}
)
}
}
impl FromStr for AntennaForm {
type Err = CoreError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"dipole" => Ok(Self::Dipole),
"dish" => Ok(Self::Dish),
"vertical" => Ok(Self::Vertical),
"efhw" => Ok(Self::EndFed),
"random" => Ok(Self::RandomWire),
"yagi" => Ok(Self::Yagi),
"other" => Ok(Self::Other),
_ => Err(CoreError::InvalidValueFromStr(s.to_string(), "AntennaForm")),
}
}
}
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<(), CoreError> {
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 {}