Files
PilotVault/Fly App/lib/fly/sensing_distances.dart
T
tajniak81andClaude Opus 5.5 e02bb0e5cd Give the Fly App GO 4-style obstacle radar, on-screen sticks, AirSense and fly zones
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>
2026-10-20 21:14:12 +02:00

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));
}
}