pamoja.mavlink

Idiomatic MAVLink wire-protocol facade.

MAVLink is the language drones speak: PX4 and ArduPilot autopilots and MAVSDK ground stations all exchange MAVLink frames, so talking to a vehicle means putting exactly the right bytes on the wire and trusting the bytes that come back. This is that byte layer: v1 and v2 frames, the CRC-16/MCRF4XX checksum every frame carries, the per-message CRC_EXTRA seed that catches a frame whose shape does not match, and MAVLink 2 signing.

Nothing here is limited to the messages this build happens to know. The common dialect's seeds are built in, and Dialect carries any others, derived from a message definition the way the specification does.

Above the bytes sits the shape: MessageSchema names a message's fields, so a MavlinkMessage is filled in and read back by name rather than by byte offset, and MessageSchemaBuilder describes a message this build has never heard of.

Above the messages sit the exchanges: MissionSender and MissionReceiver carry a plan between a station and a vehicle, CommandProtocol matches a command to its acknowledgment and counts retries, and local_position(), local_velocity(), and global_position() build setpoints. Each takes a frame off the link and hands back the frame to send, with no IO or timers of its own.

  1"""Idiomatic MAVLink wire-protocol facade.
  2
  3MAVLink is the language drones speak: PX4 and ArduPilot autopilots and MAVSDK
  4ground stations all exchange MAVLink frames, so talking to a vehicle means
  5putting exactly the right bytes on the wire and trusting the bytes that come
  6back. This is that byte layer: v1 and v2 frames, the CRC-16/MCRF4XX checksum
  7every frame carries, the per-message ``CRC_EXTRA`` seed that catches a frame
  8whose shape does not match, and MAVLink 2 signing.
  9
 10Nothing here is limited to the messages this build happens to know. The common
 11dialect's seeds are built in, and :class:`Dialect` carries any others, derived
 12from a message definition the way the specification does.
 13
 14Above the bytes sits the shape: :class:`MessageSchema` names a message's fields,
 15so a :class:`MavlinkMessage` is filled in and read back by name rather than by
 16byte offset, and :class:`MessageSchemaBuilder` describes a message this build has
 17never heard of.
 18
 19Above the messages sit the exchanges: :class:`MissionSender` and
 20:class:`MissionReceiver` carry a plan between a station and a vehicle,
 21:class:`CommandProtocol` matches a command to its acknowledgment and counts
 22retries, and :func:`local_position`, :func:`local_velocity`, and
 23:func:`global_position` build setpoints. Each takes a frame off the link and
 24hands back the frame to send, with no IO or timers of its own.
 25"""
 26
 27from __future__ import annotations
 28
 29import time
 30from enum import IntEnum, IntFlag
 31
 32from pamoja._native import (
 33    AckOutcome,
 34    CommandProtocol,
 35    Dialect,
 36    MavlinkFieldInfo,
 37    MavlinkFrame,
 38    MavlinkHeader,
 39    MavlinkMessage,
 40    MavlinkParser,
 41    MavlinkSigner,
 42    MavlinkVerifier,
 43    MessageSchema,
 44    MessageSchemaBuilder,
 45    MissionReceiver,
 46    MissionSender,
 47    ReceiverStep,
 48    SenderStep,
 49    mavlink_crc16_mcrf4xx,
 50    mavlink_enum_entries,
 51    mavlink_enum_entry,
 52    mavlink_enum_is_bitmask,
 53    mavlink_enum_names,
 54    mavlink_enum_value,
 55    mavlink_known_crc_extra,
 56    mavlink_known_enums,
 57    mavlink_known_messages,
 58    mavlink_message_crc_extra,
 59    mavlink_offboard_global_position,
 60    mavlink_offboard_local_position,
 61    mavlink_offboard_local_velocity,
 62    mavlink_offboard_type_mask,
 63    mavlink_timestamp_from_unix_micros,
 64)
 65
 66__all__ = [
 67    "AckOutcome",
 68    "CommandProtocol",
 69    "DEFAULT_TIMESTAMP_WINDOW",
 70    "Dialect",
 71    "FieldType",
 72    "KEY_LEN",
 73    "MAX_FRAME",
 74    "MAX_PAYLOAD",
 75    "MAX_RETRIES",
 76    "MavlinkFieldInfo",
 77    "MavlinkFrame",
 78    "MavlinkHeader",
 79    "MavlinkMessage",
 80    "MavlinkParser",
 81    "MavlinkSigner",
 82    "MavlinkVerifier",
 83    "MessageSchema",
 84    "MessageSchemaBuilder",
 85    "MissionReceiver",
 86    "MissionSender",
 87    "ReceiverStep",
 88    "SIGNATURE_LEN",
 89    "SenderStep",
 90    "TypeMask",
 91    "crc16",
 92    "enum_entries",
 93    "enum_entry",
 94    "enum_is_bitmask",
 95    "enum_names",
 96    "enum_value",
 97    "frame",
 98    "from_dict",
 99    "global_position",
100    "known_crc_extra",
101    "known_enums",
102    "known_messages",
103    "local_position",
104    "local_velocity",
105    "message",
106    "message_crc_extra",
107    "schema_for",
108    "timestamp_from_unix_micros",
109    "timestamp_now",
110    "to_dict",
111    "type_mask",
112]
113
114#: The largest payload a frame can carry, in bytes.
115MAX_PAYLOAD = 255
116
117#: The largest frame, in bytes, header, checksum and signature included.
118MAX_FRAME = 280
119
120#: The length of a v2 signature block, in bytes.
121SIGNATURE_LEN = 13
122
123#: The length of a signing key, in bytes.
124KEY_LEN = 32
125
126#: The default window a verifier accepts a timestamp within.
127DEFAULT_TIMESTAMP_WINDOW = 6_000_000
128
129#: The number of times a request is retransmitted before a transfer is abandoned, as
130#: the mission protocol recommends.
131MAX_RETRIES = 5
132
133
134def crc16(data: bytes) -> int:
135    """Return the CRC-16/MCRF4XX checksum of a byte string.
136
137    This is the checksum every MAVLink frame carries, exposed because a host
138    that implements part of the protocol itself needs the same arithmetic.
139
140    :param data: The data to checksum.
141    :returns: The checksum.
142    """
143    return mavlink_crc16_mcrf4xx(data)
144
145
146def message_crc_extra(name: str, fields: list[tuple[str, str, int]]) -> int:
147    """Derive the ``CRC_EXTRA`` seed of a message from its definition.
148
149    This is what makes a dialect this build has never seen usable: given a
150    message's name and its base fields in wire order, the seed comes out the
151    same as the one the dialect publishes, and a frame carrying that message
152    then checks like any other.
153
154    Extension fields are excluded from the seed and must not be listed, which is
155    what lets a peer that predates them still check the frame.
156
157    :param name: The message name, such as ``HEARTBEAT``.
158    :param fields: The base fields in wire order, as ``(type, name, array_len)``
159        triples; ``array_len`` is ``0`` for a scalar.
160    :returns: The seed.
161
162    >>> message_crc_extra("PRIVATE_STATUS", [("uint32_t", "uptime", 0)]) >= 0
163    True
164    """
165    return mavlink_message_crc_extra(name, fields)
166
167
168def known_crc_extra(msgid: int) -> int | None:
169    """Return the ``CRC_EXTRA`` the common dialect publishes for a message id.
170
171    :param msgid: The message id to look up.
172    :returns: The seed, or ``None`` for an id outside the common dialect, which
173        is what a :class:`Dialect` is for.
174
175    >>> known_crc_extra(0)
176    50
177    >>> known_crc_extra(9999) is None
178    True
179    """
180    return mavlink_known_crc_extra(msgid)
181
182
183def timestamp_from_unix_micros(unix_micros: int) -> int:
184    """Convert Unix time into the timestamp MAVLink signing counts in.
185
186    :param unix_micros: The time in microseconds since the Unix epoch.
187    :returns: The signing timestamp, in units of ten microseconds since 2015.
188    """
189    return mavlink_timestamp_from_unix_micros(unix_micros)
190
191
192def timestamp_now() -> int:
193    """Return a signing timestamp for now.
194
195    :returns: The signing timestamp matching the current clock.
196    """
197    return mavlink_timestamp_from_unix_micros(int(time.time() * 1_000_000))
198
199
200def frame(header: MavlinkHeader, msgid: int, payload: bytes) -> MavlinkFrame:
201    """Build a v2 frame carrying a message the common dialect defines.
202
203    The seed is looked up rather than passed, which is the usual case: a sender
204    emitting a standard message should not have to know its checksum constant.
205
206    :param header: The addressing fields to stamp on the frame.
207    :param msgid: The message id.
208    :param payload: The message payload.
209    :returns: The frame ready to send.
210    :raises ValueError: If the id is outside the common dialect, in which case
211        build the frame with :meth:`MavlinkFrame.raw` and a seed of your own.
212
213    >>> heartbeat = message("HEARTBEAT")
214    >>> heartbeat.set("type", enum_value("MAV_TYPE_ONBOARD_CONTROLLER"))
215    >>> heartbeat.set("system_status", enum_value("MAV_STATE_ACTIVE"))
216    >>> heartbeat.set("mavlink_version", 3)
217    >>> sent = frame(MavlinkHeader(1, 1), heartbeat.message_id, heartbeat.payload)
218    >>> sent.message_id
219    0
220    >>> MavlinkFrame.parse_known(sent.bytes).payload == heartbeat.payload
221    True
222    """
223    crc_extra = mavlink_known_crc_extra(msgid)
224    if crc_extra is None:
225        raise ValueError(
226            f"message {msgid} is not in the common dialect; "
227            "supply its CRC_EXTRA with MavlinkFrame.raw"
228        )
229    return MavlinkFrame.encode_v2(header, msgid, payload, crc_extra)
230
231
232class FieldType(IntEnum):
233    """The field types a message definition uses.
234
235    A builder accepts either one of these or the name a dialect writes, so
236    ``FieldType.UINT32`` and ``"uint32_t"`` mean the same thing.
237    """
238
239    UINT8 = 1
240    INT8 = 2
241    CHAR = 3
242    UINT16 = 4
243    INT16 = 5
244    UINT32 = 6
245    INT32 = 7
246    UINT64 = 8
247    INT64 = 9
248    FLOAT = 10
249    DOUBLE = 11
250
251
252def schema_for(message: int | str) -> MessageSchema:
253    """Return the shape of a message the engine types.
254
255    :param message: The message id or name, such as ``33`` or
256        ``"GLOBAL_POSITION_INT"``.
257    :returns: The shape.
258    :raises ValueError: If this build does not type that message, in which case
259        describe it with :class:`MessageSchemaBuilder`.
260
261    >>> schema_for("GLOBAL_POSITION_INT").id
262    33
263    >>> schema_for(0).name
264    'HEARTBEAT'
265    """
266    if isinstance(message, int):
267        return MessageSchema.for_id(message)
268    return MessageSchema.for_name(message)
269
270
271def known_messages() -> list[str]:
272    """Return the names of every message this build types, in message-id order.
273
274    :returns: The message names, each usable with :func:`schema_for`.
275
276    >>> "HEARTBEAT" in known_messages()
277    True
278    """
279    return mavlink_known_messages()
280
281
282def enum_value(entry: str) -> int:
283    """Return the value a dialect entry name stands for, whichever enumeration it
284    belongs to.
285
286    MAVLink writes each value's name with its enumeration in front, such as
287    ``MAV_TYPE_QUADROTOR``, and the names are unique across the dialect, so the
288    name alone is enough.
289
290    :param entry: The entry's name, such as ``MAV_CMD_COMPONENT_ARM_DISARM``.
291    :returns: The value, ready to set on a field.
292    :raises ValueError: If no entry of any dialect enumeration has that name.
293
294    >>> enum_value("MAV_CMD_COMPONENT_ARM_DISARM")
295    400
296    """
297    return mavlink_enum_value(entry)
298
299
300def enum_entry(enumeration: str, value: int) -> str | None:
301    """Name the entry of an enumeration that stands for a value.
302
303    :param enumeration: The enumeration's name, such as ``MAV_STATE``.
304    :param value: The value a field carried.
305    :returns: The entry's name, or ``None`` if no entry names the value.
306    :raises ValueError: If the enumeration is unknown.
307
308    >>> enum_entry("MAV_STATE", 3)
309    'MAV_STATE_STANDBY'
310    """
311    return mavlink_enum_entry(enumeration, value)
312
313
314def enum_names(enumeration: str, value: int) -> list[str]:
315    """Name the entries a value is made of.
316
317    For a bitmask such as ``MAV_MODE_FLAG`` these are the entries whose bits are
318    set in the value, in dialect order; otherwise the one entry that names it.
319
320    :param enumeration: The enumeration's name.
321    :param value: The value a field carried.
322    :returns: The names, empty when none applies.
323    :raises ValueError: If the enumeration is unknown.
324
325    >>> enum_names("MAV_MODE_FLAG", 129)
326    ['MAV_MODE_FLAG_SAFETY_ARMED', 'MAV_MODE_FLAG_CUSTOM_MODE_ENABLED']
327    """
328    return mavlink_enum_names(enumeration, value)
329
330
331def enum_entries(enumeration: str) -> list[tuple[str, int]]:
332    """Return every entry of an enumeration as ``(name, value)`` pairs, in
333    dialect order.
334
335    :param enumeration: The enumeration's name.
336    :returns: Each entry's name and value.
337    :raises ValueError: If the enumeration is unknown.
338
339    >>> enum_entries("MAV_MISSION_TYPE")[-1]
340    ('MAV_MISSION_TYPE_ALL', 255)
341    """
342    return mavlink_enum_entries(enumeration)
343
344
345def enum_is_bitmask(enumeration: str) -> bool:
346    """Report whether an enumeration's values combine as bits.
347
348    :param enumeration: The enumeration's name.
349    :returns: ``True`` for a bitmask such as ``MAV_MODE_FLAG``.
350    :raises ValueError: If the enumeration is unknown.
351    """
352    return mavlink_enum_is_bitmask(enumeration)
353
354
355def known_enums() -> list[str]:
356    """Return the names of every enumeration this build names values for.
357
358    :returns: Each enumeration a field of a typed message names, in the order
359        the dialect defines them.
360
361    >>> "MAV_RESULT" in known_enums()
362    True
363    """
364    return mavlink_known_enums()
365
366
367def message(shape: MessageSchema | int | str) -> MavlinkMessage:
368    """Create a message with every field zero.
369
370    :param shape: The shape to build, or the id or name of a message the engine
371        types.
372    :returns: The zeroed message, ready for its fields to be set.
373
374    >>> heartbeat = message("HEARTBEAT")
375    >>> heartbeat.set("type", enum_value("MAV_TYPE_ONBOARD_CONTROLLER"))
376    >>> heartbeat.set("system_status", enum_value("MAV_STATE_ACTIVE"))
377    >>> frame = heartbeat.to_frame(MavlinkHeader(1, 1))
378    >>> frame.message_id
379    0
380    """
381    if not isinstance(shape, MessageSchema):
382        shape = schema_for(shape)
383    return MavlinkMessage.empty(shape)
384
385
386def to_dict(built: MavlinkMessage, shape: MessageSchema) -> dict[str, object]:
387    """Read a whole message as plain values, keyed by field name.
388
389    A scalar field comes back as a number, an array field as a list, and a
390    ``char`` array as the text it carries, so a received message reads like an
391    ordinary mapping.
392
393    :param built: The message to read.
394    :param shape: The shape it was built from, which names its fields.
395    :returns: The fields as plain values.
396
397    >>> status = schema_for("STATUSTEXT")
398    >>> written = from_dict(status, {"severity": 4, "text": "battery low"})
399    >>> to_dict(written, status)["text"]
400    'battery low'
401    """
402    values: dict[str, object] = {}
403    for field in shape.fields:
404        if field.array_len == 0:
405            values[field.name] = built.get(field.name)
406        elif field.field_type == FieldType.CHAR:
407            values[field.name] = built.get_text(field.name)
408        else:
409            values[field.name] = [
410                built.get(field.name, index) for index in range(field.array_len)
411            ]
412    return values
413
414
415def from_dict(shape: MessageSchema, values: dict[str, object]) -> MavlinkMessage:
416    """Build a message from plain values, keyed by field name.
417
418    A field left out stays zero, which is what a sender filling in part of a
419    message wants.
420
421    :param shape: The shape to build.
422    :param values: The fields to set.
423    :returns: The message.
424    :raises PamojaError: If a name is not a field of the message, or a value does
425        not fit its field.
426
427    >>> position = schema_for("GLOBAL_POSITION_INT")
428    >>> report = from_dict(position, {"lat": -33856780, "lon": 151215300})
429    >>> report.get_int("lat")
430    -33856780
431    """
432    built = MavlinkMessage.empty(shape)
433    for name, value in values.items():
434        if isinstance(value, str):
435            built.set_text(name, value)
436        elif isinstance(value, (bytes, bytearray)):
437            built.set_bytes(name, bytes(value))
438        elif isinstance(value, (list, tuple)):
439            for index, element in enumerate(value):
440                built.set(name, float(element), index)
441        else:
442            built.set(name, float(value))
443    return built
444
445
446class TypeMask(IntFlag):
447    """The fields of a setpoint the autopilot should act on.
448
449    Combine members with ``|``; the fields left out are ignored.
450    """
451
452    POSITION = 1
453    VELOCITY = 2
454    ACCELERATION = 4
455    YAW = 8
456    YAW_RATE = 16
457    FORCE = 32
458
459
460def type_mask(fields: TypeMask | int) -> int:
461    """Build a setpoint ``type_mask`` from the fields to use.
462
463    :param fields: The fields the autopilot should act on.
464    :returns: The mask, as the ``type_mask`` field of a setpoint carries it.
465
466    >>> type_mask(TypeMask.VELOCITY | TypeMask.YAW_RATE) == mavlink_offboard_type_mask(2 | 16)
467    True
468    """
469    return mavlink_offboard_type_mask(int(fields))
470
471
472def local_position(
473    header: MavlinkHeader,
474    time_boot_ms: int,
475    coordinate_frame: int,
476    target_system: int,
477    target_component: int,
478    x: float,
479    y: float,
480    z: float,
481) -> MavlinkFrame:
482    """Build a local-frame position setpoint, ready to send.
483
484    :param header: The addressing fields to stamp on the frame.
485    :param time_boot_ms: The sender's boot timestamp, in milliseconds.
486    :param coordinate_frame: The ``MAV_FRAME`` of the setpoint.
487    :param target_system: The target system id.
488    :param target_component: The target component id.
489    :param x: The position along x, in meters in the chosen frame.
490    :param y: The position along y.
491    :param z: The position along z.
492    :returns: The ``SET_POSITION_TARGET_LOCAL_NED`` frame.
493
494    >>> local_position(MavlinkHeader(255, 190), 1000, 1, 1, 1, 10.0, 0.0, -5.0).message_id
495    84
496    """
497    return mavlink_offboard_local_position(
498        header, time_boot_ms, coordinate_frame, target_system, target_component, x, y, z
499    )
500
501
502def local_velocity(
503    header: MavlinkHeader,
504    time_boot_ms: int,
505    coordinate_frame: int,
506    target_system: int,
507    target_component: int,
508    vx: float,
509    vy: float,
510    vz: float,
511) -> MavlinkFrame:
512    """Build a local-frame velocity setpoint, ready to send.
513
514    :param header: The addressing fields to stamp on the frame.
515    :param time_boot_ms: The sender's boot timestamp, in milliseconds.
516    :param coordinate_frame: The ``MAV_FRAME`` of the setpoint.
517    :param target_system: The target system id.
518    :param target_component: The target component id.
519    :param vx: The velocity along x, in meters per second in the chosen frame.
520    :param vy: The velocity along y.
521    :param vz: The velocity along z.
522    :returns: The ``SET_POSITION_TARGET_LOCAL_NED`` frame.
523    """
524    return mavlink_offboard_local_velocity(
525        header, time_boot_ms, coordinate_frame, target_system, target_component, vx, vy, vz
526    )
527
528
529def global_position(
530    header: MavlinkHeader,
531    time_boot_ms: int,
532    coordinate_frame: int,
533    target_system: int,
534    target_component: int,
535    lat_int: int,
536    lon_int: int,
537    alt: float,
538) -> MavlinkFrame:
539    """Build a global-frame position setpoint, ready to send.
540
541    :param header: The addressing fields to stamp on the frame.
542    :param time_boot_ms: The sender's boot timestamp, in milliseconds.
543    :param coordinate_frame: The ``MAV_FRAME`` of the setpoint.
544    :param target_system: The target system id.
545    :param target_component: The target component id.
546    :param lat_int: The latitude, in degrees times ten million.
547    :param lon_int: The longitude, in degrees times ten million.
548    :param alt: The altitude, in meters.
549    :returns: The ``SET_POSITION_TARGET_GLOBAL_INT`` frame.
550
551    >>> global_position(MavlinkHeader(255, 190), 1000, 6, 1, 1, -338567800, 1512153000, 50.0).message_id
552    86
553    """
554    return mavlink_offboard_global_position(
555        header,
556        time_boot_ms,
557        coordinate_frame,
558        target_system,
559        target_component,
560        lat_int,
561        lon_int,
562        alt,
563    )
class AckOutcome:

What an incoming acknowledgment means for the command in flight.

value

The progress percent when in progress (255 when the autopilot does not report one), or the MAV_RESULT when final.

kind

"unrelated" if the ack was for another command, "in_progress" if the command is still running, or "final" if it finished.

class CommandProtocol:

Tracks one command awaiting its acknowledgment.

def on_frame(self, /, frame):

Classifies an incoming frame against the command in flight.

Returns the outcome, or None if the frame is not a COMMAND_ACK.

def on_timeout(self, /):

Records a timeout and reports whether the command may be resent.

Returns the new confirmation count to stamp on the resend, or None once the retry budget is exhausted.

command

The command id being tracked.

confirmation

The confirmation count to stamp on the command being sent: zero for the first transmission, incremented on each retransmission.

DEFAULT_TIMESTAMP_WINDOW = 6000000
class Dialect:

The CRC_EXTRA seeds of a dialect beyond the common one.

Entries added here are consulted before the built-in common-dialect registry, so a private dialect may also override an id the common one defines.

def add(self, /, msgid, crc_extra):

Adds or replaces the seed for a message id.

def add_message(self, /, msgid, name, fields):

Adds a message by its definition, deriving the seed, and returns it.

This is the whole path for a vendor dialect: describe the message once, and every frame carrying it checks from then on.

def crc_extra(self, /, msgid):

Returns the seed this dialect resolves a message id to, or None if neither it nor the common dialect knows the id.

class FieldType(enum.IntEnum):
233class FieldType(IntEnum):
234    """The field types a message definition uses.
235
236    A builder accepts either one of these or the name a dialect writes, so
237    ``FieldType.UINT32`` and ``"uint32_t"`` mean the same thing.
238    """
239
240    UINT8 = 1
241    INT8 = 2
242    CHAR = 3
243    UINT16 = 4
244    INT16 = 5
245    UINT32 = 6
246    INT32 = 7
247    UINT64 = 8
248    INT64 = 9
249    FLOAT = 10
250    DOUBLE = 11

The field types a message definition uses.

A builder accepts either one of these or the name a dialect writes, so FieldType.UINT32 and "uint32_t" mean the same thing.

UINT8 = <FieldType.UINT8: 1>
INT8 = <FieldType.INT8: 2>
CHAR = <FieldType.CHAR: 3>
UINT16 = <FieldType.UINT16: 4>
INT16 = <FieldType.INT16: 5>
UINT32 = <FieldType.UINT32: 6>
INT32 = <FieldType.INT32: 7>
UINT64 = <FieldType.UINT64: 8>
INT64 = <FieldType.INT64: 9>
FLOAT = <FieldType.FLOAT: 10>
DOUBLE = <FieldType.DOUBLE: 11>
KEY_LEN = 32
MAX_FRAME = 280
MAX_PAYLOAD = 255
MAX_RETRIES = 5
class MavlinkFieldInfo:

One field of a message shape.

type_name

The field's type name as the dialect writes it, such as uint32_t.

offset

The field's byte offset within the payload.

field_type

The field's type, one of the FieldType values.

extension

Whether this is a MAVLink 2 extension field.

array_len

The element count for an array field, or 0 for a scalar.

name

The field name as the dialect writes it, such as custom_mode.

class MavlinkFrame:

One MAVLink frame, assembled or received.

def encode_v2(header, msgid, payload, crc_extra):

Assembles a v2 frame carrying a message.

This is the current wire format and what a modern autopilot expects.

def encode_v1(header, msgid, payload, crc_extra):

Assembles a v1 frame, for a peer that predates MAVLink 2.

A v1 frame only carries message ids below 256.

def parse(data, crc_extra):

Parses one frame, checking it against a known CRC_EXTRA.

Raises PamojaError if the bytes are not a whole frame or the checksum does not match, which is what rejects a frame mangled in transit.

def parse_known(data, dialect=None):

Parses one frame, looking its CRC_EXTRA up as it goes.

This is what a receiver holding many message types uses: the id comes out of the frame, and the seed comes from the dialect or the common registry.

def raw(header, msgid, crc_extra, payload):

Assembles a v2 frame carrying a message this build does not type.

The escape hatch a private dialect needs: supply the id, the payload, and the seed, and the frame is built and checked like any other.

message_id

The id of the message the frame carries.

incompat_flags

The incompatibility flags a v2 frame declares.

header

The addressing fields the frame carries.

signed

Whether the frame carries a signature.

This says only that the frame was signed, not that the signature is good; a MavlinkVerifier decides that.

payload

The message payload.

A v2 frame drops trailing zero bytes, so a payload can arrive shorter than the message's full length; a decoder zero-extends it.

bytes

The whole frame, ready to put on the wire.

signature

The signature block, or None when the frame is not signed.

version

Which wire format this frame uses: 1 or 2.

class MavlinkHeader:

The addressing fields a sender stamps on every frame.

component_id

The sending component's id.

sequence

The sender's sequence number, which wraps at 256.

system_id

The sending system's id.

class MavlinkMessage:

A message read and written by field name against a schema.

def empty(schema):

Creates a message with every field zero.

Raises PamojaError if the shape does not fit a MAVLink payload.

def decode(schema, payload):

Reads a message out of a frame payload.

A payload shorter than the shape is zero-extended, as MAVLink 2 truncation requires, so a frame from a peer that trimmed trailing zeros or predates an extension field still decodes. Raises PamojaError if the payload is longer than the shape describes.

def to_frame(self, /, header):

Builds a v2 frame carrying this message.

Raises PamojaError if the message does not fit a frame.

def get(self, /, field, index=0):

Reads a field as a number.

Every field reads this way. An integer field wider than 53 bits can exceed what a float holds exactly; read those with get_int where the exact value matters.

def get_int(self, /, field, index=0):

Reads an integer field exactly, whatever its width or sign.

def set(self, /, field, value, index=0):

Writes a number into a field, converting it to the field's type.

A value bound for an integer field must be a whole number within that field's range, so a fractional or oversized value is refused rather than silently truncated.

def set_int(self, /, field, value, index=0):

Writes an integer into a field exactly, whatever its width or sign.

def get_bytes(self, /, field):

Copies the raw bytes of a byte-wide array field out, padding included.

def set_bytes(self, /, field, data):

Writes the raw bytes of a byte-wide array field, zero-padding the rest.

def get_text(self, /, field):

Reads a char array as text, stopping at the padding.

def set_text(self, /, field, text):

Writes text into a char array, padding the rest with zeros.

payload

The message's bytes as they go on the wire.

name

The name of the message this carries.

message_id

The id of the message this carries.

class MavlinkParser:

A streaming frame parser, and the frames it has completed.

def push(self, /, data, dialect=None):

Feeds bytes off a link and returns the frames that completed.

Whatever a serial port or socket delivers can be pushed as it arrives, however it is split. Noise between frames is skipped rather than reported, which is what lets a parser join a stream already in progress.

def feed(self, /, data, dialect=None):

Feeds bytes and queues what completed, for a caller that drains later.

def next_frame(self, /):

Takes the next queued frame, or None when the parser needs more bytes.

pending

How many completed frames are waiting to be taken.

class MavlinkSigner:

A signing key and the monotonic timestamp that goes with it.

def sign(self, /, header, msgid, payload, crc_extra):

Signs a message into a v2 frame.

Each call advances the timestamp, which is what makes a replayed frame detectable.

class MavlinkVerifier:

A signing key and the timestamps it has already accepted.

def set_window(self, /, window):

Sets how far a timestamp may run ahead of the last one accepted.

A wider window tolerates a noisier link; a narrower one narrows the chance of a replay landing inside it.

def verify(self, /, frame):

Checks a frame's signature and its place in the timestamp sequence.

Raises PamojaError when the frame is unsigned, the signature does not match the key, or the timestamp has been seen before.

class MessageSchema:

The shape of one message: its id, name, seed, and fields.

def for_id(msgid):

Returns the shape of a message the engine types, by id.

Raises ValueError if this build does not type that id, which is what a builder is for.

def for_name(name):

Returns the shape of a message the engine types, by name.

Raises ValueError if this build does not type that name.

wire_len

The length of the message on the wire, in bytes, extensions included.

crc_extra

The CRC_EXTRA seed a frame carrying this message folds into its checksum.

fields

The fields in wire order: the base fields largest first, then extensions.

id

The id of the message this schema describes.

name

The name of the message this schema describes.

class MessageSchemaBuilder:

Describes a message this build does not type, one field at a time.

Fields are added in the order the message definition lists them; building puts them in wire order and derives the CRC_EXTRA seed from the result.

def field(self, /, name, field_type, array_len=0):

Adds a base field, in the order the definition declares it.

The type is either a FieldType value or the name a dialect writes, such as "uint32_t". Raises ValueError if it is neither, or if the shape has already been built.

def extension(self, /, name, field_type, array_len=0):

Adds a MAVLink 2 extension field, in the order the definition declares it.

Extensions keep their declared order, stay out of the CRC_EXTRA seed, and read as zero from a frame sent by a peer that predates them.

def build(self, /):

Puts the declared fields in wire order and finishes the shape.

Raises PamojaError if two fields share a name or the fields do not fit a MAVLink payload, and ValueError if the shape has already been built.

class MissionReceiver:

Requests a plan's items in order and collects them, ending with an acknowledgment.

def request_list(self, /, header):

Builds the MISSION_REQUEST_LIST frame that starts a download.

def on_frame(self, /, frame, header):

Handles an incoming frame, if it is one this transfer is waiting for.

A MISSION_COUNT opens the transfer and a MISSION_ITEM_INT advances it. Returns the step taken, or None if the frame carries a message this transfer does not handle.

expected

The next sequence number the receiver expects.

complete

Whether every item has been received and the acknowledgment produced.

class MissionSender:

Holds a plan and answers a receiver's requests for its items.

def add_item(self, /, item):

Appends an item to the plan, from a MISSION_ITEM_INT payload.

The sender stamps the sequence number, target ids, and mission type onto each item as it is handed out, so the item need only carry its content: command, frame, position, and parameters. Build one by field name with the MISSION_ITEM_INT schema and pass its payload.

def count(self, /, header):

Builds the MISSION_COUNT frame that opens an upload.

def on_frame(self, /, frame, header):

Handles an incoming frame, if it is one this transfer answers.

A MISSION_REQUEST_LIST is answered with the count, a MISSION_REQUEST_INT (or the older MISSION_REQUEST) with the item asked for, and a request past the end of the plan with a MISSION_ACK reporting an invalid sequence. A MISSION_ACK from the receiver ends the transfer. Returns None for a message this transfer does not handle.

class ReceiverStep:

What one incoming frame produced for a mission receiver.

accepted

The MISSION_ITEM_INT the frame carried, if it was the one expected next, as a message read by field name.

kind

What the receiver answered with: "request" for the next item, or "ack" to end the transfer.

reply

The frame to send back.

SIGNATURE_LEN = 13
class SenderStep:

What one incoming frame produced for a mission sender.

result

The receiver's MAV_MISSION_RESULT, when the transfer finished.

reply

The frame to send back, when there is one.

kind

What happened: "reply" when there is a frame to send, or "finished" when the receiver acknowledged the transfer.

class TypeMask(enum.IntFlag):
447class TypeMask(IntFlag):
448    """The fields of a setpoint the autopilot should act on.
449
450    Combine members with ``|``; the fields left out are ignored.
451    """
452
453    POSITION = 1
454    VELOCITY = 2
455    ACCELERATION = 4
456    YAW = 8
457    YAW_RATE = 16
458    FORCE = 32

The fields of a setpoint the autopilot should act on.

Combine members with |; the fields left out are ignored.

POSITION = <TypeMask.POSITION: 1>
VELOCITY = <TypeMask.VELOCITY: 2>
ACCELERATION = <TypeMask.ACCELERATION: 4>
YAW = <TypeMask.YAW: 8>
YAW_RATE = <TypeMask.YAW_RATE: 16>
FORCE = <TypeMask.FORCE: 32>
def crc16(data: bytes) -> int:
135def crc16(data: bytes) -> int:
136    """Return the CRC-16/MCRF4XX checksum of a byte string.
137
138    This is the checksum every MAVLink frame carries, exposed because a host
139    that implements part of the protocol itself needs the same arithmetic.
140
141    :param data: The data to checksum.
142    :returns: The checksum.
143    """
144    return mavlink_crc16_mcrf4xx(data)

Return the CRC-16/MCRF4XX checksum of a byte string.

This is the checksum every MAVLink frame carries, exposed because a host that implements part of the protocol itself needs the same arithmetic.

Parameters
  • data: The data to checksum. :returns: The checksum.
def enum_entries(enumeration: str) -> list[tuple[str, int]]:
332def enum_entries(enumeration: str) -> list[tuple[str, int]]:
333    """Return every entry of an enumeration as ``(name, value)`` pairs, in
334    dialect order.
335
336    :param enumeration: The enumeration's name.
337    :returns: Each entry's name and value.
338    :raises ValueError: If the enumeration is unknown.
339
340    >>> enum_entries("MAV_MISSION_TYPE")[-1]
341    ('MAV_MISSION_TYPE_ALL', 255)
342    """
343    return mavlink_enum_entries(enumeration)

Return every entry of an enumeration as (name, value) pairs, in dialect order.

Parameters
  • enumeration: The enumeration's name. :returns: Each entry's name and value.
Raises
  • ValueError: If the enumeration is unknown.
>>> enum_entries("MAV_MISSION_TYPE")[-1]
('MAV_MISSION_TYPE_ALL', 255)
def enum_entry(enumeration: str, value: int) -> str | None:
301def enum_entry(enumeration: str, value: int) -> str | None:
302    """Name the entry of an enumeration that stands for a value.
303
304    :param enumeration: The enumeration's name, such as ``MAV_STATE``.
305    :param value: The value a field carried.
306    :returns: The entry's name, or ``None`` if no entry names the value.
307    :raises ValueError: If the enumeration is unknown.
308
309    >>> enum_entry("MAV_STATE", 3)
310    'MAV_STATE_STANDBY'
311    """
312    return mavlink_enum_entry(enumeration, value)

Name the entry of an enumeration that stands for a value.

Parameters
  • enumeration: The enumeration's name, such as MAV_STATE.
  • value: The value a field carried. :returns: The entry's name, or None if no entry names the value.
Raises
  • ValueError: If the enumeration is unknown.
>>> enum_entry("MAV_STATE", 3)
'MAV_STATE_STANDBY'
def enum_is_bitmask(enumeration: str) -> bool:
346def enum_is_bitmask(enumeration: str) -> bool:
347    """Report whether an enumeration's values combine as bits.
348
349    :param enumeration: The enumeration's name.
350    :returns: ``True`` for a bitmask such as ``MAV_MODE_FLAG``.
351    :raises ValueError: If the enumeration is unknown.
352    """
353    return mavlink_enum_is_bitmask(enumeration)

Report whether an enumeration's values combine as bits.

Parameters
  • enumeration: The enumeration's name. :returns: True for a bitmask such as MAV_MODE_FLAG.
Raises
  • ValueError: If the enumeration is unknown.
def enum_names(enumeration: str, value: int) -> list[str]:
315def enum_names(enumeration: str, value: int) -> list[str]:
316    """Name the entries a value is made of.
317
318    For a bitmask such as ``MAV_MODE_FLAG`` these are the entries whose bits are
319    set in the value, in dialect order; otherwise the one entry that names it.
320
321    :param enumeration: The enumeration's name.
322    :param value: The value a field carried.
323    :returns: The names, empty when none applies.
324    :raises ValueError: If the enumeration is unknown.
325
326    >>> enum_names("MAV_MODE_FLAG", 129)
327    ['MAV_MODE_FLAG_SAFETY_ARMED', 'MAV_MODE_FLAG_CUSTOM_MODE_ENABLED']
328    """
329    return mavlink_enum_names(enumeration, value)

Name the entries a value is made of.

For a bitmask such as MAV_MODE_FLAG these are the entries whose bits are set in the value, in dialect order; otherwise the one entry that names it.

Parameters
  • enumeration: The enumeration's name.
  • value: The value a field carried. :returns: The names, empty when none applies.
Raises
  • ValueError: If the enumeration is unknown.
>>> enum_names("MAV_MODE_FLAG", 129)
['MAV_MODE_FLAG_SAFETY_ARMED', 'MAV_MODE_FLAG_CUSTOM_MODE_ENABLED']
def enum_value(entry: str) -> int:
283def enum_value(entry: str) -> int:
284    """Return the value a dialect entry name stands for, whichever enumeration it
285    belongs to.
286
287    MAVLink writes each value's name with its enumeration in front, such as
288    ``MAV_TYPE_QUADROTOR``, and the names are unique across the dialect, so the
289    name alone is enough.
290
291    :param entry: The entry's name, such as ``MAV_CMD_COMPONENT_ARM_DISARM``.
292    :returns: The value, ready to set on a field.
293    :raises ValueError: If no entry of any dialect enumeration has that name.
294
295    >>> enum_value("MAV_CMD_COMPONENT_ARM_DISARM")
296    400
297    """
298    return mavlink_enum_value(entry)

Return the value a dialect entry name stands for, whichever enumeration it belongs to.

MAVLink writes each value's name with its enumeration in front, such as MAV_TYPE_QUADROTOR, and the names are unique across the dialect, so the name alone is enough.

Parameters
  • entry: The entry's name, such as MAV_CMD_COMPONENT_ARM_DISARM. :returns: The value, ready to set on a field.
Raises
  • ValueError: If no entry of any dialect enumeration has that name.
>>> enum_value("MAV_CMD_COMPONENT_ARM_DISARM")
400
def frame(header: MavlinkHeader, msgid: int, payload: bytes) -> MavlinkFrame:
201def frame(header: MavlinkHeader, msgid: int, payload: bytes) -> MavlinkFrame:
202    """Build a v2 frame carrying a message the common dialect defines.
203
204    The seed is looked up rather than passed, which is the usual case: a sender
205    emitting a standard message should not have to know its checksum constant.
206
207    :param header: The addressing fields to stamp on the frame.
208    :param msgid: The message id.
209    :param payload: The message payload.
210    :returns: The frame ready to send.
211    :raises ValueError: If the id is outside the common dialect, in which case
212        build the frame with :meth:`MavlinkFrame.raw` and a seed of your own.
213
214    >>> heartbeat = message("HEARTBEAT")
215    >>> heartbeat.set("type", enum_value("MAV_TYPE_ONBOARD_CONTROLLER"))
216    >>> heartbeat.set("system_status", enum_value("MAV_STATE_ACTIVE"))
217    >>> heartbeat.set("mavlink_version", 3)
218    >>> sent = frame(MavlinkHeader(1, 1), heartbeat.message_id, heartbeat.payload)
219    >>> sent.message_id
220    0
221    >>> MavlinkFrame.parse_known(sent.bytes).payload == heartbeat.payload
222    True
223    """
224    crc_extra = mavlink_known_crc_extra(msgid)
225    if crc_extra is None:
226        raise ValueError(
227            f"message {msgid} is not in the common dialect; "
228            "supply its CRC_EXTRA with MavlinkFrame.raw"
229        )
230    return MavlinkFrame.encode_v2(header, msgid, payload, crc_extra)

Build a v2 frame carrying a message the common dialect defines.

The seed is looked up rather than passed, which is the usual case: a sender emitting a standard message should not have to know its checksum constant.

Parameters
  • header: The addressing fields to stamp on the frame.
  • msgid: The message id.
  • payload: The message payload. :returns: The frame ready to send.
Raises
  • ValueError: If the id is outside the common dialect, in which case build the frame with MavlinkFrame.raw() and a seed of your own.
>>> heartbeat = message("HEARTBEAT")
>>> heartbeat.set("type", enum_value("MAV_TYPE_ONBOARD_CONTROLLER"))
>>> heartbeat.set("system_status", enum_value("MAV_STATE_ACTIVE"))
>>> heartbeat.set("mavlink_version", 3)
>>> sent = frame(MavlinkHeader(1, 1), heartbeat.message_id, heartbeat.payload)
>>> sent.message_id
0
>>> MavlinkFrame.parse_known(sent.bytes).payload == heartbeat.payload
True
def from_dict(shape: MessageSchema, values: dict[str, object]) -> MavlinkMessage:
416def from_dict(shape: MessageSchema, values: dict[str, object]) -> MavlinkMessage:
417    """Build a message from plain values, keyed by field name.
418
419    A field left out stays zero, which is what a sender filling in part of a
420    message wants.
421
422    :param shape: The shape to build.
423    :param values: The fields to set.
424    :returns: The message.
425    :raises PamojaError: If a name is not a field of the message, or a value does
426        not fit its field.
427
428    >>> position = schema_for("GLOBAL_POSITION_INT")
429    >>> report = from_dict(position, {"lat": -33856780, "lon": 151215300})
430    >>> report.get_int("lat")
431    -33856780
432    """
433    built = MavlinkMessage.empty(shape)
434    for name, value in values.items():
435        if isinstance(value, str):
436            built.set_text(name, value)
437        elif isinstance(value, (bytes, bytearray)):
438            built.set_bytes(name, bytes(value))
439        elif isinstance(value, (list, tuple)):
440            for index, element in enumerate(value):
441                built.set(name, float(element), index)
442        else:
443            built.set(name, float(value))
444    return built

Build a message from plain values, keyed by field name.

A field left out stays zero, which is what a sender filling in part of a message wants.

Parameters
  • shape: The shape to build.
  • values: The fields to set. :returns: The message.
Raises
  • PamojaError: If a name is not a field of the message, or a value does not fit its field.
>>> position = schema_for("GLOBAL_POSITION_INT")
>>> report = from_dict(position, {"lat": -33856780, "lon": 151215300})
>>> report.get_int("lat")
-33856780
def global_position( header: MavlinkHeader, time_boot_ms: int, coordinate_frame: int, target_system: int, target_component: int, lat_int: int, lon_int: int, alt: float) -> MavlinkFrame:
530def global_position(
531    header: MavlinkHeader,
532    time_boot_ms: int,
533    coordinate_frame: int,
534    target_system: int,
535    target_component: int,
536    lat_int: int,
537    lon_int: int,
538    alt: float,
539) -> MavlinkFrame:
540    """Build a global-frame position setpoint, ready to send.
541
542    :param header: The addressing fields to stamp on the frame.
543    :param time_boot_ms: The sender's boot timestamp, in milliseconds.
544    :param coordinate_frame: The ``MAV_FRAME`` of the setpoint.
545    :param target_system: The target system id.
546    :param target_component: The target component id.
547    :param lat_int: The latitude, in degrees times ten million.
548    :param lon_int: The longitude, in degrees times ten million.
549    :param alt: The altitude, in meters.
550    :returns: The ``SET_POSITION_TARGET_GLOBAL_INT`` frame.
551
552    >>> global_position(MavlinkHeader(255, 190), 1000, 6, 1, 1, -338567800, 1512153000, 50.0).message_id
553    86
554    """
555    return mavlink_offboard_global_position(
556        header,
557        time_boot_ms,
558        coordinate_frame,
559        target_system,
560        target_component,
561        lat_int,
562        lon_int,
563        alt,
564    )

Build a global-frame position setpoint, ready to send.

Parameters
  • header: The addressing fields to stamp on the frame.
  • time_boot_ms: The sender's boot timestamp, in milliseconds.
  • coordinate_frame: The MAV_FRAME of the setpoint.
  • target_system: The target system id.
  • target_component: The target component id.
  • lat_int: The latitude, in degrees times ten million.
  • lon_int: The longitude, in degrees times ten million.
  • alt: The altitude, in meters. :returns: The SET_POSITION_TARGET_GLOBAL_INT frame.
>>> global_position(MavlinkHeader(255, 190), 1000, 6, 1, 1, -338567800, 1512153000, 50.0).message_id
86
def known_crc_extra(msgid: int) -> int | None:
169def known_crc_extra(msgid: int) -> int | None:
170    """Return the ``CRC_EXTRA`` the common dialect publishes for a message id.
171
172    :param msgid: The message id to look up.
173    :returns: The seed, or ``None`` for an id outside the common dialect, which
174        is what a :class:`Dialect` is for.
175
176    >>> known_crc_extra(0)
177    50
178    >>> known_crc_extra(9999) is None
179    True
180    """
181    return mavlink_known_crc_extra(msgid)

Return the CRC_EXTRA the common dialect publishes for a message id.

Parameters
  • msgid: The message id to look up. :returns: The seed, or None for an id outside the common dialect, which is what a Dialect is for.
>>> known_crc_extra(0)
50
>>> known_crc_extra(9999) is None
True
def known_enums() -> list[str]:
356def known_enums() -> list[str]:
357    """Return the names of every enumeration this build names values for.
358
359    :returns: Each enumeration a field of a typed message names, in the order
360        the dialect defines them.
361
362    >>> "MAV_RESULT" in known_enums()
363    True
364    """
365    return mavlink_known_enums()

Return the names of every enumeration this build names values for.

:returns: Each enumeration a field of a typed message names, in the order the dialect defines them.

>>> "MAV_RESULT" in known_enums()
True
def known_messages() -> list[str]:
272def known_messages() -> list[str]:
273    """Return the names of every message this build types, in message-id order.
274
275    :returns: The message names, each usable with :func:`schema_for`.
276
277    >>> "HEARTBEAT" in known_messages()
278    True
279    """
280    return mavlink_known_messages()

Return the names of every message this build types, in message-id order.

:returns: The message names, each usable with schema_for().

>>> "HEARTBEAT" in known_messages()
True
def local_position( header: MavlinkHeader, time_boot_ms: int, coordinate_frame: int, target_system: int, target_component: int, x: float, y: float, z: float) -> MavlinkFrame:
473def local_position(
474    header: MavlinkHeader,
475    time_boot_ms: int,
476    coordinate_frame: int,
477    target_system: int,
478    target_component: int,
479    x: float,
480    y: float,
481    z: float,
482) -> MavlinkFrame:
483    """Build a local-frame position setpoint, ready to send.
484
485    :param header: The addressing fields to stamp on the frame.
486    :param time_boot_ms: The sender's boot timestamp, in milliseconds.
487    :param coordinate_frame: The ``MAV_FRAME`` of the setpoint.
488    :param target_system: The target system id.
489    :param target_component: The target component id.
490    :param x: The position along x, in meters in the chosen frame.
491    :param y: The position along y.
492    :param z: The position along z.
493    :returns: The ``SET_POSITION_TARGET_LOCAL_NED`` frame.
494
495    >>> local_position(MavlinkHeader(255, 190), 1000, 1, 1, 1, 10.0, 0.0, -5.0).message_id
496    84
497    """
498    return mavlink_offboard_local_position(
499        header, time_boot_ms, coordinate_frame, target_system, target_component, x, y, z
500    )

Build a local-frame position setpoint, ready to send.

Parameters
  • header: The addressing fields to stamp on the frame.
  • time_boot_ms: The sender's boot timestamp, in milliseconds.
  • coordinate_frame: The MAV_FRAME of the setpoint.
  • target_system: The target system id.
  • target_component: The target component id.
  • x: The position along x, in meters in the chosen frame.
  • y: The position along y.
  • z: The position along z. :returns: The SET_POSITION_TARGET_LOCAL_NED frame.
>>> local_position(MavlinkHeader(255, 190), 1000, 1, 1, 1, 10.0, 0.0, -5.0).message_id
84
def local_velocity( header: MavlinkHeader, time_boot_ms: int, coordinate_frame: int, target_system: int, target_component: int, vx: float, vy: float, vz: float) -> MavlinkFrame:
503def local_velocity(
504    header: MavlinkHeader,
505    time_boot_ms: int,
506    coordinate_frame: int,
507    target_system: int,
508    target_component: int,
509    vx: float,
510    vy: float,
511    vz: float,
512) -> MavlinkFrame:
513    """Build a local-frame velocity setpoint, ready to send.
514
515    :param header: The addressing fields to stamp on the frame.
516    :param time_boot_ms: The sender's boot timestamp, in milliseconds.
517    :param coordinate_frame: The ``MAV_FRAME`` of the setpoint.
518    :param target_system: The target system id.
519    :param target_component: The target component id.
520    :param vx: The velocity along x, in meters per second in the chosen frame.
521    :param vy: The velocity along y.
522    :param vz: The velocity along z.
523    :returns: The ``SET_POSITION_TARGET_LOCAL_NED`` frame.
524    """
525    return mavlink_offboard_local_velocity(
526        header, time_boot_ms, coordinate_frame, target_system, target_component, vx, vy, vz
527    )

Build a local-frame velocity setpoint, ready to send.

Parameters
  • header: The addressing fields to stamp on the frame.
  • time_boot_ms: The sender's boot timestamp, in milliseconds.
  • coordinate_frame: The MAV_FRAME of the setpoint.
  • target_system: The target system id.
  • target_component: The target component id.
  • vx: The velocity along x, in meters per second in the chosen frame.
  • vy: The velocity along y.
  • vz: The velocity along z. :returns: The SET_POSITION_TARGET_LOCAL_NED frame.
def message(shape: MessageSchema | int | str) -> MavlinkMessage:
368def message(shape: MessageSchema | int | str) -> MavlinkMessage:
369    """Create a message with every field zero.
370
371    :param shape: The shape to build, or the id or name of a message the engine
372        types.
373    :returns: The zeroed message, ready for its fields to be set.
374
375    >>> heartbeat = message("HEARTBEAT")
376    >>> heartbeat.set("type", enum_value("MAV_TYPE_ONBOARD_CONTROLLER"))
377    >>> heartbeat.set("system_status", enum_value("MAV_STATE_ACTIVE"))
378    >>> frame = heartbeat.to_frame(MavlinkHeader(1, 1))
379    >>> frame.message_id
380    0
381    """
382    if not isinstance(shape, MessageSchema):
383        shape = schema_for(shape)
384    return MavlinkMessage.empty(shape)

Create a message with every field zero.

Parameters
  • shape: The shape to build, or the id or name of a message the engine types. :returns: The zeroed message, ready for its fields to be set.
>>> heartbeat = message("HEARTBEAT")
>>> heartbeat.set("type", enum_value("MAV_TYPE_ONBOARD_CONTROLLER"))
>>> heartbeat.set("system_status", enum_value("MAV_STATE_ACTIVE"))
>>> frame = heartbeat.to_frame(MavlinkHeader(1, 1))
>>> frame.message_id
0
def message_crc_extra(name: str, fields: list[tuple[str, str, int]]) -> int:
147def message_crc_extra(name: str, fields: list[tuple[str, str, int]]) -> int:
148    """Derive the ``CRC_EXTRA`` seed of a message from its definition.
149
150    This is what makes a dialect this build has never seen usable: given a
151    message's name and its base fields in wire order, the seed comes out the
152    same as the one the dialect publishes, and a frame carrying that message
153    then checks like any other.
154
155    Extension fields are excluded from the seed and must not be listed, which is
156    what lets a peer that predates them still check the frame.
157
158    :param name: The message name, such as ``HEARTBEAT``.
159    :param fields: The base fields in wire order, as ``(type, name, array_len)``
160        triples; ``array_len`` is ``0`` for a scalar.
161    :returns: The seed.
162
163    >>> message_crc_extra("PRIVATE_STATUS", [("uint32_t", "uptime", 0)]) >= 0
164    True
165    """
166    return mavlink_message_crc_extra(name, fields)

Derive the CRC_EXTRA seed of a message from its definition.

This is what makes a dialect this build has never seen usable: given a message's name and its base fields in wire order, the seed comes out the same as the one the dialect publishes, and a frame carrying that message then checks like any other.

Extension fields are excluded from the seed and must not be listed, which is what lets a peer that predates them still check the frame.

Parameters
  • name: The message name, such as HEARTBEAT.
  • fields: The base fields in wire order, as (type, name, array_len) triples; array_len is 0 for a scalar. :returns: The seed.
>>> message_crc_extra("PRIVATE_STATUS", [("uint32_t", "uptime", 0)]) >= 0
True
def schema_for(message: int | str) -> MessageSchema:
253def schema_for(message: int | str) -> MessageSchema:
254    """Return the shape of a message the engine types.
255
256    :param message: The message id or name, such as ``33`` or
257        ``"GLOBAL_POSITION_INT"``.
258    :returns: The shape.
259    :raises ValueError: If this build does not type that message, in which case
260        describe it with :class:`MessageSchemaBuilder`.
261
262    >>> schema_for("GLOBAL_POSITION_INT").id
263    33
264    >>> schema_for(0).name
265    'HEARTBEAT'
266    """
267    if isinstance(message, int):
268        return MessageSchema.for_id(message)
269    return MessageSchema.for_name(message)

Return the shape of a message the engine types.

Parameters
  • message: The message id or name, such as 33 or "GLOBAL_POSITION_INT". :returns: The shape.
Raises
  • ValueError: If this build does not type that message, in which case describe it with MessageSchemaBuilder.
>>> schema_for("GLOBAL_POSITION_INT").id
33
>>> schema_for(0).name
'HEARTBEAT'
def timestamp_from_unix_micros(unix_micros: int) -> int:
184def timestamp_from_unix_micros(unix_micros: int) -> int:
185    """Convert Unix time into the timestamp MAVLink signing counts in.
186
187    :param unix_micros: The time in microseconds since the Unix epoch.
188    :returns: The signing timestamp, in units of ten microseconds since 2015.
189    """
190    return mavlink_timestamp_from_unix_micros(unix_micros)

Convert Unix time into the timestamp MAVLink signing counts in.

Parameters
  • unix_micros: The time in microseconds since the Unix epoch. :returns: The signing timestamp, in units of ten microseconds since 2015.
def timestamp_now() -> int:
193def timestamp_now() -> int:
194    """Return a signing timestamp for now.
195
196    :returns: The signing timestamp matching the current clock.
197    """
198    return mavlink_timestamp_from_unix_micros(int(time.time() * 1_000_000))

Return a signing timestamp for now.

:returns: The signing timestamp matching the current clock.

def to_dict(built: MavlinkMessage, shape: MessageSchema) -> dict[str, object]:
387def to_dict(built: MavlinkMessage, shape: MessageSchema) -> dict[str, object]:
388    """Read a whole message as plain values, keyed by field name.
389
390    A scalar field comes back as a number, an array field as a list, and a
391    ``char`` array as the text it carries, so a received message reads like an
392    ordinary mapping.
393
394    :param built: The message to read.
395    :param shape: The shape it was built from, which names its fields.
396    :returns: The fields as plain values.
397
398    >>> status = schema_for("STATUSTEXT")
399    >>> written = from_dict(status, {"severity": 4, "text": "battery low"})
400    >>> to_dict(written, status)["text"]
401    'battery low'
402    """
403    values: dict[str, object] = {}
404    for field in shape.fields:
405        if field.array_len == 0:
406            values[field.name] = built.get(field.name)
407        elif field.field_type == FieldType.CHAR:
408            values[field.name] = built.get_text(field.name)
409        else:
410            values[field.name] = [
411                built.get(field.name, index) for index in range(field.array_len)
412            ]
413    return values

Read a whole message as plain values, keyed by field name.

A scalar field comes back as a number, an array field as a list, and a char array as the text it carries, so a received message reads like an ordinary mapping.

Parameters
  • built: The message to read.
  • shape: The shape it was built from, which names its fields. :returns: The fields as plain values.
>>> status = schema_for("STATUSTEXT")
>>> written = from_dict(status, {"severity": 4, "text": "battery low"})
>>> to_dict(written, status)["text"]
'battery low'
def type_mask(fields: TypeMask | int) -> int:
461def type_mask(fields: TypeMask | int) -> int:
462    """Build a setpoint ``type_mask`` from the fields to use.
463
464    :param fields: The fields the autopilot should act on.
465    :returns: The mask, as the ``type_mask`` field of a setpoint carries it.
466
467    >>> type_mask(TypeMask.VELOCITY | TypeMask.YAW_RATE) == mavlink_offboard_type_mask(2 | 16)
468    True
469    """
470    return mavlink_offboard_type_mask(int(fields))

Build a setpoint type_mask from the fields to use.

Parameters
  • fields: The fields the autopilot should act on. :returns: The mask, as the type_mask field of a setpoint carries it.
>>> type_mask(TypeMask.VELOCITY | TypeMask.YAW_RATE) == mavlink_offboard_type_mask(2 | 16)
True