live update info page
This commit is contained in:
parent
7a36960eb6
commit
a78fb8a385
8 changed files with 181 additions and 3 deletions
|
|
@ -12,12 +12,16 @@ defmodule Aprsme.DeviceParser do
|
|||
cond do
|
||||
Map.has_key?(packet_data, :destination) and not is_nil(packet_data[:destination]) ->
|
||||
packet_data[:destination]
|
||||
|
||||
Map.has_key?(packet_data, "destination") and not is_nil(packet_data["destination"]) ->
|
||||
packet_data["destination"]
|
||||
|
||||
Map.has_key?(packet_data, :device_identifier) and not is_nil(packet_data[:device_identifier]) ->
|
||||
packet_data[:device_identifier]
|
||||
|
||||
Map.has_key?(packet_data, "device_identifier") and not is_nil(packet_data["device_identifier"]) ->
|
||||
packet_data["device_identifier"]
|
||||
|
||||
true ->
|
||||
extract_from_data_extended(packet_data)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ defmodule Aprsme.Packets do
|
|||
parsed_device_id = Aprsme.DeviceParser.extract_device_identifier(packet_data)
|
||||
device_id = parsed_device_id || Map.get(packet_attrs, :destination)
|
||||
packet_attrs = Map.put(packet_attrs, :device_identifier, device_id)
|
||||
|
||||
# Logger.debug("Inserting packet with device_identifier=#{inspect(device_id)}, destination=#{inspect(Map.get(packet_attrs, :destination))}")
|
||||
insert_packet(packet_attrs, packet_data)
|
||||
rescue
|
||||
|
|
|
|||
|
|
@ -11,6 +11,12 @@ defmodule AprsmeWeb.InfoLive.Show do
|
|||
@impl true
|
||||
def mount(%{"callsign" => callsign}, _session, socket) do
|
||||
normalized_callsign = String.upcase(String.trim(callsign))
|
||||
|
||||
# Subscribe to Postgres notifications for live updates
|
||||
if connected?(socket) do
|
||||
Phoenix.PubSub.subscribe(Aprsme.PubSub, "postgres:aprsme_packets")
|
||||
end
|
||||
|
||||
packet = get_latest_packet(normalized_callsign)
|
||||
neighbors = get_neighbors(packet, normalized_callsign)
|
||||
|
||||
|
|
@ -24,6 +30,32 @@ defmodule AprsmeWeb.InfoLive.Show do
|
|||
{:ok, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({:postgres_packet, packet}, socket) do
|
||||
# Only update if the packet is for our callsign
|
||||
if packet_matches_callsign?(packet, socket.assigns.callsign) do
|
||||
# Refresh data when new packet arrives
|
||||
packet = get_latest_packet(socket.assigns.callsign)
|
||||
neighbors = get_neighbors(packet, socket.assigns.callsign)
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(:packet, packet)
|
||||
|> assign(:neighbors, neighbors)
|
||||
|
||||
{:noreply, socket}
|
||||
else
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info(_message, socket), do: {:noreply, socket}
|
||||
|
||||
defp packet_matches_callsign?(packet, callsign) do
|
||||
packet_sender = Map.get(packet, "sender") || Map.get(packet, :sender, "")
|
||||
String.upcase(packet_sender) == String.upcase(callsign)
|
||||
end
|
||||
|
||||
defp get_latest_packet(callsign) do
|
||||
%{callsign: callsign, limit: 1}
|
||||
|> Packets.get_recent_packets()
|
||||
|
|
|
|||
|
|
@ -180,7 +180,9 @@
|
|||
<%= if @packet.manufacturer && @packet.manufacturer != "" do %>
|
||||
{@packet.manufacturer} {@packet.equipment_type || ""}
|
||||
<% else %>
|
||||
<span class="text-gray-500 font-mono">{@packet.device_identifier || "Unknown"}</span>
|
||||
<span class="text-gray-500 font-mono">
|
||||
{@packet.device_identifier || "Unknown"}
|
||||
</span>
|
||||
<% end %>
|
||||
</dd>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,12 @@ defmodule AprsmeWeb.WeatherLive.CallsignView do
|
|||
@impl true
|
||||
def mount(%{"callsign" => callsign}, _session, socket) do
|
||||
normalized_callsign = String.upcase(String.trim(callsign))
|
||||
|
||||
# Subscribe to Postgres notifications for live updates
|
||||
if connected?(socket) do
|
||||
Phoenix.PubSub.subscribe(Aprsme.PubSub, "postgres:aprsme_packets")
|
||||
end
|
||||
|
||||
weather_packet = get_latest_weather_packet(normalized_callsign)
|
||||
{start_time, end_time} = default_time_range()
|
||||
weather_history = get_weather_history(normalized_callsign, start_time, end_time)
|
||||
|
|
@ -47,6 +53,78 @@ defmodule AprsmeWeb.WeatherLive.CallsignView do
|
|||
{:ok, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({:postgres_packet, packet}, socket) do
|
||||
# Only update if the packet is for our callsign and contains weather data
|
||||
if packet_matches_callsign?(packet, socket.assigns.callsign) and has_weather_data?(packet) do
|
||||
# Refresh weather data when new weather packet arrives
|
||||
weather_packet = get_latest_weather_packet(socket.assigns.callsign)
|
||||
{start_time, end_time} = default_time_range()
|
||||
weather_history = get_weather_history(socket.assigns.callsign, start_time, end_time)
|
||||
|
||||
weather_history_json =
|
||||
weather_history
|
||||
|> Enum.map(fn pkt ->
|
||||
dew_point =
|
||||
if is_number(pkt.temperature) and is_number(pkt.humidity) do
|
||||
calc_dew_point(pkt.temperature, pkt.humidity)
|
||||
end
|
||||
|
||||
%{
|
||||
timestamp: pkt.received_at,
|
||||
temperature: pkt.temperature,
|
||||
dew_point: dew_point,
|
||||
humidity: pkt.humidity,
|
||||
pressure: pkt.pressure,
|
||||
wind_direction: pkt.wind_direction,
|
||||
wind_speed: pkt.wind_speed,
|
||||
rain_1h: pkt.rain_1h,
|
||||
rain_24h: pkt.rain_24h,
|
||||
rain_since_midnight: pkt.rain_since_midnight,
|
||||
luminosity: pkt.luminosity
|
||||
}
|
||||
end)
|
||||
|> Jason.encode!()
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(:weather_packet, weather_packet)
|
||||
|> assign(:weather_history, weather_history)
|
||||
|> assign(:weather_history_json, weather_history_json)
|
||||
|
||||
{:noreply, socket}
|
||||
else
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info(_message, socket), do: {:noreply, socket}
|
||||
|
||||
defp packet_matches_callsign?(packet, callsign) do
|
||||
packet_sender = Map.get(packet, "sender") || Map.get(packet, :sender, "")
|
||||
String.upcase(packet_sender) == String.upcase(callsign)
|
||||
end
|
||||
|
||||
defp has_weather_data?(packet) do
|
||||
# Check if packet contains any weather-related fields
|
||||
weather_fields = [
|
||||
"temperature",
|
||||
"humidity",
|
||||
"pressure",
|
||||
"wind_speed",
|
||||
"wind_direction",
|
||||
"rain_1h",
|
||||
"rain_24h",
|
||||
"rain_since_midnight",
|
||||
"luminosity"
|
||||
]
|
||||
|
||||
Enum.any?(weather_fields, fn field ->
|
||||
value = Map.get(packet, field) || Map.get(packet, String.to_atom(field))
|
||||
not is_nil(value)
|
||||
end)
|
||||
end
|
||||
|
||||
defp get_latest_weather_packet(callsign) do
|
||||
# Get weather packets from the last 7 days to find the most recent one
|
||||
end_time = DateTime.utc_now()
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@
|
|||
<dd class="mt-1 text-sm font-semibold text-gray-900">
|
||||
{get_weather_field_zero(@weather_packet, :rain_since_midnight)} in
|
||||
</dd>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<dt class="text-xs font-medium text-gray-500">Raw Packet</dt>
|
||||
<dd class="mt-1 text-xs font-mono text-gray-900 break-all">
|
||||
|
|
@ -163,7 +163,7 @@
|
|||
<% end %>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</dl>
|
||||
<div class="mt-2 text-xs text-gray-500">
|
||||
<strong>Raw comment:</strong> {@weather_packet.comment || @weather_packet["comment"]}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ defmodule Aprsme.Repo.Migrations.RecreateNotifyPacketsInsertFunction do
|
|||
BEGIN
|
||||
payload := json_build_object(
|
||||
'id', NEW.id,
|
||||
'sender', NEW.sender,
|
||||
'lat', NEW.lat,
|
||||
'lon', NEW.lon,
|
||||
'inserted_at', NEW.inserted_at
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
defmodule Aprsme.Repo.Migrations.UpdatePacketsNotificationPayload do
|
||||
use Ecto.Migration
|
||||
|
||||
def up do
|
||||
execute "DROP TRIGGER IF EXISTS packets_notify_insert ON packets;"
|
||||
execute "DROP FUNCTION IF EXISTS notify_packets_insert();"
|
||||
|
||||
execute """
|
||||
CREATE OR REPLACE FUNCTION notify_packets_insert() RETURNS trigger AS $$
|
||||
DECLARE
|
||||
payload TEXT;
|
||||
BEGIN
|
||||
payload := json_build_object(
|
||||
'id', NEW.id,
|
||||
'sender', NEW.sender,
|
||||
'lat', NEW.lat,
|
||||
'lon', NEW.lon,
|
||||
'inserted_at', NEW.inserted_at
|
||||
)::text;
|
||||
PERFORM pg_notify('aprs_packets', payload);
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
"""
|
||||
|
||||
execute """
|
||||
CREATE TRIGGER packets_notify_insert
|
||||
AFTER INSERT ON packets
|
||||
FOR EACH ROW EXECUTE FUNCTION notify_packets_insert();
|
||||
"""
|
||||
end
|
||||
|
||||
def down do
|
||||
execute "DROP TRIGGER IF EXISTS packets_notify_insert ON packets;"
|
||||
execute "DROP FUNCTION IF EXISTS notify_packets_insert();"
|
||||
|
||||
execute """
|
||||
CREATE OR REPLACE FUNCTION notify_packets_insert() RETURNS trigger AS $$
|
||||
DECLARE
|
||||
payload TEXT;
|
||||
BEGIN
|
||||
payload := json_build_object(
|
||||
'id', NEW.id,
|
||||
'lat', NEW.lat,
|
||||
'lon', NEW.lon,
|
||||
'inserted_at', NEW.inserted_at
|
||||
)::text;
|
||||
PERFORM pg_notify('aprs_packets', payload);
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
"""
|
||||
|
||||
execute """
|
||||
CREATE TRIGGER packets_notify_insert
|
||||
AFTER INSERT ON packets
|
||||
FOR EACH ROW EXECUTE FUNCTION notify_packets_insert();
|
||||
"""
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Reference in a new issue