1use colored::Colorize;
25use rfham_bands::BandPlan;
26use rfham_core::frequencies::{FrequencyRange, Wavelength, meters};
27use rfham_itu::allocations::FrequencyAllocation;
28use rfham_markdown::{
29 MarkdownError, ToMarkdown, blank_line, fenced_code_block_end, fenced_code_block_start, header,
30 italic_to_string, numbered_list_item, plain_text,
31};
32use std::fmt::Display;
33
34#[derive(Clone, Debug, PartialEq)]
87pub struct SimpleDipole {
88 band: FrequencyAllocation,
89 band_plan: Option<BandPlan>,
90}
91
92impl Display for SimpleDipole {
97 fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98 todo!()
99 }
100}
101
102impl ToMarkdown for SimpleDipole {
103 fn write_markdown<W: std::io::Write>(&self, writer: &mut W) -> Result<(), MarkdownError> {
104 if let Some(quarter_wavelength) = self.pole_length() {
105 const QUARTER_WAVE_PADDING: usize = "|<--- ".len() + " --->|".len();
106 const HALF_WAVE_PADDING: usize = "|< ".len() + " >|".len();
107
108 let wl_4 = format!("λ/4 = {quarter_wavelength:#.3}");
109 let wl_4_len: usize = wl_4.len() - 1;
110 let wl_4_padded_len = wl_4_len + QUARTER_WAVE_PADDING;
111 let wl_2 = meters(quarter_wavelength.value() * 2.0);
112 let wl_2 = format!("λ/2 = {wl_2:#.3}");
113 let wl_2_len = wl_2.len() - 1;
114 let width = wl_4_padded_len * 2 + 1;
115 let pad_width = (width - (wl_2_len + HALF_WAVE_PADDING)) / 2;
116 let pad_str = "─".repeat(pad_width);
117
118 header(
119 writer,
120 1,
121 format!("Classical half-wave dipole antenna for {} band.", self.band),
122 )?;
123 blank_line(writer)?;
124 fenced_code_block_start(writer)?;
125 let left_pad = format!("|<{pad_str}").blue().dimmed();
126 let right_pad = format!("{pad_str}{}>|", if wl_2_len % 2 == 1 { "" } else { "─" },)
127 .blue()
128 .dimmed();
129 writeln!(writer, "{} {} {}", left_pad, wl_2.bold(), right_pad)?;
130 let quarter_measure = format!(
131 "{} {} {}",
132 "|<───".blue().dimmed(),
133 wl_4.bold(),
134 "───>|".blue().dimmed(),
135 );
136 writeln!(writer, "{quarter_measure} {quarter_measure}",)?;
137 plain_text(
138 writer,
139 format!(
140 "{}┳{}",
141 "─".repeat(wl_4_padded_len),
142 "─".repeat(wl_4_padded_len)
143 ),
144 )?;
145 writeln!(
146 writer,
147 "{}│ {}",
148 " ".repeat(wl_4_padded_len),
149 "∧".blue().dimmed()
150 )?;
151 writeln!(
152 writer,
153 "{}│ {}",
154 " ".repeat(wl_4_padded_len),
155 "│".blue().dimmed()
156 )?;
157 writeln!(
158 writer,
159 "{}│ {} {}",
160 " ".repeat(wl_4_padded_len),
161 "│".blue().dimmed(),
162 wl_2.bold()
163 )?;
164 writeln!(
165 writer,
166 "{}│ {}",
167 " ".repeat(wl_4_padded_len),
168 "│".blue().dimmed()
169 )?;
170 writeln!(
171 writer,
172 "{}│ {}",
173 " ".repeat(wl_4_padded_len),
174 "∨".blue().dimmed()
175 )?;
176 fenced_code_block_end(writer)?;
177 blank_line(writer)?;
178
179 plain_text(writer, "Notes:")?;
180 blank_line(writer)?;
181
182 let range = self.band_range().unwrap();
184 numbered_list_item(
185 writer,
186 1,
187 1,
188 format!("Frequency range for {} band is {:.3}.", self.band, range,),
189 )?;
190 numbered_list_item(
191 writer,
192 2,
193 1,
194 format!(
195 "From the {}.",
196 if let Some(band_plan) = &self.band_plan {
197 format!(
198 "{} by {}",
199 italic_to_string(band_plan.name()),
200 band_plan.maintaining_agency()
201 )
202 } else {
203 "ITU frequency allocation".to_string()
204 }
205 ),
206 )?;
207 numbered_list_item(
208 writer,
209 1,
210 2,
211 format!("Mid-point of band is {:.3}.", range.mid_band()),
212 )?;
213 numbered_list_item(
214 writer,
215 1,
216 3,
217 format!(
218 "Wavelength of mid-point is {:.3}.",
219 range.mid_band().to_wavelength()
220 ),
221 )?;
222 numbered_list_item(
223 writer,
224 1,
225 4,
226 format!("Half-wave length is {wl_2} for overall antenna."),
227 )?;
228 numbered_list_item(
229 writer,
230 1,
231 5,
232 format!("Quarter-wave length is {wl_4} for each antenna pole."),
233 )?;
234 } else {
235 println!(
236 "{}",
237 "Error: could not determine wavelength for antenna".red()
238 );
239 }
240 Ok(())
241 }
242}
243
244impl SimpleDipole {
245 pub fn new(band: FrequencyAllocation) -> Self {
246 Self {
247 band,
248 band_plan: None,
249 }
250 }
251
252 pub fn new_in_plan(band: FrequencyAllocation, band_plan: BandPlan) -> Self {
253 Self {
254 band,
255 band_plan: Some(band_plan),
256 }
257 }
258
259 fn band_range(&self) -> Option<FrequencyRange> {
260 if let Some(band_plan) = &self.band_plan {
261 band_plan
262 .band(&self.band)
263 .map(|band| band.band().range())
264 .cloned()
265 } else {
266 Some(self.band.total_range())
267 }
268 }
269
270 pub fn antenna_length(&self) -> Option<Wavelength> {
271 if let Some(range) = self.band_range() {
272 let mid_band = range.mid_band();
273 let wavelength = mid_band.to_wavelength();
274 Some(meters(wavelength.value() / 2.0))
275 } else {
276 None
277 }
278 }
279
280 pub fn pole_length(&self) -> Option<Wavelength> {
281 self.antenna_length().map(|v| meters(v.value() / 2.0))
282 }
283}
284
285#[cfg(test)]
290mod tests {
291 use super::SimpleDipole;
292 use rfham_itu::allocations::FrequencyAllocation;
293
294 #[test]
295 fn test_antenna_length_2m() {
296 let dipole = SimpleDipole::new(FrequencyAllocation::Band2M);
297 let length = dipole.antenna_length().unwrap();
298 assert!(length.value() > 1.0 && length.value() < 1.1);
300 }
301
302 #[test]
303 fn test_pole_length_is_half_antenna() {
304 let dipole = SimpleDipole::new(FrequencyAllocation::Band2M);
305 let antenna = dipole.antenna_length().unwrap().value();
306 let pole = dipole.pole_length().unwrap().value();
307 assert!((antenna / 2.0 - pole).abs() < 1e-9);
308 }
309
310 #[test]
311 fn test_antenna_length_40m() {
312 let dipole = SimpleDipole::new(FrequencyAllocation::Band40M);
313 let length = dipole.antenna_length().unwrap();
314 assert!(length.value() > 20.0 && length.value() < 22.0);
316 }
317}