diff --git a/lib/core/di/injection.dart b/lib/core/di/injection.dart index 72be782b..e014f110 100644 --- a/lib/core/di/injection.dart +++ b/lib/core/di/injection.dart @@ -17,7 +17,9 @@ import 'package:maibu_satabot_v2/features/remote_control/domain/usecase/diff_ste import 'package:shared_preferences/shared_preferences.dart'; import '../../features/auth/data/datasources/auth_http_datasource.dart'; +import '../../features/auth/data/datasources/auth_tcp_datasource.dart'; import '../../features/auth/data/datasources/impl/auth_http_datasource_impl.dart'; +import '../../features/auth/data/datasources/impl/auth_tcp_datasource_impl.dart'; import '../../features/auth/data/repositories/auth_repository_impl.dart'; import '../../features/auth/domain/repositories/auth_repository.dart'; import '../../features/auth/domain/usecases/login_usecase.dart'; @@ -41,6 +43,8 @@ import '../../features/devices/domain/usecases/get_work_record_usecase.dart'; import '../../features/devices/domain/usecases/route_planning_usecase.dart'; import '../../features/devices/domain/usecases/save_work_record_usecase.dart'; import '../../features/devices/domain/usecases/select_work_record_usecase.dart'; +import '../../features/devices/domain/usecases/switch_device_usecase.dart'; +import '../../features/devices/presentation/bloc/device_status_bloc.dart'; import '../../features/remote_control/presentation/bloc/remote_control_cubit.dart'; import '../app/app_user_cubit.dart'; import '../network/dio_client.dart'; @@ -59,7 +63,9 @@ Future init() async { sl.registerLazySingleton(() => DioClient.create()); /// 1.1.2 TcpClient:TCP客户端 - sl.registerLazySingleton(() => TcpClient()); + sl.registerLazySingleton(() => TcpClient( sl(), + getUserDeviceUseCase: sl(), + switchDeviceUseCase: sl(),)); /// 1.1.3 NetMessageDispatcher:消息调度器,并将 TcpClient 注入给它 sl.registerLazySingleton(() => NetMessageDispatcher(sl())); @@ -83,7 +89,7 @@ Future init() async { () => AuthHttpDataSourceImpl(sl()), ); sl.registerLazySingleton( - () => DeviceHttpDatasourceImpl(sl()), + () => DeviceHttpDatasourceImpl(sl(), sl()), // ); /// 3. 仓库 (Repository) @@ -126,7 +132,16 @@ Future init() async { sl.registerLazySingleton(() => UnbindDeviceUseCase(sl())); sl.registerLazySingleton(() => UpdateDevicenameUsecase(sl())); sl.registerLazySingleton(() => SaveWorkRecordUseCase(sl())); + sl.registerLazySingleton(() => SwitchDeviceUseCase(sl())); + sl.registerLazySingleton( + () => AuthTcpDatasourceImpl( + sl(), + sl(), + getUserDeviceUseCase: sl(), + switchDeviceUseCase: sl(), + ), + ); /// 5. 状态管理 (Cubit/Bloc) @@ -136,6 +151,11 @@ Future init() async { () => DevicesCubit(sl(), sl(), sl(), sl(), sl(), sl(), sl(), sl(), sl(),sl(),sl()), ); sl.registerFactory(() => RemoteControlCubit(sl())); + /* // 工厂模式(留存,每次获取新实例) + sl.registerFactory(() => DeviceStatusBloc(sl())); +*/ + //单例模式(全局共享一个实例) + sl.registerLazySingleton(() => DeviceStatusBloc(sl())); /// . 路径生成 sl.registerLazySingleton( @@ -162,6 +182,7 @@ Future init() async { sl(), sl(), sl(), + sl(), ), ); diff --git a/lib/core/network/net_message_dispatcher.dart b/lib/core/network/net_message_dispatcher.dart index a0be1db7..206a4c28 100644 --- a/lib/core/network/net_message_dispatcher.dart +++ b/lib/core/network/net_message_dispatcher.dart @@ -16,7 +16,19 @@ class NetMessageDispatcher { /// 示例解析方法:将 0x02 指令解析为 String Stream onStringMessage() { - return onCommand(0x02).map((p) => utf8.decode(p.payload)); + print("0x02--TCP拦截推送解析开始"); + return onCommand(0x02).map((p) { + try { + // 尝试解码 + final result = utf8.decode(p.payload, allowMalformed: true); // 允许乱码,防止报错中断流 + print('✅ 解码 0x02 成功:$result'); // 🔥 关键日志:看这里打印了吗? + return result; + } catch (e) { + print('❌ 解码 0x02 失败:$e, 原始字节:${p.payload}'); + return ''; // 返回空字符串,避免流中断 + } + }); + /* ///return onCommand(0x02).map((p) => utf8.decode(p.payload));*/ } /// 示例解析方法:将 0x12 指令解析为 JSON 并转为模型 diff --git a/lib/core/network/tcp/tcp_client.dart b/lib/core/network/tcp/tcp_client.dart index e181c605..26ee94e7 100644 --- a/lib/core/network/tcp/tcp_client.dart +++ b/lib/core/network/tcp/tcp_client.dart @@ -6,9 +6,14 @@ import 'dart:typed_data'; import 'package:flutter/cupertino.dart'; import 'package:maibu_satabot_v2/features/devices/data/repositories/route_planning_repository_impl.dart'; +import '../../../features/auth/data/datasources/auth_tcp_datasource.dart'; import '../../../features/devices/data/models/route_plan_send_entity.dart'; +import '../../../features/devices/domain/entities/device_entity.dart'; import '../../../features/devices/domain/entities/gps_entity.dart'; import '../../../features/devices/domain/entities/running_status_entity.dart'; +import '../../../features/devices/domain/usecases/get_user_device_usecase.dart'; +import '../../../features/devices/domain/usecases/switch_device_usecase.dart'; +import '../../storage/user_storage.dart'; import '../protocol_decoder.dart'; @@ -18,39 +23,111 @@ class TcpClient { // 使用 StreamController 将原始数据流暴露给外部,以便多个 DataSource 监听 final _controller = StreamController.broadcast(); Stream get packetStream => _controller.stream; - + final GetUserDeviceUseCase getUserDeviceUseCase; + final SwitchDeviceUseCase switchDeviceUseCase; + TcpClient(this._userStorage, {required this.getUserDeviceUseCase, required this.switchDeviceUseCase}); // 新增:心跳定时器 Timer? _heartbeatTimer; + Timer? _reconnectTimer; // 重连定时器 + String? _lastHost; + int? _lastPort; + + final UserStorage _userStorage; + + bool get isConnected => _socket != null; // 通过参数配置,不硬编码 Future connect({required String host, required int port}) async { + debugPrint('🔌 [TCP] 开始连接:$host:$port'); // ✅ 必须看到这条 + _lastHost = host; + _lastPort = port; try { _socket = await Socket.connect( host, port, timeout: const Duration(seconds: 5), ); - + debugPrint('✅ [TCP] 连接成功!'); // ✅ 必须看到这条 + // 开始认证tcp + await _sendAuthPacket(); _socket!.listen((data) { + debugPrint('📥 [TCP] 收到原始数据:${data.length} 字节, 内容:$data'); + // var packets = _decoder.decode(data); + // debugPrint('📦 [TCP] 解码成功,包数量:${packets.length}'); + //for (var packet in packets) { + // // 🔥若收到服务端心跳(cmd == 0xFF),立即回复一个心跳包 + // if (packet.command == 0xFF) { + // debugPrint('收到服务端心跳,自动回复...'); + // sendHeartbeat(); // 回复 AB AA FF AA AB + // } + // } + // _controller.add(packet); + // } + try { var packets = _decoder.decode(data); + debugPrint('📦 [TCP] 解码成功,包数量:${packets.length}'); for (var packet in packets) { - // 🔥若收到服务端心跳(cmd == 0xFF),立即回复一个心跳包 - if (packet.command == 0xFF) { - debugPrint('收到服务端心跳,自动回复...'); - sendHeartbeat(); // 回复 AB AA FF AA AB + if (!_controller.isClosed) { + _controller.add(packet); + debugPrint('➡️ [TCP] 已分发 CMD: 0x${packet.command.toRadixString(16)}'); } - _controller.add(packet); + if (packet.command == 0xFF) { + debugPrint('收到服务端心跳,自动回复...'); + sendHeartbeat(); // 回复 AB AA FF AA AB + } + if (packet.command == 0x03) { + debugPrint('⚠️ 收到认证响应:${packet.payload}'); + // 解析 payload 看是否有错误信息 + } + } + } catch (e, stackTrace) { + debugPrint('❌ [TCP] 解码数据时发生异常:$e\n$stackTrace'); // 🔥 捕获解码异常 + } + }, + onDone: (){ + // TODO: 断线重连 + debugPrint('onDone❌ [TCP] 连接已断开!'); + _handleDisconnect(); + }, + onError: (e) { + debugPrint('❌ [TCP] 发生错误:$e'); + _handleDisconnect(); // 统一走重连逻辑,保护 Controller 不被关闭 }, - onDone: disconnect, - onError: (e) => disconnect(), ); } catch (e) { rethrow; // 向上抛出连接异常 } } + + void _handleDisconnect() { + stopHeartbeat(); + + _socket = null; + // 注意:这里不要关闭 _controller!否则监听者会丢失数据流 + // _controller?.close(); + _scheduleReconnect(); + } + + // ✅ 新增:调度重连 + Future _scheduleReconnect() async { + if (_reconnectTimer != null) return; + + debugPrint('⏳ 调度重连:Host=${_lastHost}, Port=${_lastPort}'); // ✅ 检查 Host/Port 是否为空 + if (_lastHost == null || _lastPort == null) { + debugPrint('❌ 无法重连:Host 或 Port 为空!'); + return; + } + + _reconnectTimer = Timer(const Duration(seconds: 5), () { + _reconnectTimer = null; + debugPrint('⏰ 定时器触发,开始执行重连...'); + connect(host: _lastHost!, port: _lastPort!); + }); + await _sendAuthPacket(); + } /// 发送数据 void send(Map data) { if (_socket == null) throw Exception("Socket not connected"); @@ -89,7 +166,7 @@ class TcpClient { // 新增:启动心跳(每 4 秒发送一次 0xFF 指令) void startHeartbeat({Duration interval = const Duration(seconds: 4)}) { if (_heartbeatTimer != null) return; // 防止重复启动 - + debugPrint('收到服务端心跳,自动回复...'); _heartbeatTimer = Timer.periodic(interval, (_) { // 发送心跳帧:AB AA FF 00 00 AA AB(与 sendRaw 一致) //sendRaw(0xFF, []); @@ -130,6 +207,88 @@ class TcpClient { } + // + Future _sendAuthPacket() async { + if (_socket == null) return; + String? username= ""; + String? token= ""; + final user = await _userStorage.getUser(); + debugPrint('tcp使用用户信息:$user'); + if (user == null || user.token == null) { + debugPrint('❌ [TCP] 认证失败:用户未登录或 Token 为空,无法发送认证包'); + return; // 直接返回,不要发送无效包 + } + username = user.username; + token = user.token; + final authString = '$username:app:$token'; + debugPrint('🔑 [TCP] 认证字符串:$authString'); + final authBytes = utf8.encode(authString); + // 构造包结构:Head(2) + Cmd(1) + Payload(N) + CRC(2) + Foot(2) + // 总长度 = 3 + N + 4 + final builder = BytesBuilder() + ..addByte(0xAB) + ..addByte(0xAA) + ..addByte(0x03) // 认证指令 + ..add(authBytes); + // 添加 CRC (这里简单用 0x00 0x00 占位,如果协议严格校验需计算真实 CRC) + // Android 代码中是 packet[tailStart] = 0x00; packet[tailStart+1] = 0x00; + builder.addByte(0x00); + builder.addByte(0x00); + + builder.addByte(0xAA); + builder.addByte(0xAB); + + _socket!.add(builder.takeBytes()); + debugPrint('🔑 [TCP] 已发送认证包 (0x03): $authString'); + + try { + // 1. 获取 Either 结果 + final eitherResult = await getUserDeviceUseCase.repository.getUserDevice(username); + + // 2. 使用 fold 解包 Either + // left: 处理错误情况 (DeviceFailure) + // right: 处理成功情况 (List) + await eitherResult.fold( + (failure) { + // 处理失败:打印日志或抛出异常 + debugPrint('❌ [AuthTcp] 获取设备列表失败:$failure'); + throw Exception('获取设备列表失败:$failure'); + }, + (devices) async { + // 处理成功:devices 现在是真正的 List + if (devices.isEmpty) { + debugPrint('⚠️ [AuthTcp] 当前用户无可用设备,跳过切换步骤'); + return; + } + + // 取第一个设备 + final DeviceEntity targetDevice = devices.first; + debugPrint('📱 [AuthTcp] 准备切换至默认设备:${targetDevice.deviceName}'); + + // 切换设备 (同样,如果 switchDeviceUseCase 也返回 Either,也需要 fold 处理) + final switchResult = await switchDeviceUseCase.deviceRepository.switchDevice("app",targetDevice.deviceName); + + await switchResult.fold( + (failure) { + debugPrint('❌ [AuthTcp] 切换设备失败:$failure'); + throw Exception('切换设备失败:$failure'); + }, + (success) { + debugPrint('✅ [AuthTcp] 设备切换成功,服务端应开始推送数据'); + }, + ); + }, + ); + } catch (e) { + debugPrint('❌ [AuthTcp] 设备订阅流程异常:$e'); + rethrow; + } + } + + void sendRawBytes(Uint8List bytes) { + if (_socket == null) return; + _socket!.add(bytes); + } } diff --git a/lib/features/auth/data/datasources/auth_tcp_datasource.dart b/lib/features/auth/data/datasources/auth_tcp_datasource.dart index e69de29b..e6456a64 100644 --- a/lib/features/auth/data/datasources/auth_tcp_datasource.dart +++ b/lib/features/auth/data/datasources/auth_tcp_datasource.dart @@ -0,0 +1,6 @@ + + +abstract class AuthTcpDatasource { + + Future sendAuthPacket(); + } \ No newline at end of file diff --git a/lib/features/auth/data/datasources/impl/auth_tcp_datasource_impl.dart b/lib/features/auth/data/datasources/impl/auth_tcp_datasource_impl.dart index e69de29b..bd5cacba 100644 --- a/lib/features/auth/data/datasources/impl/auth_tcp_datasource_impl.dart +++ b/lib/features/auth/data/datasources/impl/auth_tcp_datasource_impl.dart @@ -0,0 +1,108 @@ +// lib/features/auth/data/datasources/auth_tcp_datasource.dart +import 'dart:async'; +import 'dart:convert'; +import 'dart:typed_data'; +import 'package:flutter/cupertino.dart'; +import 'package:fpdart/src/either.dart'; +import 'package:maibu_satabot_v2/features/devices/domain/entities/device_entity.dart'; +import 'package:maibu_satabot_v2/features/devices/domain/errors/device_failure.dart'; + +import '../../../../../core/network/tcp/tcp_client.dart'; +import '../../../../../core/storage/user_storage.dart'; +import '../../../../devices/domain/usecases/get_user_device_usecase.dart'; +import '../../../../devices/domain/usecases/switch_device_usecase.dart'; +import '../auth_tcp_datasource.dart'; + + + + +class AuthTcpDatasourceImpl implements AuthTcpDatasource { + final TcpClient _tcpClient; + final UserStorage _userStorage; + final GetUserDeviceUseCase getUserDeviceUseCase; + final SwitchDeviceUseCase switchDeviceUseCase; + + + AuthTcpDatasourceImpl(this._tcpClient, this._userStorage, {required this.getUserDeviceUseCase, required this.switchDeviceUseCase}); + + @override + Future sendAuthPacket() async { + if (!_tcpClient.isConnected) { + throw Exception('TCP 未连接,无法发送认证包'); + } + + final user = await _userStorage.getUser(); + if (user == null || user.token == null || user.username == null) { + throw Exception('用户未登录,无法认证'); + } + + final username = user.username!; + final token = user.token!; + final authString = '$username:app:$token'; + + debugPrint('🔑 [AuthTcp] 准备发送认证包:$authString'); + + final authBytes = utf8.encode(authString); + + // 构造协议包:AB AA 03 [Payload] CRC(00 00) AA AB + // 注意:如果服务端校验 CRC,需在此处计算真实 CRC + final builder = BytesBuilder() + ..addByte(0xAB) + ..addByte(0xAA) + ..addByte(0x03) + ..add(authBytes) + ..addByte(0x00) + ..addByte(0x00) + ..addByte(0xAA) + ..addByte(0xAB); + + _tcpClient.sendRawBytes(builder.takeBytes()); // 假设 TcpClient 增加了此方法,或用 socket.add + debugPrint('✅ [AuthTcp] 认证包已发送'); + + // 获取用户设备列表 + try { + // 1. 获取 Either 结果 + final eitherResult = await getUserDeviceUseCase.repository.getUserDevice(username); + + // 2. 使用 fold 解包 Either + // left: 处理错误情况 (DeviceFailure) + // right: 处理成功情况 (List) + await eitherResult.fold( + (failure) { + // 处理失败:打印日志或抛出异常 + debugPrint('❌ [AuthTcp] 获取设备列表失败:$failure'); + throw Exception('获取设备列表失败:$failure'); + }, + (devices) async { + // 处理成功:devices 现在是真正的 List + if (devices.isEmpty) { + debugPrint('⚠️ [AuthTcp] 当前用户无可用设备,跳过切换步骤'); + return; + } + + // 取第一个设备 + final DeviceEntity targetDevice = devices.first; + debugPrint('📱 [AuthTcp] 准备切换至默认设备:${targetDevice.deviceName}'); + + // 切换设备 (同样,如果 switchDeviceUseCase 也返回 Either,也需要 fold 处理) + final switchResult = await switchDeviceUseCase.deviceRepository.switchDevice("app",targetDevice.deviceName); + + await switchResult.fold( + (failure) { + debugPrint('❌ [AuthTcp] 切换设备失败:$failure'); + throw Exception('切换设备失败:$failure'); + }, + (success) { + debugPrint('✅ [AuthTcp] 设备切换成功,服务端应开始推送数据'); + }, + ); + }, + ); + } catch (e) { + debugPrint('❌ [AuthTcp] 设备订阅流程异常:$e'); + rethrow; + } + } +} + + diff --git a/lib/features/auth/presentation/bloc/auth_cubit.dart b/lib/features/auth/presentation/bloc/auth_cubit.dart index 1bcaf679..96f7812d 100644 --- a/lib/features/auth/presentation/bloc/auth_cubit.dart +++ b/lib/features/auth/presentation/bloc/auth_cubit.dart @@ -8,6 +8,10 @@ import 'package:maibu_satabot_v2/core/network/net_message_dispatcher.dart'; import '../../../../core/app/app_user_cubit.dart'; import '../../../../core/network/tcp/tcp_client.dart'; import '../../../../core/storage/user_storage.dart'; +import '../../../devices/domain/usecases/get_user_device_usecase.dart'; +import '../../../devices/domain/usecases/switch_device_usecase.dart'; +import '../../data/datasources/auth_tcp_datasource.dart'; +import '../../data/datasources/impl/auth_tcp_datasource_impl.dart'; import 'auth_state.dart'; /// emit做的事情: @@ -19,9 +23,11 @@ class AuthCubit extends Cubit { final TcpClient tcp; final AppUserCubit appCubit; final NetMessageDispatcher dispatcher; + final AuthTcpDatasource _authTcpDatasource; + StreamSubscription? _kickOutSub; // 新增:用于管理监听生命周期 - AuthCubit(this.storage, this.tcp, this.appCubit, this.dispatcher) + AuthCubit(this.storage, this.tcp, this.appCubit, this.dispatcher, this._authTcpDatasource) : super(AuthInitial()) { // Cubit 一启动就开始监听 TCP 的“自动逻辑” _listenToAuthResponse(); @@ -33,6 +39,7 @@ class AuthCubit extends Cubit { if (user != null) { // 1. 建立 TCP 连接 (远程控制) tcp.connect(host: TCPConsts.TCP_IP, port: TCPConsts.TCP_PORT); + await _authTcpDatasource.sendAuthPacket();//包括发送认证包和获取列表和切换函数 // 2. 同步全局 App 状态 appCubit.setAuth(user); // 3. 进入已登录状态 @@ -46,7 +53,8 @@ class AuthCubit extends Cubit { Future loginSuccess(UserEntity user) async { await storage.saveUser(user); await tcp.connect(host: TCPConsts.TCP_IP, port: TCPConsts.TCP_PORT); - tcp.startHeartbeat(interval: const Duration(seconds: 4)); // 👈 启动心跳 + await _authTcpDatasource.sendAuthPacket();//包括发送认证包和获取列表和切换函数 + tcp.startHeartbeat(interval: const Duration(seconds: 4)); //启动心跳 appCubit.setAuth(user); emit(AuthAuthenticated(user)); } diff --git a/lib/features/devices/data/datasources/impl/device_http_datasource_impl.dart b/lib/features/devices/data/datasources/impl/device_http_datasource_impl.dart index a99149f5..f8831468 100644 --- a/lib/features/devices/data/datasources/impl/device_http_datasource_impl.dart +++ b/lib/features/devices/data/datasources/impl/device_http_datasource_impl.dart @@ -1,14 +1,25 @@ import 'package:dio/dio.dart'; +import 'package:flutter/widgets.dart'; import 'package:maibu_satabot_v2/core/consts/http_api_consts.dart'; import 'package:maibu_satabot_v2/features/devices/data/datasources/device_http_datasource.dart'; import 'package:maibu_satabot_v2/features/devices/domain/entities/device_entity.dart'; +import '../../../../../core/storage/user_storage.dart'; import '../../../domain/entities/device_location_entity.dart'; class DeviceHttpDatasourceImpl implements DeviceHttpDatasource { final Dio dio; + final UserStorage _userStorage; // 🔥 新增字段 - DeviceHttpDatasourceImpl(this.dio); + // 🔥 修改构造函数,注入 UserStorage + DeviceHttpDatasourceImpl(this.dio, this._userStorage); + + // 🔥 辅助方法:获取 Token + Future _getToken() async { + final user = await _userStorage.getUser(); + debugPrint('用户信息: $user') ; + return user?.token; + } @override Future bindDevice(String deviceId, String deviceAlias) async { @@ -31,9 +42,17 @@ class DeviceHttpDatasourceImpl implements DeviceHttpDatasource { @override Future> getUserDevices(String username) async { + final token = await _getToken(); + var response = await dio.get( HttpApiConsts.getUserDevicesList, queryParameters: {'tenantName': username}, + options: Options( + headers: { + 'Authorization': token != null ? 'Bearer $token' : '', + // 如果后端不需要 Bearer 前缀,直接写 token 即可 + }, + ), ); if (response.statusCode != 200) { throw Exception('网络请求失败:${response.statusCode}'); @@ -58,9 +77,16 @@ class DeviceHttpDatasourceImpl implements DeviceHttpDatasource { @override Future switchDevice(String platform, String deviceId) async { + final token = await _getToken(); var response = await dio.post( HttpApiConsts.switchDevice, data: {'platform': platform, 'deviceId': deviceId}, + options: Options( + headers: { + 'Authorization': token != null ? 'Bearer $token' : '', + // 如果后端不需要 Bearer 前缀,直接写 token 即可 + }, + ), ); if (response.statusCode != 200) { throw Exception('网络请求失败:${response.statusCode}'); diff --git a/lib/features/devices/domain/entities/running_status_entity.dart b/lib/features/devices/domain/entities/running_status_entity.dart index 97347197..edbb3a2c 100644 --- a/lib/features/devices/domain/entities/running_status_entity.dart +++ b/lib/features/devices/domain/entities/running_status_entity.dart @@ -1,29 +1,43 @@ +// lib/features/devices/domain/entities/running_status_entity.dart + import 'package:equatable/equatable.dart'; class RunningStatusEntity extends Equatable { - final double voltage; - final double leftTargetSpeed; - final double rightTargetSpeed; - final double leftMeasureSpeed; - final double rightMeasureSpeed; - final double leftCurrent; - final double rightCurrent; - final double leftMotorTemp; - final double rightMotorTemp; - final double chipTemp; - final double yaw; - final double pitch; - final double roll; - final int satelliteCnt; - final int qual; - final int headingStatus; - final double latitude; - final double longitude; - final String knifeCuttingSpeed; - final String controlMode; - final String battery; - final String workingArea; - final String obstacleFlag; + // === 基础电气信息 (字段 0-6) === + final double voltage; // [0] 电压 (V) + final double leftTargetSpeed; // [1] 左轮目标速度 + final double rightTargetSpeed; // [2] 右轮目标速度 + final double leftMeasureSpeed; // [3] 左轮实测速度 + final double rightMeasureSpeed; // [4] 右轮实测速度 + final double leftCurrent; // [5] 左轮电流 (A) + final double rightCurrent; // [6] 右轮电流 (A) + + // === 温度信息 (字段 7-9) === + final double leftMotorTemp; // [7] 左电机温度 (℃) + final double rightMotorTemp; // [8] 右电机温度 (℃) + final double chipTemp; // [9] 芯片温度 (℃) + + // === 姿态信息 (字段 10-12) === + final double yaw; // [10] 偏航角 (°) + final double pitch; // [11] 俯仰角 (°) + final double roll; // [12] 翻滚角 (°) + + // === GPS 信号质量 (字段 13-15) === + final int satelliteCnt; // [13] 卫星数量 + final int qual; // [14] 定位质量 + final int headingStatus; // [15] 航向状态 + + // === GPS 位置信息 (字段 16-17) === + final double latitude; // [16] 纬度 + final double longitude; // [17] 经度 + + // === 扩展信息 (字段 18-23) === + final String timestamp; // [18] 时间戳 (格式:20-1-11-31 08:00:00) + final String knifeCuttingSpeed; // [19] 割刀速度 + final String controlMode; // [20] 控制模式 + final String battery; // [21] 电量百分比 + final String workingArea; // [22] 工作面积 + final String obstacleFlag; // [23] 障碍物标志 const RunningStatusEntity({ this.voltage = 0.0, @@ -44,13 +58,61 @@ class RunningStatusEntity extends Equatable { this.headingStatus = 0, this.latitude = 0.0, this.longitude = 0.0, - this.knifeCuttingSpeed = "0", - this.controlMode = "0", - this.battery = "0", - this.workingArea = "0", - this.obstacleFlag = "0", + this.timestamp = '', + this.knifeCuttingSpeed = '0', + this.controlMode = '0', + this.battery = '0', + this.workingArea = '0', + this.obstacleFlag = '0', }); + /// 从字段数组创建实体 (核心解析方法) + factory RunningStatusEntity.fromFields(List fields) { + // 确保至少有 24 个字段,不足的用默认值填充 + while (fields.length < 24) { + fields.add(''); + } + + return RunningStatusEntity( + // 基础电气信息 + voltage: double.tryParse(fields[0]) ?? 0.0, + leftTargetSpeed: double.tryParse(fields[1]) ?? 0.0, + rightTargetSpeed: double.tryParse(fields[2]) ?? 0.0, + leftMeasureSpeed: double.tryParse(fields[3]) ?? 0.0, + rightMeasureSpeed: double.tryParse(fields[4]) ?? 0.0, + leftCurrent: double.tryParse(fields[5]) ?? 0.0, + rightCurrent: double.tryParse(fields[6]) ?? 0.0, + + // 温度信息 + leftMotorTemp: double.tryParse(fields[7]) ?? 0.0, + rightMotorTemp: double.tryParse(fields[8]) ?? 0.0, + chipTemp: double.tryParse(fields[9]) ?? 0.0, + + // 姿态信息 + yaw: double.tryParse(fields[10]) ?? 0.0, + pitch: double.tryParse(fields[11]) ?? 0.0, + roll: double.tryParse(fields[12]) ?? 0.0, + + // GPS 信号质量 + satelliteCnt: int.tryParse(fields[13]) ?? 0, + qual: int.tryParse(fields[14]) ?? 0, + headingStatus: int.tryParse(fields[15]) ?? 0, + + // GPS 位置信息 + latitude: double.tryParse(fields[16]) ?? 0.0, + longitude: double.tryParse(fields[17]) ?? 0.0, + + // 扩展信息 (保持原始字符串,由调用方决定如何解析) + timestamp: fields[18].trim(), + knifeCuttingSpeed: fields[19].trim().isNotEmpty ? fields[19].trim() : '0', + controlMode: fields[20].trim().isNotEmpty ? fields[20].trim() : '0', + battery: fields[21].trim().isNotEmpty ? fields[21].trim() : '0', + workingArea: fields[22].trim().isNotEmpty ? fields[22].trim() : '0', + obstacleFlag: fields[23].trim().isNotEmpty ? fields[23].trim() : '0', + ); + } + + /// copyWith 方法 (用于局部更新) RunningStatusEntity copyWith({ double? voltage, double? leftTargetSpeed, @@ -70,6 +132,7 @@ class RunningStatusEntity extends Equatable { int? headingStatus, double? latitude, double? longitude, + String? timestamp, String? knifeCuttingSpeed, String? controlMode, String? battery, @@ -95,6 +158,7 @@ class RunningStatusEntity extends Equatable { headingStatus: headingStatus ?? this.headingStatus, latitude: latitude ?? this.latitude, longitude: longitude ?? this.longitude, + timestamp: timestamp ?? this.timestamp, knifeCuttingSpeed: knifeCuttingSpeed ?? this.knifeCuttingSpeed, controlMode: controlMode ?? this.controlMode, battery: battery ?? this.battery, @@ -105,9 +169,35 @@ class RunningStatusEntity extends Equatable { @override List get props => [ - voltage, leftTargetSpeed, rightTargetSpeed, leftMeasureSpeed, rightMeasureSpeed, - leftCurrent, rightCurrent, leftMotorTemp, rightMotorTemp, chipTemp, - yaw, pitch, roll, satelliteCnt, qual, headingStatus, - latitude, longitude, knifeCuttingSpeed, controlMode, battery, workingArea, obstacleFlag, + voltage, + leftTargetSpeed, + rightTargetSpeed, + leftMeasureSpeed, + rightMeasureSpeed, + leftCurrent, + rightCurrent, + leftMotorTemp, + rightMotorTemp, + chipTemp, + yaw, + pitch, + roll, + satelliteCnt, + qual, + headingStatus, + latitude, + longitude, + timestamp, + knifeCuttingSpeed, + controlMode, + battery, + workingArea, + obstacleFlag, ]; + + @override + String toString() { + return 'RunningStatusEntity(voltage: $voltage, chipTemp: $chipTemp, ' + 'latitude: $latitude, longitude: $longitude, timestamp: $timestamp)'; + } } diff --git a/lib/features/devices/presentation/bloc/device_status_bloc.dart b/lib/features/devices/presentation/bloc/device_status_bloc.dart index 6e33d4dd..b78b0b13 100644 --- a/lib/features/devices/presentation/bloc/device_status_bloc.dart +++ b/lib/features/devices/presentation/bloc/device_status_bloc.dart @@ -14,77 +14,82 @@ class DeviceStatusBloc extends Bloc { final NetMessageDispatcher _dispatcher; DeviceStatusBloc(this._dispatcher) : super(DeviceStatusInitial()) { - // 直接订阅 0x02 指令的字符串流 + // 订阅 TCP 数据流 _dispatcher.onStringMessage().listen((jsonString) { + debugPrint('📩 Bloc 收到 0x02 数据'); add(DeviceStatusLoaded(jsonString)); }); - // 订阅 0x12 的 JSON 流() _dispatcher.onJsonMessage(0x12).listen((jsonData) { add(PushMessageReceived(jsonData)); }); - } - @override - Stream mapEventToState(DeviceStatusEvent event) async* { - if (event is DeviceStatusLoaded) { - yield* _mapDeviceStatusLoaded(event); - } else if (event is PushMessageReceived) { - yield* _mapPushMessageReceived(event); - } - } + // + on(_handleDeviceStatusLoaded); + on(_handlePushMessageReceived); - Stream _mapDeviceStatusLoaded(DeviceStatusLoaded event) async* { + + // 🔥 新增:模拟测试数据(调试用) + _testMockData(); + } + void _testMockData() { + // 用户提供的真实指令数据(字节数组) + final mockBytes = [ + 171, 170, 2, 48, 46, 48, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 46, 48, 48, 44, 48, 46, 48, 48, 44, 48, 46, 48, 48, 44, 48, 46, 48, 48, 44, 50, 55, 46, 52, 55, 44, 49, 56, 48, 46, 48, 48, 44, 48, 46, 48, 48, 44, 48, 46, 48, 48, 44, 52, 49, 44, 52, 44, 49, 44, 51, 50, 46, 48, 52, 49, 50, 57, 54, 56, 54, 52, 48, 48, 57, 57, 57, 44, 49, 50, 48, 46, 56, 48, 56, 57, 57, 51, 48, 56, 49, 49, 53, 56, 53, 48, 44, 50, 48, 45, 49, 45, 49, 49, 45, 51, 49, 32, 48, 56, 58, 48, 48, 58, 48, 48, 44, 48, 44, 51, 44, 54, 54, 44, 57, 53, 50, 55, 46, 48, 48, 48, 48, 44, 48, 0, 0, 0, 170, 171 + ]; + + // 解码为字符串(去掉帧头 3 字节和帧尾 5 字节) + final payloadBytes = mockBytes.sublist(3, mockBytes.length - 5); + final mockData = String.fromCharCodes(payloadBytes); + + debugPrint('🧪 模拟测试数据:$mockData'); + + // 发送模拟事件 + add(DeviceStatusLoaded(mockData)); + } + // 🔥 修改:使用 Emitter 发送状态 + Future _handleDeviceStatusLoaded( + DeviceStatusLoaded event, + Emitter emit, + ) async { try { - final fields = event.jsonString.trim().split(' '); + debugPrint('🔍 开始解析数据:${event.jsonString}'); + final fields = event.jsonString.trim().split(','); + debugPrint('🔍 字段数量:${fields.length}'); + if (fields.length < 18) { - yield DeviceStatusError('字段不足,期望 ≥18,实际: ${fields.length}'); + emit(DeviceStatusError('字段不足,期望 ≥18,实际:${fields.length}')); return; } - ///解析设备状态 - final status = RunningStatusEntity().copyWith( - voltage: double.tryParse(fields[0]) ?? 0.0, - leftTargetSpeed: double.tryParse(fields[1]) ?? 0.0, - rightTargetSpeed: double.tryParse(fields[2]) ?? 0.0, - leftMeasureSpeed: double.tryParse(fields[3]) ?? 0.0, - rightMeasureSpeed: double.tryParse(fields[4]) ?? 0.0, - leftCurrent: double.tryParse(fields[5]) ?? 0.0, - rightCurrent: double.tryParse(fields[6]) ?? 0.0, - leftMotorTemp: double.tryParse(fields[7]) ?? 0.0, - rightMotorTemp: double.tryParse(fields[8]) ?? 0.0, - chipTemp: double.tryParse(fields[9]) ?? 0.0, - yaw: double.tryParse(fields[10]) ?? 0.0, - pitch: double.tryParse(fields[11]) ?? 0.0, - roll: double.tryParse(fields[12]) ?? 0.0, - satelliteCnt: int.tryParse(fields[13]) ?? 0, - qual: int.tryParse(fields[14]) ?? 0, - headingStatus: int.tryParse(fields[15]) ?? 0, - latitude: double.tryParse(fields[16]) ?? 0.0, - longitude: double.tryParse(fields[17]) ?? 0.0, - knifeCuttingSpeed: fields.length > 19 && fields[19].isNotEmpty ? fields[19] : "0", - controlMode: fields.length > 20 && fields[20].isNotEmpty ? fields[20] : "0", - battery: fields.length > 21 && fields[21].isNotEmpty ? fields[21] : "0", - workingArea: fields.length > 22 && fields[22].isNotEmpty ? fields[22] : "0", - obstacleFlag: fields.length > 23 && fields[23].isNotEmpty ? fields[23] : "0", - ); - ///解析GPS + + final status = RunningStatusEntity.fromFields(fields); + debugPrint('设备运行时候状态:$status'); final gps = GPSEntity(status.latitude, status.longitude); - yield DeviceStatusUpdated(status, gps); + debugPrint('GPS: ${gps.latitude}, ${gps.longitude}'); + + emit(DeviceStatusUpdated(status, gps)); // 🔥 使用 emit 发送状态 } catch (e) { - yield DeviceStatusError('解析设备状态失败: $e'); + debugPrint('❌ 解析异常:$e'); + emit(DeviceStatusError('解析设备运行时候状态失败:$e')); } } -/// 解析 推送消息 - Stream _mapPushMessageReceived(PushMessageReceived event) async* { + + Future _handlePushMessageReceived( + PushMessageReceived event, + Emitter emit, + ) async { try { final eventStr = event.jsonData['event'] ?? ''; final deviceId = event.jsonData['deviceId'] ?? '未知'; - debugPrint('收到推送事件: $eventStr, 设备: $deviceId'); - - // 可扩展:yield PushMessageState(event.jsonData) + debugPrint('收到推送事件:$eventStr, 设备:$deviceId'); } catch (e) { - yield DeviceStatusError('解析推送消息失败: $e'); + emit(DeviceStatusError('解析推送消息失败:$e')); } } + @override + Future close() { + return super.close(); + } } + diff --git a/lib/features/devices/presentation/bloc/device_status_event.dart b/lib/features/devices/presentation/bloc/device_status_event.dart index 2ac964c9..c1a12f2d 100644 --- a/lib/features/devices/presentation/bloc/device_status_event.dart +++ b/lib/features/devices/presentation/bloc/device_status_event.dart @@ -21,3 +21,4 @@ class PushMessageReceived extends DeviceStatusEvent { @override List get props => [jsonData]; } + diff --git a/lib/features/home/presentation/pages/running_status_page.dart b/lib/features/home/presentation/pages/running_status_page.dart index 7f26cdd2..b55cc9d9 100644 --- a/lib/features/home/presentation/pages/running_status_page.dart +++ b/lib/features/home/presentation/pages/running_status_page.dart @@ -9,6 +9,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_bloc.dart'; // 👈 新增导入 +import '../../../devices/presentation/bloc/device_status_state.dart'; // 👈 新增导入 class RunningStatusPage extends StatefulWidget { const RunningStatusPage({super.key}); @@ -18,90 +20,18 @@ class RunningStatusPage extends StatefulWidget { } class _RunningStatusPageState extends State { - final _dispatcher = sl(); - late StreamSubscription _sub; - - // 实时设备状态数据 - String _yaw = '--'; - String _satelliteCnt = '--'; - String _qual = '--'; - String _leftSpeed = '--'; - String _rightSpeed = '--'; - String _voltage = '--'; - // 实时设备状态数据 - String _pitch = '--'; - String _roll = '--'; - String _latitude = '--'; - String _longitude = '--'; - String _timeStamp = '--'; - String _knifeCuttingSpeed = '--'; - String _controlMode = '--'; - String _workingArea = '--'; - String _battery = '--'; - String _obstacleFlag = '--'; // 视图切换状态 bool _isCardView = true; - @override - void initState() { - super.initState(); - _sub = _dispatcher.onCommand(0x02).listen(_onDeviceData); - } - - void _onDeviceData(RawPacket packet) { - try { - final csv = utf8.decode(packet.payload); - final fields = csv.split(','); - // 至少需 16 个字段(索引 10=航向角, 13=卫星数, 14=定位质量) - if (fields.length >= 16) { - setState(() { - _voltage = fields[0]; // 电压 - _leftSpeed = fields[1]; // 左轮目标速度 - _rightSpeed = fields[2]; // 右轮目标速度 - _pitch = fields[11]; // 俯仰角 - _roll = fields[12]; // 翻滚角 - _latitude = fields[16]; // 纬度 - _longitude = fields[17]; // 经度 - _timeStamp = '${fields[16]} ${fields[17]}'; // 时间戳(合并字段) - _knifeCuttingSpeed = fields[19]; // 割刀速度 - _controlMode = fields[20]; // 控制模式 - _workingArea = fields[21]; // 作业面积 - _battery = fields[22]; // 电量 - _obstacleFlag = fields[23]; // 障碍物标志 - _yaw = fields[10]; // 航向角 - _satelliteCnt = fields[13]; // 卫星数 - _qual = fields[14]; // 定位质量 - }); - } - } catch (_) { - // 解析失败时忽略,避免崩溃 - } - } - - @override - void dispose() { - _sub.cancel(); - super.dispose(); - } - - void _refreshDeviceData() { - final deviceState = context.read().state; - final currentDevice = deviceState.selectedDevice; - - if (currentDevice != null) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("数据刷新成功!"), duration: Duration(seconds: 1))); - } else { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("暂无设备,无法刷新"), duration: Duration(seconds: 1))); - } - } - @override Widget build(BuildContext context) { final deviceState = context.read().state; final currentDevice = deviceState.selectedDevice; if (currentDevice == null) { - return const Scaffold(body: Center(child: Text("加载中..."))); + return const Scaffold( + body: Center(child: Text("加载中...")), + ); } return Scaffold( @@ -125,20 +55,38 @@ class _RunningStatusPageState extends State { ), ), - // 2. 状态信息行(动态更新) - SliverToBoxAdapter( - child: Container( - color: Colors.white, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text("航向角状态: $_yaw°", style: const TextStyle(fontSize: 14)), - Text("定位质量: $_qual", style: const TextStyle(fontSize: 14)), - Text("卫星数: $_satelliteCnt", style: const TextStyle(fontSize: 14)), - ], - ), - ), + // 2. 状态信息行(动态更新)- 使用 BlocBuilder 监听 + BlocBuilder( + builder: (context, state) { + String yaw = '--'; + String qual = '--'; + String satelliteCnt = '--'; + + if (state is DeviceStatusUpdated) { + yaw = state.status.yaw.toStringAsFixed(2); + qual = state.status.qual.toString(); + satelliteCnt = state.status.satelliteCnt.toString(); + } else if (state is DeviceStatusError) { + yaw = '错误'; + qual = '-'; + satelliteCnt = '-'; + } + + return SliverToBoxAdapter( + child: Container( + color: Colors.white, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("航向角状态:$yaw°", style: const TextStyle(fontSize: 14)), + Text("定位质量:$qual", style: const TextStyle(fontSize: 14)), + Text("卫星数:$satelliteCnt", style: const TextStyle(fontSize: 14)), + ], + ), + ), + ); + }, ), // 3. 选项卡 + 刷新按钮 @@ -180,12 +128,19 @@ class _RunningStatusPageState extends State { ), ), - // 4. 内容区域 + // 4. 内容区域 - 使用 BlocBuilder 监听 SliverFillRemaining( - child: Container( - margin: const EdgeInsets.all(16), - decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)), - child: _isCardView ? _buildCardContentView() : _buildChartContentView(), + child: BlocBuilder( + builder: (context, state) { + return Container( + margin: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: _isCardView ? _buildCardContentView(state) : _buildChartContentView(), + ); + }, ), ), ], @@ -207,33 +162,89 @@ class _RunningStatusPageState extends State { ); } - Widget _buildCardContentView() { - return SingleChildScrollView( - // 添加 SingleChildScrollView 使内容可滚动 - padding: const EdgeInsets.all(20.0), + Widget _buildCardItem(String title, String value) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(title, style: const TextStyle(fontSize: 16, color: Color(0xFF333333))), + Text(value, style: const TextStyle(fontSize: 16, color: Color(0xFF666666))), + ], + ), + ); + } + + + + Widget _buildCardContentView(DeviceStatusState state) { + // 从 Bloc 状态中提取数据 + String voltage = '--'; + String leftSpeed = '--'; + String rightSpeed = '--'; + String yaw = '--'; + String pitch = '--'; + String roll = '--'; + String satelliteCnt = '--'; + String qual = '--'; + String latitude = '--'; + String longitude = '--'; + String timeStamp = '--'; + String knifeCuttingSpeed = '--'; + String controlMode = '--'; + String workingArea = '--'; + String battery = '--'; + String obstacleFlag = '--'; + + if (state is DeviceStatusUpdated) { + final status = state.status; + voltage = status.voltage.toStringAsFixed(2); + leftSpeed = status.leftTargetSpeed.toStringAsFixed(2); + rightSpeed = status.rightTargetSpeed.toStringAsFixed(2); + yaw = status.yaw.toStringAsFixed(2); + pitch = status.pitch.toStringAsFixed(2); + roll = status.roll.toStringAsFixed(2); + satelliteCnt = status.satelliteCnt.toString(); + qual = status.qual.toString(); + latitude = status.latitude.toStringAsFixed(8); + longitude = status.longitude.toStringAsFixed(8); + timeStamp = '${status.latitude} ${status.longitude}'; + knifeCuttingSpeed = status.knifeCuttingSpeed; + controlMode = status.controlMode; + workingArea = status.workingArea; + battery = status.battery; + obstacleFlag = status.obstacleFlag; + } else if (state is DeviceStatusError) { + voltage = '错误'; + } + + return Padding( + padding: const EdgeInsets.all(20.0),child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text("设备实时状态", style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), const SizedBox(height: 16), - _CardItem(title: "电压", value: "$_voltage V"), - _CardItem(title: "左轮速度", value: "$_leftSpeed rpm"), - _CardItem(title: "右轮速度", value: "$_rightSpeed rpm"), - _CardItem(title: "航向角", value: "$_yaw°"), - _CardItem(title: "俯仰角", value: "$_pitch°"), - _CardItem(title: "翻滚角", value: "$_roll°"), - _CardItem(title: "卫星数", value: _satelliteCnt), - _CardItem(title: "定位质量", value: _qual), - _CardItem(title: "纬度", value: _latitude), - _CardItem(title: "经度", value: _longitude), - _CardItem(title: "时间戳", value: _timeStamp), - _CardItem(title: "割刀速度", value: "$_knifeCuttingSpeed rpm"), - _CardItem(title: "控制模式", value: _controlMode), - _CardItem(title: "作业面积", value: "$_workingArea m²"), - _CardItem(title: "电量", value: "$_battery%"), - _CardItem(title: "障碍物标志", value: _obstacleFlag), + _buildCardItem("电压", "$voltage V"), + _buildCardItem("左轮速度", "$leftSpeed rpm"), + _buildCardItem( "右轮速度", "$rightSpeed rpm"), + _buildCardItem( "航向角", "$yaw°"), + _buildCardItem( "俯仰角", "$pitch°"), + _buildCardItem( "翻滚角", "$roll°"), + _buildCardItem( "卫星数", satelliteCnt), + _buildCardItem( "定位质量", qual), + _buildCardItem( "纬度", latitude), + _buildCardItem( "经度", longitude), + /* //_buildCardItem( "时间戳", timeStamp),*/ + _buildCardItem( "割刀速度", "$knifeCuttingSpeed rpm"), + _buildCardItem( "控制模式", controlMode), + _buildCardItem( "作业面积", "$workingArea m²"), + _buildCardItem( "电量", "$battery%"), + _buildCardItem( "障碍物标志", obstacleFlag), ], ), + + ), ); } @@ -249,25 +260,19 @@ class _RunningStatusPageState extends State { ), ); } -} -class _CardItem extends StatelessWidget { - final String title; - final String value; + void _refreshDeviceData() { + final deviceState = context.read().state; + final currentDevice = deviceState.selectedDevice; - const _CardItem({required this.title, required this.value}); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(title, style: const TextStyle(fontSize: 16, color: Color(0xFF333333))), - Text(value, style: const TextStyle(fontSize: 16, color: Color(0xFF666666))), - ], - ), - ); + if (currentDevice != null) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text("数据刷新成功!"), duration: Duration(seconds: 1)), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text("暂无设备,无法刷新"), duration: Duration(seconds: 1)), + ); + } } } diff --git a/lib/features/home/presentation/routes/home_routes.dart b/lib/features/home/presentation/routes/home_routes.dart index c7d697d8..9463f82e 100644 --- a/lib/features/home/presentation/routes/home_routes.dart +++ b/lib/features/home/presentation/routes/home_routes.dart @@ -4,6 +4,7 @@ import 'package:maibu_satabot_v2/features/home/presentation/pages/home_page.dart import '../../../../core/di/injection.dart'; import '../../../../core/router/route_paths.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'; @@ -27,7 +28,11 @@ class HomeRoutes { ), GoRoute( path: RoutePaths.runningStatus, - builder: (context, state) => const RunningStatusPage(), + // builder: (context, state) => const RunningStatusPage(), + builder: (context, state) => BlocProvider( + create: (_) => sl(), // 从 GetIt 获取单例 + child: const RunningStatusPage(), + ), ), ];