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>
65 lines
3.3 KiB
Markdown
65 lines
3.3 KiB
Markdown
# 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
|
||
|
||
```bash
|
||
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).
|