feat(display): Sprint 7 — USB serial link to AR-Concentrador via NMEA $PARP

- Add flutter_libserialport dependency (pubspec.yaml)
- New ParpCodec: XOR-checksum NMEA parser + command builders for all PARP sentences
- New ConcentradorService: manages two independent COM ports (RX-OUT broadcast,
  TX-IN commands) at 115200/8N1; auto-fires onConnectionChanged on link drop
- AutopilotState: dual-mode operation (demo timer OR live serial); connectToSerial /
  disconnectSerial; command methods (engage/disengage/adjustSetpoint) forward to
  ConcentradorService when connected; falls back to demo on disconnect
- New PortSettingsScreen (/settings/ports): RX+TX dropdowns populated from
  SerialPort.availablePorts, persisted in SharedPreferences; Connect/Disconnect
  buttons with error display and snackbar feedback
- main.dart: auto-connect to saved ports on startup (silent fail → demo mode);
  registers /settings/ports route
- CockpitScreen: gear icon replaced with PopupMenuButton (Puertos COM / Apariencia)

AR_electronics — AR-Autopilot Project
This commit is contained in:
2026-05-24 01:28:04 -04:00
parent c946d2df6d
commit abe9b764c7
7 changed files with 886 additions and 81 deletions
+31 -8
View File
@@ -1,25 +1,48 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'data/autopilot_state.dart';
import 'theme/theme_provider.dart';
import 'screens/cockpit/cockpit_screen.dart';
import 'screens/settings/appearance_settings.dart';
import 'screens/settings/port_settings_screen.dart';
// SharedPreferences keys — must match port_settings_screen.dart
const _kRxKey = 'port.rx';
const _kTxKey = 'port.tx';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// Load persisted theme before first frame.
final themeProvider = await AutopilotThemeProvider.load();
// Create state object early so we can attempt auto-connect.
final autopilotState = AutopilotState();
// Attempt to reconnect to the last-used COM ports silently.
// If the ports are not available (hardware unplugged, different PC, etc.)
// the exception is swallowed and the UI stays in demo mode.
try {
final prefs = await SharedPreferences.getInstance();
final rxPort = prefs.getString(_kRxKey);
final txPort = prefs.getString(_kTxKey);
if (rxPort != null && txPort != null) {
await autopilotState.connectToSerial(rxPort: rxPort, txPort: txPort);
}
} catch (_) {
// Hardware not available — stay in demo mode.
}
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider<AutopilotThemeProvider>.value(
value: themeProvider,
),
// AutopilotState drives the cockpit UI.
// Sprint 4: internal demo timer (vessel simulation).
// Sprint 7: replace with AutopilotStateModbus or wire Modbus RTU here.
ChangeNotifierProvider<AutopilotState>(
create: (_) => AutopilotState(),
ChangeNotifierProvider<AutopilotState>.value(
value: autopilotState,
),
],
child: const ArAutopilotApp(),
@@ -38,9 +61,9 @@ class ArAutopilotApp extends StatelessWidget {
theme: ThemeData(useMaterial3: true),
initialRoute: CockpitScreen.routeName,
routes: {
CockpitScreen.routeName: (_) => const CockpitScreen(),
AppearanceSettingsScreen.routeName: (_) =>
const AppearanceSettingsScreen(),
CockpitScreen.routeName: (_) => const CockpitScreen(),
AppearanceSettingsScreen.routeName: (_) => const AppearanceSettingsScreen(),
PortSettingsScreen.routeName: (_) => const PortSettingsScreen(),
},
);
}