more parsing fixes
This commit is contained in:
parent
388783233f
commit
8ea697f39e
2 changed files with 188 additions and 180 deletions
183
lib/parser.ex
183
lib/parser.ex
|
|
@ -6,6 +6,7 @@ defmodule Parser do
|
||||||
"""
|
"""
|
||||||
# import Bitwise
|
# import Bitwise
|
||||||
alias Aprs.Convert
|
alias Aprs.Convert
|
||||||
|
alias Parser.MicE
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
|
@ -221,8 +222,8 @@ defmodule Parser do
|
||||||
def parse_datatype(_), do: :unknown_datatype
|
def parse_datatype(_), do: :unknown_datatype
|
||||||
|
|
||||||
@spec parse_data(atom(), String.t(), String.t()) :: map() | nil
|
@spec parse_data(atom(), String.t(), String.t()) :: map() | nil
|
||||||
def parse_data(:mic_e, destination, data), do: parse_mic_e(data, destination)
|
def parse_data(:mic_e, destination, data), do: MicE.parse(data, destination)
|
||||||
def parse_data(:mic_e_old, destination, data), do: parse_mic_e(data, destination)
|
def parse_data(:mic_e_old, destination, data), do: MicE.parse(data, destination)
|
||||||
|
|
||||||
def parse_data(:position, destination, <<"!", rest::binary>>) do
|
def parse_data(:position, destination, <<"!", rest::binary>>) do
|
||||||
parse_data(:position, destination, rest)
|
parse_data(:position, destination, rest)
|
||||||
|
|
@ -687,184 +688,6 @@ defmodule Parser do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec parse_mic_e(binary(), String.t()) :: map()
|
|
||||||
def parse_mic_e(data, destination \\ nil) do
|
|
||||||
case data do
|
|
||||||
<<dti::binary-size(1), latitude::binary-size(6), longitude::binary-size(7), symbol_code::binary-size(1),
|
|
||||||
speed::binary-size(1), course::binary-size(1), symbol_table_id::binary-size(1), rest::binary>> ->
|
|
||||||
# Try to extract lat/lon from destination if provided
|
|
||||||
dest_pos = if destination, do: parse_mic_e_destination(destination), else: %{}
|
|
||||||
%{latitude: lat, longitude: lon} = parse_aprs_position(latitude, longitude)
|
|
||||||
|
|
||||||
%{
|
|
||||||
latitude: lat || Map.get(dest_pos, :latitude),
|
|
||||||
longitude: lon || Map.get(dest_pos, :longitude),
|
|
||||||
symbol_table_id: symbol_table_id,
|
|
||||||
symbol_code: symbol_code,
|
|
||||||
speed: speed,
|
|
||||||
course: course,
|
|
||||||
dti: dti,
|
|
||||||
comment: rest,
|
|
||||||
data_type: :mic_e
|
|
||||||
}
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec parse_mic_e_digit(binary()) :: [integer() | atom() | nil]
|
|
||||||
def parse_mic_e_digit(<<c>>) when c in ?0..?9, do: [c - ?0, 0, nil]
|
|
||||||
def parse_mic_e_digit(<<c>>) when c in ?A..?J, do: [c - ?A, 1, :custom]
|
|
||||||
def parse_mic_e_digit(<<c>>) when c in ?P..?Y, do: [c - ?P, 1, :standard]
|
|
||||||
def parse_mic_e_digit(<<"K">>), do: [0, 1, :custom]
|
|
||||||
def parse_mic_e_digit(<<"L">>), do: [0, 0, nil]
|
|
||||||
def parse_mic_e_digit(<<"Z">>), do: [0, 1, :standard]
|
|
||||||
def parse_mic_e_digit(_), do: [:unknown, :unknown, :unknown]
|
|
||||||
|
|
||||||
@spec parse_mic_e_destination(String.t()) :: map()
|
|
||||||
def parse_mic_e_destination(destination_field) do
|
|
||||||
digits =
|
|
||||||
destination_field
|
|
||||||
|> String.codepoints()
|
|
||||||
|> Enum.map(&parse_mic_e_digit/1)
|
|
||||||
|> Enum.map(&hd/1)
|
|
||||||
|
|
||||||
deg = digits |> Enum.slice(0..1) |> Enum.join() |> String.to_integer()
|
|
||||||
min = digits |> Enum.slice(2..3) |> Enum.join() |> String.to_integer()
|
|
||||||
fractional = digits |> Enum.slice(4..5) |> Enum.join() |> String.to_integer()
|
|
||||||
|
|
||||||
[ns, lo, ew] = destination_field |> to_charlist() |> Enum.slice(3..5)
|
|
||||||
|
|
||||||
north_south_indicator =
|
|
||||||
case ns do
|
|
||||||
x when x in ?0..?9 -> :south
|
|
||||||
x when x == ?L -> :south
|
|
||||||
x when x in ?P..?Z -> :north
|
|
||||||
_ -> :unknown
|
|
||||||
end
|
|
||||||
|
|
||||||
east_west_indicator =
|
|
||||||
case ew do
|
|
||||||
x when x in ?0..?9 -> :east
|
|
||||||
x when x == ?L -> :east
|
|
||||||
x when x in ?P..?Z -> :west
|
|
||||||
_ -> :unknown
|
|
||||||
end
|
|
||||||
|
|
||||||
longitude_offset =
|
|
||||||
case lo do
|
|
||||||
x when x in ?0..?9 -> 0
|
|
||||||
x when x == ?L -> 0
|
|
||||||
x when x in ?P..?Z -> 100
|
|
||||||
_ -> :unknown
|
|
||||||
end
|
|
||||||
|
|
||||||
statuses = [
|
|
||||||
"Emergency",
|
|
||||||
"Priority",
|
|
||||||
"Special",
|
|
||||||
"Committed",
|
|
||||||
"Returning",
|
|
||||||
"In Service",
|
|
||||||
"En Route",
|
|
||||||
"Off Duty"
|
|
||||||
]
|
|
||||||
|
|
||||||
message_digits =
|
|
||||||
destination_field
|
|
||||||
|> String.codepoints()
|
|
||||||
|> Enum.take(3)
|
|
||||||
|
|
||||||
[_, message_bit_1, message_type] = parse_mic_e_digit(Enum.at(message_digits, 0))
|
|
||||||
[_, message_bit_2, _] = parse_mic_e_digit(Enum.at(message_digits, 1))
|
|
||||||
[_, message_bit_3, _] = parse_mic_e_digit(Enum.at(message_digits, 2))
|
|
||||||
|
|
||||||
# Convert the bits to binary to get the array index
|
|
||||||
index = message_bit_1 * 4 + message_bit_2 * 2 + message_bit_3
|
|
||||||
# need to invert this from the actual array index
|
|
||||||
display_index = (7 - index) |> to_string() |> String.pad_leading(2, "0")
|
|
||||||
|
|
||||||
[message_code, message_description] =
|
|
||||||
case message_type do
|
|
||||||
:standard ->
|
|
||||||
["M" <> display_index, Enum.at(statuses, index)]
|
|
||||||
|
|
||||||
:custom ->
|
|
||||||
["C" <> display_index, "Custom-#{display_index}"]
|
|
||||||
|
|
||||||
nil ->
|
|
||||||
["", Enum.at(statuses, index)]
|
|
||||||
end
|
|
||||||
|
|
||||||
%{
|
|
||||||
lat_degrees: deg,
|
|
||||||
lat_minutes: min,
|
|
||||||
lat_fractional: fractional,
|
|
||||||
lat_direction: north_south_indicator,
|
|
||||||
lon_direction: east_west_indicator,
|
|
||||||
longitude_offset: longitude_offset,
|
|
||||||
message_code: message_code,
|
|
||||||
message_description: message_description
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_mic_e_information(
|
|
||||||
<<dti::binary-size(1), d28::integer, m28::integer, f28::integer, sp28::integer, dc28::integer, se28::integer,
|
|
||||||
symbol::binary-size(1), table::binary-size(1), message::binary>> = _information_field,
|
|
||||||
longitude_offset
|
|
||||||
) do
|
|
||||||
m =
|
|
||||||
case m28 - 28 do
|
|
||||||
x when x >= 60 -> x - 60
|
|
||||||
x -> x
|
|
||||||
end
|
|
||||||
|
|
||||||
sp =
|
|
||||||
case sp28 - 28 do
|
|
||||||
x when x >= 80 -> x - 80
|
|
||||||
x -> x
|
|
||||||
end
|
|
||||||
|
|
||||||
dc = dc28 - 28
|
|
||||||
quotient = div(dc, 10)
|
|
||||||
remainder = rem(dc, 10)
|
|
||||||
dc = sp * 10 + quotient
|
|
||||||
heading = (remainder - 4) * 100 + (se28 - 28)
|
|
||||||
|
|
||||||
# Messages should at least have a starting and ending symbol, and an optional message in between
|
|
||||||
# But, there might not be any symbols either, so it could look like any of the following:
|
|
||||||
# >^ <- TH-D74
|
|
||||||
# nil <- who knows
|
|
||||||
# ]\"55}146.820 MHz T103 -0600= <- Kenwood DM-710
|
|
||||||
|
|
||||||
regex = ~r/^(?<first>.?)(?<msg>.*)(?<secondtolast>.)(?<last>.)$/i
|
|
||||||
result = Parser.Helpers.find_matches(regex, message)
|
|
||||||
|
|
||||||
symbol1 =
|
|
||||||
if result["first"] == "" do
|
|
||||||
result["secondtolast"]
|
|
||||||
else
|
|
||||||
result["first"]
|
|
||||||
end
|
|
||||||
|
|
||||||
manufacturer =
|
|
||||||
Parser.Helpers.parse_manufacturer(symbol1 <> result["secondtolast"] <> result["last"])
|
|
||||||
|
|
||||||
%{
|
|
||||||
dti: dti,
|
|
||||||
lon_degrees: d28 - 28 + longitude_offset,
|
|
||||||
lon_minutes: m,
|
|
||||||
lon_fractional: f28 - 28,
|
|
||||||
speed: dc,
|
|
||||||
heading: heading,
|
|
||||||
symbol: symbol,
|
|
||||||
table: table,
|
|
||||||
manufacturer: manufacturer,
|
|
||||||
message: message
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec parse_manufacturer(binary()) :: String.t()
|
@spec parse_manufacturer(binary()) :: String.t()
|
||||||
def parse_manufacturer(symbols) do
|
def parse_manufacturer(symbols) do
|
||||||
Aprs.DeviceIdentification.identify_device(symbols)
|
Aprs.DeviceIdentification.identify_device(symbols)
|
||||||
|
|
|
||||||
185
lib/parser/mic_e.ex
Normal file
185
lib/parser/mic_e.ex
Normal file
|
|
@ -0,0 +1,185 @@
|
||||||
|
defmodule Parser.MicE do
|
||||||
|
@moduledoc """
|
||||||
|
Parses Mic-E encoded APRS packets.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@spec parse(binary(), String.t()) :: map()
|
||||||
|
def parse(data, destination \\ nil) do
|
||||||
|
with {:ok, dest_info} <- parse_destination(destination),
|
||||||
|
{:ok, info_info} <- parse_information(data, dest_info.longitude_offset) do
|
||||||
|
lat =
|
||||||
|
Decimal.add(
|
||||||
|
Decimal.new(dest_info.lat_degrees),
|
||||||
|
Decimal.div(
|
||||||
|
Decimal.add(
|
||||||
|
Decimal.new(dest_info.lat_minutes),
|
||||||
|
dest_info.lat_hundredths |> Decimal.new() |> Decimal.div(100)
|
||||||
|
),
|
||||||
|
60
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
lat = if dest_info.lat_direction == :south, do: Decimal.negate(lat), else: lat
|
||||||
|
|
||||||
|
lon =
|
||||||
|
Decimal.add(
|
||||||
|
Decimal.new(info_info.lon_degrees),
|
||||||
|
Decimal.div(
|
||||||
|
Decimal.add(
|
||||||
|
Decimal.new(info_info.lon_minutes),
|
||||||
|
info_info.lon_hundredths |> Decimal.new() |> Decimal.div(100)
|
||||||
|
),
|
||||||
|
60
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
lon = if dest_info.lon_direction == :west, do: Decimal.negate(lon), else: lon
|
||||||
|
|
||||||
|
%{
|
||||||
|
latitude: lat,
|
||||||
|
longitude: lon,
|
||||||
|
message_bits: dest_info.message_bits,
|
||||||
|
message_type: dest_info.message_type,
|
||||||
|
speed: info_info.speed,
|
||||||
|
course: info_info.course,
|
||||||
|
symbol_code: info_info.symbol_code,
|
||||||
|
symbol_table_id: info_info.symbol_table_id,
|
||||||
|
comment: info_info.comment,
|
||||||
|
data_type: :mic_e
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_error ->
|
||||||
|
%{
|
||||||
|
latitude: nil,
|
||||||
|
longitude: nil,
|
||||||
|
error: "Failed to parse Mic-E packet",
|
||||||
|
data_type: :mic_e_error
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp parse_destination(destination) do
|
||||||
|
if byte_size(destination) == 6 do
|
||||||
|
try do
|
||||||
|
<<c1, c2, c3, c4, c5, c6>> = destination
|
||||||
|
|
||||||
|
d1 = decode_digit(c1)
|
||||||
|
d2 = decode_digit(c2)
|
||||||
|
d3 = decode_digit(c3)
|
||||||
|
d4 = decode_digit(c4)
|
||||||
|
d5 = decode_digit(c5)
|
||||||
|
d6 = decode_digit(c6)
|
||||||
|
|
||||||
|
lat_degrees = d1.digit * 10 + d2.digit
|
||||||
|
lat_minutes = d3.digit * 10 + d4.digit
|
||||||
|
lat_hundredths = d5.digit * 10 + d6.digit
|
||||||
|
|
||||||
|
lat_direction =
|
||||||
|
case c4 do
|
||||||
|
c when c in ?0..?9 -> :south
|
||||||
|
?L -> :south
|
||||||
|
c when c in ?P..?Z -> :north
|
||||||
|
_ -> :unknown
|
||||||
|
end
|
||||||
|
|
||||||
|
longitude_offset =
|
||||||
|
case c5 do
|
||||||
|
c when c in ?0..?9 -> 0
|
||||||
|
?L -> 0
|
||||||
|
c when c in ?P..?Z -> 100
|
||||||
|
_ -> 0
|
||||||
|
end
|
||||||
|
|
||||||
|
lon_direction =
|
||||||
|
case c6 do
|
||||||
|
c when c in ?0..?9 -> :east
|
||||||
|
?L -> :east
|
||||||
|
c when c in ?P..?Z -> :west
|
||||||
|
_ -> :unknown
|
||||||
|
end
|
||||||
|
|
||||||
|
message_bits = {d1.msg_bit, d2.msg_bit, d3.msg_bit}
|
||||||
|
|
||||||
|
message_type =
|
||||||
|
cond do
|
||||||
|
d1.msg_type != nil -> d1.msg_type
|
||||||
|
d2.msg_type != nil -> d2.msg_type
|
||||||
|
d3.msg_type != nil -> d3.msg_type
|
||||||
|
true -> nil
|
||||||
|
end
|
||||||
|
|
||||||
|
{:ok,
|
||||||
|
%{
|
||||||
|
lat_degrees: lat_degrees,
|
||||||
|
lat_minutes: lat_minutes,
|
||||||
|
lat_hundredths: lat_hundredths,
|
||||||
|
lat_direction: lat_direction,
|
||||||
|
lon_direction: lon_direction,
|
||||||
|
longitude_offset: longitude_offset,
|
||||||
|
message_bits: message_bits,
|
||||||
|
message_type: message_type
|
||||||
|
}}
|
||||||
|
rescue
|
||||||
|
_ -> {:error, :invalid_character_in_destination}
|
||||||
|
end
|
||||||
|
else
|
||||||
|
{:error, :invalid_destination_length}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp decode_digit(char) do
|
||||||
|
case char do
|
||||||
|
c when c in ?0..?9 -> %{digit: c - ?0, msg_bit: 0, msg_type: nil}
|
||||||
|
c when c in ?A..?K -> %{digit: c - ?A, msg_bit: 1, msg_type: :custom}
|
||||||
|
?L -> %{digit: 0, msg_bit: 0, msg_type: nil}
|
||||||
|
c when c in ?P..?Z -> %{digit: c - ?P, msg_bit: 1, msg_type: :standard}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp parse_information(data, lon_offset) do
|
||||||
|
if byte_size(data) < 8 do
|
||||||
|
{:error, :invalid_information_field_length}
|
||||||
|
else
|
||||||
|
<<lon_deg_c, lon_min_c, lon_hmin_c, sp_c, dc_c, se_c, symbol_code, symbol_table_id, comment::binary>> = data
|
||||||
|
|
||||||
|
lon_deg =
|
||||||
|
case lon_deg_c - 28 do
|
||||||
|
d when d >= 108 and d <= 117 -> d - 80
|
||||||
|
d when d >= 118 and d <= 127 -> d - 190
|
||||||
|
d -> d
|
||||||
|
end + lon_offset
|
||||||
|
|
||||||
|
lon_min =
|
||||||
|
case lon_min_c - 28 do
|
||||||
|
m when m >= 60 -> m - 60
|
||||||
|
m -> m
|
||||||
|
end
|
||||||
|
|
||||||
|
lon_hmin = lon_hmin_c - 28
|
||||||
|
|
||||||
|
sp = sp_c - 28
|
||||||
|
dc = dc_c - 28
|
||||||
|
se = se_c - 28
|
||||||
|
|
||||||
|
speed = div(sp, 10) * 100 + rem(sp, 10) * 10 + div(dc, 10)
|
||||||
|
speed = if speed >= 800, do: speed - 800, else: speed
|
||||||
|
# Convert to knots from mph
|
||||||
|
speed = speed * 0.868976
|
||||||
|
|
||||||
|
course = rem(dc, 10) * 100 + se
|
||||||
|
course = if course >= 400, do: course - 400, else: course
|
||||||
|
|
||||||
|
{:ok,
|
||||||
|
%{
|
||||||
|
lon_degrees: lon_deg,
|
||||||
|
lon_minutes: lon_min,
|
||||||
|
lon_hundredths: lon_hmin,
|
||||||
|
speed: speed,
|
||||||
|
course: course,
|
||||||
|
symbol_code: <<symbol_code>>,
|
||||||
|
symbol_table_id: <<symbol_table_id>>,
|
||||||
|
comment: comment
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Add table
Reference in a new issue