Rules for writing code that uses mavlink2, for people and for coding
agents. The API checks its arguments: a mistake raises ArgumentError
saying what is wrong and what would be right, so read the message. Errors
from the other end of a link are returned as {:error, reason}.
Set up
Define one dialect module per set of messages, from MAVLink XML, and pass that module everywhere a
:dialectis asked for:defmodule MyApp.MAVLink do use MAVLink2.Dialect.XML, file: "priv/mavlink/common.xml" endPut the XML and every file it includes (
standard.xml,minimal.xml) side by side. Never writecrc_extravalues or payload layouts by hand.A serial or USB link needs
{:circuits_uart, "~> 1.5"}in your deps and a:devicesuch as"/dev/ttyACM0"(USB CDC) or"/dev/ttyAMA0"(a board UART). Its rate option is:speed, not:baud.Give this end real ids:
:sysid1 to 2^32-1 and:compid1 to 255. 0 is the broadcast id and is refused. A ground station is conventionallysysid: 255, compid: 190; an autopilot issysid: 1, compid: 1.Start links (and a router) under your supervision tree, with
:name, and refer to them by name.
Send
Send message structs with
MAVLink2.Link.send/2:MAVLink2.Link.send(:fc, %MyApp.MAVLink.Heartbeat{type: 6, autopilot: 8})Use
send_message/4only for a payload you already have as bytes.Field names are the XML's, as written (
target_system,param1). Integer fields must fit their type; strings fit theirchar[n]length.Enum values in structs are integers. Get them by name:
MyApp.MAVLink.enum_value(:mav_type, :mav_type_gcs). Names are the XML's, lowercased. List an enum withMyApp.MAVLink.enum(:mav_cmd).A message to a system id over 255: set
target_systemto the id and useLink.send/2, which puts it in the header. Do not do this by hand.
Receive
Subscribe the process that will handle frames, and say what it wants:
:ok = MAVLink2.Link.subscribe(:fc, self(), decode: true, only: [MyApp.MAVLink.Heartbeat]) receive do {MAVLink2.Link, :fc, frame, %MyApp.MAVLink.Heartbeat{} = hb} -> {frame.sysid, hb} endWithout
decode: truethe message is{MAVLink2.Link, link, %MAVLink2.Frame{}}(three elements) and the payload is bytes: decode it withMyApp.MAVLink.decode(frame.msgid, frame.payload).The sender is
frame.sysidandframe.compid, not a payload field.A subscriber with no
only:gets every frame on the link: handle or drop every message in aGenServer'shandle_info/2, or its mailbox grows.
Commands, parameters, missions
targetis{sysid, compid}. compid 0 means any component of the system.Commands take
MAV_CMDentry atoms or integers:{:ok, :mav_result_accepted, _ack} = MAVLink2.Command.long(:fc, {1, 1}, :mav_cmd_component_arm_disarm, [1.0])Any result but
:mav_result_acceptedmeans the target did not do it; it is not an error tuple. Positions go inCommand.int/5, latitude and longitude as integers in degrees × 10^7 in:xand:y.These calls block the calling process until the target answers or the retries run out (
{:error, :timeout}): seconds, with the default retries. AGenServerthat calls one handles nothing else meanwhile; start aTaskfor it when that matters. The call does its own subscribing, so the caller need not be subscribed.Parameters: pass
encoding: :bytewisefor PX4 and leave the default (:cast) for ArduPilot. Names are at most 16 ASCII bytes.Param.set/6returns what the target now holds; compare it with what you set.Missions: items are keyword lists of
MISSION_ITEM_INTfields. Use the plain global frames (:mav_frame_global_relative_alt), not the superseded_intones.upload/4returns{:ok, opaque_id}anddownload/3{:ok, items, opaque_id}.
Signing
- The key is 32 bytes shared with the peer:
MAVLink2.Signing.new(:crypto.hash(:sha256, passphrase), link_id: 1). Give each link its ownlink_id. - With
:signing, unsigned frames are refused unless their ids are inaccept_unsigned:(or it is:all, for a link that is secure by itself). - A board without a real-time clock should save
signing.timestampand start from it withtimestamp:; otherwise old captured frames are accepted after a reboot until its clock is set. - Never forward or log
SETUP_SIGNING; the router does not forward it.
Routing
MAVLink2.Router.start_link(links: [:radio, :usb], sysid: 1)forwards frames unchanged between links. Your application still subscribes to the links itself.
Tests
- Use
MAVLink2.Transport.Loopback:echo: truesends what a link writes back to it, andpeer: namejoins two links, so one can play the vehicle:start_link(name: :gcs, transport: MAVLink2.Transport.Loopback, peer: :vehicle, ...)and the same for:vehiclewithpeer: :gcs.