Switch between Course Lock and Home Lock through normal heading

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 <noreply@anthropic.com>
This commit is contained in:
tajniak81andClaude Opus 5.5 committed 2026-10-22 09:48:07 +02:00
1 parent 9cca65019f
commit 789818679a
4 files changed
+28 -1

No files matched your search

+6
View File
@@ -915,6 +915,12 @@ class SafetyGate {
Future<void> setOrientation(OrientationMode mode) async {
final List<GateCheck> 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);
}
+3 -1
View File
@@ -192,7 +192,9 @@ class _ModeText extends StatelessWidget {
return GlassPill(
height: StatusBar.height,
child: Text(
<String>[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.
<String>[if (mode != null && mode.isNotEmpty) mode else '—', if (orientation != null && orientation != mode) orientation].join(' · '),
style: HudText.mono.copyWith(fontWeight: FontWeight.w700),
),
);
+11
View File
@@ -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);
+8
View File
@@ -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')),
<String>['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);