The GO 4-parity HUD has to work on every aircraft MSDK V4 drives, so no screen may assume one model. This adds the capability layer the rest of the plan builds on (P0). ProductProfileBridge reads what the SDK can report at runtime (cameras and their ISO/shutter/aperture/EV ranges, gimbal axes, air link, FC and RC feature flags, batteries) and emits it as a productProfile event on connect and component change. Capability ranges stay empty until the camera handshakes, so it re-probes at 3, 10 and 30 s and emits only when something changed. On the Dart side, ProductProfile merges that with a static product_matrix keyed by the MSDK Model enum name, for facts the SDK cannot report: vision sensor layout, battery bay count, intelligent modes. SDK answers win and every fact records its source. HudFeatures lists each capability-gated element with its predicate, and Requires hides a widget the profile rules out. The flight page now gates its gimbal slider, camera column and vision pill, labels the link Wi-Fi or HD, and takes the gimbal pitch range from the profile. Settings > About > Capability profile shows the active profile, fact sources and which features are on. Matrix rows are seeded from published specs until runtime capture confirms them; nothing has run on a real aircraft yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
109 lines
3.7 KiB
Dart
109 lines
3.7 KiB
Dart
/// Synthetic `productProfile` events, shaped exactly as `ProductProfileBridge.kt`
|
|
/// emits them, for the three reference products of plan P0.
|
|
library;
|
|
|
|
Map<String, dynamic> _camera({
|
|
String name = 'FC1102',
|
|
bool aperture = false,
|
|
bool ssd = false,
|
|
bool zoom = false,
|
|
List<String> apertures = const <String>[],
|
|
}) =>
|
|
<String, dynamic>{
|
|
'index': 0,
|
|
'displayName': name,
|
|
'isThermal': false,
|
|
'ssdSupported': ssd,
|
|
'opticalZoomSupported': zoom,
|
|
'hybridZoomSupported': false,
|
|
'digitalZoomSupported': false,
|
|
'adjustableApertureSupported': aperture,
|
|
'meteringSupported': true,
|
|
'playbackSupported': true,
|
|
'mediaDownloadSupported': true,
|
|
'modes': <String>['SHOOT_PHOTO', 'RECORD_VIDEO'],
|
|
'exposureModes': <String>['PROGRAM', 'MANUAL'],
|
|
'isos': <String>['AUTO', '100', '200', '400', '800', '1600'],
|
|
'shutterSpeeds': <String>['1/8000', '1/1000', '1/100', '1/30'],
|
|
'apertures': apertures,
|
|
'exposureCompensations': <String>['-3.0', '0.0', '+3.0'],
|
|
'videoResolutions': <String>['RESOLUTION_3840x2160@FRAME_RATE_30_FPS'],
|
|
};
|
|
|
|
Map<String, dynamic> _gimbal({bool yaw = false, double pitchMax = 0}) => <String, dynamic>{
|
|
'index': 0,
|
|
'capabilitiesKnown': true,
|
|
'pitch': <String, dynamic>{'min': -90.0, 'max': pitchMax},
|
|
'yaw': yaw ? <String, dynamic>{'min': -320.0, 'max': 320.0} : null,
|
|
'roll': null,
|
|
'pitchAdjustable': true,
|
|
'yawAdjustable': yaw,
|
|
'rollAdjustable': false,
|
|
'pitchRangeExtensionSupported': pitchMax > 0,
|
|
};
|
|
|
|
Map<String, dynamic> _fc({bool assistant = true, bool sticks = true}) => <String, dynamic>{
|
|
'flightAssistantSupported': assistant,
|
|
'virtualStickAvailable': sticks,
|
|
'rtkSupported': false,
|
|
'compassCount': 1,
|
|
'imuCount': 1,
|
|
'simulatorAvailable': true,
|
|
};
|
|
|
|
/// Spark over Wi-Fi with no RC: no remote controller component, link reported
|
|
/// as Wi-Fi, one smart battery.
|
|
final Map<String, dynamic> sparkWifi = <String, dynamic>{
|
|
'type': 'productProfile',
|
|
'connected': true,
|
|
'model': 'Spark',
|
|
'modelDisplayName': 'Spark',
|
|
'productKind': 'aircraft',
|
|
'cameras': <dynamic>[_camera(name: 'FC1102')],
|
|
'gimbals': <dynamic>[_gimbal()],
|
|
'link': 'wifi',
|
|
'flightController': _fc(),
|
|
'remoteController': null,
|
|
'batteries': <dynamic>[
|
|
<String, dynamic>{'index': 0, 'isSmart': true, 'cells': 3},
|
|
],
|
|
};
|
|
|
|
/// Phantom 4 on Lightbridge, gimbal pitch extendable to +30°.
|
|
final Map<String, dynamic> phantom4 = <String, dynamic>{
|
|
'type': 'productProfile',
|
|
'connected': true,
|
|
'model': 'PHANTOM_4',
|
|
'modelDisplayName': 'Phantom 4',
|
|
'productKind': 'aircraft',
|
|
'cameras': <dynamic>[_camera(name: 'FC330')],
|
|
'gimbals': <dynamic>[_gimbal(pitchMax: 30)],
|
|
'link': 'lightbridge',
|
|
'flightController': _fc(),
|
|
'remoteController': <String, dynamic>{'masterSlaveSupported': false},
|
|
'batteries': <dynamic>[
|
|
<String, dynamic>{'index': 0, 'isSmart': true, 'cells': 4},
|
|
],
|
|
};
|
|
|
|
/// Inspire 2 with a Zenmuse X5S: dual batteries, SSD, adjustable aperture,
|
|
/// 3-axis gimbal with yaw, master/slave RC.
|
|
final Map<String, dynamic> inspire2X5s = <String, dynamic>{
|
|
'type': 'productProfile',
|
|
'connected': true,
|
|
'model': 'INSPIRE_2',
|
|
'modelDisplayName': 'Inspire 2',
|
|
'productKind': 'aircraft',
|
|
'cameras': <dynamic>[
|
|
_camera(name: 'Zenmuse X5S', aperture: true, ssd: true, apertures: <String>['f/1.7', 'f/2.8', 'f/5.6', 'f/16']),
|
|
],
|
|
'gimbals': <dynamic>[_gimbal(yaw: true, pitchMax: 30)],
|
|
'link': 'lightbridge',
|
|
'flightController': _fc(),
|
|
'remoteController': <String, dynamic>{'masterSlaveSupported': true},
|
|
'batteries': <dynamic>[
|
|
<String, dynamic>{'index': 0, 'isSmart': true, 'cells': 6},
|
|
<String, dynamic>{'index': 1, 'isSmart': true, 'cells': 6},
|
|
],
|
|
};
|