Aprs (aprs v2.0.1)
View SourceMain APRS packet parsing library.
parse/1 accepts a TNC2-format packet (SRC>DST,PATH:information) and
returns {:ok, map} or {:error, reason}.
Units
Every packet format reports the same units, so a caller does not have to know how a packet happened to encode a value:
speed- knots, floataltitude- feet, floatcourse- degrees, integer; 360 is due north, 0 (or absent) is unknownposresolution- metres, float
Summary
Functions
Parse a TNC2-format packet (SRC>DST,PATH:information).
Split a callsign into [base_callsign, ssid], validating it as AX.25.
Parse an information field that has already been classified by
parse_datatype/1.
Determine the data type of an information field.
parse_datatype/1 wrapped in an :ok tuple, for use in a with chain.
parse_position_without_timestamp/1 with the messaging flag set, for the =
data type indicator.
Parse the body of a timestamped position report (/ or @), with the data
type indicator already removed.
Parse the body of an untimestamped position report (! or =), compressed
or uncompressed, with the data type indicator already removed.
Split a packet into [sender, path, information_field].
Split the path into [destination, digipeater_path].
Returns the version of the library, the same string mix.exs declares.
Types
@type coordinate() :: float() | nil
@type packet() :: %{ :id => String.t(), :sender => String.t(), :path => String.t(), :destination => String.t(), :information_field => String.t(), :data_type => atom(), :base_callsign => String.t(), :ssid => String.t() | nil, :data_extended => map() | nil, :received_at => DateTime.t(), optional(atom()) => term() }
@type position_ambiguity() :: 0..4
@type position_data() :: %{ :latitude => coordinate(), :longitude => coordinate(), optional(:timestamp) => String.t() | integer() | nil, optional(:symbol_table_id) => String.t() | nil, optional(:symbol_code) => String.t() | nil, optional(:comment) => String.t(), optional(:altitude) => float() | nil, optional(:phg) => String.t() | nil, optional(:aprs_messaging?) => boolean(), optional(:compressed?) => boolean(), optional(:position_ambiguity) => position_ambiguity(), optional(:dao) => map() | nil, optional(:course) => integer() | nil, optional(:speed) => float() | nil, optional(:has_position) => boolean(), optional(:data_type) => atom() }
Functions
@spec parse(term()) :: parse_result()
Parse a TNC2-format packet (SRC>DST,PATH:information).
Returns {:ok, packet}, or {:error, reason} for a packet that is not
parseable at all - a malformed header, an invalid callsign or path, or a
body larger than 8192 bytes. A packet whose header is well formed but whose
information field is not always parses; the trouble is reported in
data_type (:unknown_datatype, :malformed_position, :mic_e_error, and
so on) rather than as an error.
The returned map carries the packet envelope, the type-specific fields under
data_extended, those same fields flattened into the top level, and the
reference-parser field names (srccallsign, symboltable, posambiguity,
...). See the README for the full field list.
Examples
iex> {:ok, packet} = Aprs.parse("N0CALL>APRS,TCPIP*,qAC,T2TEST:=4903.50N/07201.75W-Hi")
iex> {packet.data_type, packet.latitude, packet.comment}
{:position_with_message, 49.05833333333333, "Hi"}
iex> Aprs.parse("not a packet")
{:error, :invalid_packet}
Split a callsign into [base_callsign, ssid], validating it as AX.25.
The SSID is "0" when the callsign carries none.
Examples
iex> Aprs.parse_callsign("N0CALL-9")
{:ok, ["N0CALL", "9"]}
iex> Aprs.parse_callsign("N0CALL")
{:ok, ["N0CALL", "0"]}
Parse an information field that has already been classified by
parse_datatype/1.
destination is the TOCALL, which Mic-E packets need for the latitude. The
returned map always carries a data_type, which may be more specific than
the one passed in (a :weather position, a :message_ack, a
:mic_e_error). Returns nil when the field cannot be parsed at all.
Determine the data type of an information field.
Returns the atom for the data type indicator, :empty for an empty field,
or :unknown_datatype for an indicator that is not recognised and holds no
! within its first 40 bytes.
Examples
iex> Aprs.parse_datatype("=4903.50N/07201.75W-")
:position_with_message
iex> Aprs.parse_datatype("T#005,199,000,255,073,123,01101001")
:telemetry
iex> Aprs.parse_datatype("~nonsense")
:unknown_datatype
parse_datatype/1 wrapped in an :ok tuple, for use in a with chain.
Examples
iex> Aprs.parse_datatype_safe("!4903.50N/07201.75W-")
{:ok, :position}
parse_position_without_timestamp/1 with the messaging flag set, for the =
data type indicator.
Parse the body of a timestamped position report (/ or @), with the data
type indicator already removed.
The leading seven bytes are the timestamp; the rest is parsed as an
untimestamped position. aprs_messaging? is true for @.
Parse the body of an untimestamped position report (! or =), compressed
or uncompressed, with the data type indicator already removed.
Returns the position fields plus everything the comment carried: a data
extension, altitude, telemetry, DAO and, on the weather symbol, a weather
report. Coordinates that do not decode give data_type: :malformed_position
and has_position: false rather than an error.
Split a packet into [sender, path, information_field].
path is everything between > and the first :, destination included.
Examples
iex> Aprs.split_packet("N0CALL>APRS,TCPIP*:>Hello")
{:ok, ["N0CALL", "APRS,TCPIP*", ">Hello"]}
iex> Aprs.split_packet("N0CALL")
{:error, :invalid_packet}
Split the path into [destination, digipeater_path].
The digipeater path is "" when the packet has no digipeaters.
Examples
iex> Aprs.split_path("APRS,WIDE1-1,WIDE2-1")
{:ok, ["APRS", "WIDE1-1,WIDE2-1"]}
iex> Aprs.split_path("APRS")
{:ok, ["APRS", ""]}
@spec version() :: String.t()
Returns the version of the library, the same string mix.exs declares.
Examples
iex> [major, minor, patch] = String.split(Aprs.version(), ".")
iex> Enum.all?([major, minor, patch], &match?({_, ""}, Integer.parse(&1)))
true