Files
PilotVault/Fly Agent App/README.md
T
tajniak81andClaude Opus 5.5 3dd31b257a Add the Fly Agent App, a Remote ID companion to DJI Fly
A Flutter app that runs next to DJI Fly without the DJI SDK. It receives the
aircraft's Remote ID (Bluetooth, BT5, Wi-Fi NaN and Beacon) in a foreground
service, streams the paired aircraft to the hub over /ws/device, and turns
take-off to landing into a logbook entry filed through /api/flights, queued
when offline. Covers phases A0-A3 of the Fly Agent plan; needs no server change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 19:25:52 +02:00

3.3 KiB
Raw Blame History

PilotVault Fly Agent

Android companion app that runs next to DJI Fly. DJI Fly flies the aircraft; the Agent listens to the aircraft's Remote ID broadcast, streams the paired aircraft's position to PilotVault, and files the flight in the logbook. It uses no DJI SDK and never sends a command, so it works with every DJI Fly aircraft (Mavic 4 Pro, Air 3S, Mini 5 Pro, …) as long as Remote ID is broadcasting.

Plan and research: REA/DJI Fly/PilotVault_FlyAgentApp_Plan.md (outside this repo). Built so far: phases A0–A3 (login, Remote ID receiver, live uplink, flight session → logbook). Not yet: RTMP video (A4), flight-record import (A5), media metadata (A6), missions to DJI Fly (A7).

Build

flutter build apk --debug

build/app/outputs/flutter-apk/app-debug.apk, package com.pilotvault.flyagent.

  • permission_handler is pinned to 12.x: 13.x needs compileSdk 37.
  • kotlin.incremental=false in android/gradle.properties: the project and the pub cache are on different drives, which breaks Kotlin's incremental caches.

How it fits

Piece Where
Remote ID scanners (BLE legacy + BT5, Wi-Fi NaN, Wi-Fi Beacon) in a foreground service android/.../flyagent/*.kt
OpenDroneID decoder (ASTM F3411 / EN 4709-002) lib/rid/odid_decoder.dart
Frames → aircraft (merged by serial across transports), pairing lib/rid/rid_aircraft.dart, rid_service.dart
Live uplink to the hub, /ws/device?id=agent-<serial> lib/uplink/uplink.dart
Session state machine (armed → active → landed), auto start/stop lib/session/flight_session.dart
Filing to POST /api/flights, offline queue lib/session/session_controller.dart

The scanners only strip transport framing; all decoding is in Dart and unit tested against byte-built messages (test/).

API Server contract. No server changes were needed:

  • Live map: the hub already ingests telemetry events (latitude, longitude, altitude = height above take-off, velocityX/Y/Z = north/east/down, isFlying, phoneLatitude/Longitude). The Agent adds source: "remote-id", ridSerial, pilotLatitude/Longitude, uaClass, which ride along in the raw event. It never sends flightControllerSerial, so the Web App's fleet auto-add does not mint a drone from a Remote ID serial.
  • Logbook: entries are filed with loggingPath: "manual" and every § 5 manual field filled from the session (Remote ID is not the aircraft's own flight recorder, so "automatic" would be a false claim). logging_path only allows automatic | manual, so the plan's "companion" value would need a migration.
  • Pairing lives on the phone (SharedPreferences), not on the drone record; a remote_id_serial drone field is a later server change.

Privacy. Every aircraft in range is decoded, but only the paired one is uploaded or logged. Other operators' broadcasts stay in memory on the phone.

Limits worth knowing

  • Wi-Fi Beacon Remote ID needs Android 11+ and Android throttles Wi-Fi scans to 4 per 2 minutes (turn off "Wi-Fi scan throttling" in Developer options). An aircraft that only broadcasts over Beacon updates every ~30 s.
  • Not yet checked on a real aircraft: which transport each DJI Fly aircraft uses (research R1), and NaN on the SHT-AL09 tablet (Android 9: no Beacon IEs).