gomavlib

repository·main·Indexed 41 days ago

https://github.com/bluenviron/gomavlib

A Go library implementing the Mavlink protocol (v1.0 and v2.0) for communication with UAVs, UGVs, and ground stations. It supports various transports including serial, UDP (server, client, broadcast), and TCP (server, client), and provides tools like dialect-import to generate Go representations from custom XML Mavlink dialect definitions.

Tokens
6.6K
Snippets
27
Records
32
Agent score
51%

What's inside gomavlib

  1. Overview of gomavlib features

    main

    gomavlib is a Go implementation of the Mavlink protocol (v1.0 and v2.0) designed for interacting with UAVs (drones), UGVs, and ground stations.

    Key Capabilities:

    • Node Communication: Create Mavlink nodes using serial, UDP (server, client, broadcast), TCP (server, client), or custom transports. Supports domain names and IPs.
    • Automatic Features: Automatically emits heartbeats and can be configured to send automatic stream requests to Ardupilot devices (disabled by default).
    • Encoding/Decoding: Full support for Mavlink v2.0 features including empty-byte truncation, signatures, and message extensions. Computes and validates checksums.
    • Dialect Flexibility: Use standard dialects, custom dialects (via XML generation), or no dialect at all (allowing message routing without decoding content).
    • Telemetry: Ability to read and write telemetry logs (tlog).
  2. Generate custom Mavlink dialects

    main

    While standard dialects are provided in the pkg/dialects/ directory, you can define and use custom dialects by converting XML definitions into Go representations using the dialect-import tool.

    To generate Go files from a custom XML dialect definition, install the tool and run it against your XML file:

    go install github.com/bluenviron/gomavlib/v4/cmd/dialect-import@latest
    dialect-import my_dialect.xml
  3. Install gomavlib

    main

    To use gomavlib in your Go project, follow these steps:

    1. Ensure you have Go >= 1.26 installed.
    2. Initialize a new Go module in your project folder:
      go mod init main
    3. Download the library and any necessary example files to your folder.
    4. Compile and run your code using go run.

    Note: The library supports Mavlink v1.0 and v2.0 and can interact with devices via serial, UDP (server, client, or broadcast), TCP (server or client), or custom readers/writers.

    go mod init main
    go run name-of-the-go-file.go
  4. Use the Channel type for MAVLink communication

    main

    A Channel is a communication link created by an Endpoint. It handles the underlying reading and writing of MAVLink frames and messages.

    Depending on the Endpoint used, a Channel might represent a single connection (like a TCP client) or one of many connections (like a TCP server). It supports both stream-based and datagram-based communication.

    Key characteristics:

    • It manages its own internal reader and writer goroutines.
    • It emits events to the parent Node (e.g., EventChannelOpen, EventFrame, EventParseError, EventChannelClose).
    • It can write both high-level message.Message objects and low-level frame.Frame objects.
  5. Monitor MAVLink node activity using Events

    main

    The gomavlib node provides an event stream via node.Events(). All events implement the Event interface. You can monitor node activity by ranging over this channel and using a type switch to handle specific event types. This is useful for tracking channel lifecycle, frame reception, parsing errors, and automatic stream requests.

    for event := range node.Events() {
    	switch e := event.(type) {
    	case *gomavlib.EventFrame:
    		fmt.Printf("Received message from SystemID: %d\n", e.SystemID())
    	case *gomavlib.EventParseError:
    		fmt.Printf("Parse error on channel %v: %v\n", e.Channel, e.Error)
    	case *gomavlib.EventChannelOpen:
    		fmt.Printf("Channel opened: %v\n", e.Channel)
    	case *gomavlib.EventChannelClose:
    		fmt.Printf("Channel closed: %v, error: %v\n", e.Channel, e.Error)
    	case *gomavlib.EventStreamRequested:
    		fmt.Printf("Stream requested for SystemID: %d\n", e.SystemID)
    	}
    }
  6. Initialize and use a Node

    main

    A Node is a high-level MAVLink encoder and decoder that manages communication with one or more Endpoints. To use a Node, you must provide at least one Endpoint and specify the OutVersion (e.g., V1 or V2).

    Key Configuration Options

    • Endpoints: A slice of Endpoint objects to communicate with.
    • Dialect: (Optional) A *dialect.Dialect containing the messages to be encoded/decoded. If omitted, messages are handled as MessageRaw.
    • OutVersion: The MAVLink version used for encoding (V1 or V2).
    • OutSystemID: The system ID added to every outgoing frame.
    • OutComponentID: (Optional) The component ID for outgoing frames. Defaults to 1.
    • InKey: (Optional) A *frame.V2Key used to validate incoming signed frames. Non-signed or version < 2.0 frames are discarded if this is set.
    • OutKey: (Optional) A *frame.V2Key used to sign outgoing frames (requires OutVersion to be V2).
    • HeartbeatDisable: (Optional) If true, disables periodic heartbeat sending.
    • HeartbeatPeriod: (Optional) Period between heartbeats. Defaults to 5s.
    • ReadTimeout, WriteTimeout, IdleTimeout: Timeouts for network operations. Defaults are 10s, 10s, and 60s respectively.

    Lifecycle

    1. Create a Node instance with your desired configuration.
    2. Call n.Initialize() to start the node, initialize endpoints, and start background routines (heartbeats, stream requests, etc.).
    3. Use n.Events() to listen for incoming events.
    4. Call n.Close() to halt operations and wait for all routines to return.
    node := &gomavlib.Node{
    	Endpoints: []gomavlib.Endpoint{...},
    	OutVersion: gomavlib.V2,
    	OutSystemID: 1,
    }
    
    err := node.Initialize()
    if err != nil {
    	// handle error
    }
    
    // ... use node ...
    
    node.Close()
  7. Generate MAVLink dialect code using dialects-gen

    main

    The dialects-gen command is a utility used to automate the generation of MAVLink dialect code within the gomavlib repository. It performs the following high-level workflow:

    1. Fetches the latest MAVLink definitions: It queries the GitHub API to find the latest commit SHA from the official mavlink/mavlink repository.
    2. Downloads XML definitions: It iterates through the message_definitions/v1.0 directory of the MAVLink repository and downloads the .xml files.
    3. Imports dialects: For each XML file, it invokes the dialect-import tool (via go run ../../cmd/dialect-import --link <url>) to generate the Go source code.
    4. Generates test suites: It automatically creates unit tests for the newly generated dialects, including:
      • dialect_test.go: Tests the dialect.ReadWriter initialization.
      • enum_*_test.go: Tests the MarshalText, UnmarshalText, and String methods for generated enums.
      • package_test.go: Provides common test cases for enum unmarshaling/marshaling.

    Note: This tool is designed to be run within the repository structure, as it relies on the relative path ../../cmd/dialect-import to perform the actual code generation.

    # This tool is typically run as a command within the repository environment.
    # It automates the process of fetching, importing, and testing MAVLink dialects.
    # It requires the 'dialect-import' tool to be available at ../../cmd/dialect-import
    ```bash
    go run cmd/dialects-gen/main.go
  8. Implement the Endpoint interface for custom MAVLink transport layers

    main

    The Endpoint interface defines the requirements for a transport layer that provides communication channels for MAVLink. To implement a custom transport (e.g., for a specific serial port, network socket, or radio hardware), your type must satisfy the following methods:

    • init(node *Node) error: Initializes the endpoint with a reference to the MAVLink Node.
    • isEndpoint() : A marker method to identify the type as an endpoint.
    • close() : Closes the underlying transport.
    • oneChannelAtAtime() bool: Returns true if the endpoint can only support one communication channel at a time.
    • isDatagram() bool: Returns true if the endpoint uses a datagram-based protocol (like UDP) rather than a continuous stream.
    • provide() (string, io.ReadWriteCloser, error): Returns a unique identifier for the endpoint, an io.ReadWriteCloser for data transmission, and an error if the provider fails.
    type MyCustomEndpoint struct {
        // implementation fields
    }
    
    func (e *MyCustomEndpoint) init(node *Node) error {
        // initialization logic
        return nil
    }
    
    func (e *MyCustomEndpoint) isEndpoint() {}
    
    func (e *MyCustomEndpoint) close() {
        // cleanup logic
    }
    
    func (e *MyCustomEndpoint) oneChannelAtAtime() bool {
        return true
    }
    
    func (e *MyCustomEndpoint) isDatagram() bool {
        return false
    }
    
    func (e *MyCustomEndpoint) provide() (string, io.ReadWriteCloser, error) {
        return "my-endpoint-id", myReadWriteCloser, nil
    }
  9. Implement a custom server transport with EndpointCustomServer

    main

    Use EndpointCustomServer when you need to implement custom server-side transport logic that conforms to the net.Listener interface. This is useful for integrating encrypted protocols like DTLS or TCP with TLS into your MAVLink node.

    To use it, you must provide a Listen function that returns a net.Listener. You should also specify the Label for the protocol and set IsDatagram to true if the underlying protocol is datagram-based (e.g., UDP).

    // Example of configuring an EndpointCustomServer
    endpoint := &gomavlib.EndpointCustomServer{
    	Listen: func() (net.Listener, error) {
    		// Return your custom net.Listener here (e.g., a TLS listener or DTLS listener)
    		return net.Listen("tcp", ":5000")
    	},
    	Label:      "tls-tcp",
    	IsDatagram: false,
    }
  10. Send MAVLink messages and frames from a Node

    main

    The Node provides several methods to send data through its managed channels. You can send either high-level message.Message objects (which the node will automatically encode using the provided dialect) or low-level frame.Frame objects (used for routing pre-existing frames).

    Sending Messages

    Use these methods when you have a structured MAVLink message. The node handles encoding and checksums.

    • WriteMessageTo(channel *Channel, m message.Message) error: Writes a message to a specific channel.
    • WriteMessageAll(m message.Message) error: Writes a message to all active channels.
    • WriteMessageExcept(exceptChannel *Channel, m message.Message) error: Writes a message to all channels except the one specified.

    Sending Frames

    Use these methods when you are routing existing frames (e.g., from another node). Since frames are already formed, the node will recompute checksums and signatures if necessary via FixFrame logic internally.

    • WriteFrameTo(channel *Channel, fr frame.Frame) error: Writes a frame to a specific channel.
    • WriteFrameAll(fr frame.Frame) error: Writes a frame to all active channels.
    • WriteFrameExcept(exceptChannel *Channel, fr frame.Frame) error: Writes a frame to all channels except the one specified.
    // Writing a message to a specific channel
    err := node.WriteMessageTo(myChannel, myMessage)
    
    // Writing a message to all channels
    err := node.WriteMessageAll(myMessage)
    
    // Writing a pre-existing frame to all channels
    err := node.WriteFrameAll(myFrame)
  11. Use EndpointUDPBroadcast for MAVLink communication via UDP broadcast

    main

    The EndpointUDPBroadcast type implements the Endpoint interface, allowing MAVLink communication using UDP broadcast packets. This is useful when you want to send messages to all devices on a local network segment without knowing their specific IP addresses.

    Configuration Fields

    • BroadcastAddress: (Required) The broadcast address and port to which outgoing frames are sent (e.g., 192.168.5.255:5600).
    • LocalAddress: (Optional) The address to listen on. If left empty, the endpoint will automatically attempt to compute the local IP address associated with the provided BroadcastAddress by inspecting network interfaces.
    endpoint := &gomavlib.EndpointUDPBroadcast{
    	BroadcastAddress: "192.168.5.255:5600",
    	// LocalAddress is optional
    }
  12. Use EndpointTCPServer for TCP server connections

    main

    The EndpointTCPServer type allows you to establish a MAVLink communication endpoint that acts as a TCP server. It listens on a specified address for incoming TCP connections.

    Note on usage: While TCP is suitable for routing frames over the internet, it is noted that it may not be the most appropriate method for transferring frames from a UAV to a GCS because TCP does not allow for frame losses (it guarantees delivery, which can lead to latency issues in real-time telemetry compared to UDP).

    // Example configuration for an EndpointTCPServer
    endpoint := &EndpointTCPServer{
        Address: "0.0.0.0:5600", // Listen on all interfaces at port 5600
    }