iNAV freezes activeWaypointIndex at whatever mission waypoint was
active when RTH engaged instead of updating it to reflect the new
target - navigation.c derives NAV_Status.activeWpIndex unconditionally
from posControl.activeWaypointIndex, and none of the RTH state-entry
handlers touch that field. Drawing the guidance line to that stale
index would point at a waypoint the aircraft may have already passed.
Extracted the target selection into computeRubberBandTarget() (mission
stats.dart) so the RTH special-case and the abort-back-to-waypoint-mode
fallback are unit-tested rather than only living inline in the widget
build method - since it re-evaluates navMode on every frame, aborting
RTH switches the line back to the mission waypoint with no extra
transition logic needed.
Adds MSP_WP (118) support to query the flightcontroller's stored home
point (WP#0 is a special case for this in iNAV's getWaypoint(), per
navigation.c) - the FC reports (0,0,0) rather than an error before one
is set, so parseMspHomePoint() treats that pair as "unset". Rendered
with a small pentagon house icon (design/homepoint-pentagon.svg,
ported to a CustomPainter like the other map icons).
Deliberately not polled continuously: a new home_point_provider.dart
refreshes it only at the three moments the FC's home point can
actually change - connect/reconnect, GPS fix acquired, and arming
(iNAV's default reset_home_type=FIRST_ARM only freezes it at the first
arm; before that it continuously follows the aircraft while disarmed).
Mock's implementation offsets the point 25m from the anchor so it
doesn't sit exactly under the drone marker during UI testing.
Waehrend eines Verbindungsverlusts frieren die TelemetryFrame-Werte
bewusst ein (telemetryProvider.value bleibt auf dem letzten Stand, siehe
vorherigen Commit) - die Fusszeilen-Pille im Fly-Modus hat daraus bislang
weiterhin den zuletzt bekannten (ggf. laengst veralteten) Zustand
abgeleitet, statt den Verbindungsverlust selbst widerzuspiegeln.
- domain/telemetry/drone_status.dart: neuer DroneState.disconnected,
computeDroneStatus() bekommt einen neuen Parameter `connected` - hat
Vorrang vor allem anderen (auch vor Emergency) und uebersteuert
connectionQuality auf rot. positionQuality bleibt bewusst unveraendert
(folgt weiterhin den eingefrorenen Werten) - laut Anfrage sollen nur
Drohnen-Icon und Verbindungsqualitaet, nicht die Positionsqualitaet, rot
erzwungen werden.
- fly_screen.dart: `connected: !telemetryAsync.hasError` statt der
bisherigen Ableitung aus einzelnen Telemetriewerten.
- bottom_stats_bar.dart: neues Label/Farbe ("disconnected", rot) fuer den
neuen Zustand in der Drohnen-Status-Pille.
Auf dem Pixel_10a-Emulator verifiziert: WiFi-Verbindungsart (noch nie
verbunden) zeigt bereits korrekt rotes Papierflieger-Icon, rote
Verbindungsqualitaet, rotes Satelliten-Icon und "disconnected" in rot;
Mock-Verbindung zeigt unveraendert normal gruen "ready".
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Include the mission name in the "Mission sent"/"Mission upload failed"
System Messages (was just the waypoint count before).
Add MissionMeta.switchSeq, bumped only by an actual mission switch
(startNew/startNewFromPlace/loadMission) - not by the id a brand-new
mission gets from its first autosave, and not by restoreLastSession() on
app start. FlyScreen compares it to detect a genuine switch and reacts
two ways: logs "Mission changed to ..." and automatically re-uploads the
new route to the flight controller, reusing the same send path as the
manual send button (same _sending guard, same success/failure snackbar
and log entry).
Mission-change and drone-profile-change logging intentionally live in
FlyScreen's ref.listen callbacks, not in mission_meta_provider.dart /
active_drone_profile_provider.dart themselves - those providers have no
notion of the app mode (Plan vs Fly, tracked separately by AppModeCubit),
and logging there would record a change regardless of mode. Since
FlyScreen only exists while Fly mode is active, scoping the listeners
there means switching missions or drone profiles from Plan mode produces
no System Messages entries, and only a mission switch (not a drone
profile switch) triggers the automatic re-upload, matching what was
asked for.
Also splits systemMessageLogProvider (the plain message list + log(), no
telemetry dependency) from the new systemMessageAutoLogProvider (the
Connected/lost/battery/failsafe/connection-type auto-detection, which
does watch telemetryProvider) - discovered while wiring the mission-change
logging that logging a plain message from Plan-mode code was forcing the
entire telemetry/transport stack to spin up as a side effect, which broke
an unrelated Plan-mode test (UdpTransport threw on a double-disconnect
during teardown). Keeping the two concerns apart means calling log() for
a one-off message never has that side effect.
Verified end to end on the Pixel_10a emulator: switching to an empty
mission while in Fly mode correctly showed "No waypoints to send" and
logged "Mission changed to ..." automatically, without touching the send
button.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Renames DroneEvent/DroneEventSeverity/droneEventLogProvider/
DroneStatusWarningsPanel to SystemMessage/SystemMessageSeverity/
systemMessageLogProvider/DroneStatusMessagesPanel throughout, matching
what the tab now actually shows - general system messages, not just
drone-health warnings.
Adds a new SystemMessageSeverity.info level (blue) for messages that
aren't a warning/error, and three new message sources on top of the
existing battery/failsafe detection:
- "Connected", logged the first time telemetryProvider produces a frame
after having none - the mirror image of the existing "Connection lost"
detection (which already fires on the first stream error after having
had data), so no new transport-specific dependency was needed.
- "Connection type changed to X", from watching connectionSettingsProvider
(skips the initial load so it doesn't fire on every app start).
- "Mission sent (N waypoints)" / "Mission upload failed: ...", logged
from FlyScreen's send handler via a new public log() method on the
notifier, alongside the existing snackbar.
Verified on the Pixel_10a emulator: entering Fly mode logs "Connected"
once the mock telemetry starts, and tapping the send button logs
"Mission sent (3 waypoints)" right after the snackbar.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fills in MspFlightControllerLink.uploadMission(), which previously just
threw UnimplementedError, plus a new verifyMission() (both now on the
generic FlightControllerLink interface, protocol-neutral by signature -
MAVLink/ArduPilot get their own implementation later without touching
callers).
All iNAV-specific encoding lives in the new msp_waypoint_codec.dart:
- encodeMspSetWaypoint(): the 21-byte MSP_SET_WP payload. Action/P1/P2/P3
byte layout was checked against the actual iNAV 9.1.0 source
(navigation.c/navigation.h), not guessed - notably our generic `loiter`
action has no configurable duration, so it maps to
NAV_WP_ACTION_HOLD_TIME with the max representable p1 (int16 max, not
0xFFFF - that would read as -1 and end the hold immediately instead of
never).
- parseMspWpGetInfo(): decodes MSP_WP_GETINFO's validity/count fields,
used by verifyMission() to confirm the FC actually accepted the full
mission (Doku 2.2/4.5 Ready-to-Fly-Gate: "upload + verified").
uploadMission() sends one MSP_SET_WP per waypoint in order (iNAV has no
batch command - WP#1 resets the FC's mission list, every next number must
follow immediately, only the last carries NAV_WP_FLAG_LAST) and rejects
missions above NAV_MAX_WAYPOINTS upfront instead of silently truncating.
MissionSyncService now calls the real verifyMission() instead of always
confirming, throwing MissionVerificationException when the FC doesn't
confirm the mission.
FlyScreen's footer swaps the warnings button for a send button (Doku:
"ersetze den warnings button mit einem wp send button", pink horizontal
PaperPlaneIcon, matching the existing paper-plane drone iconography) -
warnings/event log stay reachable via the drone status pill's Warnings
tab. BottomStatsBar/MissionFooterBar gained onSendTap/sending in place of
the old forceShowWarningsButton.
Verified end to end on the Pixel_10a emulator: tapping send with WLAN as
the active connection type triggers the real WifiNetworkSpecifier flow
through MspFlightControllerLink (correctly reports "no devices found" -
expected, no real mLRS bridge on the emulator); the actual MSP_SET_WP/
MSP_WP_GETINFO wire behavior is covered by tests against a fake FC
responder over LoopbackTransport instead.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fly mode previously showed just a black placeholder. It now renders the
same MissionMap (route + waypoint markers) and footer (warnings banner,
altitude profile, BottomStatsBar) as the Plan screen, minus the
reticle/wheels since flying observes the route rather than replanning it.
Waypoints can still be edited via the details list for spontaneous
in-flight changes.
Live drone position comes from a new telemetryProvider/
flightControllerLinkProvider pair (autoDispose), the first place
FlightControllerLink/TelemetryFrame get wired into the UI - currently
backed by MockFlightControllerLink until a real MSP transport exists
(Doku 4.19). A drone marker renders on the map once a telemetry frame
arrives.
The Fly-mode header keeps the fit-to-area button but replaces "zoom to
home" with "zoom to drone" (FlyMapControls) - the first waypoint isn't a
meaningful reference point anymore once airborne.
Extracted computeMissionStats and MissionFooterBar out of PlanScreen so
Plan/Fly share the exact same stats/footer logic instead of duplicating
it, and extracted NavIconButton out of MapSearchControls so both Plan's
and Fly's map controls use the same button widget.
Widget tests that now mount FlyScreen switch from the ProviderScope-based
_wrap() helper to a manual ProviderContainer with an explicit
FlightControllerLink.disconnect() call before test end - the mock's
Timer.periodic doesn't get cancelled by container disposal alone, and
flutter_test's pending-timer check runs before addTearDown callbacks.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- TopModeBar widget: persistent pill-shaped header (HTML prototype:
#topBarWrap/#editModeBtn/#flyModeBtn) tinted in the active mode's color,
with the inactive mode offered as a solid button in the corner to switch
into it.
- AppShell now provides a single shared Scaffold + Stack, overlaying
TopModeBar on top of whichever screen (Plan/Fly) is active, instead of
each screen owning its own Scaffold.
- Wired the Fly button to AppModeCubit.toFly(), which enforces the
Ready-to-Fly gate (doc 4.5). Since upload/verify isn't wired up yet, the
gate is never satisfied yet, so tapping Fly now shows a SnackBar instead
of throwing an uncaught StateError.
- Extended the widget test to cover both the bar's presence and the
gate-rejection path (was the source of a real bug: an initial
negative-margin Container hack for the "bleed into the corner" look
violated Container's margin.isNonNegative assertion and crashed the
whole tree - fixed with padding instead).
Verified with flutter analyze, flutter test, and a real run on the
Pixel_10a emulator (visually matches the prototype screenshots in design/,
Fly-tap correctly shows the gate message without crashing or switching mode).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>