feat: Add database support for APRS items/objects and DAO extension

- Added item_name, object_name fields to store names
- Added is_item, is_object boolean flags for filtering
- Added dao field to store DAO extension data for extra position precision
- Automatic detection during packet processing to populate these fields
- Added database indexes for efficient queries
- Now preserving itemname as item_name and dao data instead of discarding

This enables future features like:
- Showing/hiding items and objects separately from stations
- Querying for specific objects (events, repeaters, etc.)
- Using DAO data for high-precision applications

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Graham McIntire 2025-08-04 10:19:34 -05:00
parent b63c3c547d
commit cd79c3df7e
No known key found for this signature in database
4 changed files with 100 additions and 6 deletions

View file

@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- Database fields for APRS items and objects support:
- `item_name`, `object_name` fields to identify items/objects
- `is_item`, `is_object` boolean flags for filtering
- `dao` field to store DAO (Datum As Offset) extension data for extra position precision
- Automatic detection of items/objects during packet processing
- Database indexes for efficient item/object queries
### Fixed
- Fixed packet storage failure in production due to non-schema fields from updated APRS parser
- Remove `type`, `digipeaters`, `daodatumbyte`, `mbits`, `message`, `phg`, `wx`, `resultcode`, `resultmsg` fields before database insertion

View file

@ -96,6 +96,15 @@ defmodule Aprsme.Packet do
field(:device_identifier, :string)
# APRS Items/Objects support
field(:item_name, :string)
field(:object_name, :string)
field(:is_item, :boolean, default: false)
field(:is_object, :boolean, default: false)
# DAO (Datum As Offset) extension for extra position precision
field(:dao, :map)
embeds_one(:data_extended, DataExtended)
timestamps(type: :utc_datetime)
@ -172,7 +181,12 @@ defmodule Aprsme.Packet do
:messaging,
:rain_midnight,
:has_weather,
:device_identifier
:device_identifier,
:item_name,
:object_name,
:is_item,
:is_object,
:dao
])
|> validate_required([
:base_callsign,

View file

@ -303,6 +303,9 @@ defmodule Aprsme.PacketConsumer do
# Extract additional data from the parsed packet including raw packet
attrs = Aprsme.Packet.extract_additional_data(attrs, attrs[:raw_packet] || "")
# Detect and set item/object fields
attrs = detect_item_or_object(attrs)
# Normalize data_type to string if it's an atom
attrs = normalize_data_type(attrs)
@ -384,15 +387,11 @@ defmodule Aprsme.PacketConsumer do
|> Map.delete("gpsfixstatus")
|> Map.delete(:raw_weather_data)
|> Map.delete("raw_weather_data")
# Additional fields found in production logs
|> Map.delete(:dao)
|> Map.delete("dao")
# Additional fields that need coordinate name mapping
|> Map.delete(:longitude)
|> Map.delete("longitude")
|> Map.delete(:latitude)
|> Map.delete("latitude")
|> Map.delete(:itemname)
|> Map.delete("itemname")
end
# Helper functions for coordinate validation and point creation
@ -424,6 +423,57 @@ defmodule Aprsme.PacketConsumer do
defp valid_packet?(%{sender: sender}) when is_binary(sender) and byte_size(sender) > 0, do: true
defp valid_packet?(_), do: false
# Detect if packet is an item or object and set appropriate fields
defp detect_item_or_object(attrs) do
cond do
# Check for itemname field from parser
Map.has_key?(attrs, :itemname) or Map.has_key?(attrs, "itemname") ->
item_name = Map.get(attrs, :itemname) || Map.get(attrs, "itemname")
attrs
|> Map.put(:item_name, item_name)
|> Map.put(:is_item, true)
|> Map.delete(:itemname)
|> Map.delete("itemname")
# Check for object data type and extract object name from data_extended
attrs[:data_type] == "object" or attrs["data_type"] == "object" ->
object_name = extract_object_name(attrs)
attrs
|> Map.put(:object_name, object_name)
|> Map.put(:is_object, true)
# Check information field for object format (starts with ;)
is_binary(attrs[:information_field]) and String.starts_with?(attrs[:information_field], ";") ->
# Extract object name from information field
object_name = extract_object_name_from_info_field(attrs[:information_field])
attrs
|> Map.put(:object_name, object_name)
|> Map.put(:is_object, true)
true ->
attrs
end
end
defp extract_object_name(attrs) do
case attrs[:data_extended] do
%{name: name} when is_binary(name) -> name
%{"name" => name} when is_binary(name) -> name
_ -> nil
end
end
defp extract_object_name_from_info_field(info_field) do
# Object format: ;OBJECTNAM*DDHHMMz...
case Regex.run(~r/^;([A-Z0-9 ]{9})\*/, info_field) do
[_, name] -> String.trim(name)
_ -> nil
end
end
# Convert latitude/longitude to lat/lon if present at top level
defp convert_coordinate_field_names(attrs) do
attrs

View file

@ -0,0 +1,22 @@
defmodule Aprsme.Repo.Migrations.AddItemAndDaoFields do
use Ecto.Migration
def change do
alter table(:packets) do
# For APRS items/objects
add :item_name, :string
add :object_name, :string
add :is_item, :boolean, default: false
add :is_object, :boolean, default: false
# DAO extension data for extra position precision
add :dao, :map
end
# Add indexes for querying items/objects
create index(:packets, [:is_item])
create index(:packets, [:is_object])
create index(:packets, [:item_name])
create index(:packets, [:object_name])
end
end