Skip to main content

MAVLink to DJI translator bridge (prototype)

Project description

mavlink-dji-bridge — FULL GUIDE

Comprehensive guide to the mavlink-dji-bridge project: design, installation, usage, developer API, backends, testing, and troubleshooting.

Overview

  • Receive MAVLink commands (e.g., TAKEOFF, LAND, MISSION uploads) and translate to DJI actions.
  • Emit MAVLink telemetry from the DJI state.
  • Pure-Python prototype: pymavlink, pyserial optional.

Quickstart

  1. Install: pip install pymavlink pyserial
  2. Run in mock mode: python -m py_mavlink_dji.cli --mock
  3. Configure with TOML: Create config.toml and run: python -m py_mavlink_dji.cli --config config.toml Notes
  • This is a prototype. See README.md for limitations and to-dos.

Notes on real DJI backend

  • A DJIOnboardBackend wrapper was added that attempts to use a Python DJI SDK binding (import name djisdk). If you want to connect to real DJI hardware, install or provide a djisdk binding that exposes activation and broadcast APIs (or adapt mavlink-dji-bridge/src/py_mavlink_dji/backend.py to your binding). If the binding is not available, the CLI falls back to the --mock backend.

Camera API

Camera and video control APIs are documented in docs/camera.md. See py_mavlink_dji.camera.CameraController for high-level usage and py_mavlink_dji.backend.HardwareBackend for required backend methods (start_video_recording, stop_video_recording, take_photo).

Building the native binding (Linux)

This repository includes a small pybind11 scaffold for a native DJI binding. The scaffold exports basic functions and is optional.

Quick local build (recommended for Linux):

  • Ensure build deps are installed (Ubuntu example):
    sudo apt-get update
    sudo apt-get install -y build-essential cmake
    
  • Build with pip (installs pybind11 as part of the process):
    python -m pip install --upgrade pip setuptools wheel pybind11 build
    python -m build --wheel
    
  • The wheel will appear in dist/. Install it via pip install dist/your_wheel.whl.

Configuring DJI SDK include/libs

If you have the DJI Onboard SDK installed and want the binding to link against it, set environment variables before building:

  • DJI_SDK_INCLUDE — colon-separated extra include directories (e.g. /opt/dji/include)
  • DJI_SDK_LIB_DIR — colon-separated library directories (e.g. /opt/dji/lib)
  • DJI_SDK_LIBS — comma-separated libraries to link (e.g. djisdk,otherlib)

Example:

export DJI_SDK_INCLUDE=/opt/dji/include
export DJI_SDK_LIB_DIR=/opt/dji/lib
export DJI_SDK_LIBS=djisdk
python -m build --wheel

CI

A GitHub Actions workflow ./github/workflows/build-linux.yml is included to build wheels on ubuntu-latest for Python 3.9-3.11. It uploads built wheels as workflow artifacts.

Notes

  • The current C++ binding is a safe stub by default; replace stub implementations in py_mavlink_dji/djibindings/bindings.cpp with real SDK calls once the SDK and headers are available.
  • If you prefer not to build native extensions, continue using the MockBackend.

Enabling real DJI SDK linkage

To compile the extension so it links against the real DJI SDK, set DJI_SDK_ENABLED=1 (or provide DJI_SDK_LIBS) in the environment when building. Example:

export DJI_SDK_ENABLED=1
export DJI_SDK_INCLUDE=/opt/dji/include
export DJI_SDK_LIB_DIR=/opt/dji/lib
export DJI_SDK_LIBS=djisdk
python -m build --wheel

When DJI_SDK_ENABLED=1 the C++ binding will compile with DJI_SDK_AVAILABLE defined and attempt to call SDK symbols. You must ensure the SDK headers and libraries provide the expected symbols (or adapt bindings.cpp to your SDK API).

IMPORTANT: How this package uses the DJI Onboard SDK (READ CAREFULLY)

  • This package does NOT include DJI's Onboard SDK. The native SDK is proprietary and must be installed separately by you or linked at build time. The Python package provides a binding scaffold and a safe prototype fallback so the package is usable for development without DJI hardware.

  • Two integration modes (choose one):

    • Build-time native binding (recommended for production) — compile the pybind11 extension against the DJI SDK on your machine or in CI. Use the environment variables documented above (DJI_SDK_ENABLED, DJI_SDK_INCLUDE, DJI_SDK_LIB_DIR, DJI_SDK_LIBS) before running python -m build or pip install .[djibindings]. This produces a compiled extension (py_mavlink_dji.djibindings._djibindings) that the package will prefer at runtime.
    • Runtime Python binding or subprocess (optional) — install a separate Python binding package (an importable module named djisdk) or run a helper subprocess that links the DJI SDK and exposes a simple JSON/UDP API. If djisdk is importable, DJIOnboardBackend will use it.
  • Autodetection behavior (runtime):

    • At runtime the package attempts the following (in order) when choosing a hardware backend:
      1. If the compiled extension py_mavlink_dji.djibindings._djibindings is importable, it is used. You can check with:
        python -c "import py_mavlink_dji.djibindings as d; print(bool(getattr(d, '_core', None)))"
        
      2. Otherwise the package tries to import a Python SDK binding named djisdk (so import djisdk must succeed).
      3. If neither the compiled extension nor djisdk are available, the CLI/application falls back to MockBackend. This is for development/testing only and does NOT control real DJI hardware.
  • Safety & development guidance

    • Always test first in --mock mode or using MockBackend before attempting hardware builds or flight.
    • Never store activation keys or credentials in the repository. Use environment variables or an encrypted secrets store for CI.
    • If you build the native binding locally, run the simple import test shown above before starting the bridge.

Publishing & PyPI notes

  • If you intend to publish to PyPI, DO NOT bundle DJI SDK binaries or headers in your wheel. Instead:

    • Publish pure-Python wheels that work with the prototype backends (these are safe and hardware-free), and separately publish platform-specific wheels that include the compiled binding only if your CI can legally and technically link the DJI SDK on that runner.
    • Recommended: use cibuildwheel to build manylinux/macos/windows wheels and upload them to GitHub Releases or PyPI. For wheels that link the DJI SDK, ensure licensing allows automated linking on the CI runner and that required SDK artifacts are available to the build (often not possible for proprietary SDKs).
  • For users, the simplest install paths are:

    • Development (no SDK): pip install . — uses prototype backends.
    • Development with compiled binding (Linux): set env vars, then python -m build --wheel and pip install dist/*.whl.
    • Production (recommended): install a wheel built on a controlled CI environment that was linked against the appropriate SDK.

Troubleshooting checklist

  • If the bridge falls back to prototype mode unexpectedly:

    1. Confirm compiled binding is importable:
      python -c "import py_mavlink_dji.djibindings as d; print(getattr(d, '_core', None))"
      
    2. If it's None, check that you built the extension with DJI_SDK_ENABLED=1 and correct DJI_SDK_INCLUDE/DJI_SDK_LIB_DIR/DJI_SDK_LIBS.
    3. Check the CLI startup output: it prints which backend was selected.
  • If you need a no-build option for production (avoid compiling on user machines), consider the subprocess helper approach — contact the maintainers or request that option and we can add a packaged helper.

Summary (one-liner)

  • The package is a normal Python package installable with pip; to control real DJI hardware you must either install a Python SDK binding named djisdk or build the compiled pybind11 extension against DJI's SDK (env-vars + build). Otherwise the package runs in safe prototype/mock mode suitable for development and CI.

Developer reference (expanded)

The following sections expand the quick overview above with concrete developer-focused details, API surface, and actionable examples.

API & internals (detailed)

Bridge

  • Location: src/py_mavlink_dji/adapter.py
  • Purpose: central routing between incoming MAVLink messages and the configured backend. Holds:
    • self.backend — instance of HardwareBackend
    • self.stateSharedState instance with telemetry and SDK flags
    • self.commandsCommands wrapper providing safe, queued command execution
    • self.mission_mgrMissionManager for mission uploads/execution state
  • Lifecycle:
    • start() attempts to obtain control authority and start backend polling when available
    • stop() releases authority and stops backend resources
    • _handle(msg) receives parsed MAVLink messages and maps them into actions (takeoff/land/RTH, mission items, velocity messages). This function now contains the routing logic used by tests and quick integrations.

Commands & SafetyController

  • Location: src/py_mavlink_dji/commands.py
  • Commands provides a small command queue and avoids overlap by:
    • Using an internal _cmd_send_flag and _cmd_queue
    • Calling backend methods via _enqueue_or_send(...)
    • Expecting backends to register command ack callbacks via set_cmd_send_cb(cb) and forward ack tokens to Commands.notify_cmd_status(cmd_id, status_str) so the queue can proceed.
  • SafetyController enforces:
    • Battery preflight thresholds (blocks takeoff)
    • Geofence center/radius and altitude bounds
    • Velocity and altitude delta validations
    • Emergency stop / failsafe behavior (allow recovery-only actions such as landing/RTH)

Motion controls

  • Location: src/py_mavlink_dji/motion_controls.py
  • Helpers:
    • fly_to_localpos(backend, x, y, z, safety=None) — uses set_velocity if available otherwise falls back to set_attitude
    • fly_to_globalpos(backend, lat, lon, alt, reference=None, safety=None) — projects lat/lon deltas to local meters and calls fly_to_localpos
    • set_velocity(backend, vx, vy, vz, frame="local", safety=None) — validates velocity via safety then calls backend

Mission manager

  • Location: src/py_mavlink_dji/missions.py
  • API:
    • start_upload(count, mission_id=0) — prepares to receive count mission items
    • receive_item(item, mission_id=0) — append item and returns done boolean when upload complete
    • begin_fly(mission_id=0, start_seq=0) — set mission running and call backend start_mission if present

Transport

  • Location: src/py_mavlink_dji/transport.py
  • UDPTransport and SerialTransport provide simple send/receive primitives for custom backends or tooling.

Examples (expanded)

Programmatic bridge with mock backend

from py_mavlink_dji.adapter import Bridge
from py_mavlink_dji.backend import MockBackend

backend = MockBackend()
bridge = Bridge(source_uri="udp:0.0.0.0:14550", backend=backend)
bridge.activate_backend()   # optional activation step
bridge.start()
bridge.commands.takeoff()
bridge.commands.land()
bridge.stop()

Send a velocity command via MAVLink parsing (adapter flow)

  1. Ensure your MAVLink handler exposes a message object with get_type() returning SET_POSITION_TARGET_LOCAL_NED or a similar type and with vx, vy, vz attributes.
  2. The adapter will extract velocities and enqueue a set_velocity call to the backend respecting safety checks.

Manual enqueue (direct)

bridge.commands._enqueue_or_send(lambda: backend.set_velocity(2.0, 0.0, 0.0, frame="local"))

Mission upload (programmatic)

items = [{"seq": 0, "x": 37.0, "y": -122.0, "z": 30.0, "command": 16}]
bridge.mission_mgr.start_upload(len(items), mission_id=0)
for it in items:
    bridge.mission_mgr.receive_item(it, mission_id=0)
bridge.backend.upload_mission(bridge.mission_mgr.missions[0])

Command ack / backend responsibilities

  • Backends that speak to real SDKs should:
    • Implement set_cmd_send_cb(cb) so the adapter can register Commands.notify_cmd_status.
    • When SDK reports command lifecycle events, call the stored callback with (cmd_id, status_str) where status_str matches tokens documented in docs/commands.md.
    • Use terminal tokens (STATUS_CMD_EXE_SUCCESS, STATUS_CMD_EXE_FAIL, REQ_TIME_OUT, REQ_REFUSE) to allow queued commands to progress.

Testing

  • Unit tests are in tests/. Use pytest -q to run the suite locally.
  • For creating new tests:
    • Prefer MockBackend or small recorder backends that assert calls.
    • Keep tests deterministic; do not rely on real hardware.

Roadmap and suggested next work

  • Precise SET_POSITION_TARGET_* parsing: read type_mask and honor which fields are present (position vs velocity vs acceleration).
  • Implement MAV_CMD_DO_CHANGE_SPEED (MAV command 178) mapping to backend speed controls or throttle adjustments.
  • Add integration tests with a subprocess helper that bridges to a compiled DJI binding or hardware emulator.

If you want, I will:

  • Split this README into docs/ pages and add a navigable index.
  • Add concrete code snippets for type_mask parsing and a unit test demonstrating velocity mapping.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

py_mavlink_dji-0.0.9.tar.gz (40.0 kB view details)

Uploaded Source

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page