feat(rover): add ModeThresholds for SSB/CW/Q65 SNR table

This commit is contained in:
Graham McIntire 2026-04-25 15:59:52 -05:00
parent 418f6426e6
commit cf7dec79cf
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
2 changed files with 70 additions and 0 deletions

View file

@ -0,0 +1,35 @@
defmodule Microwaveprop.Propagation.ModeThresholds do
@moduledoc """
Required-SNR thresholds (dB) for the modes the rover planner supports.
The rover predicts a per-link SNR margin; subtracting the mode's required
SNR yields "dB above decode threshold". Numbers come from the rover spec
SSB needs full quieting, CW pulls signals well below the noise floor, and
Q65 modes go deeper still.
"""
@thresholds %{
ssb: 0.0,
cw: -18.0,
q65_60a: -24.0,
q65_120b: -26.0
}
@modes [
{:ssb, "SSB"},
{:cw, "CW"},
{:q65_60a, "Q65-60A"},
{:q65_120b, "Q65-120B"}
]
@type mode :: :ssb | :cw | :q65_60a | :q65_120b
@spec required_snr_db(mode()) :: float()
def required_snr_db(mode) when is_map_key(@thresholds, mode), do: Map.fetch!(@thresholds, mode)
@spec modes() :: [{mode(), String.t()}]
def modes, do: @modes
@spec default_mode() :: mode()
def default_mode, do: :ssb
end

View file

@ -0,0 +1,35 @@
defmodule Microwaveprop.Propagation.ModeThresholdsTest do
use ExUnit.Case, async: true
alias Microwaveprop.Propagation.ModeThresholds
describe "required_snr_db/1" do
test "returns the documented thresholds for each supported mode" do
assert ModeThresholds.required_snr_db(:ssb) == 0.0
assert ModeThresholds.required_snr_db(:cw) == -18.0
assert ModeThresholds.required_snr_db(:q65_60a) == -24.0
assert ModeThresholds.required_snr_db(:q65_120b) == -26.0
end
test "raises FunctionClauseError for unknown modes" do
assert_raise FunctionClauseError, fn -> ModeThresholds.required_snr_db(:fm) end
end
end
describe "modes/0" do
test "returns ordered {atom, label} pairs covering every threshold" do
modes = ModeThresholds.modes()
assert {:ssb, "SSB"} in modes
assert {:cw, "CW"} in modes
assert {:q65_60a, "Q65-60A"} in modes
assert {:q65_120b, "Q65-120B"} in modes
assert length(modes) == 4
end
end
describe "default_mode/0" do
test "returns the SSB atom" do
assert ModeThresholds.default_mode() == :ssb
end
end
end