diff --git a/Fly App/lib/fly/safety_gate.dart b/Fly App/lib/fly/safety_gate.dart index 167c4cb..6c1c963 100644 --- a/Fly App/lib/fly/safety_gate.dart +++ b/Fly App/lib/fly/safety_gate.dart @@ -915,6 +915,12 @@ class SafetyGate { Future setOrientation(OrientationMode mode) async { final List checks = checkOrientation(mode); if (checks.any((GateCheck c) => c.blocks)) throw MissionRefused(checks); + // The flight controller refuses Course Lock ⇄ Home Lock directly ("There + // is a conflicting setting in the mission", Mavic Pro): go through normal. + final OrientationMode now = OrientationMode.fromSdk(_a.orientationMode); + if (now != OrientationMode.normal && mode != OrientationMode.normal && now != mode) { + await dji.setFlightOrientationMode(OrientationMode.normal.sdk); + } await dji.setFlightOrientationMode(mode.sdk); } diff --git a/Fly App/lib/ui/fly/top/status_bar.dart b/Fly App/lib/ui/fly/top/status_bar.dart index d1d3196..7bcbe04 100644 --- a/Fly App/lib/ui/fly/top/status_bar.dart +++ b/Fly App/lib/ui/fly/top/status_bar.dart @@ -192,7 +192,9 @@ class _ModeText extends StatelessWidget { return GlassPill( height: StatusBar.height, child: Text( - [if (mode != null && mode.isNotEmpty) mode else '—', if (orientation != null) orientation].join(' · '), + // Some aircraft already name the lock as the mode (a Mavic Pro says + // "CL"), so the tag is only added when it says something new. + [if (mode != null && mode.isNotEmpty) mode else '—', if (orientation != null && orientation != mode) orientation].join(' · '), style: HudText.mono.copyWith(fontWeight: FontWeight.w700), ), ); diff --git a/Fly App/test/fly/hud_test.dart b/Fly App/test/fly/hud_test.dart index 159dc0c..be7fcc5 100644 --- a/Fly App/test/fly/hud_test.dart +++ b/Fly App/test/fly/hud_test.dart @@ -213,6 +213,17 @@ void main() { expect(tester.takeException(), isNull); }); + testWidgets('the mode chip does not repeat a lock the mode already names', (WidgetTester tester) async { + await pump(tester, modelFor(phantom4, tick: telemetryTick()..['flightMode'] = 'CL'..['orientationMode'] = 'COURSE_LOCK')); + expect(find.text('CL'), findsOneWidget); + expect(find.text('CL · CL'), findsNothing); + }); + + testWidgets('the mode chip adds the lock to a plain mode', (WidgetTester tester) async { + await pump(tester, modelFor(phantom4, tick: telemetryTick()..['orientationMode'] = 'HOME_LOCK')); + expect(find.text('P-GPS · HL'), findsOneWidget); + }); + testWidgets('hudHidden leaves only the feed, full-screen', (WidgetTester tester) async { final FlightModel m = modelFor(phantom4); await pump(tester, m); diff --git a/Fly App/test/fly/p7_modes_test.dart b/Fly App/test/fly/p7_modes_test.dart index ac19880..72873a5 100644 --- a/Fly App/test/fly/p7_modes_test.dart +++ b/Fly App/test/fly/p7_modes_test.dart @@ -135,6 +135,14 @@ void main() { expect(OrientationMode.fromSdk('HOME_LOCK'), OrientationMode.homeLock); }); + test('Course Lock to Home Lock goes through normal heading', () async { + final FlightModel m = _model(telemetryTick()..['orientationMode'] = 'COURSE_LOCK'); + final FakeDjiService dji = FakeDjiService(); + await SafetyGate(model: m, dji: dji).setOrientation(OrientationMode.homeLock); + expect(dji.calls.where((String c) => c.startsWith('setFlightOrientationMode')), + ['setFlightOrientationMode(AIRCRAFT_HEADING)', 'setFlightOrientationMode(HOME_LOCK)']); + }); + test('Tripod and Cinematic are key writes behind the gate', () async { final FakeDjiService dji = FakeDjiService(); final SafetyGate g = SafetyGate(model: _model(telemetryTick()), dji: dji);