From 4348d34f624c085be98d2f58b09bcd0e5f790535 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Sat, 12 Jul 2025 10:53:11 -0500 Subject: [PATCH] fix weather --- lib/aprsme/cached_queries.ex | 6 +- lib/aprsme/packets.ex | 14 ++-- lib/aprsme/packets/query_builder.ex | 6 +- lib/aprsme_web/live/map_live/index.ex | 4 +- lib/aprsme_web/live/map_live/packet_utils.ex | 15 ++-- .../live/weather_live/callsign_view.ex | 9 ++- .../aprsme/packet_weather_extraction_test.exs | 71 +++++++++++++++++++ 7 files changed, 110 insertions(+), 15 deletions(-) create mode 100644 test/aprsme/packet_weather_extraction_test.exs diff --git a/lib/aprsme/cached_queries.ex b/lib/aprsme/cached_queries.ex index 416a7b1..2ed3d05 100644 --- a/lib/aprsme/cached_queries.ex +++ b/lib/aprsme/cached_queries.ex @@ -93,7 +93,7 @@ defmodule Aprsme.CachedQueries do result _ -> - result = Packets.get_latest_weather_packet_optimized(callsign) + result = Packets.get_latest_weather_packet(callsign) # Cache for 5 minutes since weather updates are less frequent Cachex.put(:query_cache, cache_key, result, ttl: @cache_ttl_short) result @@ -118,7 +118,9 @@ defmodule Aprsme.CachedQueries do query = from p in Packet, where: p.sender == ^callsign, - where: p.data_type == "weather" or (p.symbol_table_id == "/" and p.symbol_code == "_"), + where: + not is_nil(p.temperature) or not is_nil(p.humidity) or not is_nil(p.pressure) or + not is_nil(p.wind_speed) or not is_nil(p.wind_direction) or not is_nil(p.rain_1h), limit: 1, select: true diff --git a/lib/aprsme/packets.ex b/lib/aprsme/packets.ex index ab97f58..d35e821 100644 --- a/lib/aprsme/packets.ex +++ b/lib/aprsme/packets.ex @@ -737,10 +737,10 @@ defmodule Aprsme.Packets do @doc """ Gets the most recent weather packet for a callsign. - Optimized to check only recent data first before expanding the search window. + Looks for any packet containing weather data fields. """ - @spec get_latest_weather_packet_optimized(String.t()) :: struct() | nil - def get_latest_weather_packet_optimized(callsign) when is_binary(callsign) do + @spec get_latest_weather_packet(String.t()) :: struct() | nil + def get_latest_weather_packet(callsign) when is_binary(callsign) do # First try last 24 hours one_day_ago = TimeUtils.one_day_ago() @@ -748,7 +748,9 @@ defmodule Aprsme.Packets do Repo.one( from(p in Packet, where: p.sender == ^callsign, - where: p.data_type == "weather", + where: + not is_nil(p.temperature) or not is_nil(p.humidity) or not is_nil(p.pressure) or + not is_nil(p.wind_speed) or not is_nil(p.wind_direction) or not is_nil(p.rain_1h), where: p.received_at >= ^one_day_ago, order_by: [desc: p.received_at], limit: 1 @@ -763,7 +765,9 @@ defmodule Aprsme.Packets do Repo.one( from(p in Packet, where: p.sender == ^callsign, - where: p.data_type == "weather", + where: + not is_nil(p.temperature) or not is_nil(p.humidity) or not is_nil(p.pressure) or + not is_nil(p.wind_speed) or not is_nil(p.wind_direction) or not is_nil(p.rain_1h), where: p.received_at >= ^one_week_ago, order_by: [desc: p.received_at], limit: 1 diff --git a/lib/aprsme/packets/query_builder.ex b/lib/aprsme/packets/query_builder.ex index 5776b61..f5d68a2 100644 --- a/lib/aprsme/packets/query_builder.ex +++ b/lib/aprsme/packets/query_builder.ex @@ -76,7 +76,11 @@ defmodule Aprsme.Packets.QueryBuilder do @spec weather_only(Ecto.Query.t()) :: Ecto.Query.t() def weather_only(query) do from p in query, - where: p.data_type == "weather" or (p.symbol_table_id == "/" and p.symbol_code == "_") + where: + not is_nil(p.temperature) or not is_nil(p.humidity) or not is_nil(p.pressure) or + not is_nil(p.wind_speed) or not is_nil(p.wind_direction) or not is_nil(p.rain_1h) or + not is_nil(p.rain_24h) or not is_nil(p.rain_since_midnight) or not is_nil(p.snow) or + not is_nil(p.luminosity) end @doc """ diff --git a/lib/aprsme_web/live/map_live/index.ex b/lib/aprsme_web/live/map_live/index.ex index 4e91314..59235e5 100644 --- a/lib/aprsme_web/live/map_live/index.ex +++ b/lib/aprsme_web/live/map_live/index.ex @@ -1464,7 +1464,9 @@ defmodule AprsmeWeb.MapLive.Index do query = from p in Aprsme.Packet, where: fragment("UPPER(?)", p.sender) in ^normalized_callsigns, - where: p.data_type == "weather" or (p.symbol_table_id == "/" and p.symbol_code == "_"), + where: + not is_nil(p.temperature) or not is_nil(p.humidity) or not is_nil(p.pressure) or + not is_nil(p.wind_speed) or not is_nil(p.wind_direction) or not is_nil(p.rain_1h), select: fragment("UPPER(?)", p.sender), distinct: true diff --git a/lib/aprsme_web/live/map_live/packet_utils.ex b/lib/aprsme_web/live/map_live/packet_utils.ex index 192142e..b5acf74 100644 --- a/lib/aprsme_web/live/map_live/packet_utils.ex +++ b/lib/aprsme_web/live/map_live/packet_utils.ex @@ -76,10 +76,17 @@ defmodule AprsmeWeb.MapLive.PacketUtils do """ @spec weather_packet?(map()) :: boolean() def weather_packet?(packet) do - data_type = get_packet_field(packet, :data_type, "") - {symbol_table_id, symbol_code} = get_symbol_info(packet) - - data_type == "weather" or (symbol_table_id == "/" and symbol_code == "_") + # Check if packet contains any weather data fields + not is_nil(get_packet_field(packet, :temperature, nil)) or + not is_nil(get_packet_field(packet, :humidity, nil)) or + not is_nil(get_packet_field(packet, :pressure, nil)) or + not is_nil(get_packet_field(packet, :wind_speed, nil)) or + not is_nil(get_packet_field(packet, :wind_direction, nil)) or + not is_nil(get_packet_field(packet, :rain_1h, nil)) or + not is_nil(get_packet_field(packet, :rain_24h, nil)) or + not is_nil(get_packet_field(packet, :rain_since_midnight, nil)) or + not is_nil(get_packet_field(packet, :snow, nil)) or + not is_nil(get_packet_field(packet, :luminosity, nil)) end @doc """ diff --git a/lib/aprsme_web/live/weather_live/callsign_view.ex b/lib/aprsme_web/live/weather_live/callsign_view.ex index ccd26be..f3c86bf 100644 --- a/lib/aprsme_web/live/weather_live/callsign_view.ex +++ b/lib/aprsme_web/live/weather_live/callsign_view.ex @@ -74,8 +74,13 @@ defmodule AprsmeWeb.WeatherLive.CallsignView do # Keep the old handler for backward compatibility def handle_info({:postgres_packet, packet}, socket) do - # Only process if it's a weather packet for this callsign - if packet.sender == socket.assigns.callsign && packet.data_type == "weather" do + # Only process if it's a packet with weather data for this callsign + has_weather_data = + not is_nil(packet.temperature) or not is_nil(packet.humidity) or + not is_nil(packet.pressure) or not is_nil(packet.wind_speed) or + not is_nil(packet.wind_direction) or not is_nil(packet.rain_1h) + + if packet.sender == socket.assigns.callsign && has_weather_data do handle_info({:weather_packet, packet}, socket) else {:noreply, socket} diff --git a/test/aprsme/packet_weather_extraction_test.exs b/test/aprsme/packet_weather_extraction_test.exs new file mode 100644 index 0000000..8bc9e08 --- /dev/null +++ b/test/aprsme/packet_weather_extraction_test.exs @@ -0,0 +1,71 @@ +defmodule Aprsme.PacketWeatherExtractionTest do + use Aprsme.DataCase + + alias Aprsme.Packet + + describe "extract_additional_data/2 with weather data" do + test "extracts weather data from parsed APRS weather packet" do + # Simulate a real weather packet + raw_packet = "AE5PL-13>API510,DSTAR*:!3240.46N/09657.49W_225/004g009t075r000p000h61b10206Plano, TX weather" + + # Parse the packet using the real APRS parser + {:ok, parsed_packet} = Aprs.parse(raw_packet) + + # Create packet data as it would come from the parser + attrs = %{ + base_callsign: "AE5PL", + ssid: "13", + sender: "AE5PL-13", + destination: "API510", + data_type: to_string(parsed_packet[:data_type]), + path: "DSTAR*", + information_field: "!3240.46N/09657.49W_225/004g009t075r000p000h61b10206Plano, TX weather", + data_extended: parsed_packet + } + + # Extract additional data + result = Packet.extract_additional_data(attrs, raw_packet) + + # Verify weather data was extracted + assert result.temperature == 75 + assert result.humidity == 61 + assert result.wind_direction == 225 + assert result.wind_speed == 4 + assert result.wind_gust == 9 + assert result.pressure == 1020.6 + assert result.rain_1h == 0 + end + + test "extracts weather data from pure weather packet type" do + # Simulate a pure weather packet (starts with _) + raw_packet = "AE5PL-WX>APRS:_07121545c220s004g009t075r000p000h61b10206" + + # Parse the packet using the real APRS parser + {:ok, parsed_packet} = Aprs.parse(raw_packet) + + # Create packet data as it would come from the parser + attrs = %{ + base_callsign: "AE5PL", + ssid: "WX", + sender: "AE5PL-WX", + destination: "APRS", + data_type: to_string(parsed_packet[:data_type]), + path: nil, + information_field: "_07121545c220s004g009t075r000p000h61b10206", + data_extended: parsed_packet + } + + # Extract additional data + result = Packet.extract_additional_data(attrs, raw_packet) + + # Verify weather data was extracted + assert result.temperature == 75 + assert result.humidity == 61 + assert result.wind_direction == 220 + assert result.wind_speed == 4 + assert result.wind_gust == 9 + assert result.pressure == 1020.6 + assert result.rain_1h == 0 + end + end +end