Files
PilotVault/Fly App/lib/fly/state/gimbal_state.dart
T
tajniak81andClaude Opus 5.5 60c2fd7f64 Give the Fly App GO 4-style gimbal controls
Plan P5: a value bubble on the pitch slider while dragging, press-and-drag
on the video as a speed joystick (re-sent at 10 Hz, zero on release), and a
gimbal settings sheet: mode, 30° extension, yaw follow, recenter / face down /
yaw recenter, roll fine-tune, auto calibration and advanced speed/smoothness
rows gated by the gimbal's capability ranges.

GimbalBridge reports mode, trim, calibration and stop/overload state in the
gimbal telemetry group, and getter-only settings in a new gimbalSettings slow
group. The reported-plus-pending overlay moves out of CameraState into a
SettingsOverlay mixin shared by camera and gimbal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 20:22:42 +02:00

90 lines
2.8 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/foundation.dart';
import 'parse.dart';
import 'settings_overlay.dart';
/// Gimbal attitude, mode and status, fed from the `gimbal` group of the
/// telemetry tick; the getter-only settings (pitch extension, yaw follow,
/// controller tuning) from the `gimbalSettings` group, overlaid with the
/// gimbal sheet's pending writes ([SettingsOverlay]).
class GimbalState extends ChangeNotifier with SettingsOverlay {
double? pitch; // −90..+30-ish
double? roll;
double? yaw;
/// `YAW_FOLLOW` / `FPV` / `FREE`.
String? mode;
/// Yaw relative to the aircraft's nose, for gimbals that pan.
double? yawRelative;
double? pitchFineTune;
double? rollFineTune;
double? yawFineTune;
bool calibrating = false;
int? calibrationProgress; // 0..100
bool? calibrationSuccessful;
/// How the last calibration seen from start to end went; null until one ends.
bool? lastCalibrationOk;
bool pitchAtStop = false;
bool yawAtStop = false;
bool motorOverloaded = false;
DateTime? updatedAt;
// The `gimbalSettings` group, as the sheet shows it (pending writes included).
bool? get pitchExtension => asBool(settings['pitchExtension']);
bool? get yawFollow => asBool(settings['yawFollow']);
void apply(Map<String, dynamic> g) {
pitch = finite(g['pitch']);
roll = finite(g['roll']);
yaw = finite(g['yaw']);
mode = asString(g['mode']);
yawRelative = finite(g['yawRelative']);
pitchFineTune = finite(g['pitchFineTune']);
rollFineTune = finite(g['rollFineTune']);
yawFineTune = finite(g['yawFineTune']);
final bool wasCalibrating = calibrating;
calibrating = asBool(g['calibrating']) ?? false;
calibrationProgress = asInt(g['calibrationProgress']);
calibrationSuccessful = asBool(g['calibrationSuccessful']);
if (wasCalibrating && !calibrating) lastCalibrationOk = calibrationSuccessful;
pitchAtStop = asBool(g['pitchAtStop']) ?? false;
yawAtStop = asBool(g['yawAtStop']) ?? false;
motorOverloaded = asBool(g['motorOverloaded']) ?? false;
updatedAt = DateTime.now();
notifyListeners();
}
/// From the telemetry tick; an absent group means the gimbal reported none.
void applySettings(Map<String, dynamic>? s) {
replaceReported(s ?? const <String, dynamic>{});
notifyListeners();
}
/// Shows [mode] ahead of the gimbal's next state callback.
void setModeOptimistic(String mode) {
if (this.mode == mode) return;
this.mode = mode;
notifyListeners();
}
void clear() {
pitch = roll = yaw = yawRelative = null;
pitchFineTune = rollFineTune = yawFineTune = null;
mode = null;
calibrating = false;
calibrationProgress = null;
calibrationSuccessful = null;
lastCalibrationOk = null;
pitchAtStop = yawAtStop = motorOverloaded = false;
clearSettings();
updatedAt = null;
notifyListeners();
}
}