Vision radar: graded obstacle sectors around the centre of the video, the nearest distance per side, the obstacle overhead, and BLOCKED on a side whose sensors cannot see once the motors run. On aircraft with 360° sensing, sides without their own readings are filled from the perception ring. The same sectors sit on the attitude ball's rim, turned by the heading. Settings > Vision gains Display radar chart and the braking / sensing distances per direction. On-screen sticks: the rail's STICKS button hands the aircraft to two stick pads after a slide-to-confirm. The 25 Hz stream runs natively and sends zeros once Dart's last command is 300 ms old, so a stalled UI leaves the aircraft hovering. The sticks close when the remote takes over, on RTH or on landing. They are simulator-only on aircraft with a remote unless Settings > General > Developer allows real flights; Spark and Mavic Air on Wi-Fi may fly them for real. Which MSDK axis is forward in velocity + body mode is coded from DJI's documentation and must be checked in the simulator first. AirSense: a banner for manned aircraft at warning level 2 and up, with distance, direction and ICAO code; the planes on the map; the full list in a sheet and Settings > Safety; and the aircraft's own avoidance action with its countdown. Fly zones: DJI GEO zones around the aircraft (or the phone) on the map with their height limits, a banner for FlySafe notices and in/near-zone states, take-off refused in restricted and locked authorization zones, self-unlock of authorization zones, the aircraft's licences (enable, sync from the DJI account, remove all; ground only), and the GEO database version with an update button. Tested with unit and widget tests and screenshot renders only; nothing has run on an aircraft or the simulator yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
72 lines
3.0 KiB
Dart
72 lines
3.0 KiB
Dart
import 'package:flutter/foundation.dart';
|
|
|
|
import 'state/parse.dart';
|
|
|
|
/// The three directions MSDK's FlightAssistant sets distances for.
|
|
enum SensingDirection {
|
|
horizontal('Horizontal', 'Horizontal'),
|
|
up('Upward', 'Upward'),
|
|
down('Downward', 'Downward');
|
|
|
|
const SensingDirection(this.sdk, this.label);
|
|
|
|
/// `DJIFlightAssistantObstacleSensingDirection` name.
|
|
final String sdk;
|
|
final String label;
|
|
}
|
|
|
|
/// Which of the two distances.
|
|
enum SensingKind {
|
|
/// `setVisualObstaclesAvoidanceDistance`: where the aircraft brakes.
|
|
avoidance,
|
|
|
|
/// `setMaxPerceptionDistance`: how far it looks (the warning distance).
|
|
perception,
|
|
}
|
|
|
|
/// The aircraft's obstacle braking and perception distances (plan P8, GO 4 /
|
|
/// Pilot's Vision "obstacle distance" rows). A null value is a direction the
|
|
/// aircraft refused to report, and its row hides.
|
|
@immutable
|
|
class SensingDistances {
|
|
const SensingDistances({this.avoidance = const <SensingDirection, double?>{}, this.perception = const <SensingDirection, double?>{}});
|
|
|
|
factory SensingDistances.fromBridge(Map<String, dynamic> m) {
|
|
Map<SensingDirection, double?> read(Object? v) {
|
|
final Map<String, dynamic>? g = asMap(v);
|
|
return <SensingDirection, double?>{for (final SensingDirection d in SensingDirection.values) d: finite(g?[d.sdk])};
|
|
}
|
|
|
|
return SensingDistances(avoidance: read(m['avoidance']), perception: read(m['perception']));
|
|
}
|
|
|
|
final Map<SensingDirection, double?> avoidance;
|
|
final Map<SensingDirection, double?> perception;
|
|
|
|
double? value(SensingKind k, SensingDirection d) => (k == SensingKind.avoidance ? avoidance : perception)[d];
|
|
|
|
bool get isEmpty => avoidance.values.every((double? v) => v == null) && perception.values.every((double? v) => v == null);
|
|
|
|
SensingDistances withValue(SensingKind k, SensingDirection d, double v) => SensingDistances(
|
|
avoidance: k == SensingKind.avoidance ? <SensingDirection, double?>{...avoidance, d: v} : avoidance,
|
|
perception: k == SensingKind.perception ? <SensingDirection, double?>{...perception, d: v} : perception,
|
|
);
|
|
|
|
/// Range and step a row offers. MSDK 4.18 documents no limits; these follow
|
|
/// DJI Pilot's sliders and the flight controller refuses anything it does not
|
|
/// accept (the row then rolls back). Unverified on hardware.
|
|
static ({double min, double max, double step}) range(SensingKind k, SensingDirection d) => switch ((k, d)) {
|
|
(SensingKind.avoidance, SensingDirection.down) => (min: 0.5, max: 3, step: 0.1),
|
|
(SensingKind.avoidance, _) => (min: 1, max: 10, step: 0.5),
|
|
(SensingKind.perception, SensingDirection.down) => (min: 1, max: 10, step: 0.5),
|
|
(SensingKind.perception, _) => (min: 5, max: 40, step: 1),
|
|
};
|
|
|
|
/// [v] moved [steps] steps within the range, snapped to the step.
|
|
static double stepped(SensingKind k, SensingDirection d, double v, int steps) {
|
|
final ({double min, double max, double step}) r = range(k, d);
|
|
final double next = ((v / r.step).round() + steps) * r.step;
|
|
return double.parse(next.clamp(r.min, r.max).toStringAsFixed(2));
|
|
}
|
|
}
|