diff --git a/docs/mobile-api.md b/docs/mobile-api.md index db0c232..08bcf6b 100644 --- a/docs/mobile-api.md +++ b/docs/mobile-api.md @@ -44,7 +44,7 @@ Currently, connections are anonymous. Future versions will support token-based a ### Subscribing to Geographic Bounds -Subscribe to receive packets within specific geographic bounds. +Subscribe to receive packets within specific geographic bounds. Upon subscription, the server will immediately send historical packets within the bounds, followed by real-time streaming packets. **Event:** `subscribe_bounds` @@ -54,10 +54,23 @@ Subscribe to receive packets within specific geographic bounds. "north": 33.2, "south": 33.0, "east": -96.0, - "west": -96.2 + "west": -96.2, + "limit": 1000, + "hours_back": 1 } ``` +**Payload Fields:** + +| Field | Type | Required | Default | Description | +|-------|------|----------|---------|-------------| +| `north` | float | Yes | - | Northern boundary latitude | +| `south` | float | Yes | - | Southern boundary latitude | +| `east` | float | Yes | - | Eastern boundary longitude | +| `west` | float | Yes | - | Western boundary longitude | +| `limit` | integer | No | 1000 | Maximum historical packets to load (max: 5000) | +| `hours_back` | integer | No | 1 | How many hours of historical data to load (max: 24) | + **Example:** ```json { @@ -67,7 +80,9 @@ Subscribe to receive packets within specific geographic bounds. "north": 33.2, "south": 33.0, "east": -96.0, - "west": -96.2 + "west": -96.2, + "limit": 2000, + "hours_back": 2 }, "ref": "2" } @@ -133,7 +148,7 @@ Update the geographic bounds (e.g., when user pans/zooms the map). ### Receiving Packets -Once subscribed, you'll receive real-time packets within your bounds. +Once subscribed, you'll receive packets within your bounds. Historical packets are sent immediately after subscription, followed by real-time streaming packets as they arrive. **Event:** `packet` diff --git a/lib/aprsme_web/channels/mobile_channel.ex b/lib/aprsme_web/channels/mobile_channel.ex index efe584d..e7ede07 100644 --- a/lib/aprsme_web/channels/mobile_channel.ex +++ b/lib/aprsme_web/channels/mobile_channel.ex @@ -53,7 +53,11 @@ defmodule AprsmeWeb.MobileChannel do end @impl true - def handle_in("subscribe_bounds", %{"north" => north, "south" => south, "east" => east, "west" => west}, socket) do + def handle_in( + "subscribe_bounds", + %{"north" => north, "south" => south, "east" => east, "west" => west} = payload, + socket + ) do bounds = %{ north: ensure_float(north), south: ensure_float(south), @@ -62,24 +66,28 @@ defmodule AprsmeWeb.MobileChannel do } # Validate bounds - with :ok <- validate_bounds(bounds) do - # Generate unique client ID - client_id = "mobile_#{:erlang.phash2(self())}" + case validate_bounds(bounds) do + :ok -> + # Generate unique client ID + client_id = "mobile_#{:erlang.phash2(self())}" - # Subscribe to StreamingPacketsPubSub with bounds - Aprsme.StreamingPacketsPubSub.subscribe_to_bounds(self(), bounds) + # Subscribe to StreamingPacketsPubSub with bounds + Aprsme.StreamingPacketsPubSub.subscribe_to_bounds(self(), bounds) - # Store client info in socket - socket = - socket - |> assign(:client_id, client_id) - |> assign(:bounds, bounds) - |> assign(:subscribed, true) + # Store client info in socket + socket = + socket + |> assign(:client_id, client_id) + |> assign(:bounds, bounds) + |> assign(:subscribed, true) - Logger.info("Mobile client #{client_id} subscribed to bounds: #{inspect(bounds)}") + Logger.info("Mobile client #{client_id} subscribed to bounds: #{inspect(bounds)}") + + # Load and send historical packets + socket = load_historical_packets(socket, bounds, payload) + + {:reply, {:ok, %{bounds: bounds, message: "Subscribed to packet stream"}}, socket} - {:reply, {:ok, %{bounds: bounds, message: "Subscribed to packet stream"}}, socket} - else {:error, reason} -> {:reply, {:error, %{message: reason}}, socket} end @@ -194,7 +202,7 @@ defmodule AprsmeWeb.MobileChannel do callsign: get_field(packet, :sender) || get_field(packet, :base_callsign), lat: to_float(lat), lng: to_float(lon), - timestamp: get_field(packet, :received_at) |> format_timestamp(), + timestamp: packet |> get_field(:received_at) |> format_timestamp(), symbol_table_id: get_field(packet, :symbol_table_id, "/"), symbol_code: get_field(packet, :symbol_code, ">"), comment: get_field(packet, :comment), @@ -238,4 +246,53 @@ defmodule AprsmeWeb.MobileChannel do end defp format_timestamp(_), do: nil + + defp load_historical_packets(socket, bounds, payload) do + # Get optional parameters from payload + limit = Map.get(payload, "limit", 1000) + hours_back = Map.get(payload, "hours_back", 1) + + # Ensure reasonable limits + limit = min(limit, 5000) + hours_back = min(hours_back, 24) + + # Query historical packets within bounds + bounds_list = [ + bounds.west, + bounds.south, + bounds.east, + bounds.north + ] + + params = %{ + bounds: bounds_list, + limit: limit, + offset: 0, + hours_back: hours_back + } + + Logger.debug("Mobile client loading historical packets with params: #{inspect(params)}") + + packets = + try do + Aprsme.Packets.get_recent_packets(params) + rescue + error -> + Logger.error("Error loading historical packets for mobile client: #{inspect(error)}") + [] + end + + Logger.info("Loaded #{length(packets)} historical packets for mobile client") + + # Send historical packets to client + if length(packets) > 0 do + # Convert packets to mobile format and send them + Enum.each(packets, fn packet -> + packet_data = build_mobile_packet(packet) + push(socket, "packet", packet_data) + end) + end + + socket + end end