import 'dart:convert'; import 'dart:collection'; import 'package:flutter/rendering.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:get_it/get_it.dart'; import 'package:maibu_satabot_v2/features/devices/domain/entities/device_entity.dart'; import 'package:maibu_satabot_v2/features/devices/domain/repositories/device_repository.dart'; import 'package:maibu_satabot_v2/features/devices/domain/usecases/generate_path_usecase.dart'; import 'package:maibu_satabot_v2/features/devices/domain/usecases/get_user_device_usecase.dart'; import 'package:maibu_satabot_v2/features/devices/data/models/work_record_entity.dart'; import 'package:maibu_satabot_v2/features/devices/domain/usecases/save_work_record_usecase.dart'; import 'package:maibu_satabot_v2/features/devices/domain/usecases/select_work_record_usecase.dart'; import 'package:maibu_satabot_v2/features/devices/domain/usecases/unbind_device_usecase.dart'; import 'package:maibu_satabot_v2/features/devices/domain/usecases/update_devicename_usecase.dart'; import '../../../../core/consts/tcp_consts.dart'; import '../../../../core/logging/i_logger_service.dart'; import '../../../../core/logging/log_time.dart'; import '../../../../core/network/tcp/tcp_client.dart'; import '../../../../core/network/mqtt/domain/repositories/task_message_repository.dart'; import '../../data/models/device_add_path_point_model.dart'; import '../../data/models/device_work_area_param_model.dart'; import '../../domain/usecases/bind_device_usecase.dart'; import '../../domain/usecases/delete_work_record_usecase.dart'; import '../../domain/usecases/get_device_location_usecase.dart'; import '../../domain/usecases/get_work_record_usecase.dart'; import '../../domain/usecases/get_work_records_by_site_id_usecase.dart'; import '../../domain/usecases/route_planning_usecase.dart'; import '../../services/path_planning_service.dart'; import 'device_status_bloc.dart'; import 'device_status_event.dart'; import 'devices_state.dart'; // 定义全局的TaskMessageRepository获取方式 TaskMessageRepository get _taskMessageRepo => GetIt.I(); class DevicesCubit extends Cubit { final GetUserDeviceUseCase _getUserDeviceUseCase; final GetDeviceLocationUseCase _getDeviceLocationUseCase; final DeviceRepository repository; final GetWorkRecordUseCase _getWorkRecordUseCase; final GetWorkRecordsBySiteIdUseCase _getWorkRecordsBySiteIdUseCase; final DeleteWorkRecordUseCase _deleteWorkRecordUseCase; final BindDeviceUseCase _bindDeviceUseCase; final UnbindDeviceUseCase _unbindDeviceUseCase; final UpdateDevicenameUsecase _updateDevicename; final SelectWorkRecordUseCase _selectWorkRecordUseCase; final SaveWorkRecordUseCase _saveWorkRecordUseCase; final GeneratePathUseCase _generatePathUseCase; final RoutePlanningUseCase _routePlanningUseCase; // final DeviceStatusBloc _deviceStatusBloc; // 🔥 新增字段 final TcpClient _tcpClient; // 🔥 关键:注入全局服务 final PathPlanningService _pathPlanningService; final ILoggerService _logger = GetIt.I(); DevicesCubit( this.repository, this._getUserDeviceUseCase, this._getDeviceLocationUseCase, this._getWorkRecordUseCase, this._getWorkRecordsBySiteIdUseCase, this._deleteWorkRecordUseCase, this._unbindDeviceUseCase, this._updateDevicename, this._selectWorkRecordUseCase, this._saveWorkRecordUseCase, this._generatePathUseCase, this._routePlanningUseCase, this._bindDeviceUseCase, this._deviceStatusBloc, this._tcpClient, this._pathPlanningService, ) : super(const DevicesState()); Future unbindDevice(String deviceId, String deviceName) async { emit( state.copyWith( isLoading: true, errorMessage: '', operationType: DeviceOperationType.unbind, ), ); try { final params = UnbindDeviceParams(deviceId, deviceName); final result = await _unbindDeviceUseCase.call(params); result.fold( (failure) => emit( state.copyWith( isLoading: false, errorMessage: failure.message ?? '解绑设备失败', operationType: DeviceOperationType.none, // 操作结束重置 ), ), (successCode) { // 核心修复:int 转 bool 条件判断 final isSuccess = successCode == 1; // 显式转为 bool if (isSuccess) { final updatedDevices = state.devices?.where((device) { return device.deviceName != deviceId; }).toList(); emit( state.copyWith( isLoading: false, devices: updatedDevices, selectedDevice: state.selectedDevice?.deviceName == deviceId ? null : state.selectedDevice, errorMessage: '', operationType: DeviceOperationType.none, ), ); } else { emit( state.copyWith( isLoading: false, errorMessage: '解绑失败:状态码 $successCode', operationType: DeviceOperationType.none, ), ); } }, ); } catch (e) { emit( state.copyWith( isLoading: false, errorMessage: '解绑异常:${e.toString()}', operationType: DeviceOperationType.none, ), ); } } Future updateDeviceName(String deviceId, String deviceName) async { emit( state.copyWith( isLoading: true, errorMessage: '', operationType: DeviceOperationType.updateName, ), ); try { final params = UpdateDevicenameParams(deviceId, deviceName); final result = await _updateDevicename.call(params); result.fold( (failure) => emit( state.copyWith( isLoading: false, errorMessage: failure.message ?? '更新设备名称失败', operationType: DeviceOperationType.none, ), ), (successCode) { // 核心修复:int 转 bool 条件判断 //print('更新设备名称结果代码: $successCode'); // 调试输出结果代码 _logger.logWithLevel('更新设备名称结果代码: $successCode'); final isSuccess = successCode == 1; // 显式转为 bool if (isSuccess) { //final updatedDevices = state.devices?.map((device) { // return device.deviceName == deviceId ? device.copyWith(deviceName: deviceName) : device; //}).toList(); emit( state.copyWith( isLoading: false, //devices: updatedDevices, //selectedDevice: state.selectedDevice?.deviceName == deviceId ? state.selectedDevice?.copyWith(deviceName: deviceName) : state.selectedDevice, errorMessage: '', operationType: DeviceOperationType.none, ), ); } else { emit( state.copyWith( isLoading: false, errorMessage: '更新设备名称失败:状态码 $successCode', operationType: DeviceOperationType.none, ), ); } }, ); } catch (e) { emit( state.copyWith( isLoading: false, errorMessage: '更新设备名称异常:${e.toString()}', operationType: DeviceOperationType.none, ), ); } } // 获取所有设备列表 // 获取所有设备列表 Future fetchAllDevices(String username) async { emit(state.copyWith(isLoading: true)); try { // 🔥 关键步骤1:记录刷新前的选中设备标识(用 deviceName 作为唯一标识) final String? oldSelectedDeviceName = state.selectedDevice?.deviceName; // 网络请求获取新列表 var resultEither = await _getUserDeviceUseCase.call( GetUserDeviceParams(username), ); resultEither.fold( (failure) => emit( state.copyWith(isLoading: false, errorMessage: failure.message), ), (deviceList) { // 🔥 关键步骤2:匹配新列表中对应的旧选中设备 DeviceEntity? newSelectedDevice; if (oldSelectedDeviceName != null && deviceList.isNotEmpty) { // 在新列表中查找和旧选中设备名称一致的设备 newSelectedDevice = deviceList.firstWhere( (device) => device.deviceName == oldSelectedDeviceName, // 如果找不到(如设备已解绑),返回 null orElse: () => deviceList.first, // 兜底:选中第一个 ); } else { // 无旧选中设备,默认选中第一个 newSelectedDevice = deviceList.isNotEmpty ? deviceList.first : null; } // 🔥 关键步骤3:更新状态,使用匹配后的选中设备 emit( state.copyWith( devices: deviceList, selectedDevice: newSelectedDevice, // 保留旧选中设备 isLoading: false, ), ); }, ); } catch (e) { emit(state.copyWith(isLoading: false, errorMessage: e.toString())); } } // 切换当前选中的设备 void selectDevice(DeviceEntity device) { emit(state.copyWith(selectedDevice: device)); } // 更新单个设备的状态(例如从 Tcp 收到实时电量更新) void updateDeviceStatus(DeviceEntity updatedDevice) { final newList = state.devices.map((d) { return d.deviceName == updatedDevice.deviceName ? updatedDevice : d; }).toList(); // 如果更新的是当前选中的设备,也要同步更新 selectedDevice final newSelected = state.selectedDevice?.deviceName == updatedDevice.deviceName ? updatedDevice : state.selectedDevice; emit(state.copyWith(devices: newList, selectedDevice: newSelected)); } /// 切换设备 Future switchDevice(DeviceEntity device) async { debugPrint('${LogTime.now()} 👆 [DevicesCubit] 开始切换设备: ${device.deviceName}'); // 保持现有列表,只改 loading emit(state.copyWith(isLoading: true)); final result = await repository.switchDevice("app", device.deviceName); result.fold( // 失败处理 (l) { debugPrint('${LogTime.now()} ❌ [DevicesCubit] 切换设备失败: ${device.deviceName}, 原因: ${l.message}'); emit(state.copyWith(isLoading: false, errorMessage: l.message)); selectDevice(device); }, // 成功处理:改为 async 函数块 (r) async { // 🔥 关键修复:不再断开 TCP 并重新连接 // TCP 已在登录时建立,设备切换只用 HTTP // 避免创建新 TCP 连接发送 0x03 触发服务端推送 have_logged_in // 重置设备状态 Bloc,清除旧设备图表数据 _deviceStatusBloc.add(DeviceStatusReset()); // 🔥 机器状态数据源已切换为MQTT:用与TCP相同的方式获取设备号(deviceName), // 订阅 mower/{sn}/property/* 主题(内部自动退订旧设备、同SN去重) debugPrint('${LogTime.now()} 📡 [DevicesCubit] HTTP切换成功,开始MQTT订阅: ${device.deviceName}'); _deviceStatusBloc.startMqttRealtimeListening(device.deviceName); debugPrint('${LogTime.now()} ✅ 新设备切换完成(HTTP-only,无新TCP连接): ${device.deviceName}'); emit(state.copyWith(isLoading: false, selectedDevice: device)); }, ); } /// 绑定设备 Future bindDevice(String deviceId, String deviceAlias) async { emit(state.copyWith(isLoading: true, errorMessage: '')); try { final params = BindDeviceParams(deviceId, deviceAlias); final result = await _bindDeviceUseCase.call(params); result.fold( // 失败处理 (failure) { emit( state.copyWith( isLoading: false, errorMessage: failure.message ?? '绑定设备失败', ), ); // 抛出异常,携带后端返回的错误消息(如“设备不存在”) throw Exception(failure.message ?? '绑定设备失败'); }, // 成功处理(返回 int 状态码) (successCode) { if (successCode == 1) { emit(state.copyWith(isLoading: false, errorMessage: '')); // 绑定成功后刷新设备列表 //fetchAllDevices(state.?.username ?? ''); } else { emit( state.copyWith( isLoading: false, errorMessage: '绑定失败:状态码 $successCode', ), ); } }, ); } catch (e) { // emit(state.copyWith( // isLoading: false, // errorMessage: '绑定异常:${e.toString()}', // )); // 🔥 重新抛出,传递给 UI rethrow; } } /// 获取设备位置 Future getDeviceLocation(DeviceEntity device) async { emit(state.copyWith(isLoading: true)); final result = await _getDeviceLocationUseCase.call(device.deviceName); result.fold( (failure) => emit(state.copyWith(isLoading: false, errorMessage: failure.message)), (location) => emit( state.copyWith( isLoading: false, deviceLatitude: location.latitude, deviceLongitude: location.longitude, ), ), ); } ///获取记录 Future loadWorkRecords(String userId) async { emit(state.copyWith(isLoading: true)); final result = await _getWorkRecordUseCase(userId); result.fold( (failure) => emit(state.copyWith(isLoading: false, errorMessage: failure.message)), (records) => emit(state.copyWith(isLoading: false, workRecords: records)), ); } /// 根据场站ID获取工作记录(XML格式接口) Future loadWorkRecordsBySiteId(int siteId) async { print('🔍 [DevicesCubit] 开始加载场站ID=$siteId的工作记录'); emit(state.copyWith(isLoading: true)); final result = await _getWorkRecordsBySiteIdUseCase(siteId); result.fold( (failure) { print('❌ [DevicesCubit] 加载失败: ${failure.message}'); emit(state.copyWith(isLoading: false, errorMessage: failure.message)); }, (records) { print('🔍 [DevicesCubit] selectBySiteId 加载成功,记录数: ${records.length}'); // 将 WorkRecordEntity 转换为 Map 以兼容现有UI final mappedRecords = records.map((record) { // 🔥 核心修复:根据 jsonData 类型决定存储方式 // XML接口返回的已是 JSON 字符串,直接使用,避免二次序列化丢失路径数据 // JSON接口返回的 WorkRecordJsonData 对象,仍需序列化 String? jsonDataStr; if (record.jsonData is String) { jsonDataStr = record.jsonData as String; } else if (record.jsonData is WorkRecordJsonData) { jsonDataStr = _workRecordJsonDataToJson(record.jsonData as WorkRecordJsonData); } final mapped = { 'id': record.id.toString(), 'workName': record.workName, 'imgUrl': record.imgUrl ?? '', 'jsonData': jsonDataStr, }; print(' ├─ [${record.workName}] id=${record.id}, jsonData长度=${jsonDataStr?.length ?? "null"}'); // 打印 jsonData 内部的 path/outer 信息 if (jsonDataStr != null && jsonDataStr.isNotEmpty) { try { final decoded = jsonDecode(jsonDataStr); if (decoded is Map) { print(' │ path类型=${decoded['path']?.runtimeType}, outer类型=${decoded['outer']?.runtimeType}, planModel=${decoded['planModel']}'); if (decoded['path'] is List) print(' │ path长度=${(decoded['path'] as List).length}'); if (decoded['outer'] is List) print(' │ outer长度=${(decoded['outer'] as List).length}'); } } catch (_) {} } return mapped; }).toList(); print('🔍 [DevicesCubit] 转换完成,emit workRecords 数量: ${mappedRecords.length}'); emit(state.copyWith(isLoading: false, workRecords: mappedRecords)); }, ); } /// 将 WorkRecordJsonData 转换为 JSON 字符串 String _workRecordJsonDataToJson(dynamic jsonData) { // 将 jsonData 对象序列化为 JSON 字符串供UI使用 if (jsonData is WorkRecordJsonData) { return jsonEncode({ 'name': jsonData.name, 'path': jsonData.path, 'outer': jsonData.outer, 'img': jsonData.img, 'planModel': jsonData.planModel, }); } return jsonData.toString(); } /// 删除工作记录 Future deleteWorkRecord(String workName) async { emit(state.copyWith(isLoading: true)); final result = await _deleteWorkRecordUseCase(workName); result.fold( (failure) => emit(state.copyWith(isLoading: false, errorMessage: failure.message)), (_) => emit( state.copyWith( isLoading: false, workRecords: state.workRecords ?.where((record) => record != workName) .toList(), ), ), ); } /// 加载指定作业名的路径数据 Future loadSelectedPath(String workName) async { emit(state.copyWith(isLoading: true)); debugPrint('📡 [loadSelectedPath] 开始查询, workName=$workName'); try { final result = await _selectWorkRecordUseCase.call(workName); result.fold( (failure) { debugPrint('❌ [loadSelectedPath] 接口失败: ${failure.message}'); emit( state.copyWith(isLoading: false, errorMessage: failure.message), ); }, (data) { debugPrint('✅ [loadSelectedPath] 接口成功, 记录数: ${data.length}'); if (data.isNotEmpty) { final first = data.first; debugPrint(' ├─ 第一条记录 keys: ${first.keys.toList()}'); debugPrint(' ├─ path 类型: ${first['path']?.runtimeType}, 长度: ${(first['path'] as List?)?.length ?? "null"}'); debugPrint(' ├─ outer 类型: ${first['outer']?.runtimeType}, 长度: ${(first['outer'] as List?)?.length ?? "null"}'); debugPrint(' └─ planModel: ${first['planModel']}'); if (first['path'] is List && (first['path'] as List).isNotEmpty) { debugPrint(' └─ path[0]: ${(first['path'] as List).first}'); } if (first['outer'] is List && (first['outer'] as List).isNotEmpty) { debugPrint(' └─ outer[0]: ${(first['outer'] as List).first}'); } } emit(state.copyWith(isLoading: false, pathData: data)); }, ); } catch (e) { debugPrint('❌ [loadSelectedPath] 异常: $e'); emit(state.copyWith(isLoading: false, errorMessage: e.toString())); } } /// 保存路径数据 Future saveWorkRecord( String workName, String userId, String jsonData, ) async { emit(state.copyWith(isLoading: true)); final result = await _saveWorkRecordUseCase.call( workName: workName, userId: userId, jsonData: jsonData, ); result.fold( (failure) => emit(state.copyWith(isLoading: false, errorMessage: failure.message)), (data) => emit(state.copyWith(isLoading: false)), ); } /// generatePath Future generatePath({ required ReferencePoint reference, required int heading, required OuterBoundary outer, required Map holes, required int workType, }) async { //holes.asMap().forEach((index, hole) { // final pointsStr = hole.position.map((p) => "(${p.lat.toStringAsFixed(6)}, ${p.lon.toStringAsFixed(6)})").join(', '); // debugPrint('第${index + 1}组holes:$pointsStr'); //}); emit(state.copyWith(isLoading: true)); final result = await _generatePathUseCase.execute( reference: reference, heading: heading, outer: outer, holes: holes, workType: workType, ); result.fold( (failure) => emit(state.copyWith(errorMessage: failure.message)), (pathData) => emit(state.copyWith(generatedPath: pathData)), ); } // 开始路径规划 Future startRoutePlanning( Queue locationQueue, ) async { /// print("cubit层开始路径规划"); _logger.logWithLevel('开始路径规划'); // 清空全局 Service 中的队列 // _routePlanningUseCase.clearLocationQueue(); final List pathList = locationQueue.toList(); // print('✅ 队列转换为 List,长度:${pathList.length}'); _logger.logWithLevel('队列转换为 List,长度:${pathList.length}'); _pathPlanningService.updateQueue(pathList); //print('✅ 队列更新成功'); _logger.logWithLevel('队列更新成功'); // 开始路径规划 emit(state.copyWith(isLoading: true)); //传入全局的Service 中路径规划队列 Queue queue = _pathPlanningService.getQueue(); //print('📦 从 Service 获取的队列长度:${queue.length}'); _logger.logWithLevel('从 Service 获取的队列长度:${queue.length}'); final result = await _routePlanningUseCase.startRoutePlanning(queue); result.fold( (failure) => emit( state.copyWith( isLoading: false, errorMessage: failure.message ?? '路径规划启动失败', ), ), (_) => emit(state.copyWith(isLoading: false, errorMessage: '')), ); } // 暂停 Future pauseRoutePlanning() async { emit(state.copyWith(isLoading: true)); final result = await _routePlanningUseCase.pauseRPWork(); result.fold( (failure) => emit( state.copyWith( isLoading: false, errorMessage: failure.message ?? '暂停失败', ), ), (_) => emit(state.copyWith(isLoading: false)), ); } // 恢复 Future resumeRoutePlanning() async { emit(state.copyWith(isLoading: true)); final result = await _routePlanningUseCase.resumeRPWork(); result.fold( (failure) => emit( state.copyWith( isLoading: false, errorMessage: failure.message ?? '恢复失败', ), ), (_) => emit(state.copyWith(isLoading: false)), ); } // 停止 Future stopRoutePlanning() async { emit(state.copyWith(isLoading: true)); final result = await _routePlanningUseCase.stopRoutePlanning(); // 🔥 重置 PathPlanningService 中的全局的队列 _pathPlanningService.clear(); result.fold( (failure) => emit( state.copyWith( isLoading: false, errorMessage: failure.message ?? '停止失败', ), ), (_) => emit(state.copyWith(isLoading: false)), ); } void updateAppState(AppState appState) { emit(state.copyWith(appState: appState)); (_routePlanningUseCase as dynamic).repository.setAppState(appState); } void finishWork() { emit(state.copyWith(isFinshWork: true)); } /// 重置完成状态 void resetWorkStatus() { emit(state.copyWith(isFinshWork: false)); } void setArrivedLocation(double latitude, double longitude) { emit(state.copyWith(arriLatitude: latitude, arriLongitude: longitude)); // print('✅ [DevicesCubit] 监听更新已完成到达位置:Lat=$latitude, Lng=$longitude'); _logger.logWithLevel( '✅ [DevicesCubit] 监听更新已完成到达位置:Lat=$latitude, Lng=$longitude', ); } //获取已到达的点的经纬度 (double, double) getArrivedLocation() { return (state.arriLatitude ?? 0.0, state.arriLongitude ?? 0.0); } // 重置已到达记录 void resetArrivedLocation() { emit(state.copyWith(arriLatitude: null, arriLongitude: null)); //print('🔄 [DevicesCubit] 已重置到达位置'); _logger.logWithLevel('🔄 [DevicesCubit] 已重置到达位置'); } /// 🔥 获取当前选中的设备 DeviceEntity? getSelectedDevice() { return state.selectedDevice; } /// 🔥 异步获取当前选中的设备(兼容 RemoteControlCubit 接口) Future getDevice() async { return state.selectedDevice; } /// 🔥 判断是否有选中设备 bool hasSelectedDevice() { return state.selectedDevice != null; } /// 🔥 获取选中设备名称(安全获取,返回空字符串而非null) String getSelectedDeviceName() { return state.selectedDevice?.deviceName ?? ''; } /// 🔥 获取选中设备ID(安全获取,返回空字符串而非null) String getSelectedDeviceId() { return state.selectedDevice?.deviceName ?? ''; } /// 🔥 清除选中设备 void clearSelectedDevice() { debugPrint('🧹 [DevicesCubit] 清除选中设备'); _logger.logWithLevel('🧹 [DevicesCubit] 清除选中设备'); emit(state.copyWith(selectedDevice: null)); } /// 🔥 检查设备是否在列表中 bool isDeviceInList(String deviceName) { return state.devices.any((device) => device.deviceName == deviceName); } /// 🔥 根据设备名称查找设备 DeviceEntity? findDeviceByName(String deviceName) { try { return state.devices.firstWhere( (device) => device.deviceName == deviceName, orElse: () => throw Exception('Device not found'), ); } catch (e) { return null; } } /// 🔥 启动MQTT到达点监听(用于路径规划动画) /// [deviceId] - 目标设备ID,即targetDevice的deviceId /// [taskId] - 任务ID,用于订阅 task/{taskId}/status 和 task/{taskId}/arrive Future startListeningMqttArrive({required String deviceId, required int taskId}) async { debugPrint('📡 [DevicesCubit] 启动MQTT到达点监听 - deviceId: $deviceId, taskId: $taskId'); _logger.logWithLevel('📡 [DevicesCubit] 启动MQTT到达点监听 - deviceId: $deviceId, taskId: $taskId'); try { // 启动MQTT订阅 await _taskMessageRepo.startListening(deviceId: deviceId, taskId: taskId); // 设置DeviceStatusBloc监听的设备ID _deviceStatusBloc.setListeningDeviceId(deviceId); debugPrint('✅ [DevicesCubit] MQTT到达点监听已启动'); _logger.logWithLevel('✅ [DevicesCubit] MQTT到达点监听已启动'); } catch (e) { debugPrint('❌ [DevicesCubit] 启动MQTT监听失败: $e'); _logger.logWithLevel('❌ [DevicesCubit] 启动MQTT监听失败: $e'); rethrow; } } /// 🔥 停止MQTT到达点监听 Future stopListeningMqttArrive() async { debugPrint('🛑 [DevicesCubit] 停止MQTT到达点监听'); _logger.logWithLevel('🛑 [DevicesCubit] 停止MQTT到达点监听'); try { await _taskMessageRepo.stopListening(); _deviceStatusBloc.setListeningDeviceId(''); debugPrint('✅ [DevicesCubit] MQTT到达点监听已停止'); _logger.logWithLevel('✅ [DevicesCubit] MQTT到达点监听已停止'); } catch (e) { debugPrint('❌ [DevicesCubit] 停止MQTT监听失败: $e'); _logger.logWithLevel('❌ [DevicesCubit] 停止MQTT监听失败: $e'); } } }