Files
PilotVault/Fly App/test/fly/fake_dji.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

394 lines
18 KiB
Dart

import 'package:pilotvault_fly/dji_service.dart';
import 'package:pilotvault_fly/fly/state/key_state.dart';
/// Records every bridge call instead of reaching the platform channel.
class FakeDjiService extends DjiService {
final List<String> calls = <String>[];
Object? failWith;
Future<void> _record(String call) async {
calls.add(call);
if (failWith != null) throw failWith!;
}
@override
Future<void> takeOff() => _record('takeOff');
@override
Future<void> precisionTakeOff() => _record('precisionTakeOff');
@override
Future<void> land() => _record('land');
@override
Future<void> cancelLanding() => _record('cancelLanding');
@override
Future<void> confirmLanding() => _record('confirmLanding');
@override
Future<void> startGoHome() => _record('startGoHome');
@override
Future<void> cancelGoHome() => _record('cancelGoHome');
@override
Future<void> confirmSmartRTH(bool goHome) => _record('confirmSmartRTH($goHome)');
@override
Future<void> setGoHomeHeight(int m) => _record('setGoHomeHeight($m)');
@override
Future<void> setHomeLocation(double latitude, double longitude) => _record('setHomeLocation');
@override
Future<void> stopWaypointMission() => _record('stopWaypointMission');
// ── Missions (plan P7) ─────────────────────────────────────────────────────
/// The last `uploadWaypointMission` payload.
Map<String, Object?>? uploaded;
/// Whether the last upload went to the Waypoints 2.0 operator.
bool uploadedV2 = false;
@override
Future<void> uploadWaypointMission(Map<String, Object?> mission, {bool v2 = false}) {
uploaded = mission;
uploadedV2 = v2;
return _record('uploadWaypointMission(${(mission['waypoints'] as List<Object?>).length})');
}
@override
Future<void> startWaypointMission() => _record('startWaypointMission');
@override
Future<void> pauseWaypointMission() => _record('pauseWaypointMission');
@override
Future<void> resumeWaypointMission() => _record('resumeWaypointMission');
@override
Future<void> setWaypointAutoFlightSpeed(double speed) => _record('setWaypointAutoFlightSpeed(${speed.toStringAsFixed(0)})');
/// What `getWaypointInterruption` answers.
Map<String, dynamic>? interruption;
@override
Future<Map<String, dynamic>?> getWaypointInterruption() async => interruption;
// ── POI / Follow Me / orientation / QuickShots (plan P7) ───────────────────
Map<String, Object?>? hotpoint;
double? maxAngular;
@override
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',
}) {
hotpoint = <String, Object?>{'lat': lat, 'lon': lon, 'altitude': altitude, 'radius': radius, 'angularVelocity': angularVelocity, 'clockwise': clockwise, 'startPoint': startPoint, 'heading': heading};
return _record('startHotpoint');
}
@override
Future<void> pauseHotpoint() => _record('pauseHotpoint');
@override
Future<void> resumeHotpoint() => _record('resumeHotpoint');
@override
Future<void> stopHotpoint() => _record('stopHotpoint');
@override
Future<void> setHotpointRadius(double radius) => _record('setHotpointRadius(${radius.round()})');
@override
Future<void> setHotpointAngularVelocity(double value) => _record('setHotpointAngularVelocity(${value.toStringAsFixed(1)})');
@override
Future<void> resetHotpointHeading() => _record('resetHotpointHeading');
@override
Future<double?> getHotpointMaxAngularVelocity(double radius) async => maxAngular;
@override
Future<void> startFollowMe({required double lat, required double lon, required double altitude, String heading = 'TOWARD_FOLLOW_POSITION'}) =>
_record('startFollowMe(${altitude.round()}, $heading)');
@override
Future<void> updateFollowMeTarget(double lat, double lon) => _record('updateFollowMeTarget');
@override
Future<void> stopFollowMe() => _record('stopFollowMe');
@override
Future<void> setFlightOrientationMode(String mode) => _record('setFlightOrientationMode($mode)');
@override
Future<void> lockCourseUsingCurrentHeading() => _record('lockCourseUsingCurrentHeading');
@override
Future<void> startQuickShot(String name, {double? x, double? y, double? w, double? h}) => _record('startQuickShot($name)');
@override
Future<void> setActiveTrackCircularSpeed(double speed) => _record('setActiveTrackCircularSpeed(${speed.toStringAsFixed(0)})');
@override
Future<void> setShootPhotoMode(String mode) => _record('setShootPhotoMode($mode)');
// ── Remote / link / cache (plan P6) ────────────────────────────────────────
@override
Future<void> searchMasters(bool on) => _record('searchMasters($on)');
@override
Future<String?> connectToMaster({required int id, required String name, required String password}) async {
await _record('connectToMaster($id, $name)');
return 'ACCEPTED';
}
@override
Future<List<Map<String, dynamic>>> getSlaves() async => const <Map<String, dynamic>>[];
@override
Future<void> watchSpectrum(bool on) async => calls.add('watchSpectrum($on)');
@override
Future<void> setWifiCredentials({String? ssid, String? password}) => _record('setWifiCredentials(${ssid ?? '-'}, ${password == null ? '-' : '***'})');
@override
Future<void> rebootWifi() => _record('rebootWifi');
@override
Future<void> setVideoCacheRecording(bool on, {int? limitMb}) async => calls.add('setVideoCacheRecording($on)');
@override
Future<Map<String, dynamic>> getVideoCacheInfo() async => <String, dynamic>{'bytes': 0, 'files': 0, 'recording': false};
@override
Future<void> clearVideoCache() => _record('clearVideoCache');
@override
Future<void> calibrateInfinityFocus() => _record('calibrateInfinityFocus');
// ── ActiveTrack / TapFly (plan P7b) ────────────────────────────────────────
@override
Future<void> startActiveTrack(double x, double y, double w, double h, {String mode = 'TRACE'}) =>
_record('startActiveTrack(${x.toStringAsFixed(2)}, ${y.toStringAsFixed(2)}, ${w.toStringAsFixed(2)}, ${h.toStringAsFixed(2)}, $mode)');
@override
Future<void> acceptActiveTrackConfirmation() => _record('acceptActiveTrackConfirmation');
@override
Future<void> rejectActiveTrackConfirmation() => _record('rejectActiveTrackConfirmation');
@override
Future<void> switchActiveTrackMode(String mode) => _record('switchActiveTrackMode($mode)');
@override
Future<void> startTapFly(double x, double y, {String mode = 'FORWARD', double speed = 5, bool avoidance = true}) =>
_record('startTapFly(${x.toStringAsFixed(2)}, ${y.toStringAsFixed(2)}, $mode, ${speed.toStringAsFixed(0)}, $avoidance)');
@override
Future<void> stopTapFly() => _record('stopTapFly');
@override
Future<void> setTapFlySpeed(double speed) => _record('setTapFlySpeed(${speed.toStringAsFixed(0)})');
@override
Future<void> resetTapFlyHeading() => _record('resetTapFlyHeading');
@override
Future<void> setActiveTrackAutoSensing(bool on) => _record('setActiveTrackAutoSensing($on)');
@override
Future<void> startActiveTrackSubject(int index, {String mode = 'TRACE'}) => _record('startActiveTrackSubject($index, $mode)');
@override
Future<void> setActiveTrackRetreat(bool on) => _record('setActiveTrackRetreat($on)');
@override
Future<bool?> getActiveTrackRetreat() async => false;
@override
Future<void> stopActiveTrack() => _record('stopActiveTrack');
// ── Sensing distances / fly zones (plan P8) ────────────────────────────────
Map<String, dynamic> sensing = <String, dynamic>{};
List<Map<String, dynamic>> flyZones = <Map<String, dynamic>>[];
List<int> unlockedZones = <int>[];
/// Zone fetches, counted apart from [calls]: the flight screen fetches on its own.
int zoneFetches = 0;
@override
Future<Map<String, dynamic>> getSensingDistances() async => sensing;
@override
Future<void> setAvoidanceDistance(String direction, double metres) => _record('setAvoidanceDistance($direction, $metres)');
@override
Future<void> setMaxPerceptionDistance(String direction, double metres) => _record('setMaxPerceptionDistance($direction, $metres)');
@override
Future<List<Map<String, dynamic>>> getFlyZonesNearby({double? latitude, double? longitude}) async {
zoneFetches++;
return flyZones;
}
@override
Future<List<int>> getUnlockedFlyZones() async => unlockedZones;
@override
Future<void> unlockAuthorizationFlyZone(int id) async {
await _record('unlockAuthorizationFlyZone($id)');
unlockedZones = <int>[...unlockedZones, id];
}
/// What `getFlyZoneLicences` / `getFlyZoneDatabase` answer.
Map<String, dynamic> licences = <String, dynamic>{'supported': false, 'zones': <Object?>[]};
Map<String, dynamic> flyZoneDb = <String, dynamic>{};
@override
Future<Map<String, dynamic>> getFlyZoneLicences() async => licences;
@override
Future<void> setFlyZoneLicenceEnabled(int id, bool enabled) => _record('setFlyZoneLicenceEnabled($id, $enabled)');
@override
Future<void> syncFlyZoneLicences() => _record('syncFlyZoneLicences');
@override
Future<void> deleteAllFlyZoneLicences() => _record('deleteAllFlyZoneLicences');
@override
Future<Map<String, dynamic>> getFlyZoneDatabase() async => flyZoneDb;
@override
Future<void> startFlyZoneDatabaseUpgrade() => _record('startFlyZoneDatabaseUpgrade');
@override
Future<void> stopFlyZoneDatabaseUpgrade() => _record('stopFlyZoneDatabaseUpgrade');
// ── Virtual sticks (plan P8) ───────────────────────────────────────────────
@override
Future<void> setVirtualStickEnabled(bool on) => _record('setVirtualStickEnabled($on)');
@override
Future<void> sendVirtualStick({required double forward, required double right, required double yaw, required double up}) =>
_record('sendVirtualStick(${forward.toStringAsFixed(1)}, ${right.toStringAsFixed(1)}, ${yaw.toStringAsFixed(0)}, ${up.toStringAsFixed(1)})');
@override
Future<void> startCompassCalibration() => _record('startCompassCalibration');
// ── Calibration (plan P6b) ─────────────────────────────────────────────────
@override
Future<void> stopCompassCalibration() => _record('stopCompassCalibration');
@override
Future<void> startIMUCalibration({int? index}) => _record(index == null ? 'startIMUCalibration' : 'startIMUCalibration($index)');
@override
Future<void> startGravityCenterCalibration() => _record('startGravityCenterCalibration');
@override
Future<void> stopGravityCenterCalibration() => _record('stopGravityCenterCalibration');
@override
Future<void> startPropellerCalibration() => _record('startPropellerCalibration');
@override
Future<void> stopPropellerCalibration() => _record('stopPropellerCalibration');
@override
Future<void> startLensCalibration() => _record('startLensCalibration');
@override
Future<void> stopLensCalibration() => _record('stopLensCalibration');
@override
Future<void> initSensorCleaning() => _record('initSensorCleaning');
@override
Future<void> startSensorCleaning() => _record('startSensorCleaning');
@override
Future<void> exitSensorCleaning() => _record('exitSensorCleaning');
@override
Future<void> setRcCalibrationStep(String step) => _record('setRcCalibrationStep($step)');
@override
Future<void> watchRcHardware(bool on) async => calls.add('watchRcHardware($on)');
@override
Future<void> formatStorage({String location = 'SDCARD'}) => _record('formatStorage');
@override
Future<void> setHomeToCurrent() => _record('setHomeToCurrent');
// ── Media + live streaming (plan P9) ───────────────────────────────────────
/// What `deleteMedia` answers: how many files the camera refused.
int deleteRefused = 0;
@override
Future<void> refreshMediaList() => _record('refreshMediaList');
@override
Future<String?> fetchThumbnail(int index) async => null;
@override
Future<String?> fetchMediaPreview(int index) async => null;
@override
Future<String?> downloadMedia(int index) async {
await _record('downloadMedia($index)');
return '/files/$index';
}
@override
Future<int> deleteMedia(List<int> indices) async {
await _record('deleteMedia($indices)');
return deleteRefused;
}
@override
Future<void> playMediaVideo(int index) => _record('playMediaVideo($index)');
@override
Future<void> stopMediaVideo() => _record('stopMediaVideo');
@override
Future<void> startLiveStream(String url, {bool audio = false}) => _record('startLiveStream($url, $audio)');
@override
Future<void> stopLiveStream() => _record('stopLiveStream');
// ── KeyManager (plan P6) ───────────────────────────────────────────────────
/// Keys `isKeySupported` answers false for (by [DjiKey.key] name).
final Set<String> unsupportedKeys = <String>{};
/// When set, a successful `setKey` reports the new value back, as the
/// native listener would.
KeyState? echo;
@override
Future<bool> isKeySupported(DjiKey key) async => !unsupportedKeys.contains(key.key);
@override
Future<String> listenKey(DjiKey key) async {
calls.add('listenKey(${key.key})');
return key.id;
}
@override
Future<void> cancelKey(String id) async => calls.add('cancelKey');
@override
Future<void> setKey(DjiKey key, Object? value) async {
await _record('setKey(${key.key}, $value)');
echo?.apply(key.id, value);
}
@override
Future<void> performKeyAction(DjiKey key) => _record('performKeyAction(${key.key})');
@override
Future<Object?> getKey(DjiKey key) async => switch (key.key) {
'MAX_FLIGHT_HEIGHT' => 120,
'MAX_FLIGHT_RADIUS' => 500,
'MAX_FLIGHT_RADIUS_ENABLED' => true,
_ => null,
};
// ── Camera (plan P4) ───────────────────────────────────────────────────────
@override
Future<void> setCameraMode(String mode) => _record('setCameraMode($mode)');
@override
Future<void> startShootPhoto() => _record('startShootPhoto');
@override
Future<void> stopShootPhoto() => _record('stopShootPhoto');
@override
Future<void> startRecordVideo() => _record('startRecordVideo');
@override
Future<void> stopRecordVideo() => _record('stopRecordVideo');
@override
Future<void> setCameraValue(String id, Object value) => _record('setCameraValue($id, $value)');
@override
Future<void> setISO(String value) => _record('setISO($value)');
@override
Future<void> setShutterSpeed(String value) => _record('setShutterSpeed($value)');
@override
Future<void> setAperture(String value) => _record('setAperture($value)');
@override
Future<void> setEV(String value) => _record('setEV($value)');
@override
Future<void> setWhiteBalance(String value) => _record('setWhiteBalance($value)');
@override
Future<void> setVideoResolutionAndFrameRate(String resolution, String frameRate) =>
_record('setVideoResolutionAndFrameRate($resolution, $frameRate)');
@override
Future<void> setPhotoTimeInterval({required int count, required int seconds}) => _record('setPhotoTimeInterval($count, $seconds)');
@override
Future<void> setPictureStyle({required int sharpness, required int contrast, required int saturation}) =>
_record('setPictureStyle($sharpness, $contrast, $saturation)');
@override
Future<void> setFocusTarget(double x, double y) => _record('setFocusTarget(${x.toStringAsFixed(2)}, ${y.toStringAsFixed(2)})');
@override
Future<void> setSpotMeteringTarget(double x, double y) =>
_record('setSpotMeteringTarget(${x.toStringAsFixed(2)}, ${y.toStringAsFixed(2)})');
@override
Future<void> startContinuousZoom(String direction) => _record('startContinuousZoom($direction)');
@override
Future<void> stopContinuousZoom() => _record('stopContinuousZoom');
@override
Future<void> setZoomFocalLength(int value) => _record('setZoomFocalLength($value)');
@override
Future<void> restoreCameraDefaults() => _record('restoreCameraDefaults');
@override
Future<void> setHistogramEnabled(bool on) => _record('setHistogramEnabled($on)');
// ── Gimbal (plan P5) ───────────────────────────────────────────────────────
@override
Future<void> rotateGimbalPitch(double deg) => _record('rotateGimbalPitch(${deg.round()})');
@override
Future<void> rotateGimbalSpeed({double? pitch, double? yaw}) =>
_record('rotateGimbalSpeed(${pitch?.toStringAsFixed(1)}, ${yaw?.toStringAsFixed(1)})');
@override
Future<void> setGimbalMode(String mode) => _record('setGimbalMode($mode)');
@override
Future<void> resetGimbal() => _record('resetGimbal');
@override
Future<void> resetGimbalYaw() => _record('resetGimbalYaw');
@override
Future<void> fineTuneGimbal(String axis, double degrees) => _record('fineTuneGimbal($axis, ${degrees.toStringAsFixed(1)})');
@override
Future<void> startGimbalCalibration() => _record('startGimbalCalibration');
@override
Future<void> setGimbalSetting(String id, Object value) => _record('setGimbalSetting($id, $value)');
@override
Future<void> resetGimbalControlParameters() => _record('resetGimbalControlParameters');
}