diff --git a/lib/microwaveprop_web/live/beacon_live/show.ex b/lib/microwaveprop_web/live/beacon_live/show.ex index 3b97918a..5d2b8334 100644 --- a/lib/microwaveprop_web/live/beacon_live/show.ex +++ b/lib/microwaveprop_web/live/beacon_live/show.ex @@ -285,11 +285,41 @@ defmodule MicrowavepropWeb.BeaconLive.Show do "dst_height_ft" => to_string(beacon.height_ft) } - params = maybe_put(base, "band", BandResolver.resolve_as_string(beacon.frequency_mhz)) + {tx_power_dbm, src_gain_dbi} = baseline_tx_for_eirp(beacon.power_mw) + + params = + base + |> maybe_put("band", BandResolver.resolve_as_string(beacon.frequency_mhz)) + |> maybe_put("tx_power_dbm", tx_power_dbm) + |> maybe_put("src_gain_dbi", src_gain_dbi) "/path?" <> URI.encode_query(params) end + # Splits the beacon's EIRP (the beacon schema's `power_mw` field is + # already an EIRP value, per Beacons.RangeEstimate) into a + # tx_power_dbm + src_gain_dbi pair so the path calculator's TX chain + # emits the same EIRP as the beacon. We default the antenna gain to + # 30 dBi (typical 2-3 ft microwave dish) and derive the TX power from + # there, clamping to a 0 dBm / 1 mW minimum so very-low-EIRP beacons + # produce sane baseline values instead of -10 dBm absurdities. + defp baseline_tx_for_eirp(nil), do: {nil, nil} + defp baseline_tx_for_eirp(power_mw) when power_mw <= 0, do: {nil, nil} + + defp baseline_tx_for_eirp(power_mw) do + eirp_dbm = 10.0 * :math.log10(power_mw) + default_gain = 30.0 + min_tx_dbm = 0.0 + + tx_dbm = max(min_tx_dbm, eirp_dbm - default_gain) + gain_dbi = eirp_dbm - tx_dbm + + {format_dbm(tx_dbm), format_dbi(gain_dbi)} + end + + defp format_dbm(v), do: v |> Float.round(1) |> to_string() + defp format_dbi(v), do: v |> Float.round(1) |> to_string() + defp precise_grid(%Beacon{lat: lat, lon: lon}) when is_number(lat) and is_number(lon) do Maidenhead.from_latlon(lat, lon, 8) end diff --git a/test/microwaveprop_web/live/beacon_live_test.exs b/test/microwaveprop_web/live/beacon_live_test.exs index 5a011acb..e9c1d027 100644 --- a/test/microwaveprop_web/live/beacon_live_test.exs +++ b/test/microwaveprop_web/live/beacon_live_test.exs @@ -92,6 +92,25 @@ defmodule MicrowavepropWeb.BeaconLiveTest do {:ok, _view, html} = live(conn, ~p"/beacons/#{beacon}") assert html =~ "Pending approval" end + + test "plot-path-to-beacon link pre-populates tx_power + gain to match beacon EIRP", + %{conn: conn} do + # power_mw: 10_000 → EIRP 40 dBm → default-gain 30 dBi → tx 10 dBm + beacon = approved_beacon_fixture(user_fixture(), power_mw: 10_000.0) + {:ok, _view, html} = live(conn, ~p"/beacons/#{beacon}") + + assert html =~ "tx_power_dbm=10.0" + assert html =~ "src_gain_dbi=30.0" + end + + test "low-EIRP beacon clamps tx_power to 0 dBm floor and lowers gain", %{conn: conn} do + # power_mw: 100 → EIRP 20 dBm → tx 0 dBm (clamped), gain 20 dBi + beacon = approved_beacon_fixture(user_fixture(), power_mw: 100.0, callsign: "W5LOW") + {:ok, _view, html} = live(conn, ~p"/beacons/#{beacon}") + + assert html =~ "tx_power_dbm=0.0" + assert html =~ "src_gain_dbi=20.0" + end end describe "Form" do