Files
PilotVault/Fly Agent App
tajniak81andClaude Opus 5.5 bc41d8b128 Split the automatic logging path by the app that logged the flight
logging_path is now manual, automatic_flyapp or automatic_flyagentapp. The
Fly App's entries are backed by the aircraft's flight recorder and keep the
missing-FDR red flag; the Fly Agent App's come from Remote ID, which is not a
flight recorder, so they must carry the § 5 fields like a manual entry. The API
maps the old "automatic" onto automatic_flyapp so Fly App builds already on
devices keep filing. The migration has been applied to the remote PocketBase.

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

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. The only server change is the logging-path split:

  • 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: "automatic_flyagentapp" (migration 1720301200_split_logging_path.js; the paths are manual, automatic_flyapp, automatic_flyagentapp). Remote ID is not the aircraft's own flight recorder, so the API Server still requires every § 5 field on this path, and the session fills them all in.
  • 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).