add more device ids

This commit is contained in:
Graham McIntire 2025-06-18 12:31:39 -05:00
parent 369ce05f1a
commit 5c171e6acb
No known key found for this signature in database
6 changed files with 226 additions and 41 deletions

View file

@ -13,13 +13,6 @@ config :aprs, AprsWeb.Endpoint, cache_static_manifest: "priv/static/cache_manife
# Runtime production configuration, including reading
# Do not print debug messages in production
# of environment variables, is done on config/runtime.exs.
config :logger, level: :info
# Configures Swoosh API Client
config :swoosh, :api_client, Aprs.Finch
config :esbuild,
version: "0.17.11",
default: [
@ -27,3 +20,10 @@ config :esbuild,
cd: Path.expand("../assets", __DIR__),
env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
]
# Do not print debug messages in production
# of environment variables, is done on config/runtime.exs.
config :logger, level: :info
# Configures Swoosh API Client
config :swoosh, :api_client, Aprs.Finch

View file

@ -0,0 +1,80 @@
defmodule Aprs.DeviceIdentification do
@moduledoc """
Handles APRS device identification based on the APRS device identification database.
"""
@doc """
Identifies the manufacturer and model of an APRS device based on its symbol pattern.
Returns a tuple of {manufacturer, model} or "Unknown" if the device cannot be identified.
"""
@spec identify_device(String.t()) :: String.t()
def identify_device(symbols) do
case symbols do
# Original MIC-E devices
" " <> <<0, 0>> -> "Original MIC-E"
# Kenwood devices
">" <> <<0>> <> "^" -> "Kenwood TH-D74"
">" <> <<0, 0>> -> "Kenwood TH-D74A"
"]" <> <<0>> <> "=" -> "Kenwood DM-710"
"]" <> <<0, 0>> -> "Kenwood DM-700"
# Yaesu devices
"`_" <> " " -> "Yaesu VX-8"
"`_" <> "\"" -> "Yaesu FTM-350"
"`_" <> "#" -> "Yaesu VX-8G"
"`_" <> "$" -> "Yaesu FT1D"
"`_" <> "%" -> "Yaesu FTM-400DR"
"`_" <> ")" -> "Yaesu FTM-100D"
"`_" <> "(" -> "Yaesu FT2D"
# Other devices
"` X" -> "AP510"
"`" <> <<0, 0>> -> "Mic-Emsg"
"'|3" -> "Byonics TinyTrack3"
"'|4" -> "Byonics TinyTrack4"
"':4" -> "SCS GmbH & Co. P4dragon DR-7400 modems"
"':8" -> "SCS GmbH & Co. P4dragon DR-7800 modems"
"'" <> <<0, 0>> -> "McTrackr"
<<0>> <> "\"" <> <<0>> -> "Hamhud"
<<0>> <> "/" <> <<0>> -> "Argent"
<<0>> <> "^" <> <<0>> -> "HinzTec anyfrog"
<<0>> <> "*" <> <<0>> -> "APOZxx www.KissOZ.dk Tracker. OZ1EKD and OZ7HVO"
<<0>> <> "~" <> <<0>> -> "Other"
# Unknown devices
_ -> "Unknown"
end
end
@doc """
Returns a list of all known device manufacturers.
"""
@spec known_manufacturers() :: [String.t()]
def known_manufacturers do
[
"Original MIC-E",
"Kenwood",
"Yaesu",
"AP510",
"Byonics",
"SCS GmbH & Co.",
"McTrackr",
"Hamhud",
"Argent",
"HinzTec",
"APOZxx",
"Other"
]
end
@doc """
Returns a list of all known device models for a given manufacturer.
"""
@spec known_models(String.t()) :: [String.t()]
def known_models(manufacturer) do
case manufacturer do
"Kenwood" -> ["TH-D74", "TH-D74A", "DM-710", "DM-700"]
"Yaesu" -> ["VX-8", "FTM-350", "VX-8G", "FT1D", "FTM-400DR", "FTM-100D", "FT2D"]
"Byonics" -> ["TinyTrack3", "TinyTrack4"]
"SCS GmbH & Co." -> ["P4dragon DR-7400 modems", "P4dragon DR-7800 modems"]
_ -> []
end
end
end

View file

@ -9,8 +9,16 @@
</.live_title>
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
<link phx-track-static rel="stylesheet" href={~p"/assets/vendor/leaflet/leaflet.css"} />
<script defer phx-track-static type="text/javascript" src={~p"/assets/vendor/leaflet/leaflet.js"} data-source-map="false"></script>
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}></script>
<script
defer
phx-track-static
type="text/javascript"
src={~p"/assets/vendor/leaflet/leaflet.js"}
data-source-map="false"
>
</script>
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
</script>
</head>
<body class={body_class(assigns)}>
{@inner_content}

View file

@ -644,31 +644,9 @@ defmodule Parser do
end
@spec parse_manufacturer(binary()) :: String.t()
def parse_manufacturer(<<" ", _::binary-size(2)>>), do: "Original MIC-E"
def parse_manufacturer(<<">", _, "^">>), do: "Kenwood TH-D74"
def parse_manufacturer(<<">", _::binary-size(2)>>), do: "Kenwood TH-D74A"
def parse_manufacturer(<<"]", _, "=">>), do: "Kenwood DM-710"
def parse_manufacturer(<<"]", _::binary-size(2)>>), do: "Kenwood DM-700"
def parse_manufacturer(<<"`", "_", " ">>), do: "Yaesu VX-8"
def parse_manufacturer(<<"`", "_", "\"">>), do: "Yaesu FTM-350"
def parse_manufacturer(<<"`", "_", "#">>), do: "Yaesu VX-8G"
def parse_manufacturer(<<"`", "_", "$">>), do: "Yaesu FT1D"
def parse_manufacturer(<<"`", "_", "%">>), do: "Yaesu FTM-400DR"
def parse_manufacturer(<<"`", "_", ")">>), do: "Yaesu FTM-100D"
def parse_manufacturer(<<"`", "_", "(">>), do: "Yaesu FT2D"
def parse_manufacturer(<<"`", " ", "X">>), do: "AP510"
def parse_manufacturer(<<"`", _::binary-size(2)>>), do: "Mic-Emsg"
def parse_manufacturer(<<"'", "|", "3">>), do: "Byonics TinyTrack3"
def parse_manufacturer(<<"'", "|", "4">>), do: "Byonics TinyTrack4"
def parse_manufacturer(<<"'", ":", "4">>), do: "SCS GmbH & Co. P4dragon DR-7400 modems"
def parse_manufacturer(<<"'", ":", "8">>), do: "SCS GmbH & Co. P4dragon DR-7800 modems"
def parse_manufacturer(<<"'", _::binary-size(2)>>), do: "McTrackr"
def parse_manufacturer(<<_, "\"", _>>), do: "Hamhud ?"
def parse_manufacturer(<<_, "/", _>>), do: "Argent ?"
def parse_manufacturer(<<_, "^", _>>), do: "HinzTec anyfrog"
def parse_manufacturer(<<_, "*", _>>), do: "APOZxx www.KissOZ.dk Tracker. OZ1EKD and OZ7HVO"
def parse_manufacturer(<<_, "~", _>>), do: "Other"
def parse_manufacturer(_), do: "Unknown"
def parse_manufacturer(symbols) do
Aprs.DeviceIdentification.identify_device(symbols)
end
@spec find_matches(Regex.t(), String.t()) :: [String.t()]
defp find_matches(regex, text) do

View file

@ -0,0 +1,92 @@
defmodule Aprs.DeviceIdentificationTest do
use ExUnit.Case
alias Aprs.DeviceIdentification
describe "identify_device/1" do
test "identifies Original MIC-E devices" do
assert DeviceIdentification.identify_device(" " <> <<0, 0>>) == "Original MIC-E"
end
test "identifies Kenwood devices" do
assert DeviceIdentification.identify_device(">" <> <<0>> <> "^") == "Kenwood TH-D74"
assert DeviceIdentification.identify_device(">" <> <<0, 0>>) == "Kenwood TH-D74A"
assert DeviceIdentification.identify_device("]" <> <<0>> <> "=") == "Kenwood DM-710"
assert DeviceIdentification.identify_device("]" <> <<0, 0>>) == "Kenwood DM-700"
end
test "identifies Yaesu devices" do
assert DeviceIdentification.identify_device("`_" <> " ") == "Yaesu VX-8"
assert DeviceIdentification.identify_device("`_" <> "\"") == "Yaesu FTM-350"
assert DeviceIdentification.identify_device("`_" <> "#") == "Yaesu VX-8G"
assert DeviceIdentification.identify_device("`_" <> "$") == "Yaesu FT1D"
assert DeviceIdentification.identify_device("`_" <> "%") == "Yaesu FTM-400DR"
assert DeviceIdentification.identify_device("`_" <> ")") == "Yaesu FTM-100D"
assert DeviceIdentification.identify_device("`_" <> "(") == "Yaesu FT2D"
end
test "identifies other devices" do
assert DeviceIdentification.identify_device("` X") == "AP510"
assert DeviceIdentification.identify_device("`" <> <<0, 0>>) == "Mic-Emsg"
assert DeviceIdentification.identify_device("'|3") == "Byonics TinyTrack3"
assert DeviceIdentification.identify_device("'|4") == "Byonics TinyTrack4"
assert DeviceIdentification.identify_device("':4") ==
"SCS GmbH & Co. P4dragon DR-7400 modems"
assert DeviceIdentification.identify_device("':8") ==
"SCS GmbH & Co. P4dragon DR-7800 modems"
assert DeviceIdentification.identify_device("'" <> <<0, 0>>) == "McTrackr"
assert DeviceIdentification.identify_device(<<0>> <> "\"" <> <<0>>) == "Hamhud"
assert DeviceIdentification.identify_device(<<0>> <> "/" <> <<0>>) == "Argent"
assert DeviceIdentification.identify_device(<<0>> <> "^" <> <<0>>) == "HinzTec anyfrog"
assert DeviceIdentification.identify_device(<<0>> <> "*" <> <<0>>) ==
"APOZxx www.KissOZ.dk Tracker. OZ1EKD and OZ7HVO"
assert DeviceIdentification.identify_device(<<0>> <> "~" <> <<0>>) == "Other"
end
test "returns Unknown for unidentified devices" do
assert DeviceIdentification.identify_device(<<0, 0, 0>>) == "Unknown"
end
end
describe "known_manufacturers/0" do
test "returns list of known manufacturers" do
manufacturers = DeviceIdentification.known_manufacturers()
assert is_list(manufacturers)
assert "Kenwood" in manufacturers
assert "Yaesu" in manufacturers
assert "Byonics" in manufacturers
end
end
describe "known_models/1" do
test "returns list of known models for Kenwood" do
models = DeviceIdentification.known_models("Kenwood")
assert is_list(models)
assert "TH-D74" in models
assert "TH-D74A" in models
assert "DM-710" in models
assert "DM-700" in models
end
test "returns list of known models for Yaesu" do
models = DeviceIdentification.known_models("Yaesu")
assert is_list(models)
assert "VX-8" in models
assert "FTM-350" in models
assert "VX-8G" in models
assert "FT1D" in models
assert "FTM-400DR" in models
assert "FTM-100D" in models
assert "FT2D" in models
end
test "returns empty list for unknown manufacturer" do
assert DeviceIdentification.known_models("Unknown") == []
end
end
end

View file

@ -297,16 +297,34 @@ defmodule Parser.ParserTest do
test "handles various timestamp errors" do
# Invalid hour (>23)
result = Parser.parse_position_with_timestamp(false, "252345z4903.50N/07201.75W>", :timestamped_position)
result =
Parser.parse_position_with_timestamp(
false,
"252345z4903.50N/07201.75W>",
:timestamped_position
)
assert result.data_type == :position
assert result.time =~ "252345"
# Invalid minute (>59)
result = Parser.parse_position_with_timestamp(false, "096045z4903.50N/07201.75W>", :timestamped_position)
result =
Parser.parse_position_with_timestamp(
false,
"096045z4903.50N/07201.75W>",
:timestamped_position
)
assert result.data_type == :position
# Invalid second (>59)
result = Parser.parse_position_with_timestamp(false, "092361z4903.50N/07201.75W>", :timestamped_position)
result =
Parser.parse_position_with_timestamp(
false,
"092361z4903.50N/07201.75W>",
:timestamped_position
)
assert result.data_type == :position
# Wrong format/length
@ -316,7 +334,13 @@ defmodule Parser.ParserTest do
test "handles MDHM timestamp format errors" do
# Invalid MDHM format
result = Parser.parse_position_with_timestamp(false, "9999999/4903.50N/07201.75W>", :timestamped_position)
result =
Parser.parse_position_with_timestamp(
false,
"9999999/4903.50N/07201.75W>",
:timestamped_position
)
assert result.data_type == :position
end
end
@ -475,10 +499,13 @@ defmodule Parser.ParserTest do
%{matcher: "':4", result: "SCS GmbH & Co. P4dragon DR-7400 modems"},
%{matcher: "':8", result: "SCS GmbH & Co. P4dragon DR-7800 modems"},
%{matcher: "'" <> <<0, 0>>, result: "McTrackr"},
%{matcher: <<0>> <> "\"" <> <<0>>, result: "Hamhud ?"},
%{matcher: <<0>> <> "/" <> <<0>>, result: "Argent ?"},
%{matcher: <<0>> <> "\"" <> <<0>>, result: "Hamhud"},
%{matcher: <<0>> <> "/" <> <<0>>, result: "Argent"},
%{matcher: <<0>> <> "^" <> <<0>>, result: "HinzTec anyfrog"},
%{matcher: <<0>> <> "*" <> <<0>>, result: "APOZxx www.KissOZ.dk Tracker. OZ1EKD and OZ7HVO"},
%{
matcher: <<0>> <> "*" <> <<0>>,
result: "APOZxx www.KissOZ.dk Tracker. OZ1EKD and OZ7HVO"
},
%{matcher: <<0>> <> "~" <> <<0>>, result: "Other"},
%{matcher: <<0, 0, 0>>, result: "Unknown"}
],