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 utils.ex
Raw

lib/mavlink/utils.ex

defmodule XMAVLink.Utils do
@moduledoc ~s"""
MAVLink support functions used during code generation and runtime
Parts of this module are ported from corresponding implementations
in mavutils.py
"""
import Bitwise
import List, only: [flatten: 1]
import Enum, only: [sort_by: 2, join: 2, map: 2, reverse: 1]
@doc """
Sort parsed message fields into wire order according
to https://mavlink.io/en/guide/serialization.html
List extension fields separately so that we can
not include them for MAVLink 1 messages
"""
@spec wire_order([%{type: String.t, is_extension: boolean}]) :: [[%{}]]
def wire_order(fields) do
type_order_map = %{
uint64_t: 1,
int64_t: 1,
double: 1,
uint32_t: 2,
int32_t: 2,
float: 2,
uint16_t: 3,
int16_t: 3,
uint8_t: 4,
uint8_t_mavlink_version: 4,
int8_t: 4,
char: 4
}
[
sort_by(
Enum.filter(fields, & !&1.is_extension),
&Map.fetch(type_order_map, String.to_atom(&1.type))
),
Enum.filter(fields, & &1.is_extension)
]
end
def eight_bit_checksum(value) do
Bitwise.bxor((value &&& 0xFF), (value >>> 8))
end
@doc """
Calculate an x25 checksum of a list or binary based on
pymavlink mavcrc.x25crc
"""
@spec x25_crc([ ] | binary()) :: pos_integer
def x25_crc(list) when is_list(list) do
x25_crc(0xffff, flatten(list))
end
def x25_crc(bin) when is_binary(bin) do
x25_crc(0xffff, bin)
end
def x25_crc(crc, []), do: crc
def x25_crc(crc, <<>>), do: crc
def x25_crc(crc, [head | tail]) do
crc |> x25_accumulate(head) |> x25_crc(tail)
end
def x25_crc(crc, << head :: size(8), tail :: binary>>) do
crc |> x25_accumulate(head) |> x25_crc(tail)
end
defp x25_accumulate(crc, value) do
tmp = Bitwise.bxor(value, (crc &&& 0xff))
tmp = (Bitwise.bxor(tmp, (tmp <<< 4))) &&& 0xff
crc = Bitwise.bxor(Bitwise.bxor(Bitwise.bxor((crc >>> 8), (tmp <<< 8)), (tmp <<< 3)), (tmp >>> 4))
crc &&& 0xffff
end
@doc "Helper function for messages to pack string fields"
@spec pack_string(binary, non_neg_integer) :: binary
def pack_string(s, ordinality) do
s |> String.pad_trailing(ordinality, <<0>>)
end
@doc "Helper function for messages to pack array fields"
@spec pack_array(list(), integer, (any() -> binary())) :: binary()
def pack_array(a, ordinality, _) when length(a) > ordinality, do: {:error, "Maximum elements allowed is \#{ordinality}"}
def pack_array(a, ordinality, field_packer) when length(a) < ordinality, do: pack_array(a ++ [0], ordinality, field_packer)
def pack_array(a, _, field_packer), do: a |> map(field_packer) |> join(<<>>)
@doc "Helper function for decode() to unpack array fields"
# TODO bitstring generator instead? https://elixir-lang.org/getting-started/comprehensions.html
@spec unpack_array(binary(), (binary()-> {any(), list()})) :: list()
def unpack_array(bin, fun), do: unpack_array(bin, fun, [])
def unpack_array(<<>>, _, lst), do: reverse(lst)
def unpack_array(bin, fun, lst) do
{elem, rest} = fun.(bin)
unpack_array(rest, fun, [elem | lst])
end
@doc "Parse an ip address string into a tuple"
def parse_ip_address(address) when is_binary(address) do
parse_ip_address(String.split(address, "."), [], 0)
end
def parse_ip_address([], address, 4) do
List.to_tuple(reverse address)
end
def parse_ip_address([], _, _) do
{:error, :invalid_ip_address}
end
def parse_ip_address([component | rest], address, count) do
case Integer.parse(component) do
:error ->
{:error, :invalid_ip_address}
{n, _} ->
cond do
n >= 0 and n <= 255 ->
parse_ip_address(rest, [n | address], count + 1)
true ->
{:error, :invalid_ip_address}
end
end
end
def parse_positive_integer(port) when is_binary(port) do
case Integer.parse(port) do
:error ->
:error
{n, _} when n > 0 ->
n
_ ->
:error
end
end
def pack_float(f) when is_float(f), do: <<f::little-signed-float-size(32)>>
def pack_float(:nan), do: <<0, 0, 192, 127>> # Have received these from QGroundControl
def unpack_float(<<f::little-signed-float-size(32)>>), do: f
def unpack_float(<<0, 0, 192, 127>>), do: :nan
def pack_double(f) when is_float(f), do: <<f::little-signed-float-size(64)>>
def pack_double(:nan), do: <<0, 0, 0, 0, 0, 0, 248, 127>> # Quick test in C gave this for double NaN
def unpack_double(<<f::little-signed-float-size(64)>>), do: f
def unpack_double(<<0, 0, 0, 0, 0, 0, 248, 127>>), do: :nan
end