Annex A — Mappings to ONVIF, MAVLink, MISB ST 0601 and the DJI Cloud APILink to the section Annex A — Mappings to ONVIF, MAVLink, MISB ST 0601 and the DJI Cloud API
On this page
Maturity: Draft · Informative (normative for gateway authors who claim a mapping). Items marked ⚠ require verification against the primary specification.
A.1 ONVIF ↔ UNIVIPLink to the section A.1 ONVIF ↔ UNIVIP
A.1.1 Services and resourcesLink to the section A.1.1 Services and resources
| ONVIF | UNIVIP |
|---|---|
Device GetDeviceInformation |
GET /devices/{id} → manufacturer, model, firmware_version, serial_number |
Device GetCapabilities, GetServices |
GET /devices/{id}/capabilities |
Device GetSystemDateAndTime |
telemetry.time + RFC 3339 timestamps |
Media2 GetProfiles, GetStreamUri |
capabilities.media.streams[]; rtsp.url when RTSP offered |
| WebRTC (Profile V) | signal envelopes / POST .../webrtc |
| Uplink | Device-initiated session channel |
Events CreatePullPointSubscription / MQTT binding |
Session channel event envelopes / MQTT alternative |
PTZ GetConfigurations, GetNodes |
capabilities.payloads[].gimbal, .zoom |
PTZ AbsoluteMove |
camera.gimbal.set_orientation (+ camera.zoom.set) |
PTZ RelativeMove |
camera.gimbal.move_relative |
PTZ ContinuousMove with Timeout |
camera.gimbal.move_continuous with expires_at |
PTZ Stop |
camera.gimbal.stop, camera.zoom.stop |
PTZ GotoHomePosition |
camera.gimbal.recenter |
PTZ GeoMove ⚠ |
camera.gimbal.point_at |
Imaging SetImagingSettings |
camera.imaging.set |
Imaging Move (focus) |
camera.focus.set, camera.focus.trigger |
| Recording control (Profile G) | camera.recording.start/stop for on-board recording; client recording is the VMS's own |
A.1.2 PTZ space conversionLink to the section A.1.2 PTZ space conversion
ONVIF generic pan/tilt space is normalized [-1, 1]. A gateway exposing a UNIVIP gimbal as ONVIF PTZ maps yaw_deg ∈ [yaw.min, yaw.max] linearly onto pan ∈ [-1, 1] and pitch_deg onto tilt likewise, and zoom_ratio ∈ [min_ratio, max_ratio] onto zoom ∈ [0, 1]. The gateway MUST publish an ONVIF SphericalDegrees space when it exposes EARTH frame angles. Continuous velocities map rate_degps / max_rate_degps.
A.1.3 Event topicsLink to the section A.1.3 Event topics
| UNIVIP topic | ONVIF topic (suggested) |
|---|---|
vehicle.health.fault |
tns1:Device/HardwareFailure/* by component, else tns1:Device/Trigger/DigitalInput ⚠ |
vehicle.camera.recording.started/stopped |
tns1:RecordingHistory/Recording/State ⚠ |
vehicle.analytics.object.detected |
tns1:RuleEngine/ObjectDetector/Object with Profile M object metadata |
vehicle.link.lost, vehicle.battery.*, vehicle.operation.*, vehicle.navigation.*, vehicle.control.* |
No ONVIF equivalent → vendor namespace tns1:Device/Trigger/univip/<topic> or UNIVIP namespace univip: |
A.1.4 Metadata streamLink to the section A.1.4 Metadata stream
UNIVIP telemetry MAY be carried in the ONVIF metadata stream as tt:MetadataStream/tt:Extension/univip:Telemetry containing the JSON telemetry sample (as a CDATA string) so a Profile M/T client records it. The ONVIF tt:GeoLocation element SHOULD be populated from position (lat, lon, altitude_msl_m as elevation) and tt:GeoOrientation from motion (roll, pitch, yaw) ⚠ element names.
A.2 MAVLink ↔ UNIVIPLink to the section A.2 MAVLink ↔ UNIVIP
A.2.1 TelemetryLink to the section A.2.1 Telemetry
| MAVLink | UNIVIP | Conversion |
|---|---|---|
GLOBAL_POSITION_INT.lat/lon |
position.latitude_deg/longitude_deg |
÷ 1e7 |
GLOBAL_POSITION_INT.alt |
position.altitude_msl_m |
mm → m (MSL) |
GLOBAL_POSITION_INT.relative_alt |
position.altitude_rel_m |
mm → m |
GLOBAL_POSITION_INT.alt + (GPS_RAW_INT.alt_ellipsoid − GPS_RAW_INT.alt) |
position.altitude_ellipsoid_m |
mm → m: the fused MSL altitude plus the receiver's geoid separation. alt_ellipsoid alone is the raw receiver height and disagrees with the fused MSL altitude. |
DISTANCE_SENSOR (down) / RANGEFINDER |
position.altitude_agl_m (agl_source: RANGEFINDER) |
cm → m / m |
GLOBAL_POSITION_INT.vx/vy/vz |
motion.velocity_north/east/down_mps |
cm/s → m/s |
GLOBAL_POSITION_INT.hdg |
motion.heading_deg |
cdeg → deg; 65535 = unknown → omit |
ATTITUDE.roll/pitch/yaw |
motion.roll_deg/pitch_deg/yaw_deg |
rad → deg; yaw wrapped to [0,360) |
ATTITUDE.rollspeed/... |
motion.*_rate_degps |
rad/s → deg/s |
BATTERY_STATUS.battery_remaining |
power.battery_pct |
% |
BATTERY_STATUS.voltages[] sum / SYS_STATUS.voltage_battery |
power.voltage_v |
mV → V |
BATTERY_STATUS.current_battery |
power.current_a |
cA → A |
BATTERY_STATUS.time_remaining |
power.remaining_endurance_s |
s |
RADIO_STATUS.rssi, remrssi |
link.rssi_dbm (vendor-scaled) / signal_quality_pct |
vendor; declare |
HEARTBEAT.system_status, base_mode, custom_mode, EXTENDED_SYS_STATE.landed_state |
vehicle.vehicle_state, armed, flight_mode |
see A.2.2 |
HOME_POSITION |
vehicle.home |
1e7 / mm |
GPS_RAW_INT.fix_type |
health.gnss.fix |
0,1→NONE; 2→2D; 3→3D; 4→DGPS; 5→RTK_FLOAT; 6→RTK_FIXED |
GPS_RAW_INT.satellites_visible, eph |
health.gnss.satellites, hdop |
eph/100 ⚠ |
SYS_STATUS.onboard_control_sensors_health |
health.sensors, health.faults[] |
bitmask → named entries |
STATUSTEXT severity ≥ WARNING |
vehicle.health.fault / vehicle.operation.emergency |
map MAV_SEVERITY |
STATUSTEXT text at the start of RTL |
vehicle.navigation.returning_home.reason |
⚠ MAVLink has no failsafe-reason message; the reason is inferred from autopilot-specific wording (battery, link/RC/GCS loss, geofence), and is CLIENT when the gateway commanded the return, OTHER when nothing matches |
GIMBAL_DEVICE_ATTITUDE_STATUS.q + flags |
payloads[].gimbal |
quaternion → Euler deg. With GIMBAL_DEVICE_FLAGS_YAW_IN_EARTH_FRAME (or yaw lock) the yaw is an EARTH azimuth; otherwise it is relative to the vehicle and is reported as BODY, or converted to EARTH by adding the vehicle heading. MAVLink's "vehicle frame" keeps pitch and roll level, which is exactly UNIVIP BODY (Part 10 §10.3). |
CAMERA_SETTINGS.zoomLevel |
payloads[].zoom_ratio |
⚠ percent of the zoom range; ratio = 1 + (max − 1) × percent / 100, where the maximum ratio is not in CAMERA_INFORMATION and must come from configuration or the camera definition file. Without it, zoom is not advertised. |
CAMERA_INFORMATION.focal_length, sensor_size_h/v |
payloads[].hfov_deg/vfov_deg |
2·atan(sensor/2f) |
CAMERA_CAPTURE_STATUS.video_status |
payloads[].recording.active |
|
VIDEO_STREAM_INFORMATION |
media.streams[] |
uri → rtsp.url |
A.2.2 Vehicle state derivation (AIR domain; ArduSub uses the same messages, read DEPLOYING/OPERATING/HOLDING as dive/transit/depth-hold)Link to the section A.2.2 Vehicle state derivation (AIR domain; ArduSub uses the same messages, read DEPLOYING/OPERATING/HOLDING as dive/transit/depth-hold)
| Condition | vehicle_state |
|---|---|
Not armed, landed_state = ON_GROUND or UNDEFINED, no flight since arming |
IDLE |
| Armed, ON_GROUND, no flight since arming | PREDEPLOY |
| ON_GROUND after a flight since the last arming | RECOVERED |
landed_state = TAKEOFF |
DEPLOYING |
IN_AIR, mode RTL/Return (or Smart RTL), within 5 m of home and at rest for 10 s (a return with land_on_arrival: false has arrived; both autopilots stay in RTL) |
HOLDING |
| IN_AIR, mode RTL/Return (or Smart RTL) otherwise | RETURNING |
| IN_AIR, ground speed < 0.5 m/s and vertical speed < 0.5 m/s for > 2 s, in a hold mode | HOLDING |
| IN_AIR otherwise | OPERATING |
landed_state = LANDING |
RECOVERING |
system_status = EMERGENCY or FLIGHT_TERMINATION |
EMERGENCY |
MAV_STATE_CRITICAL means a failsafe is active while the vehicle can still navigate; PX4 and ArduPilot report it for every active failsafe, including a low-battery return. It is not EMERGENCY: the state follows the mode (typically RETURNING).
Hold modes are autopilot-specific: PX4 AUTO_LOITER (Hold, in which PX4 also flies a reposition) and POSCTL; ArduPilot copter LOITER, BRAKE, POSHOLD and GUIDED (a vehicle in GUIDED that has reached its target hovers), rover HOLD. A pilot override is a switch into a manual mode the gateway did not command (PX4 MANUAL, ALTCTL, POSCTL, STABILIZED, ACRO; ArduPilot copter STABILIZE, ALT_HOLD, LOITER, POSHOLD, ACRO, SPORT, DRIFT), or lift-off in one, judged on HEARTBEAT alone (a landed-state report can be newer than the last heartbeat), or RC input reported by the autopilot.
STATUSTEXT at WARNING or worse becomes vehicle.health.fault events, not the critical faults of Part 11 §11.2: autopilots send pre-arm and arming refusals at CRITICAL, which would otherwise block client commands. The failsafe reason for vehicle.navigation.returning_home comes from text where the autopilot gives it: ArduPilot says "Battery Failsafe" and "GCS Failsafe"; PX4 1.17 names the cause only through its events protocol (which needs PX4's event metadata), so a PX4 link-loss return reads OTHER from text alone.
A.2.3 CommandsLink to the section A.2.3 Commands
| UNIVIP | MAVLink |
|---|---|
vehicle.hold |
PX4: MAV_CMD_DO_REPOSITION with latitude, longitude and altitude all NaN (pause in place; selecting Hold mode again does not stop a reposition already flying in Hold). ArduPilot copter: BRAKE (LOITER follows the throttle stick and descends with no RC input). Either way the vehicle MUST keep its altitude without pilot input. |
vehicle.return_home |
MAV_CMD_NAV_RETURN_TO_LAUNCH; RTH altitude via param RTL_RETURN_ALT (PX4, m) / RTL_ALT (ArduCopter before 4.7, cm) or RTL_ALT_M (ArduCopter 4.7, m); land_on_arrival: false via RTL_LAND_DELAY (PX4, −1 = hover) / RTL_ALT_FINAL (cm) or RTL_ALT_FINAL_M (ArduCopter 4.7, m; > 0 = hover at that height). Parameter names and units change between firmware versions; choose them by the reported version, with a fallback. The command completes on landing, or, without landing, when the vehicle holds over home (A.2.2). The gateway MUST reject altitude_rel_m or land_on_arrival if it cannot set them, MUST read the original values first, and MUST restore them when the commanded return ends or is rejected, so that later failsafe returns behave as configured |
vehicle.goto |
PX4: MAV_CMD_DO_REPOSITION, whose altitude PX4 1.17 always reads as AMSL whatever the frame: convert a REL target with the home altitude, and reject AGL. ArduPilot: SET_POSITION_TARGET_GLOBAL_INT in Guided with frame MAV_FRAME_GLOBAL_RELATIVE_ALT_INT (REL), MAV_FRAME_GLOBAL_INT (MSL) or MAV_FRAME_GLOBAL_TERRAIN_ALT_INT (AGL). |
vehicle.move_relative |
SET_POSITION_TARGET_LOCAL_NED with MAV_FRAME_BODY_OFFSET_NED (BODY) or MAV_FRAME_LOCAL_OFFSET_NED (NED) |
vehicle.set_heading |
MAV_CMD_CONDITION_YAW; complete when the heading is reached and the yaw rate has settled (≤ 5°/s) |
vehicle.land |
MAV_CMD_NAV_LAND |
vehicle.takeoff |
MAV_CMD_NAV_TAKEOFF (after arm) |
vehicle.mission.upload/start/pause/resume/abort |
Mission protocol upload; MAV_CMD_MISSION_START; MAV_CMD_DO_PAUSE_CONTINUE; abort → vehicle.hold semantics |
camera.gimbal.set_orientation |
MAV_CMD_DO_GIMBAL_MANAGER_PITCHYAW (EARTH ↔ GIMBAL_MANAGER_FLAGS_YAW_LOCK), after taking primary control with MAV_CMD_DO_GIMBAL_MANAGER_CONFIGURE (PX4 denies commands from any other sender; BUSY when another system holds control). An omitted axis is sent at its current angle: ArduPilot ignores a pitch/yaw pair with one NaN member. |
camera.gimbal.move_continuous |
MAV_CMD_DO_GIMBAL_MANAGER_PITCHYAW with rates and NaN angles (PX4 1.17 has no GIMBAL_MANAGER_SET_PITCHYAW handler) |
camera.zoom.set / move_continuous |
MAV_CMD_SET_CAMERA_ZOOM (ZOOM_TYPE_RANGE / ZOOM_TYPE_CONTINUOUS) |
camera.focus.* |
MAV_CMD_SET_CAMERA_FOCUS |
camera.recording.start/stop |
MAV_CMD_VIDEO_START_CAPTURE / STOP_CAPTURE |
camera.snapshot |
MAV_CMD_IMAGE_START_CAPTURE count=1 |
camera.mode.set |
MAV_CMD_SET_CAMERA_MODE |
COMMAND_ACK.result maps: ACCEPTED→ACCEPTED; TEMPORARILY_REJECTED→REJECTED/BUSY; DENIED→REJECTED/SAFETY; UNSUPPORTED→REJECTED/UNSUPPORTED_COMMAND; FAILED→ACCEPTED then FAILED/INTERNAL; IN_PROGRESS→ACCEPTED then EXECUTING with progress where the autopilot reports it; CANCELLED→CANCELLED; COMMAND_LONG_ONLY/COMMAND_INT_ONLY → retry once in the other form, then REJECTED/UNSUPPORTED_COMMAND. The synchronous answer waits for the acknowledgment (Part 10 §10.1). A gateway waits at least 3 s for an acknowledgment and retransmits once (MAVLink command protocol); no acknowledgment after that is REJECTED/LINK_LOST.
A gateway on the ground-station side of the radio link observes vehicle link loss and stale telemetry as the same thing; it raises vehicle.telemetry.stale and does not advertise vehicle.link.lost (Part 08 §8.3).
Control leases have no MAVLink equivalent; the gateway implements them, and holds gimbal control through the gimbal manager's primary controller (MAV_CMD_DO_GIMBAL_MANAGER_CONFIGURE, GIMBAL_MANAGER_STATUS.primary_control_sysid).
Deployment notes: PX4 AUX-output gimbals report GIMBAL_MANAGER_INFORMATION limits in degrees in fields defined in radians, as ±range while moving within ±range/2; a gateway should check limits for plausibility. PX4 answers only the first peer of a MAVLink UDP link for the link's lifetime, so a gateway should listen on a fixed port (udpin) rather than an ephemeral udpout port that changes on restart. Validated against PX4 1.17.0 and ArduCopter 4.7.2 SITL (docs/sitl-validation.md).
A.3 MISB ST 0601 ↔ UNIVIPLink to the section A.3 MISB ST 0601 ↔ UNIVIP
One UAS Datalink Local Set per video frame, Precision Time Stamp first and checksum (tag 1) last. Tag numbers below were checked against an independent open-source decoder and ST 0902 worked examples; the standard itself could not be consulted for 0.2, and the points marked ⚠ remain to be confirmed in the formal review (docs/prior-art.md §5).
| ST 0601 tag | Name | UNIVIP |
|---|---|---|
| 2 | Precision Time Stamp | the frame's device time (Part 09 §9.5), µs since the epoch ⚠ POSIX time vs MISP Time (ST 0603) |
| 3 | Mission ID | session_id |
| 4 | Platform Tail Number | device.serial_number |
| 5 | Platform Heading Angle | motion.heading_deg |
| 6, 7 | Platform Pitch / Roll Angle | motion.pitch_deg, motion.roll_deg (±20°, ±50°); beyond that range the out-of-range indicator, with the value in 90, 91 |
| 10 | Platform Designation | device.model |
| 11 | Image Source Sensor | payload_id |
| 13, 14 | Sensor Latitude / Longitude | position.latitude_deg/longitude_deg |
| 15 | Sensor True Altitude | position.altitude_msl_m |
| 16, 17 | Sensor Horizontal / Vertical FOV | payloads[].hfov_deg/vfov_deg |
| 18, 19, 20 | Sensor Relative Azimuth / Elevation / Roll | the gimbal orientation relative to the platform body (full attitude): convert the UNIVIP gimbal to EARTH (for BODY, add heading_deg to the yaw, Part 10 §10.3), then remove the platform attitude, with both rotations applied in Z-Y-X order (heading, pitch, roll). Subtracting the heading from the yaw is correct only for a level platform. |
| 21 | Slant Range | geospatial.slant_range_m |
| 23, 24, 25 | Frame Center Lat / Lon / Elevation | geospatial.frame_center |
| 26–33 | Offset Corner Lat / Lon 1–4 | geospatial.footprint, as offsets from the frame center when every offset is within ±0.075°; otherwise 82–89. Corner order: upper left, then clockwise ⚠ |
| 48 | Security Local Set | out of scope (client policy) |
| 65 | UAS Datalink LS Version Number | the ST 0601 edition the encoder follows (the reference recorder writes 17) ⚠ |
| 67, 68 | Alternate Platform Latitude / Longitude | controller or dock position (not modeled) |
| 72 | Event Start Time – UTC | start of the operation (vehicle.operation.started), when known |
| 74 | VMTI Local Set (ST 0903) | vehicle.analytics.object.detected (target_id, bbox, geolocation, confidence) |
| 75 | Sensor Ellipsoid Height | position.altitude_ellipsoid_m |
| 82–89 | Corner Lat / Lon Point 1–4 (Full) | geospatial.footprint when the offsets do not fit |
| 90, 91 | Platform Pitch / Roll Angle (Full) | motion.pitch_deg/roll_deg beyond the range of 6 and 7 |
There is no full-range heading tag (92 is the Angle of Attack). A footprint the client estimated itself MUST NOT be exported in tags 23–33 or 82–89 unless the export also marks it as estimated; the reference recorder estimates footprints only on request and records the fact in its manifest. Strings are encoded in UTF-8 ⚠ (earlier editions say ISO 646).
A.4 DJI Cloud API ↔ UNIVIPLink to the section A.4 DJI Cloud API ↔ UNIVIP
Status: Draft for merging into Annex A (spec/annex-a-mappings.md) for specification 0.2. Informative.
Source: DJI Cloud API documentation v1.11.3 (dji-sdk/Cloud-API-Doc, commit 4ec6b0c, 2024-11-07), dock-to-cloud MQTT thing model for DJI Dock (Matrice 30/30T) and DJI Dock 2 (Matrice 3D/3TD), cross-checked against the reference server dji-sdk/DJI-Cloud-API-Demo (commit bef525c). Field names, methods, units and enumerations below are DJI's; descriptions are paraphrased.
Implementation: the reference gateway's dji_cloud adapter (../gateway/univip_gateway/adapters/dji_cloud.py). Verified only against a fake dock built from the same documents (../gateway/univip_gateway/fakes/dji_dock.py); no real dock has been tested. ⚠ marks a mapping that is lossy, rests on an undocumented convention, or is ambiguous; J-numbers refer to the implementation findings for spec 0.2.
Model. In DJI terms the dock is a gateway device and the aircraft its sub-device; a UNIVIP device represents the aircraft together with its dock (Part 02, "Dock"). The UNIVIP gateway takes the role DJI calls the cloud server: it is an MQTT client of the broker the dock is bound to. DJI uses these topics ({sn} is the device's serial number, {gw} the dock's):
| Topic | Direction | Use |
|---|---|---|
thing/product/{sn}/osd |
dock → cloud | Periodic properties (pushMode 0), documented at 0.5 Hz |
thing/product/{sn}/state |
dock → cloud | Properties pushed on change only (pushMode 1) ⚠ a late subscriber misses them (J14) |
thing/product/{gw}/services, …/services_reply |
cloud → dock, reply | Commands; reply data.result 0 = success |
thing/product/{gw}/events, …/events_reply |
dock → cloud, reply | Events; reply required when need_reply = 1 |
sys/product/{gw}/status, …/status_reply |
dock → cloud, reply | update_topo: gateway and sub-device online, topology |
thing/product/{gw}/drc/up, …/drc/down |
both | Live flight controls (DRC): high-rate OSD, heartbeat, brake, sticks |
Every message carries tid (transaction), bid (business flow), timestamp (milliseconds, send time) and, for services and events, method. The gateway matches a services_reply to its request by tid. MQTT 3.1.1 at QoS 1 is used for services and replies (DJI's overview cites MQTT 5.0 concepts but the reference server uses a 3.1.1 client).
A.4.1 TelemetryLink to the section A.4.1 Telemetry
| DJI | UNIVIP | Conversion |
|---|---|---|
aircraft latitude, longitude |
position.latitude_deg/longitude_deg |
degrees; omitted when 0/0 or when the fix is NONE |
aircraft height |
position.altitude_ellipsoid_m |
m above the WGS84 ellipsoid (DJI: takeoff point's ellipsoid height + elevation) |
aircraft elevation |
position.altitude_rel_m |
m above the takeoff point ⚠ UNIVIP says "home/launch"; DJI's home can differ from the takeoff point (return_specific_home, multi-dock) (J2) |
height − N |
position.altitude_msl_m, geoid_model |
only with a configured geoid undulation N at the site ⚠ the thing model has no MSL height (J2) |
| — | position.altitude_agl_m |
not available ⚠ no height above ground in the thing model |
DRC osd_info_push.latitude/longitude (1–30 Hz) |
position.latitude_deg/longitude_deg |
while live flight controls are active |
DRC osd_info_push.height |
change applied to the altitudes above | ⚠ reference undocumented: English "Flight altitude" with unit "degree", Chinese 海拔 (above sea level). Only its change since the last thing-model sample is used (J13) |
position_state.is_fixed, quality, gps_number, rtk_number |
health.gnss.fix, health.gnss.satellites, position.source |
quality 10 or is_fixed 2 → RTK_FIXED; is_fixed 1 with rtk_number > 0 → RTK_FLOAT ⚠; gps_number = rtk_number = 0 → NONE; otherwise 3D ⚠ (no 2D/3D distinction, no HDOP, no accuracy); satellites = gps_number; source RTK_GNSS for RTK fixes, else GNSS |
attitude_head |
motion.heading_deg, motion.yaw_deg |
degrees from true north, east positive, (−180, 180] → [0, 360) |
attitude_pitch, attitude_roll |
motion.pitch_deg, motion.roll_deg |
degrees ⚠ sign convention undocumented; taken as UNIVIP's (J13) |
horizontal_speed |
motion.ground_speed_mps |
m/s |
vertical_speed |
motion.velocity_down_mps |
m/s, negated ⚠ sign undocumented; taken as positive up (J13) |
DRC speed_x, speed_y, speed_z |
motion.ground_speed_mps = √(x² + y²) |
⚠ axis frame undocumented: no north/east/down components and no course_deg (J13) |
battery.capacity_percent |
power.battery_pct |
% |
battery.remain_flight_time |
power.remaining_endurance_s |
s |
battery.batteries[] index, capacity_percent, voltage, temperature, loop_times |
power.batteries[] id, pct, voltage_v, temperature_c, cycles |
mV → V; 32767 means unavailable and is omitted |
lowest battery.batteries[].voltage |
power.voltage_v |
⚠ packs in parallel; no pack voltage or current is reported |
battery.return_home_power, battery.landing_power; state low_battery_warning_threshold, serious_low_battery_warning_threshold |
— | ⚠ no UNIVIP member; vehicle.battery.low/critical use the gateway's configured thresholds (J8) |
dock drone_charge_state.capacity_percent, drone_battery_maintenance_info.batteries[] |
power.battery_pct, power.batteries[] |
while the aircraft is powered off in the dock |
dock wireless_link sdr_link_state, 4g_link_state, sdr_quality, 4g_quality |
link.state, link.signal_quality_pct, link.transport |
best quality of the links up, 0–5 × 20 %; below 30 % → DEGRADED; SDR up → RADIO, 4G only → CELLULAR ⚠ |
DRC delay_info_push.sdr_cmd_delay |
link.latency_ms |
ms |
aircraft mode_code, dock drone_in_dock |
vehicle.vehicle_state, domain_state, flight_mode |
A.4.2; flight_mode is the mode_code name in upper snake case (e.g. AUTO_RETURNING_TO_HOME) |
state home_latitude, home_longitude |
vehicle.home |
degrees; altitude_msl_m only with N, taken from the takeoff point ⚠ GeoPoint has no altitude_ellipsoid_m (J2) |
| — | vehicle.armed |
not reported |
{type-subtype-gimbalindex}.gimbal_pitch/gimbal_roll/gimbal_yaw; DRC gimbal_pitch/roll/yaw |
payloads[].gimbal (frame: EARTH) |
degrees ⚠ the yaw reference is undocumented; taken as true north (DJI gimbal attitude convention) (J13) |
cameras[].zoom_factor |
payloads[].zoom_ratio |
⚠ documented range 2–200 (IR 2–20); the base field of view is undocumented, so no hfov_deg/vfov_deg (J7) |
cameras[].recording_state, record_time |
payloads[].recording.active, started_at |
started_at = receipt time − record_time |
cameras[].camera_mode |
— | read before recording or a photo (A.4.3) |
events hms |
health.faults[], vehicle.dock.fault |
A.4.4 |
aircraft control_source, payloads[].control_source (DJI Dock: dock drone_authority_info) |
vehicle.control.pilot_override |
A.4.5 |
aircraft rc_lost_action (DJI Dock/M30), else the value sent with takeoff_to_point |
vehicle.link.lost failsafe_action |
0 → HOLD, 1 → LAND, 2 → RETURN_HOME |
dock cover_state, drone_charge_state.state |
vehicle.dock.state_changed from/to |
0 → CLOSED (CHARGING while charging), 1 → OPEN, 2 (half open) → OPENING or CLOSING by the previous state ⚠, 3 → FAULT (J10) |
| aircraft powered off in the dock | position = dock latitude, longitude, height; link.state UNKNOWN |
⚠ the dock's surveyed position stands in for the aircraft's |
message timestamp |
— | send time, not time of validity; observations are stamped on receipt ⚠ |
thing-model osd rate 0.5 Hz |
telemetry.rate_hz.max 0.5 |
⚠ Part 07 §7.11 asks for ≥ 2 Hz deployed; only met with DRC osd_frequency (J1) |
A.4.2 Vehicle state derivation (AIR)Link to the section A.4.2 Vehicle state derivation (AIR)
Aircraft mode_code |
vehicle_state |
domain_state |
|---|---|---|
| 0 Standby, 13 Upgrading | IDLE |
DOCKED when dock drone_in_dock = 1, else LANDED ⚠ DOCKED is not used as vehicle_state (J4) |
| 1 Takeoff preparation, 2 Takeoff preparation completed | PREDEPLOY |
LANDED |
| 4 Automatic takeoff | DEPLOYING |
TAKING_OFF |
| 9 Auto returning to home | RETURNING |
FLYING |
| 10 Automatic landing | RECOVERING |
LANDING |
| 11 Forced landing | EMERGENCY (kind: FORCED_LANDING) ⚠ |
FLYING |
| 12 Three-blade landing | EMERGENCY (kind: MOTOR_FAILURE) |
FLYING |
14 Not connected, or no aircraft osd within the stale period while the dock is heard |
UNKNOWN, link.state: LOST, no position |
— |
| Any other airborne mode (3, 5–8, 15–20) | HOLDING when horizontal speed < 0.5 m/s and vertical speed < 0.3 m/s for ≥ 1.5 s and no flyto is in progress, else OPERATING |
HOVERING / FLYING |
Aircraft absent from update_topo (powered off) and dock drone_in_dock = 1 |
IDLE |
DOCKED |
RECOVERED is never produced: DJI reports no arming state. A silent dock is a stale device (vehicle.telemetry.stale, vehicle.device.offline); a silent aircraft under a dock that is still heard is a lost vehicle link (vehicle.link.lost), which settles finding F9 for dock gateways.
A.4.3 CommandsLink to the section A.4.3 Commands
| UNIVIP | DJI | Notes |
|---|---|---|
vehicle.takeoff |
takeoff_to_point |
Target = the dock's position; target_height = takeoff ellipsoid height + altitude_rel_m (documented range 2–1500 m). security_takeoff_height (≥ 20 m), rth_altitude, rc_lost_action, commander_*, max_speed come from configuration ⚠ the aircraft first climbs to max(security_takeoff_height, commander_flight_height), so a target below 20 m is reached from above; dock preparation (cover, power-on) shows as PREDEPLOY (J15) |
vehicle.goto |
fly_to_point (fly_to_id, max_speed, one point with ellipsoid height); fly_to_point_update while one is in progress |
altitude_rel_m + takeoff ellipsoid height; altitude_msl_m + N (rejected INVALID_PARAMETERS without N); altitude_agl_m rejected (no terrain model) (J2); heading_deg rejected (no heading); max_speed integer 0–15 m/s, rounded ⚠; below 20 m the aircraft first climbs to 20 m ⚠; transit at commander_flight_height ⚠ so the path is not the straight segment the geofence path check assumes |
vehicle.move_relative |
through vehicle.goto |
yaw_deg rejected |
vehicle.hold |
depends on what moves the aircraft: returning → return_home_cancel; wayline → flighttask_pause; flyto or takeoff → fly_to_point_stop; otherwise, with DRC active, drone_emergency_stop on drc/down; already hovering → nothing to send |
⚠ there is no single documented "hover now" service. drone_emergency_stop is a brake, not a motor stop: it is documented as cancelling a forced landing (J3) |
vehicle.cancel |
as vehicle.hold |
⚠ during the dock's takeoff preparation (PREDEPLOY) no documented service aborts the launch (J15) |
vehicle.return_home |
return_home |
altitude_rel_m rejected: the return altitude is fixed at takeoff (rth_altitude) ⚠; land_on_arrival: false rejected: a dock return always lands in the dock |
vehicle.set_heading |
— | Not offered: only stick-level drone_control over DRC could yaw the aircraft |
vehicle.land |
— | Not offered: drc_force_landing and drc_emergency_landing ignore obstacles and need manual recovery; a dock aircraft lands through return_home |
vehicle.mission.* |
flighttask_prepare / flighttask_execute / flighttask_pause / flighttask_recovery / flighttask_undo with a WPML KMZ fetched from a URL |
Not implemented ⚠ needs mission → WPML conversion and an object store |
camera.gimbal.set_orientation (EARTH) |
camera_look_at with locked: false, aimed at a point 200 m along the requested line of sight |
⚠ indirect (accuracy follows the position estimate); whether look-at keeps tracking is undocumented; gimbal limits are not reported. locked: true is never sent because it turns the aircraft (Part 10 §10.2) (J6) |
camera.gimbal.move_continuous / camera.gimbal.stop |
camera_screen_drag (pitch_speed, yaw_speed in rad/s, locked: false), repeated every 0.5 s; stop = both speeds 0 |
⚠ how long one drag lasts and the sign of each speed are undocumented (J13) |
camera.gimbal.recenter |
gimbal_reset reset_mode 0 |
|
camera.gimbal.point_at (Part 12) |
camera_look_at |
Not implemented (the reference gateway has no Part 12) |
| — | camera_aim (normalized image x, y), camera_frame_zoom (image region) |
No UNIVIP command (J6) |
camera.zoom.set |
camera_focal_length_set (camera_type: zoom, zoom_factor) |
Documented range 2–200 (IR 2–20) ⚠ the camera's real range is not reported (J7) |
camera.zoom.move_continuous / camera.zoom.stop |
— | Not offered |
camera.recording.start / stop |
camera_mode_switch to 1 when needed, camera_recording_start / camera_recording_stop |
No media id is returned ⚠ |
camera.snapshot |
camera_mode_switch to 0 when needed, camera_photo_take |
No media id; rejected BUSY while recording ⚠ |
camera.mode.set |
camera_mode_switch (0 photo, 1 video, 2 low light, 3 panorama) |
Used internally; not dispatched by the reference gateway |
camera.focus.*, camera.imaging.set, camera.thermal.set |
camera_focus_mode_set, camera_focus_value_set, camera_point_focus_action, camera_exposure_mode_set, camera_exposure_set, ir_metering_* |
Not implemented |
Before a vehicle.* command the gateway sends flight_authority_grab, and before a camera.* command payload_authority_grab (payload_index), unless the authority is already the cloud's (A.4.5).
services_reply and drc/up data.result map as follows (codes from the Cloud API error code table). No reply within the timeout → REJECTED / LINK_LOST.
| UNIVIP | DJI result |
|---|---|
ACCEPTED |
0; live_start_push also 513003 (already streaming) |
REJECTED / PILOT_OVERRIDE |
316008, 316023, 321778, 327012 |
REJECTED / LINK_LOST |
325012, 327003, 336006, 514120 |
REJECTED / FLIGHT_STATE |
314000, 316022, 316024, 321777, 337025 |
REJECTED / INVALID_PARAMETERS |
325001, 337026, 337027, 513013, 513014, 514171 |
REJECTED / PAYLOAD_UNAVAILABLE |
513002 |
REJECTED / GEOFENCE |
336513, 336515, 337537, 337539, 337546 |
REJECTED / OUT_OF_LIMITS |
327014, 336514, 336516, 336517, 337030, 337538, 337540, 337541 |
REJECTED / SAFETY |
316009, 316026, 319026, 321769, 321772, 336002, 336003, 336017, 337543, 337544, 514103, 514121, 514134, 514135, 514137, 514173 |
REJECTED / BUSY |
319001, 319016, 319025, 319030, 319036–319038, 325005–325011, 327002, 337029, 514116, 514145–514148, 514150, 514170 |
ACCEPTED then FAILED / INTERNAL |
any other non-zero code |
327003 and 327012 share one description in the error code table; the meanings used here (aircraft offline, authority locked) come from the reference server's control error enumeration ⚠.
A.4.4 EventsLink to the section A.4.4 Events
| DJI | UNIVIP |
|---|---|
hms (the complete active list each time; an alarm missing from the next list is resolved): level 1 → WARNING, 2 → ERROR; level 0 (notification) is not a fault ⚠ |
Aircraft entries (device_type domain 0) → health.faults[] → vehicle.health.fault: the Part 08 §8.5 code is classified from the alarm text looked up in DJI's hms.json (fpv_tip_{code}, fpv_tip_{code}_in_the_sky), else OTHER ⚠ (J11). Dock entries (domain 3, dock_tip_{code}) → vehicle.dock.fault with DJI's code |
mode_code changes to 9 |
vehicle.navigation.returning_home reason: CLIENT when the gateway sent return_home within 10 s; else mode_code_reason 1, 2, 3, 17 → LOW_BATTERY, 4 → PILOT, 6 → LINK_LOST; else a recent joystick_invalid_notify (0 LINK_LOST, 1 and 2 LOW_BATTERY, 3 GEOFENCE, 4 PILOT) or flyto exit result (338049 PILOT, 338051 and 338052 LOW_BATTERY, 336006 LINK_LOST); else OTHER ⚠ strong wind (23), crewed aircraft nearby (15), obstacles (22), hardware fault (19) have no UNIVIP reason (J9) |
fly_to_point_progress, takeoff_to_point_progress (status, remaining_distance, remaining_time, result) |
Tracks whether a flyto is in progress; the gateway reports its own progress |
return_home_info (planned path) |
— ⚠ no UNIVIP member for a planned path |
joystick_invalid_notify reason 4 (remote controller takeover) |
vehicle.control.pilot_override through control_source (A.4.5) |
drc_status_notify |
DRC link state (internal) |
cover_state changes |
vehicle.dock.state_changed |
update_topo without the aircraft |
Aircraft powered off: IDLE/DOCKED in the dock, otherwise vehicle.link.lost |
need_reply: 1 |
events_reply with data.result 0 |
A.4.5 Control authority and leasesLink to the section A.4.5 Control authority and leases
DJI arbitrates two authorities per aircraft: flight control and payload control (per payload_index). The holder is reported as control_source: A and B for the two controllers (in a dock deployment the dock's cloud link and an on-site remote controller), a browser-generated UUID in the Pilot 2 cloud case, empty when unassigned. UNIVIP leases (Part 11 §11.3) live in the gateway; they are not DJI authority.
- Acquiring a UNIVIP lease sends nothing to the dock. The gateway grabs flight or payload authority lazily, right before the first command that needs it, and again after it was lost.
control_sourcein the configured pilot sources (defaultB) →pilot_override: true:vehicle.*commands are rejected withPILOT_OVERRIDE, running movement fails withPILOT_OVERRIDE, and the lease staysACTIVE(Part 11 §11.3.3). The gateway never grabs authority from the pilot.- Payload authority held by the pilot →
camera.*rejected withPILOT_OVERRIDE⚠ without the camera-scopedvehicle.control.pilot_overridePart 10 §10.8 asks for, because telemetry has one vehicle-level flag (J5). 327012(authority locked on the controller) →PILOT_OVERRIDE.- ⚠ Authority is per dock link: every cloud client of the same dock shares
A, so a UNIVIP lease cannot exclude a non-UNIVIP cloud user (J5).
A.4.6 Live videoLink to the section A.4.6 Live video
live_start_push (url_type, url, video_id = {aircraft_sn}/{payload_index}/{video_index}, video_quality) makes the dock push the stream: url_type 1 RTMP, 4 WebRTC via WHIP, 3 GB28181, 0 Agora (dock documents do not offer RTSP). The UNIVIP media plane reads the stream back from the media server that receives it (for example RTMP into MediaMTX or SRS, read as rtsp://… or rtmp://…, which FFmpeg opens) and serves it over WebRTC (Part 09). video_index comes from the dock's live_capacity; live_status reports the push; live_stop_push ends it. video_quality 0 is adaptive, 1–4 fix 960×540 at 512 kbps up to 1920×1080 at 3 Mbps, so a UNIVIP stream descriptor can only state the configured ceiling ⚠. The documented API carries no capture timestamp, so frame-to-telemetry alignment (Part 09 §9.5) can only use arrival time at the gateway ⚠ (J12).
A.4.7 Identity and payloadsLink to the section A.4.7 Identity and payloads
| DJI | UNIVIP |
|---|---|
update_topo type/sub_type of dock and sub-device (Product Support table: 1-0 DJI Dock, 2-0 DJI Dock 2; 67-0/1 Matrice 30/30T, 91-0/1 Matrice 3D/3TD) |
device.manufacturer DJI, device.model e.g. "Matrice 3D (DJI Dock 2)" |
aircraft serial number (update_topo sub_devices[].sn, dock sub_device.device_sn) |
device.serial_number |
aircraft state firmware_version |
device.firmware_version |
camera payload_index (52-0-0 M30, 53-0-0 M30T, 80-0-0 M3D, 81-0-0 M3TD) |
one payload, cam-1 by default; FPV (39-0-7), vision assist (176-0-0) and dock cameras (165-0-7) are not mapped ⚠ wide, zoom and IR lenses of one camera share the payload (J7) |