Files
PilotVault/Fly App/lib/fly/action_rail_state.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

102 lines
4.0 KiB
Dart

import 'state/aircraft_state.dart';
import 'state/mission_state.dart';
/// Every command the flight screen can send that moves the aircraft (or stops
/// it moving). All of them go through `SafetyGate`.
enum FlightCommand { takeOff, land, cancelLanding, confirmLanding, goHome, cancelGoHome, stopMode }
/// What is running when the rail offers [FlightCommand.stopMode], i.e. which
/// operator "Stop" has to address.
enum ActiveMode { none, waypoint, activeTrack, tapFly, hotpoint, followMe, other }
/// The left rail's slots for the current flight state (plan P3, GO 4's
/// `fpv_leftmenu`): one primary button that walks Take-off → Land → Cancel
/// landing / Confirm landing, an RTH slot that turns into Cancel RTH, and a
/// Stop button while an intelligent mode flies the aircraft.
///
/// Pure: read off the flight controller and mission state, so the whole state
/// machine is unit-tested without a widget.
class RailState {
const RailState({required this.primary, this.rth, this.mode = ActiveMode.none, this.enabled = true});
/// Take-off, Land, Cancel landing or Confirm landing.
final FlightCommand primary;
/// Go home or Cancel RTH; null on the ground.
final FlightCommand? rth;
/// The intelligent mode a Stop button would end; [ActiveMode.none] hides it.
final ActiveMode mode;
/// False until the flight controller has reported: the buttons show but do nothing.
final bool enabled;
bool get showStop => mode != ActiveMode.none;
/// Flight-controller modes in which an intelligent mode is flying the aircraft.
static const Set<String> _waypointModes = <String>{'GPS_WAYPOINT'};
static const Set<String> _trackModes = <String>{'ACTIVE_TRACK', 'ACTIVE_TRACK_SPOTLIGHT'};
static const Set<String> _otherModes = <String>{'DRAW', 'TERRAIN_FOLLOW'};
static RailState of({required bool connected, required AircraftState a, MissionState? mission}) {
final bool reported = connected && a.updatedAt != null;
final bool airborne = a.isFlying == true;
final String mode = a.flightModeEnum ?? '';
final bool goingHome = a.goingHome == true || mode == 'GO_HOME';
final FlightCommand primary;
if (a.landingConfirmationNeeded == true || mode == 'CONFIRM_LANDING') {
primary = FlightCommand.confirmLanding;
} else if (mode == 'AUTO_LANDING') {
primary = FlightCommand.cancelLanding;
} else if (airborne) {
primary = FlightCommand.land;
} else {
primary = FlightCommand.takeOff;
}
return RailState(
primary: primary,
rth: airborne ? (goingHome ? FlightCommand.cancelGoHome : FlightCommand.goHome) : null,
mode: airborne ? activeMode(a, mission) : ActiveMode.none,
enabled: reported,
);
}
/// The intelligent mode in control, from the bridge's mission state first
/// (it knows which operator it started) and the flight mode second.
static ActiveMode activeMode(AircraftState a, MissionState? mission) {
switch (mission?.state) {
case 'executing':
return ActiveMode.waypoint;
case 'tracking':
case 'quickshot':
return ActiveMode.activeTrack;
case 'tapfly':
return ActiveMode.tapFly;
case 'hotpoint':
return ActiveMode.hotpoint;
case 'followme':
return ActiveMode.followMe;
}
final String mode = a.flightModeEnum ?? '';
if (_waypointModes.contains(mode)) return ActiveMode.waypoint;
if (_trackModes.contains(mode)) return ActiveMode.activeTrack;
if (mode == 'TAP_FLY') return ActiveMode.tapFly;
if (mode == 'GPS_HOT_POINT') return ActiveMode.hotpoint;
if (mode == 'GPS_FOLLOW_ME') return ActiveMode.followMe;
if (_otherModes.contains(mode)) return ActiveMode.other;
return ActiveMode.none;
}
@override
bool operator ==(Object other) =>
other is RailState && other.primary == primary && other.rth == rth && other.mode == mode && other.enabled == enabled;
@override
int get hashCode => Object.hash(primary, rth, mode, enabled);
@override
String toString() => 'RailState($primary, rth: $rth, mode: $mode, enabled: $enabled)';
}