Packages

An elixir library for MAVLink, an application that enables communication with other systems using the MAVLink protocol over serial, UDP and TCP connections, and utility modules for performing common MAVLink commands and tasks with one or more remote vehicles.

Current section

Files

Jump to
xmavlink lib mavlink frame.ex
Raw

lib/mavlink/frame.ex

defmodule XMAVLink.Frame do
@moduledoc """
Represent and work with MAVLink v1/2 message frames
"""
require Logger
import XMAVLink.Utils, only: [x25_crc: 1, x25_crc: 2]
defstruct [
# Which raw attributes are populated?
version: nil,
payload_length: nil,
# MAVLink 2 only
incompatible_flags: 0,
# MAVLink 2 only
compatible_flags: 0,
sequence_number: nil,
source_system: nil,
source_component: nil,
# Default to broadcast assumed elsewhere
target_system: 0,
target_component: 0,
target: nil,
message_id: nil,
crc_extra: nil,
payload: nil,
checksum: nil,
# MAVLink 2 signing only (not implemented)
signature: nil,
# Original binary frame
mavlink_1_raw: nil,
mavlink_2_raw: nil,
message: nil
]
@type message :: XMAVLink.Message.t()
@type version :: 1 | 2
@type t :: %XMAVLink.Frame{
version: version,
payload_length: 1..255,
incompatible_flags: non_neg_integer,
compatible_flags: non_neg_integer,
sequence_number: 0..255,
source_system: 1..255,
source_component: 1..255,
target_system: 1..255,
target_component: 1..255,
target: :broadcast | :system | :system_component | :component,
message_id: XMAVLink.Types.message_id(),
crc_extra: XMAVLink.Types.crc_extra(),
payload: binary,
checksum: pos_integer,
mavlink_1_raw: binary,
mavlink_2_raw: binary,
message: message
}
@spec binary_to_frame_and_tail(binary) ::
{XMAVLink.Frame.t(), binary} | {nil, binary} | :not_a_frame
# MAVLink version 1
def binary_to_frame_and_tail(
raw_and_rest =
<<0xFE, payload_length::unsigned-integer-size(8),
sequence_number::unsigned-integer-size(8), source_system::unsigned-integer-size(8),
source_component::unsigned-integer-size(8), message_id::unsigned-integer-size(8),
payload::binary-size(payload_length), checksum::little-unsigned-integer-size(16),
rest::binary>>
) do
{
struct(XMAVLink.Frame,
version: 1,
payload_length: payload_length,
sequence_number: sequence_number,
source_system: source_system,
source_component: source_component,
message_id: message_id,
payload: payload,
checksum: checksum,
mavlink_1_raw:
binary_part(
raw_and_rest,
0,
byte_size(raw_and_rest) - byte_size(rest)
)
),
rest
}
end
# MAVLink version 2
def binary_to_frame_and_tail(
raw_and_rest =
<<0xFD, payload_length::unsigned-integer-size(8),
incompatible_flags::unsigned-integer-size(8),
compatible_flags::unsigned-integer-size(8), sequence_number::unsigned-integer-size(8),
source_system::unsigned-integer-size(8), source_component::unsigned-integer-size(8),
message_id::little-unsigned-integer-size(24), payload::binary-size(payload_length),
checksum::little-unsigned-integer-size(16), rest::binary>>
) do
case incompatible_flags do
0 ->
# Vanilla MAVLink 2, we can deal with this
{
struct(XMAVLink.Frame,
version: 2,
payload_length: payload_length,
incompatible_flags: 0,
compatible_flags: compatible_flags,
sequence_number: sequence_number,
source_system: source_system,
source_component: source_component,
message_id: message_id,
payload: payload,
checksum: checksum,
mavlink_2_raw:
binary_part(
raw_and_rest,
0,
byte_size(raw_and_rest) - byte_size(rest)
)
),
rest
}
_ ->
# We don't support any incompatible flags at present
# e.g. signing, so drop the frame
{nil, rest}
end
end
def binary_to_frame_and_tail(unfinished_mavlink_1_frame = <<0xFE, _::binary>>),
do: {nil, unfinished_mavlink_1_frame}
def binary_to_frame_and_tail(unfinished_mavlink_2_frame = <<0xFD, _::binary>>),
do: {nil, unfinished_mavlink_2_frame}
def binary_to_frame_and_tail(<<_, rest::binary>>), do: binary_to_frame_and_tail(rest)
def binary_to_frame_and_tail(<<>>), do: :not_a_frame
@spec validate_and_unpack(XMAVLink.Frame.t(), module) ::
{:ok, XMAVLink.Frame.t()} | :failed_to_unpack | :checksum_invalid | :unknown_message
def validate_and_unpack(
frame = %XMAVLink.Frame{message_id: message_id, version: version, payload: payload},
dialect
) do
case apply(dialect, :msg_attributes, [message_id]) do
{:ok, crc_extra, expected_length, target} ->
if frame.checksum ==
:binary.bin_to_list(
%{1 => frame.mavlink_1_raw, 2 => frame.mavlink_2_raw}[frame.version],
{1, frame.payload_length + %{1 => 5, 2 => 9}[frame.version]}
)
|> x25_crc()
|> x25_crc([crc_extra]) do
# Only used to undo MAVLink 2 payload truncation
payload_truncated_length = 8 * (expected_length - frame.payload_length)
# Too many ways for unpack to fail with dodgy messages...
try do
case apply(dialect, :unpack, [
message_id,
version,
payload <>
if(payload_truncated_length > 0 and version > 1,
do: <<0::size(payload_truncated_length)>>,
else: <<>>
)
]) do
{:ok, message} ->
case target do
:broadcast ->
{:ok,
struct(frame,
message: message,
target_system: 0,
target_component: 0,
target: target,
crc_extra: crc_extra
)}
:system ->
{:ok,
struct(frame,
message: message,
target_system: message.target_system,
target_component: 0,
target: target,
crc_extra: crc_extra
)}
:system_component ->
{:ok,
struct(frame,
message: message,
target_system: message.target_system,
target_component: message.target_component,
target: target,
crc_extra: crc_extra
)}
:component ->
{:ok,
struct(frame,
message: message,
target_system: 0,
target_component: message.target_component,
target: target,
crc_extra: crc_extra
)}
end
_ ->
:failed_to_unpack
end
rescue
_ ->
:ok =
Logger.debug(
"validate_and_unpack: Failed to unpack #{inspect(frame)}, couldn't match payload"
)
:failed_to_unpack
end
else
:ok = Logger.debug("validate_and_unpack: Checksum invalid #{inspect(frame)}")
:checksum_invalid
end
_ ->
:ok = Logger.debug("validate_and_unpack: Unknown message #{inspect(frame)}")
:unknown_message
end
end
# Pack message frame
def pack_frame(frame = %XMAVLink.Frame{version: 1}) do
payload_length = byte_size(frame.payload)
mavlink_1_frame =
<<payload_length::unsigned-integer-size(8), frame.sequence_number::unsigned-integer-size(8),
frame.source_system::unsigned-integer-size(8),
frame.source_component::unsigned-integer-size(8),
frame.message_id::little-unsigned-integer-size(8), frame.payload::binary>>
frame
|> struct(
mavlink_1_raw: <<0xFE>> <> mavlink_1_frame <> checksum(mavlink_1_frame, frame.crc_extra)
)
end
def pack_frame(frame = %XMAVLink.Frame{version: 2}) do
{truncated_length, truncated_payload} = truncate_payload(frame.payload)
mavlink_2_frame =
<<
truncated_length::unsigned-integer-size(8),
# Incompatible flags
0::unsigned-integer-size(8),
# Compatible flags
0::unsigned-integer-size(8),
frame.sequence_number::unsigned-integer-size(8),
frame.source_system::unsigned-integer-size(8),
frame.source_component::unsigned-integer-size(8),
frame.message_id::little-unsigned-integer-size(24),
truncated_payload::binary
>>
struct(frame,
mavlink_2_raw: <<0xFD>> <> mavlink_2_frame <> checksum(mavlink_2_frame, frame.crc_extra)
)
end
# MAVLink 2 truncate trailing 0s in payload
defp truncate_payload(payload) do
truncated_payload = String.replace_trailing(payload, <<0>>, "")
if byte_size(truncated_payload) == 0 do
# First byte of payload never truncated
{1, <<0>>}
else
{byte_size(truncated_payload), truncated_payload}
end
end
# Calculate checksum
defp checksum(frame, crc_extra) do
cs = x25_crc(frame <> <<crc_extra::unsigned-integer-size(8)>>)
<<cs::little-unsigned-integer-size(16)>>
end
end