diff --git a/lib/core/di/injection.dart b/lib/core/di/injection.dart index 0b72409f..3285ad4f 100644 --- a/lib/core/di/injection.dart +++ b/lib/core/di/injection.dart @@ -185,13 +185,15 @@ Future init() async { sl.registerLazySingleton(() => AppUserCubit()); // AuthCubit 依赖它,必须先注册 sl.registerLazySingleton(() => GetDeviceLocationUseCase(sl())); sl.registerLazySingleton(() => DevicesCubit(sl(), sl(), sl(), sl(), sl(), sl(), sl(), sl(), sl(), sl(), sl(), sl(),sl(),sl(),sl())); - sl.registerFactory(() => RemoteControlCubit(sl(),sl(),sl())); - /* // 工厂模式(留存,每次获取新实例)r - - sl.registerFactory(() => DeviceStatusBloc(sl())); -*/ - //单例模式(全局共享一个实例) - sl.registerLazySingleton(() => DeviceStatusBloc(sl())); + + // 🔥 DeviceStatusBloc 必须在 RemoteControlCubit 之前注册(单例) + sl.registerLazySingleton(() => DeviceStatusBloc( + sl(), + client: sl(), // 🔥 传入 TcpClient + )); + + // 🔥 RemoteControlCubit 注入 DeviceStatusBloc(工厂模式,每次新建) + sl.registerFactory(() => RemoteControlCubit(sl(), sl(), sl(), sl())); /// . 路径生成 sl.registerLazySingleton( diff --git a/lib/core/network/net_message_dispatcher.dart b/lib/core/network/net_message_dispatcher.dart index bb929ba0..f3644240 100644 --- a/lib/core/network/net_message_dispatcher.dart +++ b/lib/core/network/net_message_dispatcher.dart @@ -88,6 +88,27 @@ class NetMessageDispatcher { } }); } +//机器状态页面专用 + Stream onStringMessageOfdeviceStatus() { + //print("0x02--TCP拦截推送解析开始"); + _logger.log('0x02--TCP拦截推送解析开始'); + // 🔥 关键修复:使用asBroadcastStream()确保多个监听者都能收到数据 + return onCommand(0x02).map((p) { + try { + // 尝试解码 + final result = utf8.decode(p.payload, allowMalformed: true); // 允许乱码,防止报错中断流 + //print('✅ 解码 0x02 成功:$result'); // 🔥 关键日志:看这里打印了吗? + _logger.log('✅ 解码 0x02 成功:$result'); + return result; + } catch (e) { + // print('❌ 解码 0x02 失败:$e, 原始字节:${p.payload}'); + _logger.log('❌ 解码 0x02 失败:$e, 原始字节:${p.payload}'); + return ''; // 返回空字符串,避免流中断 + } + }).asBroadcastStream(); + /* ///return onCommand(0x02).map((p) => utf8.decode(p.payload));*/ + } + //监听路径规划的开始作业的指令的应答 diff --git a/lib/features/devices/presentation/bloc/device_status_bloc.dart b/lib/features/devices/presentation/bloc/device_status_bloc.dart index ca91366b..b9f8e212 100644 --- a/lib/features/devices/presentation/bloc/device_status_bloc.dart +++ b/lib/features/devices/presentation/bloc/device_status_bloc.dart @@ -9,17 +9,21 @@ import 'package:maibu_satabot_v2/core/network/protocol_decoder.dart'; import '../../../../core/logging/i_logger_service.dart'; import '../../../../core/network/net_message_dispatcher.dart'; +import '../../../../core/network/tcp/tcp_client.dart'; import 'device_status_event.dart'; import 'device_status_state.dart'; class DeviceStatusBloc extends Bloc { final NetMessageDispatcher _dispatcher; + final TcpClient tcpClient; // 🔥 新增:直接访问 TcpClient final ILoggerService _logger = GetIt.I(); // 🔥 保存订阅引用,用于管理生命周期 StreamSubscription? _tcpSubscription; - DeviceStatusBloc(this._dispatcher) : super(DeviceStatusInitial()) { + DeviceStatusBloc(this._dispatcher, {TcpClient? client}) + : tcpClient = client ?? GetIt.I(), + super(DeviceStatusInitial()) { // 🔥 核心改动:直接在构造函数中建立TCP监听,类似RemoteControlCubit _initDirectTcpListener(); @@ -29,7 +33,10 @@ class DeviceStatusBloc extends Bloc { on(_handlePushMessageReceived); } - // 🔥 新增:重新初始化TCP监听器(用于页面重新进入时调用) + // 🔥 已废弃:重新初始化TCP监听器会导致数据流中断 + // DeviceStatusBloc是单例,TCP监听器在构造函数中建立后应始终保持活跃 + // 任何页面只需通过 BlocBuilder 或 stream 订阅状态即可 + /* void reinitializeListener() { debugPrint('🔄 [DeviceStatusBloc] 重新初始化TCP监听器 - isClosed=$isClosed'); _logger.log('🔄 [DeviceStatusBloc] 重新初始化TCP监听器 - isClosed=$isClosed'); @@ -40,11 +47,11 @@ class DeviceStatusBloc extends Bloc { return; } - // 取消旧的订阅 + // ❌ 取消旧的订阅会导致数据流中断! debugPrint('📝 [DeviceStatusBloc] 取消旧订阅'); _tcpSubscription?.cancel(); - // 重置状态为初始状态 + // ❌ 重置状态会清空UI显示的数据! debugPrint('📝 [DeviceStatusBloc] emit Initial状态'); emit(DeviceStatusInitial()); @@ -52,16 +59,31 @@ class DeviceStatusBloc extends Bloc { debugPrint('📝 [DeviceStatusBloc] 调用 _initDirectTcpListener'); _initDirectTcpListener(); } + */ // 🔥 新增:直接监听TCP 0x02指令,实时解析并emit状态 void _initDirectTcpListener() { - debugPrint('🔗 [DeviceStatusBloc] 初始化直接TCP监听器(类似RemoteControlCubit)'); + debugPrint('🔗 [DeviceStatusBloc] 初始化直接TCP监听器 - 使用 tcpClient.packetStream'); _logger.log('🔗 [DeviceStatusBloc] 初始化直接TCP监听器'); - _tcpSubscription = _dispatcher.onStringMessage().listen( - (message) { + // 🔥 关键修复:直接监听 tcpClient.packetStream,不经过 dispatcher 的 filtered stream + // 这样即使没有其他监听者,TCP流也不会暂停 + _tcpSubscription = tcpClient.packetStream + .where((p) => p.command == 0x02) + .map((p) { + try { + final result = utf8.decode(p.payload, allowMalformed: true); + debugPrint('✅ [DeviceStatusBloc] 收到0x02数据: $result'); + return result; + } catch (e) { + debugPrint('❌ [DeviceStatusBloc] 解码失败: $e'); + return ''; + } + }) + .listen( + (message) { //debugPrint('📩 [DeviceStatusBloc] 直接收到0x02数据,长度:${message.length}'); - + if (message.isEmpty) { //debugPrint('⚠️ [DeviceStatusBloc] 消息为空,跳过'); return; @@ -115,6 +137,7 @@ class DeviceStatusBloc extends Bloc { obstacleFlag: status.obstacleFlag, ); final newGps = GPSEntity(status.latitude, status.longitude); + debugPrint('📤 [DeviceStatusBloc] emit DeviceStatusUpdated - 电压:${status.voltage}, 电量:${status.battery}, 模式:${status.controlMode}'); emit(DeviceStatusUpdated(newStatus, newGps)); } } catch (e, stack) { @@ -135,11 +158,12 @@ class DeviceStatusBloc extends Bloc { // 🔥 重置时仅清空状态 Future _handleReset(DeviceStatusReset event, Emitter emit) async { - debugPrint('🔄 [DeviceStatusBloc] 收到重置事件:清空状态'); - _logger.log('🔄 [DeviceStatusBloc] 收到重置事件:清空状态'); + debugPrint('🔄 [DeviceStatusBloc] 收到重置事件:清空状态 - 当前状态:${state.runtimeType}'); + _logger.log('🔄 [DeviceStatusBloc] 收到重置事件:清空状态 - 当前状态:${state.runtimeType}'); // 只 emit 初始状态,让 UI 清除旧设备的数据 emit(DeviceStatusInitial()); + debugPrint('⚠️ [DeviceStatusBloc] 已emit DeviceStatusInitial'); } Future _handleDeviceStatusLoaded( @@ -189,7 +213,7 @@ class DeviceStatusBloc extends Bloc { Future close() { debugPrint('🚫 [DeviceStatusBloc] 页面退出,仅取消TCP订阅(不关闭BLoC)'); _logger.log('🚫 [DeviceStatusBloc] 页面退出,仅取消TCP订阅(不关闭BLoC)'); - _tcpSubscription?.cancel(); + //_tcpSubscription?.cancel(); // 🔥 关键修复:不调用 super.close(),保持 BLoC 活跃 return Future.value(); } diff --git a/lib/features/devices/presentation/bloc/device_status_cubit.dart b/lib/features/devices/presentation/bloc/device_status_cubit.dart index fbba9076..61f4c1d1 100644 --- a/lib/features/devices/presentation/bloc/device_status_cubit.dart +++ b/lib/features/devices/presentation/bloc/device_status_cubit.dart @@ -25,7 +25,7 @@ class DeviceStatusCubit extends Cubit { _stringMessageSub?.cancel(); - _stringMessageSub = _dispatcher.onStringMessage().listen((message) { + _stringMessageSub = _dispatcher.onStringMessageOfdeviceStatus().listen((message) { if (message.isEmpty) { return; } diff --git a/lib/features/home/presentation/pages/running_status_page.dart b/lib/features/home/presentation/pages/running_status_page.dart index 6966f657..ff50503f 100644 --- a/lib/features/home/presentation/pages/running_status_page.dart +++ b/lib/features/home/presentation/pages/running_status_page.dart @@ -15,7 +15,8 @@ import '../../../../core/di/injection.dart'; import '../../../../core/network/net_message_dispatcher.dart'; import '../../../../core/network/protocol_decoder.dart'; import '../../../devices/presentation/bloc/devices_cubit.dart'; -import '../../../devices/presentation/bloc/device_status_cubit.dart'; +import '../../../devices/presentation/bloc/device_status_bloc.dart'; +import '../../../devices/presentation/bloc/device_status_event.dart'; import '../../../devices/presentation/bloc/device_status_state.dart'; // 配置:数据超时时间(5秒) @@ -64,8 +65,9 @@ class _RunningStatusPageState extends State with WidgetsBindi // 🔥 注册静态实例 _instance = this; debugPrint('🚀 [RunningStatusPage] initState 被调用'); - // 🔥 简化:直接重置状态,不需要重新初始化监听器 - context.read().reset(); + // 🔥 使用 DeviceStatusBloc + // context.read().add(DeviceStatusReset()); + // 🔥 关键:立即启动超时计时器,如果5秒内没收到数据就显示"暂无数据" _startDataTimeoutTimer(); @@ -841,7 +843,7 @@ class _RunningStatusPageState extends State with WidgetsBindi body: CustomScrollView( physics: const BouncingScrollPhysics(), slivers: [ - BlocBuilder( + BlocBuilder( builder: (context, state) { // 新增:超时状态下显示-- String qual = _isDataTimeout ? '--' : '--'; @@ -949,7 +951,7 @@ class _RunningStatusPageState extends State with WidgetsBindi ), SliverToBoxAdapter( - child: BlocBuilder( + child: BlocBuilder( builder: (context, state) { debugPrint('🎨 [UI-Build] BlocBuilder 重建!当前状态类型:${state.runtimeType}'); diff --git a/lib/features/home/presentation/routes/home_routes.dart b/lib/features/home/presentation/routes/home_routes.dart index 3aa62178..526e609c 100644 --- a/lib/features/home/presentation/routes/home_routes.dart +++ b/lib/features/home/presentation/routes/home_routes.dart @@ -7,7 +7,7 @@ import 'package:maibu_satabot_v2/features/home/presentation/pages/productDesc.da import '../../../../core/di/injection.dart'; import '../../../../core/router/route_paths.dart'; -import '../../../devices/presentation/bloc/device_status_cubit.dart'; +import '../../../devices/presentation/bloc/device_status_bloc.dart'; import '../../../remote_control/presentation/bloc/remote_control_cubit.dart'; import '../../../remote_control/presentation/pages/remote_control_page.dart'; import '../pages/route_plan_page.dart'; @@ -31,9 +31,8 @@ class HomeRoutes { GoRoute(path: RoutePaths.usage, builder: (context, state) => const ManualPage()), GoRoute( path: RoutePaths.runningStatus, - // builder: (context, state) => const RunningStatusPage(), - builder: (context, state) => BlocProvider( - create: (_) => DeviceStatusCubit(sl()), // 使用简化版 Cubit + builder: (context, state) => BlocProvider.value( + value: sl(), // 使用全局单例 DeviceStatusBloc,确保数据流不中断 child: const RunningStatusPage(), ), ), diff --git a/lib/features/machine_details/presentation/pages/machine_details_page.dart b/lib/features/machine_details/presentation/pages/machine_details_page.dart index 84c33926..af849615 100644 --- a/lib/features/machine_details/presentation/pages/machine_details_page.dart +++ b/lib/features/machine_details/presentation/pages/machine_details_page.dart @@ -7,6 +7,8 @@ import 'package:maibu_satabot_v2/core/router/route_paths.dart'; import 'package:maibu_satabot_v2/features/devices/domain/entities/device_entity.dart'; import 'package:maibu_satabot_v2/features/devices/presentation/bloc/devices_cubit.dart'; import 'package:maibu_satabot_v2/features/devices/presentation/bloc/devices_state.dart'; +import 'package:maibu_satabot_v2/features/devices/presentation/bloc/device_status_bloc.dart'; +import 'package:maibu_satabot_v2/features/devices/presentation/bloc/device_status_state.dart'; class MachineDetailsPage extends StatefulWidget { final DeviceEntity? device; @@ -26,6 +28,10 @@ class _MachineDetailsPageState extends State { BuildContext? _loadingDialogContext; // 解绑加载弹窗 BuildContext? _updateLoadingContext; // 修改名称加载弹窗 bool _isUpdatingName = false; + + // 🔥 保存 stream 引用,避免每次 rebuild 都重新获取 + late Stream _deviceStatusStream; + late DeviceStatusState _initialState; @override void initState() { @@ -33,6 +39,12 @@ class _MachineDetailsPageState extends State { _deviceName = (widget.device?.deviceAlias?.trim().isEmpty ?? true) ? '未知设备' : widget.device!.deviceAlias!; _deviceId = widget.device?.deviceName ?? ''; _nameController = TextEditingController(text: _deviceName); + + // 🔥 在 initState 中保存 stream 和 initial state + final bloc = context.read(); + _deviceStatusStream = bloc.stream; + _initialState = bloc.state; + debugPrint('📦 [MachineDetails] initState - 初始状态: ${_initialState.runtimeType}'); } @override @@ -188,6 +200,9 @@ class _MachineDetailsPageState extends State { children: [ _buildDeviceStatusCard(currentDevice), const SizedBox(height: 16), + // 🔥 新增:实时状态卡片 + _buildRealTimeStatusCard(), + const SizedBox(height: 16), _buildBasicInfoCard(context, currentDevice), const SizedBox(height: 16), _buildResourceCenterCard(), @@ -200,6 +215,64 @@ class _MachineDetailsPageState extends State { ); } + /// 🔥 新增:实时状态卡片(显示控制模式、电压、电量) + Widget _buildRealTimeStatusCard() { + return StreamBuilder( + stream: _deviceStatusStream, + initialData: _initialState, + builder: (context, snapshot) { + final blocState = snapshot.data; + debugPrint('🔍 [MachineDetails] StreamBuilder 收到状态: ${blocState?.runtimeType}'); + + String controlMode = '未知'; + String voltage = '--'; + String battery = '--'; + + if (blocState is DeviceStatusUpdated) { + debugPrint('✅ [MachineDetails] 有实时数据 - 电压:${blocState.status.voltage}, 电量:${blocState.status.battery}'); + controlMode = blocState.status.controlMode == '3' ? '远程模式' : '本地模式'; + voltage = '${blocState.status.voltage}V'; + battery = '${blocState.status.battery}%'; + } else { + debugPrint('⚠️ [MachineDetails] 没有实时数据,状态类型: ${blocState?.runtimeType}'); + } + + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.1), blurRadius: 8, offset: const Offset(0, 2))], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Padding( + padding: EdgeInsets.only(bottom: 12), + child: Text('实时状态', style: TextStyle(fontSize: 14, color: Color(0xFF999999))), + ), + _buildStatusRow('控制模式', controlMode), + const Divider(height: 24, color: Color(0xFFF0F0F0)), + _buildStatusRow('电池电压', voltage), + const Divider(height: 24, color: Color(0xFFF0F0F0)), + _buildStatusRow('剩余电量', battery), + ], + ), + ); + }, + ); + } + + Widget _buildStatusRow(String label, String value) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(label, style: const TextStyle(fontSize: 16, color: Color(0xFF333333))), + Text(value, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFF007AFF))), + ], + ); + } + /// 设备状态卡片(保持不变) Widget _buildDeviceStatusCard(DeviceEntity device) { return Container( diff --git a/lib/features/remote_control/data/datasources/remote_http_datasource.dart b/lib/features/remote_control/data/datasources/remote_http_datasource.dart index 1cb89ca9..7a1ed6d1 100644 --- a/lib/features/remote_control/data/datasources/remote_http_datasource.dart +++ b/lib/features/remote_control/data/datasources/remote_http_datasource.dart @@ -94,7 +94,7 @@ class RemoteHttpDatasource { 'Authorization': 'Bearer ${token}', }, ), - data: {'platform': "app"}); + data: {'platform': "app",'type':2}); try { final responseData = response.data as Map; //debugPrint("📊 [HTTP 响应] 完整数据:$responseData"); diff --git a/lib/features/remote_control/presentation/bloc/remote_control_cubit.dart b/lib/features/remote_control/presentation/bloc/remote_control_cubit.dart index f89b1d11..9f432cae 100644 --- a/lib/features/remote_control/presentation/bloc/remote_control_cubit.dart +++ b/lib/features/remote_control/presentation/bloc/remote_control_cubit.dart @@ -7,6 +7,8 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:get_it/get_it.dart'; +import 'package:maibu_satabot_v2/features/devices/presentation/bloc/device_status_bloc.dart'; +import 'package:maibu_satabot_v2/features/devices/presentation/bloc/device_status_state.dart'; import 'package:maibu_satabot_v2/features/remote_control/data/models/running_status_model.dart'; import 'package:maibu_satabot_v2/features/remote_control/presentation/bloc/remote_control_state.dart'; @@ -23,7 +25,8 @@ class RemoteControlCubit extends Cubit { Timer? _timer; StreamSubscription? _kickOutSub; // 新增:用于管理监听生命周期 final NetMessageDispatcher dispatcher; - StreamSubscription? _stringMessageSub; // + final DeviceStatusBloc deviceStatusBloc; // 🔥 注入 DeviceStatusBloc + StreamSubscription? _deviceStatusSub; // 🔥 订阅 DeviceStatusBloc 的状态流 static const platform = MethodChannel('com.maibu.satabot/ping'); int _currentPing = 50; final ILoggerService _logger = GetIt.I(); @@ -34,77 +37,48 @@ class RemoteControlCubit extends Cubit { - RemoteControlCubit(this._repository, this._requestControlPermissionUseCase, this.dispatcher) - : super( + RemoteControlCubit( + this._repository, + this._requestControlPermissionUseCase, + this.dispatcher, + this.deviceStatusBloc, // 🔥 注入 + ) : super( RemoteControlState( controlEntity: MachineControlStatusEntity(), runningStatusModel: RunningStatusModel(), ), ) { _initPacketListener(); - _initStringMessageListener(); // 🔥 新增 + _initDeviceStatusListener(); // 🔥 改为订阅 DeviceStatusBloc } - void _initStringMessageListener() { - //print('>>> [RemoteControl] begin 初始化 0x02 字符串监听器'); - _logger.logWithLevel('>>> [RemoteControl] begin 初始化 0x02 字符串监听器'); - _stringMessageSub?.cancel(); + // 🔥 新增:订阅 DeviceStatusBloc 的状态流,而不是直接监听 TCP + void _initDeviceStatusListener() { + _logger.logWithLevel('>>> [RemoteControl] begin 订阅 DeviceStatusBloc 状态流'); + _deviceStatusSub?.cancel(); - _stringMessageSub = dispatcher.onStringMessage().listen((message) async { - // print('>>> [RemoteControl] 收到 0x02 字符串推送:$message'); - _logger.logWithLevel('>>> [RemoteControl] 收到 0x02 字符串推送:$message'); + _deviceStatusSub = deviceStatusBloc.stream.listen((deviceState) async { + if (deviceState is DeviceStatusUpdated) { + final voltage = deviceState.status.voltage; + final battery = deviceState.status.battery; + final controlMode = deviceState.status.controlMode == '3' ? '远程模式' : '本地模式'; - if (message.isEmpty) { - //print('⚠️ [RemoteControl] 消息为空,跳过解析'); - _logger.logWithLevel('⚠️ [RemoteControl] 消息为为空,跳过解析'); - return; - } - - try { - // 🔥 关键:使用与 DeviceStatusBloc 相同的解析方法 - // print('>>> [RemoteControl] 🔍 开始解析数据(使用 fromFields)...'); - _logger.logWithLevel('>>> [RemoteControl] 🔍 开始解析数据(使用 fromFields)...'); - - final fields = message.trim().split(','); - //print('>>> [RemoteControl] 字段数量:${fields.length}'); - _logger.logWithLevel('>>> [RemoteControl] 字段数量:${fields.length}'); - - if (fields.length < 18) { - //print('⚠️ [RemoteControl] 字段不足:${fields.length},期望 ≥18'); - _logger.logWithLevel('⚠️ [RemoteControl] 字段不足:${fields.length},期望 ≥18'); - return; - } - - // 🔥 使用 RunningStatusEntity.fromFields() 解析 - final status = RunningStatusEntity.fromFields(fields); - // 🔥 只提取电压、电量、控制模式 - final voltage = status.voltage; - final battery = status.battery; - final controlMode = status.controlMode == '3' ? '远程模式' : '本地模式'; - - - final c = getNetworkDelay(); // + final c = getNetworkDelay(); emit(state.copyWith( runningStatusModel: state.runningStatusModel.copyWith( - voltage: voltage.toString(), // 更新电压 - battery: battery.toString(), // 更新电量 - controlMode: controlMode, // 更新控制模式 + voltage: voltage.toString(), + battery: battery.toString(), + controlMode: controlMode, ), battery: int.tryParse(battery) ?? 0, - ping: await c, // 这里直接使用异步返回的数值 + ping: await c, )); - - } catch (e, stackTrace) { - // print('>>> [RemoteControl] ❌ 解析失败:$e'); - _logger.logWithLevel('>>> [RemoteControl] ❌ 解析失败:$e'); - // print('>>> [RemoteControl] ❌ 堆栈跟踪:$stackTrace'); - // print('>>> [RemoteControl] ❌ 原始消息:$message'); + _logger.logWithLevel('✅ [RemoteControl] 从 DeviceStatusBloc 收到更新: 电压=$voltage, 电量=$battery, 模式=$controlMode'); } }); - //print('>>> [RemoteControl] ✅ 0x02 字符串监听器已建立完成'); - _logger.logWithLevel('>>> [RemoteControl] ✅ 0x02 字符串监听器已建立完成'); + _logger.logWithLevel('>>> [RemoteControl] ✅ DeviceStatusBloc 订阅已建立完成'); } @@ -426,9 +400,8 @@ class RemoteControlCubit extends Cubit { @override Future close() { - _timer?.cancel(); // 退出页面时务必销毁定时器 - _kickOutSub?.cancel(); - _stringMessageSub?.cancel(); + _timer?.cancel(); + _deviceStatusSub?.cancel(); // 🔥 取消订阅 DeviceStatusBloc return super.close(); }