P7b of the GO 4-parity plan. Both modes open from the MODES picker on the live video, where they take over the touches the camera and gimbal used. ActiveTrack: drag a box round the subject (or tap it), then GO. The operator's own box follows on the video, coloured by confidence, with confirm / reject when it asks and the reason when it cannot follow. Trace, Profile and Spotlight switch live from the mode panel. TapFly: tap where to fly, then GO; the aircraft's heading point and any obstacle bypass show while it flies, and the speed changes live. Forward only on aircraft without rear sensors; Backward and Free otherwise. Both go through new SafetyGate checks (flying, P mode, above 2 m, nothing else in control, battery), start on GO when the check is clean and ask for the slide when it warns. Stop on the rail and Exit stop them. MissionBridge now listens to the ActiveTrack and TapFly operators. Not yet run on a simulator or aircraft. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
96 lines
3.8 KiB
Dart
96 lines
3.8 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, 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>{'GPS_HOT_POINT', 'GPS_FOLLOW_ME', '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;
|
|
}
|
|
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 (_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)';
|
|
}
|