prop/lib/microwaveprop/commercial/poll_worker.ex
Graham McIntire 10e8feb486
Add commercial link monitoring via SNMP polling
Poll UBNT AirFiber radios (AF11X + AF60-LR) every 5 minutes via
net-snmp CLI, storing signal metrics in commercial_samples. Fetches
ASOS weather alongside each cycle for propagation correlation.

Includes 7 seeded link definitions, Oban cron worker, and net-snmp
in the Docker image.
2026-03-30 13:02:59 -05:00

80 lines
2.1 KiB
Elixir

defmodule Microwaveprop.Commercial.PollWorker do
@moduledoc false
use Oban.Worker, queue: :commercial, max_attempts: 1
alias Microwaveprop.Commercial
alias Microwaveprop.Commercial.SnmpClient
alias Microwaveprop.Weather
alias Microwaveprop.Weather.IemClient
require Logger
@impl Oban.Worker
def perform(%Oban.Job{}) do
links = Commercial.enabled_links()
poll_and_record(links, &SnmpClient.poll/3)
fetch_weather(links)
:ok
end
def poll_and_record(links, poll_fn) do
now = DateTime.utc_now()
Enum.each(links, fn link ->
case poll_fn.(link.host, link.community, link.radio_type) do
{:ok, data} ->
attrs =
data
|> Map.put(:link_id, link.id)
|> Map.put(:sampled_at, now)
case Commercial.create_sample(attrs) do
{:ok, _sample} ->
Logger.info("Recorded sample for #{link.label}")
{:error, changeset} ->
Logger.warning("Failed to save sample for #{link.label}: #{inspect(changeset.errors)}")
end
{:error, reason} ->
Logger.warning("SNMP poll failed for #{link.label} (#{link.host}): #{inspect(reason)}")
end
end)
end
defp fetch_weather(links) do
stations =
links
|> Enum.map(& &1.weather_station)
|> Enum.reject(&is_nil/1)
|> Enum.uniq()
now = DateTime.utc_now()
start_dt = DateTime.add(now, -3600, :second)
Enum.each(stations, fn station_code ->
{:ok, station} =
Weather.find_or_create_station(%{
station_code: station_code,
station_type: "asos",
name: station_code,
lat: 0.0,
lon: 0.0
})
case IemClient.fetch_asos(station_code, start_dt, now) do
{:ok, rows} ->
Enum.each(rows, fn row ->
if row.observed_at, do: Weather.upsert_surface_observation(station, row)
end)
Logger.info("Fetched #{length(rows)} ASOS observations for #{station_code}")
{:error, reason} ->
Logger.warning("ASOS fetch failed for #{station_code}: #{inspect(reason)}")
end
end)
end
end