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 )
What an incoming acknowledgment means for the command in flight.
Tracks one command awaiting its acknowledgment.
Classifies an incoming frame against the command in flight.
Returns the outcome, or None if the frame is not a COMMAND_ACK.
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.
The confirmation count to stamp on the command being sent: zero for
the first transmission, incremented on each retransmission.
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.
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.
One field of a message shape.
One MAVLink frame, assembled or received.
Assembles a v2 frame carrying a message.
This is the current wire format and what a modern autopilot expects.
Assembles a v1 frame, for a peer that predates MAVLink 2.
A v1 frame only carries message ids below 256.
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.
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.
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.
Whether the frame carries a signature.
This says only that the frame was signed, not that the signature is good;
a MavlinkVerifier decides that.
The addressing fields a sender stamps on every frame.
A message read and written by field name against a schema.
Creates a message with every field zero.
Raises PamojaError if the shape does not fit a MAVLink 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.
Builds a v2 frame carrying this message.
Raises PamojaError if the message does not fit a frame.
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.
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.
Writes an integer into a field exactly, whatever its width or sign.
Copies the raw bytes of a byte-wide array field out, padding included.
A streaming frame parser, and the frames it has completed.
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.
A signing key and the monotonic timestamp that goes with it.
A signing key and the timestamps it has already accepted.
The shape of one message: its id, name, seed, and fields.
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.
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.
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.
Requests a plan's items in order and collects them, ending with an acknowledgment.
Holds a plan and answers a receiver's requests for its items.
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.
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.
What one incoming frame produced for a mission receiver.
What one incoming frame produced for a mission sender.
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.
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.
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)
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
Noneif no entry names the value.
Raises
- ValueError: If the enumeration is unknown.
>>> enum_entry("MAV_STATE", 3)
'MAV_STATE_STANDBY'
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:
Truefor a bitmask such asMAV_MODE_FLAG.
Raises
- ValueError: If the enumeration is unknown.
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']
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
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
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
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_FRAMEof 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_INTframe.
>>> global_position(MavlinkHeader(255, 190), 1000, 6, 1, 1, -338567800, 1512153000, 50.0).message_id
86
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
Nonefor an id outside the common dialect, which is what aDialectis for.
>>> known_crc_extra(0)
50
>>> known_crc_extra(9999) is None
True
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
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
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_FRAMEof 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_NEDframe.
>>> local_position(MavlinkHeader(255, 190), 1000, 1, 1, 1, 10.0, 0.0, -5.0).message_id
84
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_FRAMEof 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_NEDframe.
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
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_lenis0for a scalar. :returns: The seed.
>>> message_crc_extra("PRIVATE_STATUS", [("uint32_t", "uptime", 0)]) >= 0
True
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
33or"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'
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.
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.
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'
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_maskfield of a setpoint carries it.
>>> type_mask(TypeMask.VELOCITY | TypeMask.YAW_RATE) == mavlink_offboard_type_mask(2 | 16)
True