New /eme page — antenna aiming + link budget for moonbounce.
Given a station (callsign / grid / lat,lon), TX power, antenna gain,
detection bandwidth, and system Tsys, the page shows in real time:
- Moon azimuth / elevation from the observer (ticks every 10 s)
- Slant range to the Moon
- EIRP breakdown: TX(dBm) + gain(dBi) = EIRP(dBm)
- Path-loss decomposition (Earth → Moon → Earth):
free-space spreading (×2)
− moon reflection gain 10·log(4π σ / λ²) where σ = ρ·π·R²
= round-trip path loss
- Band-dependent lunar albedo ρ (VHF ≈ 0.065, microwave ≈ 0.08,
mm-wave ≈ 0.02) surfaced as a named line item with the current
ρ shown to three decimals
- Received power, thermal noise floor, and SNR with a badge that
classifies the margin (strong / marginal / below noise)
- Self-echo Doppler shift with its radial-velocity driver
New modules:
- `Microwaveprop.Moon` — low-precision lunar ephemeris
(Astronomical Almanac Sec. D.4 truncated series). Provides
`julian_day/1`, `geocentric/1`, `observer_position/3`, and
`radial_velocity_km_s/3`. Accurate to ~0.3° position / ~200 km
distance — well inside any amateur EME-dish beamwidth.
- `Microwaveprop.Propagation.Eme` — path-loss helpers:
`moon_albedo/1` (band lookup), `moon_rcs_m2/1`,
`fspl_round_trip_db/2`, `moon_reflection_gain_db/2`,
`path_loss_db/3`, `received_power_dbm/1`, `noise_floor_dbm/2`,
`snr_db/1`, `doppler_shift_hz/2`.
Wired into the main nav ("EME" button) and the router between
/path and /rover.
Tests: 3 properties + 41 unit tests cover JD conversion, moon
position envelope, band-dependent albedo lookup, decomposition
identity, linearity/monotonicity of path loss and Doppler, and a
LiveView integration test against the rendered DOM.
Full suite: 2,460 tests + 170 properties; credo strict clean.
58 lines
1.7 KiB
Elixir
58 lines
1.7 KiB
Elixir
defmodule MicrowavepropWeb.EmeLiveTest do
|
|
use MicrowavepropWeb.ConnCase, async: true
|
|
|
|
import Phoenix.LiveViewTest
|
|
|
|
describe "GET /eme" do
|
|
test "renders the empty form when no source is supplied", %{conn: conn} do
|
|
{:ok, _view, html} = live(conn, ~p"/eme")
|
|
|
|
assert html =~ "EME Calculator"
|
|
assert html =~ "Station (callsign, grid, or"
|
|
assert html =~ "Calculate"
|
|
# No aim readout yet.
|
|
refute html =~ "Antenna aim"
|
|
end
|
|
|
|
test "renders the live Moon aim + link budget when a grid source is provided", %{conn: conn} do
|
|
{:ok, _view, html} = live(conn, ~p"/eme?source=EM13&band=1296&tx_power_dbm=50&tx_gain_dbi=30")
|
|
|
|
assert html =~ "Antenna aim"
|
|
assert html =~ "Link budget"
|
|
assert html =~ "EM13"
|
|
assert html =~ "Azimuth"
|
|
assert html =~ "Elevation"
|
|
assert html =~ "Slant range"
|
|
# Path loss should be in the high 200s (dB) for 1296 MHz.
|
|
assert html =~ "dB"
|
|
end
|
|
|
|
test "submitting the form patches the URL with the supplied params", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/eme")
|
|
|
|
view
|
|
|> form("form",
|
|
source: "EM13",
|
|
band: "432",
|
|
tx_power_dbm: "60",
|
|
tx_gain_dbi: "25",
|
|
bandwidth_hz: "100",
|
|
t_sys_k: "150"
|
|
)
|
|
|> render_submit()
|
|
|
|
# Flush any pending assign updates to catch redirects/patches.
|
|
html = render(view)
|
|
|
|
assert html =~ "EM13"
|
|
assert html =~ "Antenna aim"
|
|
end
|
|
|
|
test "coordinate pair 'lat,lon' resolves without touching the callsign API", %{conn: conn} do
|
|
{:ok, _view, html} = live(conn, ~p"/eme?source=32.9,-97.0&band=1296")
|
|
assert html =~ "Antenna aim"
|
|
assert html =~ "32.9"
|
|
assert html =~ "-97.0"
|
|
end
|
|
end
|
|
end
|