From 789818679a7300b77695c4057abcf3b88ee5fd3f Mon Sep 17 00:00:00 2001 From: tajniak81 <13187254+tajniak81@users.noreply.github.com> Date: Thu, 22 Oct 2026 09:48:07 +0200 Subject: [PATCH] Switch between Course Lock and Home Lock through normal heading MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On the Mavic Pro the flight controller refused Course Lock -> Home Lock with "There is a conflicting setting in the mission"; from normal heading the same switch works, so the gate now goes through normal first. The mode chip also stops repeating the lock when the aircraft already names it as the mode ("CL · CL" -> "CL"). Co-Authored-By: Claude Opus 5.5 --- Fly App/lib/fly/safety_gate.dart | 6 ++++++ Fly App/lib/ui/fly/top/status_bar.dart | 4 +++- Fly App/test/fly/hud_test.dart | 11 +++++++++++ Fly App/test/fly/p7_modes_test.dart | 8 ++++++++ 4 files changed, 28 insertions(+), 1 deletion(-) 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);