pamoja for TypeScript - v0.2.0
    Preparing search index...

    Module @pamoja/mavlink

    Ergonomic facade over the generated MAVLink binding.

    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 offboard builds setpoints. Each takes a frame off the link and hands back the frame to send, with no IO or timers of its own.

    @pamoja/mavlink

    MAVLink v1 and v2 framing, signing, named message fields and enum values, and the mission, command, and offboard protocols. One capability of pamoja, one memory-safe Rust core with bindings for TypeScript, Python, and C#.

    read the guide documentation API reference

    npm install @pamoja/mavlink
    

    This pulls in @pamoja/native, the compiled engine. npm install pamoja is the whole framework in one package.

    The test that runs in CI, spliced here as it ran.

    From bindings/node/guides/mavlink.ts:

    import {
    type MavlinkFrame,
    type MavlinkHeader,
    MavlinkMessage,
    MavlinkParser,
    enumEntry,
    enumNames,
    enumValue,
    fromObject,
    message,
    schemaFor,
    } from '@pamoja/mavlink'

    const VEHICLE = 1
    const AUTOPILOT = 1
    const STATION = 255
    const PLANNER = 190

    // An enum field travels as a number, and the dialect names each number. Printing the name
    // keeps a reader from looking up what 2 or 81 means.
    const name = (enumeration: string, value: number): string =>
    enumEntry(enumeration, value) ?? String(value)
    const header = (systemId: number, componentId: number, sequence: number): MavlinkHeader => ({
    systemId,
    componentId,
    sequence,
    })

    // Every node broadcasts a heartbeat to say what it is and that it is alive. The frame wraps
    // the payload in a header and a checksum seeded with the message's own value.
    const heartbeatShape = schemaFor('HEARTBEAT')
    const announce = fromObject(heartbeatShape, {
    type: enumValue('MAV_TYPE_GCS'),
    autopilot: enumValue('MAV_AUTOPILOT_INVALID'),
    system_status: enumValue('MAV_STATE_ACTIVE'),
    mavlink_version: 3,
    })
    const sent = announce.toFrame(header(STATION, PLANNER, 0))
    console.log(`sent ${heartbeatShape.name} in ${sent.bytes.length} bytes`)

    // The vehicle answers with its own heartbeat, which reaches the station behind some noise
    // and a copy with its last byte flipped in flight.
    const vehicle = fromObject(heartbeatShape, {
    type: enumValue('MAV_TYPE_QUADROTOR'),
    autopilot: enumValue('MAV_AUTOPILOT_ARDUPILOTMEGA'),
    base_mode:
    enumValue('MAV_MODE_FLAG_CUSTOM_MODE_ENABLED') |
    enumValue('MAV_MODE_FLAG_STABILIZE_ENABLED') |
    enumValue('MAV_MODE_FLAG_MANUAL_INPUT_ENABLED'),
    system_status: enumValue('MAV_STATE_STANDBY'),
    mavlink_version: 3,
    })
    const good = vehicle.toFrame(header(VEHICLE, AUTOPILOT, 0))
    const garbled = Buffer.from(good.bytes)
    garbled[garbled.length - 1] ^= 0xff
    const delivered = Buffer.concat([Buffer.from('???'), garbled, good.bytes])

    // The parser skips whatever does not start a frame and drops a frame whose checksum fails,
    // so only the good copy comes out.
    const frames = new MavlinkParser().push(delivered)
    console.log(
    `parsed ${frames.length} frame out of ${delivered.length} bytes,` +
    ' past the noise and the garbled copy',
    )
    const heard = MavlinkMessage.decode(heartbeatShape, frames[0]!.payload)
    console.log(
    `heard ${name('MAV_TYPE', heard.get('type'))} on` +
    ` ${name('MAV_AUTOPILOT', heard.get('autopilot'))},` +
    ` in ${name('MAV_STATE', heard.get('system_status'))}`,
    )

    // The base mode is a bitmask, so it names a set of flags rather than one value.
    const baseMode = heard.get('base_mode')
    console.log(`flags ${enumNames('MAV_MODE_FLAG', baseMode).join(' | ')}`)
    if ((baseMode & enumValue('MAV_MODE_FLAG_SAFETY_ARMED')) === 0) {
    console.log('disarmed MAV_MODE_FLAG_SAFETY_ARMED is not among them')
    }
    Language Package Reference
    Rust pamoja-mavlink reference, docs.rs, install
    TypeScript @pamoja/mavlink reference, install
    Python pamoja-mavlink reference, install
    C# Pamoja.Mavlink reference, install

    MIT

    CommandProtocol
    Dialect
    MavlinkFrame
    MavlinkMessage
    MavlinkParser
    MavlinkSigner
    MavlinkVerifier
    MessageSchema
    MessageSchemaBuilder
    MissionReceiver
    MissionSender
    ReceiverStep
    SenderStep
    AckOutcome
    MavlinkEnumEntry
    MavlinkField
    MavlinkFieldInfo
    MavlinkHeader
    MavlinkFields
    MavlinkFieldTypeValue
    MavlinkFieldValue
    MavlinkVersion
    DEFAULT_TIMESTAMP_WINDOW
    KEY_LEN
    MavlinkFieldType
    MavlinkTypeMask
    MavlinkVersion
    MAX_FRAME
    MAX_PAYLOAD
    MAX_RETRIES
    offboard
    SIGNATURE_LEN
    crc16
    enumEntries
    enumEntry
    enumIsBitmask
    enumNames
    enumValue
    frame
    fromObject
    knownCrcExtra
    knownEnums
    knownMessages
    message
    messageCrcExtra
    schemaFor
    timestampFromUnixMicros
    timestampNow
    toObject