The US 9cm amateur allocation was cut from 3300-3500 to 3300-3450 MHz in 2020, so 3456 (the legacy weak-signal calling frequency) is no longer in-band. Move the canonical key to 3400, migrate existing contacts, and keep 3456 input resolving to 3400 via the nearest-band snap so historical ADIF/CSV logs still import cleanly.
133 lines
3.6 KiB
Elixir
133 lines
3.6 KiB
Elixir
defmodule Microwaveprop.Radio.BandResolver do
|
|
@moduledoc """
|
|
Converts user-supplied band input — ADIF wavelength labels ("33cm",
|
|
"13cm"), numeric frequencies in MHz ("903.100", "10368"), or bare
|
|
channel numbers ("902", "1296") — into one of the site's canonical
|
|
MHz values.
|
|
|
|
Used by `Microwaveprop.Radio.AdifImport`, `Microwaveprop.Radio.CsvImport`,
|
|
and the `SubmitLive` manual form so all four entry points agree on
|
|
what "33cm" means.
|
|
"""
|
|
|
|
# Keep in sync with:
|
|
# * `Microwaveprop.Radio.Contact.@allowed_bands`
|
|
# * `Microwaveprop.Propagation.BandConfig.@band_configs`
|
|
@allowed_bands [
|
|
50,
|
|
144,
|
|
222,
|
|
432,
|
|
902,
|
|
1296,
|
|
2304,
|
|
3400,
|
|
5760,
|
|
10_000,
|
|
24_000,
|
|
47_000,
|
|
68_000,
|
|
75_000,
|
|
122_000,
|
|
134_000,
|
|
241_000
|
|
]
|
|
|
|
# ADIF amateur wavelength band labels → our MHz channel centers.
|
|
# The `normalize/1` helper strips whitespace and lower-cases before
|
|
# looking up, so "33 CM", "33cm", " 33Cm " all resolve.
|
|
@band_name_to_mhz %{
|
|
"6m" => 50,
|
|
"2m" => 144,
|
|
"1.25m" => 222,
|
|
"70cm" => 432,
|
|
"33cm" => 902,
|
|
"23cm" => 1_296,
|
|
"13cm" => 2_304,
|
|
"9cm" => 3_400,
|
|
"6cm" => 5_760,
|
|
"3cm" => 10_000,
|
|
"1.25cm" => 24_000,
|
|
"6mm" => 47_000,
|
|
"4mm" => 75_000,
|
|
"2.5mm" => 122_000,
|
|
"2mm" => 134_000,
|
|
"1mm" => 241_000
|
|
}
|
|
|
|
@doc "All MHz band centers the site accepts, ascending."
|
|
@spec allowed_bands() :: [pos_integer()]
|
|
def allowed_bands, do: @allowed_bands
|
|
|
|
@doc """
|
|
Resolve any user-supplied band input to a canonical MHz integer.
|
|
|
|
Accepts:
|
|
|
|
* ADIF wavelength labels ("33cm", "1.25cm", "6mm" — case/whitespace
|
|
insensitive)
|
|
* numeric frequencies in MHz as a string or number ("903.100",
|
|
10368, "10368.000") — returns the nearest allowed band
|
|
* pre-canonicalized band values (902, "902") — passes through
|
|
|
|
Returns `nil` for unparseable input and for values below the 6m
|
|
amateur band (<50 MHz).
|
|
"""
|
|
@spec resolve(term()) :: pos_integer() | nil
|
|
def resolve(nil), do: nil
|
|
def resolve(""), do: nil
|
|
|
|
def resolve(mhz) when is_integer(mhz), do: nearest_band(mhz * 1.0)
|
|
def resolve(mhz) when is_float(mhz), do: nearest_band(mhz)
|
|
|
|
def resolve(value) when is_binary(value) do
|
|
normalized = normalize(value)
|
|
|
|
cond do
|
|
mhz = @band_name_to_mhz[normalized] -> mhz
|
|
freq = parse_float(normalized) -> nearest_band(freq)
|
|
true -> nil
|
|
end
|
|
end
|
|
|
|
def resolve(_), do: nil
|
|
|
|
@doc "Return `resolve/1` as a string, or `nil`."
|
|
@spec resolve_as_string(term()) :: String.t() | nil
|
|
def resolve_as_string(value) do
|
|
case resolve(value) do
|
|
nil -> nil
|
|
mhz -> Integer.to_string(mhz)
|
|
end
|
|
end
|
|
|
|
defp normalize(s) do
|
|
s
|
|
|> String.trim()
|
|
|> String.downcase()
|
|
|> String.replace(~r/\s+/, "")
|
|
end
|
|
|
|
defp parse_float(s) do
|
|
case Float.parse(s) do
|
|
{f, ""} -> f
|
|
{f, _rest} -> f
|
|
:error -> nil
|
|
end
|
|
end
|
|
|
|
# Amateur allocations: 6m is 50-54 MHz, 2m is 144-148 MHz, 1.25m is
|
|
# 222-225 MHz (US-only), 70cm is 420-450 MHz (US). Accept a little slop on
|
|
# each side to tolerate ADIF FREQ fields that are slightly off-band or
|
|
# informal "220" labels.
|
|
defp nearest_band(freq_mhz) when freq_mhz >= 44 and freq_mhz <= 60, do: 50
|
|
defp nearest_band(freq_mhz) when freq_mhz >= 140 and freq_mhz <= 150, do: 144
|
|
defp nearest_band(freq_mhz) when freq_mhz >= 218 and freq_mhz <= 226, do: 222
|
|
defp nearest_band(freq_mhz) when freq_mhz >= 420 and freq_mhz <= 450, do: 432
|
|
|
|
defp nearest_band(freq_mhz) when freq_mhz >= 900 do
|
|
Enum.min_by(@allowed_bands, &abs(&1 - freq_mhz))
|
|
end
|
|
|
|
defp nearest_band(_), do: nil
|
|
end
|