From 37d2af70b78072e2640017828794f6750a8fff67 Mon Sep 17 00:00:00 2001 From: Developer <91611@user.local> Date: Thu, 4 Jun 2026 16:57:45 +0800 Subject: [PATCH] =?UTF-8?q?feat(device):=20=E5=90=AF=E5=8A=A8=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E8=BF=9E=E6=8E=A5=20USB=20=E4=B8=B2=E5=8F=A3=20+=20?= =?UTF-8?q?=E9=9A=90=E8=97=8F=E8=AE=BE=E7=BD=AE=E9=A1=B5=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E9=A1=B9=20+=20=E6=A0=87=E9=A2=98=E6=A0=8F=E8=BF=9E=E6=8E=A5?= =?UTF-8?q?=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 AutoSerialConnect 服务:启动后自动连接第一个 USB 串口设备, 固定 115200/8/N/1,连接失败时每 3s 重试,断开后重新进入重试循环 - main.dart 通过 ProviderContainer 在 runApp 之前触发 autoSerialConnectProvider - 移除设置页「串口配置」菜单项及对应面板分支 - StatusBar 在「设备运行状态」前增加串口连接状态指示(已连接/连接中/未连接) --- .../device/providers/serial_provider.dart | 13 +++ .../device/services/auto_serial_connect.dart | 109 ++++++++++++++++++ lib/features/home/widgets/status_bar.dart | 51 ++++++++ .../settings/pages/settings_page.dart | 12 +- lib/main.dart | 13 ++- 5 files changed, 186 insertions(+), 12 deletions(-) create mode 100644 lib/features/device/services/auto_serial_connect.dart diff --git a/lib/features/device/providers/serial_provider.dart b/lib/features/device/providers/serial_provider.dart index 6078366..384dc68 100644 --- a/lib/features/device/providers/serial_provider.dart +++ b/lib/features/device/providers/serial_provider.dart @@ -2,6 +2,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../core/database/database_service.dart'; import '../models/serial_config.dart'; +import '../services/auto_serial_connect.dart'; import '../services/device_message_service.dart'; import '../services/json_protocol.dart'; import '../services/runner_interface.dart'; @@ -15,6 +16,18 @@ final serialPortServiceProvider = Provider((ref) { return service; }); +/// 启动自动连接服务 +/// +/// 通过 [main] 中的 ProviderContainer 在 runApp 之前触发一次, +/// 服务内部立即尝试连接第一个 USB 串口设备,失败时按 3s 间隔重试。 +final autoSerialConnectProvider = Provider((ref) { + final service = ref.watch(serialPortServiceProvider); + final auto = AutoSerialConnect(service); + auto.start(); + ref.onDispose(auto.dispose); + return auto; +}); + /// JSON 协议编解码器(可在调试/真机协议不一致时整体替换) final jsonProtocolProvider = Provider((ref) { return JsonProtocolService(); diff --git a/lib/features/device/services/auto_serial_connect.dart b/lib/features/device/services/auto_serial_connect.dart new file mode 100644 index 0000000..db5caf6 --- /dev/null +++ b/lib/features/device/services/auto_serial_connect.dart @@ -0,0 +1,109 @@ +import 'dart:async'; + +import 'package:usb_serial/usb_serial.dart'; + +import '../models/serial_config.dart'; +import 'device_log.dart'; +import 'serial_port_service.dart'; + +/// 启动自动连接服务 +/// +/// App 启动时调用 [start],自动连接第一个可用的 USB 串口设备: +/// - 固定 115200 波特率 / 8 数据位 / 1 停止位 / 默认其它 +/// - 连接失败时每 3 秒重试一次 +/// - 连接成功后停止重试;连接断开后重新进入重试循环 +class AutoSerialConnect { + /// 重试间隔 + static const Duration retryInterval = Duration(seconds: 3); + + /// 自动连接使用的固定参数 + static const SerialConfig autoConfig = SerialConfig( + baudRate: 115200, + dataBits: 8, + stopBits: 1, + ); + + final SerialPortService _service; + Timer? _retryTimer; + StreamSubscription? _stateSub; + bool _disposed = false; + + AutoSerialConnect(this._service); + + /// 启动自动连接:立即尝试一次,失败则按 [retryInterval] 周期重试 + void start() { + if (_stateSub != null) return; + _stateSub = _service.connectionStateChanges.listen(_onStateChange); + unawaited(_tryConnect()); + } + + void _onStateChange(SerialConnectionState s) { + if (_disposed) return; + switch (s) { + case SerialConnectionState.connected: + // 连接成功:停止重试 + _cancelRetry(); + case SerialConnectionState.disconnected: + case SerialConnectionState.error: + // 设备断开或出错:进入重试 + _scheduleRetry(); + case SerialConnectionState.connecting: + // 正在连接中,忽略 + break; + } + } + + Future _tryConnect() async { + if (_disposed) return; + if (_service.isConnected || + _service.state == SerialConnectionState.connecting) { + return; + } + + final List devices; + try { + devices = await _service.listDevices(); + } catch (e) { + DeviceLog.warn('列出 USB 设备失败: $e,${retryInterval.inSeconds}s 后重试'); + _scheduleRetry(); + return; + } + + if (devices.isEmpty) { + DeviceLog.info('未检测到 USB 串口设备,${retryInterval.inSeconds}s 后重试'); + _scheduleRetry(); + return; + } + + final device = devices.first; + final ok = await _service.connect(device, autoConfig); + if (!ok) { + DeviceLog.warn('串口自动连接失败: ' + '${_service.lastError ?? "未知错误"},${retryInterval.inSeconds}s 后重试'); + _scheduleRetry(); + } + } + + void _scheduleRetry() { + if (_disposed) return; + if (_retryTimer != null) return; + _retryTimer = Timer(retryInterval, () { + _retryTimer = null; + unawaited(_tryConnect()); + }); + } + + void _cancelRetry() { + _retryTimer?.cancel(); + _retryTimer = null; + } + + /// 释放资源 + Future dispose() async { + if (_disposed) return; + _disposed = true; + _cancelRetry(); + await _stateSub?.cancel(); + _stateSub = null; + } +} diff --git a/lib/features/home/widgets/status_bar.dart b/lib/features/home/widgets/status_bar.dart index 9c32911..1786784 100644 --- a/lib/features/home/widgets/status_bar.dart +++ b/lib/features/home/widgets/status_bar.dart @@ -5,6 +5,8 @@ import '../../../core/localization/app_localizations.dart'; import '../../../core/theme/app_theme.dart'; import '../../../shared/widgets/status_indicator.dart'; import '../../device/providers/device_info_provider.dart'; +import '../../device/providers/serial_provider.dart'; +import '../../device/services/serial_port_service.dart'; /// 状态栏标签项数据 class StatusBarTab { @@ -76,6 +78,7 @@ class _StatusBarState extends ConsumerState { Widget build(BuildContext context) { final l10n = AppLocalizations.of(context); final deviceInfo = ref.watch(deviceInfoProvider); + final serialState = ref.watch(serialPortServiceProvider).state; return Container( height: 56, @@ -117,6 +120,8 @@ class _StatusBarState extends ConsumerState { onTap: _onLightTap, ), const SizedBox(width: 20), + _SerialConnectionIndicator(state: serialState), + const SizedBox(width: 20), StatusIndicator( text: widget.isRunning ? (l10n?.running ?? '运行中') @@ -262,3 +267,49 @@ class _LightToggleButton extends StatelessWidget { ); } } + +/// 串口连接状态指示器 +/// +/// 位于「设备运行状态」之前,反映当前 USB 串口的连接情况。 +class _SerialConnectionIndicator extends StatelessWidget { + final SerialConnectionState state; + const _SerialConnectionIndicator({required this.state}); + + @override + Widget build(BuildContext context) { + final connected = state == SerialConnectionState.connected; + final connecting = state == SerialConnectionState.connecting; + final text = connected + ? '已连接' + : connecting + ? '连接中' + : '未连接'; + final color = connected + ? AppTheme.statusRunning + : connecting + ? AppTheme.statusPaused + : AppTheme.statusStopped; + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 10, + height: 10, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: color, + ), + ), + const SizedBox(width: 6), + Text( + text, + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 13, + ), + ), + ], + ); + } +} diff --git a/lib/features/settings/pages/settings_page.dart b/lib/features/settings/pages/settings_page.dart index b258dde..8c00f8f 100644 --- a/lib/features/settings/pages/settings_page.dart +++ b/lib/features/settings/pages/settings_page.dart @@ -5,11 +5,10 @@ import '../../../core/theme/app_theme.dart'; import '../../../shared/widgets/common_button.dart'; import '../widgets/language_panel.dart'; import '../widgets/password_panel.dart'; -import '../widgets/serial_config_panel.dart'; import '../widgets/usb_import_panel.dart'; /// 设置页菜单 -enum _SettingsMenu { upgrade, language, password, usbImport, serialConfig } +enum _SettingsMenu { upgrade, language, password, usbImport } /// 系统设置页面 class SettingsPage extends ConsumerStatefulWidget { @@ -70,14 +69,6 @@ class _SettingsPageState extends ConsumerState { onTap: () => setState( () => _currentMenu = _SettingsMenu.upgrade), ), - // 串口配置 - _buildMenuItem( - icon: Icons.settings_input_hdmi, - title: '串口配置', - selected: _currentMenu == _SettingsMenu.serialConfig, - onTap: () => setState( - () => _currentMenu = _SettingsMenu.serialConfig), - ), // 语言设置 _buildMenuItem( icon: Icons.language, @@ -127,7 +118,6 @@ class _SettingsPageState extends ConsumerState { Widget _buildContent() { return switch (_currentMenu) { - _SettingsMenu.serialConfig => const SerialConfigPanel(), _SettingsMenu.language => const LanguagePanel(), _SettingsMenu.password => const PasswordPanel(), _SettingsMenu.usbImport => const UsbImportPanel(), diff --git a/lib/main.dart b/lib/main.dart index 9378274..916ae83 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,6 +8,7 @@ import 'core/theme/app_theme.dart'; import 'core/localization/app_localizations.dart'; import 'core/localization/locale_provider.dart'; import 'core/database/database_service.dart'; +import 'features/device/providers/serial_provider.dart'; /// 应用入口 void main() async { @@ -22,7 +23,17 @@ void main() async { final db = DatabaseService.instance; await db.database; await db.initTestData(); - runApp(const ProviderScope(child: KuaishaiApp())); + + // 使用 ProviderContainer 在 runApp 之前触发启动自动连接 + final container = ProviderContainer(); + container.read(autoSerialConnectProvider); + + runApp( + UncontrolledProviderScope( + container: container, + child: const KuaishaiApp(), + ), + ); } /// 应用主体