Files
PilotVault/Fly App/lib/dji_service.dart
T
tajniak81andClaude Opus 5.5 636da1ac2c Give the Fly App every remaining GO 4 flight mode and the missing settings
Intelligent modes: Point of Interest (orbit on the map, live radius and
speed), Follow Me (follows the phone), QuickShots on a boxed subject,
Course Lock / Home Lock, Tripod and Cinematic, a Free hyperlapse, and
Waypoints 2.0 for V2-only aircraft. ActiveTrack gains Spotlight Pro and
Circle. Every mode has a first-use tutorial and stops from the rail.
Waypoints can resume an interrupted mission, change cruise speed in
flight and check terrain clearance (Open-Meteo, on tap).

Settings: units, C1/C2 actions, dual-operator master/slave, HDMI/SDI
output, a link spectrum chart, Wi-Fi name and password, aircraft name,
video cache, battery warning history and pairing, non-smart cell
voltages. Calibration: compass interference meter, per-IMU calibration,
lens infinity focus and a first-manual-focus prompt. Also an RTK chip,
gimbal movement presets and motor tuning, Precision Takeoff gated on the
flight controller, and the precise GEO database's server version.

Built and unit/widget-tested only; nothing has flown yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 23:06:16 +02:00

613 lines
35 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/services.dart';
/// Thin Dart wrapper over the native DJI Mobile SDK bridge.
///
/// Mirrors the channels defined in `DjiSdkBridge.kt` (+ its per-subsystem
/// helpers):
/// * method channel `dji_msdk/methods` for imperative calls
/// * event channel `dji_msdk/events` for the SDK's async updates
///
/// Every method returns cleanly whether or not a product is connected; the
/// native side answers with a typed error the UI can surface. The event stream
/// carries maps keyed by `type`: `registration`, `connection`, `identity`,
/// `telemetry` (the batched snapshot — see `TelemetryReducer`), `camera`,
/// `exposure`, `histogram`, `diagnostics`, `mission`, `key`, `mediaList`, `mediaDownload`,
/// `djiAccount`, `productProfile`, `database`, `init`, `rcButton`, `rcMasters`,
/// `spectrum`, `flyZoneDbRemote`.
class DjiService {
static const MethodChannel _methods = MethodChannel('dji_msdk/methods');
static const EventChannel _events = EventChannel('dji_msdk/events');
Stream<Map<String, dynamic>> events() {
return _events
.receiveBroadcastStream()
.map((dynamic e) => Map<String, dynamic>.from(e as Map));
}
// ── SDK / registration / connection ─────────────────────────────────────────
Future<String> getSdkVersion() async =>
await _methods.invokeMethod<String>('getSdkVersion') ?? 'unknown';
Future<void> registerApp() => _methods.invokeMethod<void>('registerApp');
Future<bool> startConnection() async =>
await _methods.invokeMethod<bool>('startConnection') ?? false;
Future<void> stopConnection() => _methods.invokeMethod<void>('stopConnection');
Future<Map<String, dynamic>> getProductInfo() async {
final dynamic info = await _methods.invokeMethod('getProductInfo');
return Map<String, dynamic>.from(info as Map);
}
/// The raw capability map `ProductProfile.fromBridge` consumes. The same map
/// arrives unasked as a `productProfile` event on connect and component change.
Future<Map<String, dynamic>> getProductProfile() async {
final dynamic profile = await _methods.invokeMethod('getProductProfile');
return Map<String, dynamic>.from(profile as Map);
}
// ── Flight controller commands ──────────────────────────────────────────────
Future<void> takeOff() => _methods.invokeMethod<void>('takeOff');
/// Take-off that records the spot visually, so RTH lands back on it.
Future<void> precisionTakeOff() => _methods.invokeMethod<void>('precisionTakeOff');
Future<void> land() => _methods.invokeMethod<void>('land');
Future<void> confirmLanding() => _methods.invokeMethod<void>('confirmLanding');
Future<void> cancelLanding() => _methods.invokeMethod<void>('cancelLanding');
Future<void> startGoHome() => _methods.invokeMethod<void>('startGoHome');
Future<void> cancelGoHome() => _methods.invokeMethod<void>('cancelGoHome');
/// Answers the smart-RTH countdown (`goHome.smartRTHState == COUNTING_DOWN`):
/// true flies home now, false cancels it.
Future<void> confirmSmartRTH(bool goHome) =>
_methods.invokeMethod<void>('confirmSmartRTH', <String, dynamic>{'value': goHome});
Future<void> setHomeToCurrent() => _methods.invokeMethod<void>('setHomeToCurrent');
/// Moves the home point (the RTH target dialog's "Current Position" = the pilot).
Future<void> setHomeLocation(double latitude, double longitude) =>
_methods.invokeMethod<void>('setHomeLocation', <String, dynamic>{'latitude': latitude, 'longitude': longitude});
/// Starts compass calibration; `telemetry.compassCalibrationState` then walks
/// HORIZONTAL → VERTICAL → SUCCESSFUL / FAILED as the pilot rotates the aircraft.
Future<void> startCompassCalibration() => _methods.invokeMethod<void>('startCompassCalibration');
Future<void> stopCompassCalibration() => _methods.invokeMethod<void>('stopCompassCalibration');
// ── Calibration & maintenance (plan P6b, `CalibrationBridge.kt`) ────────────
// Progress arrives in the `calibration` telemetry group (`CalibrationState`).
/// Every IMU, or only [index]. There is no stop: it runs to the end.
Future<void> startIMUCalibration({int? index}) =>
_methods.invokeMethod<void>('startIMUCalibration', <String, dynamic>{if (index != null) 'index': index});
Future<void> startGravityCenterCalibration() => _methods.invokeMethod<void>('startGravityCenterCalibration');
Future<void> stopGravityCenterCalibration() => _methods.invokeMethod<void>('stopGravityCenterCalibration');
Future<void> startPropellerCalibration() => _methods.invokeMethod<void>('startPropellerCalibration');
Future<void> stopPropellerCalibration() => _methods.invokeMethod<void>('stopPropellerCalibration');
/// Manual-focus lens calibration (interchangeable-lens cameras).
Future<void> startLensCalibration() => _methods.invokeMethod<void>('startLensCalibration');
Future<void> stopLensCalibration() => _methods.invokeMethod<void>('stopLensCalibration');
/// Sensor dedusting: enter the mode, clean (lens off), leave it.
Future<void> initSensorCleaning() => _methods.invokeMethod<void>('initSensorCleaning');
Future<void> startSensorCleaning() => _methods.invokeMethod<void>('startSensorCleaning');
Future<void> exitSensorCleaning() => _methods.invokeMethod<void>('exitSensorCleaning');
/// Remote-controller calibration step: `MIDDLE` → `LIMITS` → `QUIT`.
Future<void> setRcCalibrationStep(String step) =>
_methods.invokeMethod<void>('setRcCalibrationStep', <String, dynamic>{'step': step});
/// Streams the sticks and dials into `calibration.rcHardware` while on.
Future<void> watchRcHardware(bool on) => _methods.invokeMethod<void>('watchRcHardware', <String, dynamic>{'on': on});
/// Starts the flight controller's built-in simulator at [latitude]/[longitude]
/// (developer only; propellers off). `telemetry.simulatorActive` reflects it.
Future<void> startSimulator({required double latitude, required double longitude, int satellites = 12}) =>
_methods.invokeMethod<void>('startSimulator', <String, dynamic>{
'latitude': latitude,
'longitude': longitude,
'satelliteCount': satellites,
'updateFrequency': 10,
});
Future<void> stopSimulator() => _methods.invokeMethod<void>('stopSimulator');
// ── Flight settings (Safety / Control) ──────────────────────────────────────
Future<void> setMaxFlightHeight(int m) =>
_methods.invokeMethod<void>('setMaxFlightHeight', <String, dynamic>{'value': m});
Future<void> setMaxFlightRadius(int m) =>
_methods.invokeMethod<void>('setMaxFlightRadius', <String, dynamic>{'value': m});
Future<void> setMaxRadiusEnabled(bool on) =>
_methods.invokeMethod<void>('setMaxRadiusEnabled', <String, dynamic>{'value': on});
Future<void> setGoHomeHeight(int m) =>
_methods.invokeMethod<void>('setGoHomeHeight', <String, dynamic>{'value': m});
Future<void> setNoviceMode(bool on) =>
_methods.invokeMethod<void>('setNoviceMode', <String, dynamic>{'value': on});
Future<void> setObstacleAvoidance(bool on) =>
_methods.invokeMethod<void>('setObstacleAvoidance', <String, dynamic>{'value': on});
// ── KeyManager (generic simple values) ──────────────────────────────────────
/// Reads one SDK key. Enums come back as their name, arrays as lists.
Future<Object?> getKey(DjiKey key) => _methods.invokeMethod<Object?>('getKey', key.args);
/// Writes one SDK key; [value] is coerced natively to the key's Java type
/// (an enum by name, a number to the exact width).
Future<void> setKey(DjiKey key, Object? value) =>
_methods.invokeMethod<void>('setKey', <String, dynamic>{...key.args, 'value': value});
Future<bool> isKeySupported(DjiKey key) async =>
await _methods.invokeMethod<bool>('isKeySupported', key.args) ?? false;
/// Subscribes to a key: its current value and every change arrive as `key`
/// events whose `id` is the string returned here. Idempotent per key.
Future<String> listenKey(DjiKey key) async =>
await _methods.invokeMethod<String>('listenKey', key.args) ?? key.id;
/// Runs an action key that takes no arguments (`START_PAIRING`, …).
Future<void> performKeyAction(DjiKey key) => _methods.invokeMethod<void>('performKeyAction', key.args);
Future<void> cancelKey(String id) => _methods.invokeMethod<void>('cancelKey', <String, dynamic>{'id': id});
// ── Camera ──────────────────────────────────────────────────────────────────
/// mode: `photo` | `video` | `mediaDownload` | `playback`
Future<void> setCameraMode(String mode) =>
_methods.invokeMethod<void>('setCameraMode', <String, dynamic>{'mode': mode});
Future<void> startShootPhoto() => _methods.invokeMethod<void>('startShootPhoto');
Future<void> stopShootPhoto() => _methods.invokeMethod<void>('stopShootPhoto');
Future<void> startRecordVideo() => _methods.invokeMethod<void>('startRecordVideo');
Future<void> stopRecordVideo() => _methods.invokeMethod<void>('stopRecordVideo');
/// mode: SINGLE | HDR | BURST | AEB | INTERVAL | PANORAMA
Future<void> setShootPhotoMode(String mode) =>
_methods.invokeMethod<void>('setShootPhotoMode', <String, dynamic>{'mode': mode});
/// program: `auto` (PROGRAM) | `pro` (MANUAL)
Future<void> setExposureProgram(String program) =>
_methods.invokeMethod<void>('setExposureProgram', <String, dynamic>{'program': program});
Future<void> setISO(String value) =>
_methods.invokeMethod<void>('setISO', <String, dynamic>{'value': value});
Future<void> setShutterSpeed(String value) =>
_methods.invokeMethod<void>('setShutterSpeed', <String, dynamic>{'value': value});
Future<void> setAperture(String value) =>
_methods.invokeMethod<void>('setAperture', <String, dynamic>{'value': value});
Future<void> setEV(String value) =>
_methods.invokeMethod<void>('setEV', <String, dynamic>{'value': value});
Future<void> setWhiteBalance(String value) =>
_methods.invokeMethod<void>('setWhiteBalance', <String, dynamic>{'value': value});
/// Erases the card. location: `SDCARD` | `INTERNAL_STORAGE`
Future<void> formatStorage({String location = 'SDCARD'}) =>
_methods.invokeMethod<void>('formatStorage', <String, dynamic>{'location': location});
Future<void> setHistogramEnabled(bool on) =>
_methods.invokeMethod<void>('setHistogramEnabled', <String, dynamic>{'value': on});
/// One simple camera setting by its `cameraSettings` id (`photoAspectRatio`,
/// `exposureMode`, `aeLock`, …) — see the table in `CameraBridge.kt`. [value]
/// is the SDK enum name, or a bool.
Future<void> setCameraValue(String id, Object value) =>
_methods.invokeMethod<void>('setCameraValue', <String, dynamic>{'id': id, 'value': value});
/// resolution / frameRate: SDK enum names (`RESOLUTION_3840x2160`, `FRAME_RATE_30_FPS`).
Future<void> setVideoResolutionAndFrameRate(String resolution, String frameRate) =>
_methods.invokeMethod<void>('setVideoResolutionAndFrameRate', <String, dynamic>{
'resolution': resolution,
'frameRate': frameRate,
});
/// Interval shooting: [count] photos (255 = until stopped) every [seconds].
Future<void> setPhotoTimeInterval({required int count, required int seconds}) =>
_methods.invokeMethod<void>('setPhotoTimeInterval', <String, dynamic>{'count': count, 'seconds': seconds});
/// The picture style, each −3…+3 on most cameras.
Future<void> setPictureStyle({required int sharpness, required int contrast, required int saturation}) =>
_methods.invokeMethod<void>('setPictureStyle', <String, dynamic>{
'sharpness': sharpness,
'contrast': contrast,
'saturation': saturation,
});
/// Tap-to-focus at x/y, each 0..1 across the live view.
Future<void> setFocusTarget(double x, double y) =>
_methods.invokeMethod<void>('setFocusTarget', <String, dynamic>{'x': x, 'y': y});
/// Spot metering at x/y (0..1); switches the metering mode to SPOT first.
Future<void> setSpotMeteringTarget(double x, double y) =>
_methods.invokeMethod<void>('setSpotMeteringTarget', <String, dynamic>{'x': x, 'y': y});
/// Optical zoom while a T / W button is held. direction: `in` | `out`.
Future<void> startContinuousZoom(String direction) =>
_methods.invokeMethod<void>('startContinuousZoom', <String, dynamic>{'direction': direction});
Future<void> stopContinuousZoom() => _methods.invokeMethod<void>('stopContinuousZoom');
/// Hybrid or optical focal length, in the units `cameraSettings.zoom*` report.
Future<void> setZoomFocalLength(int value) =>
_methods.invokeMethod<void>('setZoomFocalLength', <String, dynamic>{'value': value});
Future<void> setDigitalZoomFactor(double value) =>
_methods.invokeMethod<void>('setDigitalZoomFactor', <String, dynamic>{'value': value});
/// The camera's own "Reset camera settings".
Future<void> restoreCameraDefaults() => _methods.invokeMethod<void>('restoreCameraDefaults');
// ── Gimbal ──────────────────────────────────────────────────────────────────
/// Absolute pitch in degrees (the pitch slider).
Future<void> rotateGimbalPitch(double deg) =>
_methods.invokeMethod<void>('rotateGimbalPitch', <String, dynamic>{'pitch': deg});
/// Speed-mode rotation in °/s (drag on the video). The gimbal only keeps
/// moving while this is repeated (10 Hz); send zero to stop. A null axis is
/// left alone.
Future<void> rotateGimbalSpeed({double? pitch, double? yaw}) =>
_methods.invokeMethod<void>('rotateGimbalSpeed', <String, dynamic>{
if (pitch != null) 'pitch': pitch,
if (yaw != null) 'yaw': yaw,
});
/// mode: `YAW_FOLLOW` | `FPV` | `FREE`
Future<void> setGimbalMode(String mode) =>
_methods.invokeMethod<void>('setGimbalMode', <String, dynamic>{'mode': mode});
/// Pitch and yaw back to centre.
Future<void> resetGimbal() => _methods.invokeMethod<void>('resetGimbal');
Future<void> resetGimbalYaw() => _methods.invokeMethod<void>('resetGimbalYaw');
/// Nudges the gimbal's [axis] (`roll` | `pitch` | `yaw`) trim by [degrees].
Future<void> fineTuneGimbal(String axis, double degrees) =>
_methods.invokeMethod<void>('fineTuneGimbal', <String, dynamic>{'axis': axis, 'degrees': degrees});
/// Auto-calibration; `telemetry.gimbal.calibrating` / `calibrationProgress` follow it.
Future<void> startGimbalCalibration() => _methods.invokeMethod<void>('startGimbalCalibration');
/// One `gimbalSettings` value by id (`pitchExtension`, `yawFollow`,
/// `pitchSmoothing`, `yawMaxSpeed`, …) — see the table in `GimbalBridge.kt`.
Future<void> setGimbalSetting(String id, Object value) =>
_methods.invokeMethod<void>('setGimbalSetting', <String, dynamic>{'id': id, 'value': value});
/// The controller tuning values back to their defaults.
Future<void> resetGimbalControlParameters() => _methods.invokeMethod<void>('resetGimbalControlParameters');
// ── Missions ────────────────────────────────────────────────────────────────
/// Loads and uploads a waypoint mission — `WaypointMissionSpec.toBridge()`'s
/// map (see `MissionBridge.kt`). Resolves once every waypoint is on the
/// aircraft; progress arrives as `mission` events with `operator: 'waypoint'`.
///
/// [v2] sends it to the Waypoints 2.0 operator (Matrice 300) instead; the
/// start / pause / resume / stop below then address that one.
Future<void> uploadWaypointMission(Map<String, Object?> mission, {bool v2 = false}) =>
_methods.invokeMethod<void>('uploadWaypointMission', <String, Object?>{...mission, if (v2) 'v2': true});
Future<void> cancelWaypointUpload() => _methods.invokeMethod<void>('cancelWaypointUpload');
/// The operator's `WaypointMissionState` name; also re-published as an event.
Future<String?> getWaypointMissionState() => _methods.invokeMethod<String>('getWaypointMissionState');
Future<void> startWaypointMission() => _methods.invokeMethod<void>('startWaypointMission');
Future<void> stopWaypointMission() => _methods.invokeMethod<void>('stopWaypointMission');
Future<void> pauseWaypointMission() => _methods.invokeMethod<void>('pauseWaypointMission');
Future<void> resumeWaypointMission() => _methods.invokeMethod<void>('resumeWaypointMission');
/// Changes the cruise speed of the mission in flight (m/s, −15…15).
Future<void> setWaypointAutoFlightSpeed(double speed) =>
_methods.invokeMethod<void>('setWaypointAutoFlightSpeed', <String, dynamic>{'speed': speed});
/// Rect is normalized 0..1 in the live view. mode: TRACE|PROFILE|SPOTLIGHT|QUICK_SHOT
Future<void> startActiveTrack(
double x,
double y,
double w,
double h, {
String mode = 'TRACE',
}) =>
_methods.invokeMethod<void>('startActiveTrack', <String, dynamic>{
'x': x,
'y': y,
'w': w,
'h': h,
'mode': mode,
});
Future<void> stopActiveTrack() => _methods.invokeMethod<void>('stopActiveTrack');
/// Answers the operator's WAITING_FOR_CONFIRMATION: yes follows the subject, no drops it.
Future<void> acceptActiveTrackConfirmation() => _methods.invokeMethod<void>('acceptActiveTrackConfirmation');
Future<void> rejectActiveTrackConfirmation() => _methods.invokeMethod<void>('rejectActiveTrackConfirmation');
/// mode: TRACE | PROFILE | SPOTLIGHT | SPOTLIGHT_PRO, while tracking.
Future<void> switchActiveTrackMode(String mode) =>
_methods.invokeMethod<void>('switchActiveTrackMode', <String, dynamic>{'mode': mode});
/// Lets Trace fly backwards ahead of a subject walking towards the aircraft.
Future<void> setActiveTrackRetreat(bool on) =>
_methods.invokeMethod<void>('setActiveTrackRetreat', <String, dynamic>{'on': on});
Future<bool?> getActiveTrackRetreat() => _methods.invokeMethod<bool>('getActiveTrackRetreat');
/// Lets the aircraft look for subjects itself; they arrive in `tracking.subjects`.
Future<void> setActiveTrackAutoSensing(bool on) =>
_methods.invokeMethod<void>('setActiveTrackAutoSensing', <String, dynamic>{'on': on});
/// Follows the auto-sensed subject with operator index [index].
Future<void> startActiveTrackSubject(int index, {String mode = 'TRACE'}) =>
_methods.invokeMethod<void>('startActiveTrackSubject', <String, dynamic>{'index': index, 'mode': mode});
// ── TapFly ──────────────────────────────────────────────────────────────────
/// Flies towards the point [x], [y] (0..1 across the live view).
/// mode: FORWARD | BACKWARD | FREE; speed in m/s.
Future<void> startTapFly(double x, double y, {String mode = 'FORWARD', double speed = 5, bool avoidance = true}) =>
_methods.invokeMethod<void>('startTapFly', <String, dynamic>{
'x': x,
'y': y,
'mode': mode,
'speed': speed,
'avoidance': avoidance,
});
Future<void> stopTapFly() => _methods.invokeMethod<void>('stopTapFly');
Future<void> setTapFlySpeed(double speed) => _methods.invokeMethod<void>('setTapFlySpeed', <String, dynamic>{'speed': speed});
Future<void> resetTapFlyHeading() => _methods.invokeMethod<void>('resetTapFlyHeading');
// ── Virtual sticks (plan P8) ────────────────────────────────────────────────
/// Puts the flight controller in (or out of) virtual-stick mode; while on, the
/// native side streams the last [sendVirtualStick] at 25 Hz, or zeros once it
/// is more than 300 ms old.
Future<void> setVirtualStickEnabled(bool on) =>
_methods.invokeMethod<void>('setVirtualStickEnabled', <String, dynamic>{'on': on});
/// Body-frame velocities (m/s, forward / right / up positive) and yaw rate (°/s, clockwise).
Future<void> sendVirtualStick({required double forward, required double right, required double yaw, required double up}) =>
_methods.invokeMethod<void>('sendVirtualStick', <String, dynamic>{'forward': forward, 'right': right, 'yaw': yaw, 'up': up});
// ── Sensing distances (plan P8) ─────────────────────────────────────────────
/// `{avoidance: {Horizontal, Upward, Downward}, perception: {…}}` in metres;
/// a direction the aircraft does not support reads null.
Future<Map<String, dynamic>> getSensingDistances() async {
final dynamic m = await _methods.invokeMethod('getSensingDistances');
return Map<String, dynamic>.from(m as Map);
}
/// Where the aircraft brakes for an obstacle in [direction] (`Horizontal` / `Upward` / `Downward`).
Future<void> setAvoidanceDistance(String direction, double metres) =>
_methods.invokeMethod<void>('setAvoidanceDistance', <String, dynamic>{'direction': direction, 'value': metres});
/// How far the aircraft looks for obstacles in [direction].
Future<void> setMaxPerceptionDistance(String direction, double metres) =>
_methods.invokeMethod<void>('setMaxPerceptionDistance', <String, dynamic>{'direction': direction, 'value': metres});
// ── Fly zones (plan P8) ─────────────────────────────────────────────────────
/// GEO zones around the point, or around the aircraft when omitted.
Future<List<Map<String, dynamic>>> getFlyZonesNearby({double? latitude, double? longitude}) async {
final List<Object?>? l = await _methods.invokeMethod<List<Object?>>('getFlyZonesNearby', <String, dynamic>{
if (latitude != null) 'latitude': latitude,
if (longitude != null) 'longitude': longitude,
});
return <Map<String, dynamic>>[for (final Object? z in l ?? const <Object?>[]) if (z is Map) Map<String, dynamic>.from(z)];
}
/// Ids of the authorization zones unlocked on the connected aircraft.
Future<List<int>> getUnlockedFlyZones() async {
final List<Object?>? l = await _methods.invokeMethod<List<Object?>>('getUnlockedFlyZones');
return <int>[for (final Object? id in l ?? const <Object?>[]) if (id is int) id];
}
/// Self-unlocks an authorization zone; needs a logged-in DJI account.
Future<void> unlockAuthorizationFlyZone(int id) =>
_methods.invokeMethod<void>('unlockAuthorizationFlyZone', <String, dynamic>{'id': id});
/// The aircraft's licences (custom unlock zones): `{supported, zones: [...]}`.
Future<Map<String, dynamic>> getFlyZoneLicences() async {
final dynamic m = await _methods.invokeMethod('getFlyZoneLicences');
return Map<String, dynamic>.from(m as Map);
}
Future<void> setFlyZoneLicenceEnabled(int id, bool enabled) =>
_methods.invokeMethod<void>('setFlyZoneLicenceEnabled', <String, dynamic>{'id': id, 'enabled': enabled});
/// Downloads the logged-in DJI account's licences and writes them to the aircraft.
Future<void> syncFlyZoneLicences() => _methods.invokeMethod<void>('syncFlyZoneLicences');
Future<void> deleteAllFlyZoneLicences() => _methods.invokeMethod<void>('deleteAllFlyZoneLicences');
/// `{basicVersion, basicState, preciseVersion, preciseState}`; unknown parts are null.
Future<Map<String, dynamic>> getFlyZoneDatabase() async {
final dynamic m = await _methods.invokeMethod('getFlyZoneDatabase');
return Map<String, dynamic>.from(m as Map);
}
/// Downloads the basic GEO database; progress arrives as `flyZoneDbProgress` events.
Future<void> startFlyZoneDatabaseUpgrade() => _methods.invokeMethod<void>('startFlyZoneDatabaseUpgrade');
Future<void> stopFlyZoneDatabaseUpgrade() => _methods.invokeMethod<void>('stopFlyZoneDatabaseUpgrade');
/// A QuickShot by `QuickShotMode` name (DRONIE / ROCKET / CIRCLE / HELIX /
/// BOOMERANG / ASTEROID / DOLLY_ZOOM) on the subject boxed at [x], [y], [w],
/// [h] (0..1 of the live view); an aircraft without that shot answers a
/// typed error.
Future<void> startQuickShot(String name, {double? x, double? y, double? w, double? h}) =>
_methods.invokeMethod<void>('startQuickShot', <String, dynamic>{
'name': name,
if (x != null) 'x': x,
if (y != null) 'y': y,
if (w != null) 'w': w,
if (h != null) 'h': h,
});
/// ActiveTrack Circle's orbit speed (m/s, negative anticlockwise).
Future<void> setActiveTrackCircularSpeed(double speed) =>
_methods.invokeMethod<void>('setActiveTrackCircularSpeed', <String, dynamic>{'speed': speed});
Future<double?> getActiveTrackCircularSpeed() => _methods.invokeMethod<double>('getActiveTrackCircularSpeed');
/// Where the last waypoint mission was cut short: `{missionId, index, lat,
/// lon, altitude}`, or null when it was not.
Future<Map<String, dynamic>?> getWaypointInterruption() async {
final dynamic m = await _methods.invokeMethod('getWaypointInterruption');
return m == null ? null : Map<String, dynamic>.from(m as Map);
}
// ── Point of Interest (plan P7, `HotpointBridge.kt`) ────────────────────────
/// Circles [lat]/[lon] at [radius] m and [altitude] m; [angularVelocity] °/s.
/// startPoint: NORTH / SOUTH / WEST / EAST / NEAREST; heading:
/// TOWARDS_HOT_POINT / AWAY_FROM_HOT_POINT / ALONG_CIRCLE_LOOKING_FORWARDS /
/// ALONG_CIRCLE_LOOKING_BACKWARDS / CONTROLLED_BY_REMOTE_CONTROLLER / USING_INITIAL_HEADING.
Future<void> startHotpoint({
required double lat,
required double lon,
required double altitude,
required double radius,
required double angularVelocity,
required bool clockwise,
String startPoint = 'NEAREST',
String heading = 'TOWARDS_HOT_POINT',
}) =>
_methods.invokeMethod<void>('startHotpoint', <String, dynamic>{
'lat': lat,
'lon': lon,
'altitude': altitude,
'radius': radius,
'angularVelocity': angularVelocity,
'clockwise': clockwise,
'startPoint': startPoint,
'heading': heading,
});
Future<void> pauseHotpoint() => _methods.invokeMethod<void>('pauseHotpoint');
Future<void> resumeHotpoint() => _methods.invokeMethod<void>('resumeHotpoint');
Future<void> stopHotpoint() => _methods.invokeMethod<void>('stopHotpoint');
Future<void> setHotpointRadius(double radius) =>
_methods.invokeMethod<void>('setHotpointRadius', <String, dynamic>{'radius': radius});
/// °/s; the sign is the direction (positive clockwise).
Future<void> setHotpointAngularVelocity(double value) =>
_methods.invokeMethod<void>('setHotpointAngularVelocity', <String, dynamic>{'value': value});
Future<void> resetHotpointHeading() => _methods.invokeMethod<void>('resetHotpointHeading');
/// The fastest the aircraft circles at [radius] m, °/s.
Future<double?> getHotpointMaxAngularVelocity(double radius) =>
_methods.invokeMethod<double>('getHotpointMaxAngularVelocity', <String, dynamic>{'radius': radius});
// ── Follow Me (plan P7, `FollowMeBridge.kt`) ────────────────────────────────
/// heading: TOWARD_FOLLOW_POSITION | CONTROLLED_BY_REMOTE_CONTROLLER.
Future<void> startFollowMe({required double lat, required double lon, required double altitude, String heading = 'TOWARD_FOLLOW_POSITION'}) =>
_methods.invokeMethod<void>('startFollowMe', <String, dynamic>{'lat': lat, 'lon': lon, 'altitude': altitude, 'heading': heading});
Future<void> updateFollowMeTarget(double lat, double lon) =>
_methods.invokeMethod<void>('updateFollowMeTarget', <String, dynamic>{'lat': lat, 'lon': lon});
Future<void> stopFollowMe() => _methods.invokeMethod<void>('stopFollowMe');
// ── Orientation modes (plan P7) ─────────────────────────────────────────────
/// mode: AIRCRAFT_HEADING | COURSE_LOCK | HOME_LOCK.
Future<void> setFlightOrientationMode(String mode) =>
_methods.invokeMethod<void>('setFlightOrientationMode', <String, dynamic>{'mode': mode});
/// Course Lock's "forward" becomes the aircraft's current heading.
Future<void> lockCourseUsingCurrentHeading() => _methods.invokeMethod<void>('lockCourseUsingCurrentHeading');
// ── Remote controller extras (plan P6, `RemoteControllerBridge.kt`) ─────────
/// Looks for master remotes in range; they arrive as `rcMasters` events.
Future<void> searchMasters(bool on) => _methods.invokeMethod<void>('searchMasters', <String, dynamic>{'on': on});
/// Joins a master remote; answers `ConnectToMasterResult` (ACCEPTED, PASSWORD_ERROR, …).
Future<String?> connectToMaster({required int id, required String name, required String password}) =>
_methods.invokeMethod<String>('connectToMaster', <String, dynamic>{'id': id, 'name': name, 'password': password});
/// The slaves linked to this (master) remote: `[{id, name, signal}]`.
Future<List<Map<String, dynamic>>> getSlaves() async {
final List<Object?>? l = await _methods.invokeMethod<List<Object?>>('getSlaves');
return <Map<String, dynamic>>[for (final Object? s in l ?? const <Object?>[]) if (s is Map) Map<String, dynamic>.from(s)];
}
/// The video link's interference while on, as `spectrum` events.
Future<void> watchSpectrum(bool on) => _methods.invokeMethod<void>('watchSpectrum', <String, dynamic>{'on': on});
/// The Wi-Fi link's network name and/or password; [rebootWifi] applies them.
Future<void> setWifiCredentials({String? ssid, String? password}) => _methods.invokeMethod<void>('setWifiCredentials', <String, dynamic>{
if (ssid != null) 'ssid': ssid,
if (password != null) 'password': password,
});
Future<void> rebootWifi() => _methods.invokeMethod<void>('rebootWifi');
// ── Video cache (plan P6, `VideoCacheBridge.kt`) ────────────────────────────
Future<void> setVideoCacheRecording(bool on, {int? limitMb}) =>
_methods.invokeMethod<void>('setVideoCacheRecording', <String, dynamic>{'on': on, if (limitMb != null) 'limitMb': limitMb});
/// `{bytes, files, recording}`.
Future<Map<String, dynamic>> getVideoCacheInfo() async {
final dynamic m = await _methods.invokeMethod('getVideoCacheInfo');
return Map<String, dynamic>.from(m as Map);
}
Future<void> clearVideoCache() => _methods.invokeMethod<void>('clearVideoCache');
/// Records the lens's current focus as infinity (focus on a far subject first).
Future<void> calibrateInfinityFocus() => _methods.invokeMethod<void>('calibrateInfinityFocus');
// ── Media (MediaManager) ────────────────────────────────────────────────────
Future<void> refreshMediaList() => _methods.invokeMethod<void>('refreshMediaList');
Future<String?> fetchThumbnail(int index) =>
_methods.invokeMethod<String>('fetchThumbnail', <String, dynamic>{'index': index});
Future<String?> downloadMedia(int index) =>
_methods.invokeMethod<String>('downloadMedia', <String, dynamic>{'index': index});
/// The camera's screen-sized preview of a photo (or a still of a video).
Future<String?> fetchMediaPreview(int index) =>
_methods.invokeMethod<String>('fetchMediaPreview', <String, dynamic>{'index': index});
/// Deletes the files off the card; answers how many the camera refused. A new
/// `mediaList` (re-indexed) follows.
Future<int> deleteMedia(List<int> indices) async =>
await _methods.invokeMethod<int>('deleteMedia', <String, dynamic>{'indices': indices}) ?? 0;
/// Plays a video on the aircraft, into the live video feed (plan P9);
/// `mediaPlayback` events follow its position.
Future<void> playMediaVideo(int index) =>
_methods.invokeMethod<void>('playMediaVideo', <String, dynamic>{'index': index});
Future<void> pauseMediaVideo() => _methods.invokeMethod<void>('pauseMediaVideo');
Future<void> resumeMediaVideo() => _methods.invokeMethod<void>('resumeMediaVideo');
Future<void> stopMediaVideo() => _methods.invokeMethod<void>('stopMediaVideo');
Future<void> seekMediaVideo(double seconds) =>
_methods.invokeMethod<void>('seekMediaVideo', <String, dynamic>{'seconds': seconds});
// ── Live streaming (plan P9, `LiveStreamBridge.kt`) ─────────────────────────
/// Streams the primary video feed to an RTMP [url]; `liveStream` events follow.
Future<void> startLiveStream(String url, {bool audio = false}) =>
_methods.invokeMethod<void>('startLiveStream', <String, dynamic>{'url': url, 'audio': audio});
Future<void> stopLiveStream() => _methods.invokeMethod<void>('stopLiveStream');
// ── DJI account (optional) ──────────────────────────────────────────────────
Future<void> djiLogin() => _methods.invokeMethod<void>('djiLogin');
Future<void> djiLogout() => _methods.invokeMethod<void>('djiLogout');
Future<void> refreshDjiAccountState() => _methods.invokeMethod<void>('getDjiAccountState');
}
/// One MSDK V4 KeyManager key, as `KeyBridge.kt` addresses it.
///
/// [key] is the key class's constant name (`LOW_BATTERY_WARNING_THRESHOLD`) or
/// the raw parameter string; [index] picks the camera / gimbal / battery and
/// [subIndex] a camera lens.
class DjiKey {
const DjiKey(this.component, this.key, {this.index = 0, this.subIndex});
const DjiKey.flightController(String key) : this('FlightController', key);
const DjiKey.flightAssistant(String key) : this('FlightAssistant', key);
const DjiKey.camera(String key, {int index = 0, int? lens}) : this('Camera', key, index: index, subIndex: lens);
const DjiKey.gimbal(String key, {int index = 0}) : this('Gimbal', key, index: index);
const DjiKey.battery(String key, {int index = 0}) : this('Battery', key, index: index);
const DjiKey.airLink(String key) : this('AirLink', key);
const DjiKey.ocuSync(String key) : this('OcuSyncLink', key);
const DjiKey.lightbridge(String key) : this('LightbridgeLink', key);
const DjiKey.wifi(String key) : this('WiFiLink', key);
const DjiKey.remoteController(String key) : this('RemoteController', key);
const DjiKey.product(String key) : this('Product', key);
final String component;
final String key;
final int index;
final int? subIndex;
/// The id `listenKey` answers with and `key` events carry.
String get id => '$component/$key/$index/${subIndex ?? 'null'}';
Map<String, dynamic> get args => <String, dynamic>{
'component': component,
'key': key,
'index': index,
if (subIndex != null) 'subIndex': subIndex,
};
@override
bool operator ==(Object other) => other is DjiKey && other.id == id;
@override
int get hashCode => id.hashCode;
@override
String toString() => id;
}