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>
102 lines
4.0 KiB
Dart
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)';
|
|
}
|