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>
85 lines
2.5 KiB
Dart
85 lines
2.5 KiB
Dart
import 'package:flutter/foundation.dart';
|
||
|
||
import '../rc_buttons.dart';
|
||
import 'parse.dart';
|
||
|
||
/// One press of a remote's custom button (`rcButton` event). [seq] grows with
|
||
/// every press, so two presses of the same button are two values.
|
||
@immutable
|
||
class RcButtonPress {
|
||
const RcButtonPress(this.button, this.seq);
|
||
|
||
final RcButton button;
|
||
final int seq;
|
||
|
||
@override
|
||
bool operator ==(Object other) => other is RcButtonPress && other.button == button && other.seq == seq;
|
||
|
||
@override
|
||
int get hashCode => Object.hash(button, seq);
|
||
}
|
||
|
||
/// A remote seen by master search, or linked as a slave (`rcMasters`, `getSlaves`).
|
||
@immutable
|
||
class RcPeer {
|
||
const RcPeer({required this.id, this.name, this.signal});
|
||
|
||
factory RcPeer.fromMap(Map<String, dynamic> m) => RcPeer(id: asInt(m['id']) ?? 0, name: asString(m['name']), signal: asInt(m['signal']));
|
||
|
||
final int id;
|
||
final String? name;
|
||
|
||
/// 0–100.
|
||
final int? signal;
|
||
|
||
@override
|
||
bool operator ==(Object other) => other is RcPeer && other.id == id && other.name == name && other.signal == signal;
|
||
|
||
@override
|
||
int get hashCode => Object.hash(id, name, signal);
|
||
}
|
||
|
||
/// One bar of the video link's spectrum chart.
|
||
@immutable
|
||
class SpectrumBar {
|
||
const SpectrumBar(this.label, this.power);
|
||
|
||
/// Channel number, or the band's centre in MHz.
|
||
final String label;
|
||
|
||
/// dBm (OcuSync RSSI, Lightbridge / Wi-Fi channel power); more negative is quieter.
|
||
final double power;
|
||
}
|
||
|
||
/// The video link's interference (`spectrum` events, while the chart is up).
|
||
@immutable
|
||
class Spectrum {
|
||
const Spectrum(this.link, this.bars);
|
||
|
||
/// `ocusync` / `lightbridge` / `wifi`.
|
||
final String link;
|
||
final List<SpectrumBar> bars;
|
||
|
||
factory Spectrum.fromEvent(Map<String, dynamic> e) {
|
||
final List<SpectrumBar> bars = <SpectrumBar>[
|
||
for (final Map<String, dynamic> b in asMapList(e['bands']))
|
||
if (finite(b['from']) case final double from)
|
||
if (finite(b['to']) case final double to)
|
||
if (finite(b['rssi']) case final double rssi) SpectrumBar(((from + to) / 2).toStringAsFixed(0), rssi),
|
||
for (final Map<String, dynamic> c in asMapList(e['channels']))
|
||
if (asInt(c['channel']) case final int ch)
|
||
if (finite(c['power']) case final double p) SpectrumBar('$ch', p),
|
||
];
|
||
return Spectrum(asString(e['link']) ?? '', bars);
|
||
}
|
||
|
||
/// The quietest bar, the channel to pick in Custom mode.
|
||
SpectrumBar? get quietest {
|
||
SpectrumBar? best;
|
||
for (final SpectrumBar b in bars) {
|
||
if (best == null || b.power < best.power) best = b;
|
||
}
|
||
return best;
|
||
}
|
||
}
|