Add beacon keying field (on/off or FSK)

This commit is contained in:
Graham McIntire 2026-04-08 16:13:25 -05:00
parent 73112eb44b
commit f5b5af171e
6 changed files with 65 additions and 2 deletions

View file

@ -11,6 +11,10 @@ defmodule Microwaveprop.Beacons.Beacon do
alias Microwaveprop.Radio.Maidenhead
@keyings ~w(on_off fsk)
def keyings, do: @keyings
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "beacons" do
@ -23,20 +27,32 @@ defmodule Microwaveprop.Beacons.Beacon do
field :height_ft, :float
field :on_the_air, :boolean, default: true
field :approved, :boolean, default: false
field :keying, :string, default: "on_off"
field :user_id, :binary_id
timestamps(type: :utc_datetime)
end
@required_fields [:frequency_mhz, :callsign, :lat, :lon, :power_mw, :height_ft]
@required_fields [:frequency_mhz, :callsign, :lat, :lon, :power_mw, :height_ft, :keying]
def changeset(beacon, attrs) do
beacon
|> cast(attrs, [:frequency_mhz, :callsign, :grid, :lat, :lon, :power_mw, :height_ft, :on_the_air])
|> cast(attrs, [
:frequency_mhz,
:callsign,
:grid,
:lat,
:lon,
:power_mw,
:height_ft,
:on_the_air,
:keying
])
|> update_change(:callsign, fn cs -> cs && String.upcase(String.trim(cs)) end)
|> maybe_fill_latlon()
|> maybe_fill_grid()
|> validate_required(@required_fields)
|> validate_inclusion(:keying, @keyings)
|> validate_number(:frequency_mhz, greater_than: 0)
|> validate_number(:power_mw, greater_than_or_equal_to: 0)
|> validate_number(:height_ft, greater_than_or_equal_to: 0)

View file

@ -43,6 +43,13 @@ defmodule MicrowavepropWeb.BeaconLive.Form do
step="any"
required
/>
<.input
field={@form[:keying]}
type="select"
label="Keying"
options={[{"On/Off", "on_off"}, {"FSK", "fsk"}]}
required
/>
<.input field={@form[:on_the_air]} type="checkbox" label="On the air" />
<footer>
<.button phx-disable-with="Saving..." variant="primary">Save Beacon</.button>

View file

@ -34,6 +34,7 @@ defmodule MicrowavepropWeb.BeaconLive.Index do
<:col :let={{_id, beacon}} label="Lon">{beacon.lon}</:col>
<:col :let={{_id, beacon}} label="EIRP (mW)">{format_mw(beacon.power_mw)}</:col>
<:col :let={{_id, beacon}} label="Height AGL (ft)">{beacon.height_ft}</:col>
<:col :let={{_id, beacon}} label="Keying">{keying_label(beacon.keying)}</:col>
<:col :let={{_id, beacon}} label="On air">
<span class={["badge badge-sm", (beacon.on_the_air && "badge-success") || "badge-ghost"]}>
{if beacon.on_the_air, do: "Yes", else: "No"}
@ -168,6 +169,10 @@ defmodule MicrowavepropWeb.BeaconLive.Index do
defp authenticated?(%{user: %{}}), do: true
defp authenticated?(_), do: false
defp keying_label("on_off"), do: "On/Off"
defp keying_label("fsk"), do: "FSK"
defp keying_label(other), do: other
# Format a milliwatt power value without scientific notation.
# Keeps up to 3 decimal places, drops trailing zeros.
defp format_mw(nil), do: ""

View file

@ -94,6 +94,7 @@ defmodule MicrowavepropWeb.BeaconLive.Show do
<:item title="Longitude">{@beacon.lon}</:item>
<:item title="TX power (EIRP) (mW)">{@beacon.power_mw}</:item>
<:item title="Height above ground (ft)">{@beacon.height_ft}</:item>
<:item title="Keying">{keying_label(@beacon.keying)}</:item>
<:item title="On the air">{if @beacon.on_the_air, do: "Yes", else: "No"}</:item>
</.list>
</Layouts.app>
@ -148,4 +149,8 @@ defmodule MicrowavepropWeb.BeaconLive.Show do
defp admin?(%{user: %{is_admin: true}}), do: true
defp admin?(_), do: false
defp keying_label("on_off"), do: "On/Off"
defp keying_label("fsk"), do: "FSK"
defp keying_label(other), do: other
end

View file

@ -0,0 +1,9 @@
defmodule Microwaveprop.Repo.Migrations.AddKeyingToBeacons do
use Ecto.Migration
def change do
alter table(:beacons) do
add :keying, :string, null: false, default: "on_off"
end
end
end

View file

@ -135,6 +135,27 @@ defmodule Microwaveprop.BeaconsTest do
assert beacon.on_the_air == false
end
test "defaults keying to on_off" do
user = user_fixture()
{:ok, beacon} = Beacons.create_beacon(user, valid_beacon_attrs())
assert beacon.keying == "on_off"
end
test "accepts keying: fsk" do
user = user_fixture()
{:ok, beacon} = Beacons.create_beacon(user, valid_beacon_attrs(keying: "fsk"))
assert beacon.keying == "fsk"
end
test "rejects an unknown keying" do
user = user_fixture()
assert {:error, changeset} =
Beacons.create_beacon(user, valid_beacon_attrs(keying: "cw"))
assert "is invalid" in errors_on(changeset).keying
end
test "upcases the callsign" do
user = user_fixture()
{:ok, beacon} = Beacons.create_beacon(user, valid_beacon_attrs(callsign: "w5hn"))