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,pyserialoptional.
Quickstart
- Install: pip install pymavlink pyserial
- Run in mock mode: python -m py_mavlink_dji.cli --mock
- Configure with TOML:
Create
config.tomland run: python -m py_mavlink_dji.cli --config config.toml Notes
- This is a prototype. See
README.mdfor limitations and to-dos.
Notes on real DJI backend
- A
DJIOnboardBackendwrapper was added that attempts to use a Python DJI SDK binding (import namedjisdk). If you want to connect to real DJI hardware, install or provide adjisdkbinding that exposes activation and broadcast APIs (or adaptmavlink-dji-bridge/src/py_mavlink_dji/backend.pyto your binding). If the binding is not available, the CLI falls back to the--mockbackend.
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 viapip 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.cppwith 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 runningpython -m buildorpip 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. Ifdjisdkis importable,DJIOnboardBackendwill use it.
- 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 (
-
Autodetection behavior (runtime):
- At runtime the package attempts the following (in order) when choosing a hardware backend:
- If the compiled extension
py_mavlink_dji.djibindings._djibindingsis importable, it is used. You can check with:python -c "import py_mavlink_dji.djibindings as d; print(bool(getattr(d, '_core', None)))"
- Otherwise the package tries to import a Python SDK binding named
djisdk(soimport djisdkmust succeed). - If neither the compiled extension nor
djisdkare available, the CLI/application falls back toMockBackend. This is for development/testing only and does NOT control real DJI hardware.
- If the compiled extension
- At runtime the package attempts the following (in order) when choosing a hardware backend:
-
Safety & development guidance
- Always test first in
--mockmode or usingMockBackendbefore 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.
- Always test first in
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
cibuildwheelto 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 --wheelandpip install dist/*.whl. - Production (recommended): install a wheel built on a controlled CI environment that was linked against the appropriate SDK.
- Development (no SDK):
Troubleshooting checklist
-
If the bridge falls back to prototype mode unexpectedly:
- Confirm compiled binding is importable:
python -c "import py_mavlink_dji.djibindings as d; print(getattr(d, '_core', None))"
- If it's
None, check that you built the extension withDJI_SDK_ENABLED=1and correctDJI_SDK_INCLUDE/DJI_SDK_LIB_DIR/DJI_SDK_LIBS. - Check the CLI startup output: it prints which backend was selected.
- Confirm compiled binding is importable:
-
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
djisdkor 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 ofHardwareBackendself.state—SharedStateinstance with telemetry and SDK flagsself.commands—Commandswrapper providing safe, queued command executionself.mission_mgr—MissionManagerfor mission uploads/execution state
- Lifecycle:
start()attempts to obtain control authority and start backend polling when availablestop()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 Commandsprovides a small command queue and avoids overlap by:- Using an internal
_cmd_send_flagand_cmd_queue - Calling
backendmethods via_enqueue_or_send(...) - Expecting backends to register command ack callbacks via
set_cmd_send_cb(cb)and forward ack tokens toCommands.notify_cmd_status(cmd_id, status_str)so the queue can proceed.
- Using an internal
SafetyControllerenforces:- 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)— usesset_velocityif available otherwise falls back toset_attitudefly_to_globalpos(backend, lat, lon, alt, reference=None, safety=None)— projects lat/lon deltas to local meters and callsfly_to_localposset_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 receivecountmission itemsreceive_item(item, mission_id=0)— append item and returnsdoneboolean when upload completebegin_fly(mission_id=0, start_seq=0)— set mission running and call backendstart_missionif present
Transport
- Location:
src/py_mavlink_dji/transport.py UDPTransportandSerialTransportprovide 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)
- Ensure your MAVLink handler exposes a message object with
get_type()returningSET_POSITION_TARGET_LOCAL_NEDor a similar type and withvx,vy,vzattributes. - The adapter will extract velocities and enqueue a
set_velocitycall 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 registerCommands.notify_cmd_status. - When SDK reports command lifecycle events, call the stored callback with
(cmd_id, status_str)wherestatus_strmatches tokens documented indocs/commands.md. - Use terminal tokens (
STATUS_CMD_EXE_SUCCESS,STATUS_CMD_EXE_FAIL,REQ_TIME_OUT,REQ_REFUSE) to allow queued commands to progress.
- Implement
Testing
- Unit tests are in
tests/. Usepytest -qto run the suite locally. - For creating new tests:
- Prefer
MockBackendor small recorder backends that assert calls. - Keep tests deterministic; do not rely on real hardware.
- Prefer
Roadmap and suggested next work
- Precise
SET_POSITION_TARGET_*parsing: readtype_maskand 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_maskparsing 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
File details
Details for the file py_mavlink_dji-0.0.9.tar.gz.
File metadata
- Download URL: py_mavlink_dji-0.0.9.tar.gz
- Upload date:
- Size: 40.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.25
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
de8989f6ecc28970bab3682ae26b198a3aa4d20d16e8e025908b783285740654
|
|
| MD5 |
bc2a5566f598a7b4e51bd83b7dea35c4
|
|
| BLAKE2b-256 |
603d571c0a82c934f20dd8bd0188e1f28f022b5ca46793712c5a86bc8ccb8142
|