705 lines
24 KiB
Dart
705 lines
24 KiB
Dart
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/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<TaskMessageRepository>();
|
||
|
||
class DevicesCubit extends Cubit<DevicesState> {
|
||
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<ILoggerService>();
|
||
|
||
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<void> 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<void> 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<void> 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<void> switchDevice(DeviceEntity device) async {
|
||
// 保持现有列表,只改 loading
|
||
emit(state.copyWith(isLoading: true));
|
||
|
||
final result = await repository.switchDevice("app", device.deviceName);
|
||
|
||
result.fold(
|
||
// 失败处理
|
||
(l) {
|
||
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());
|
||
|
||
debugPrint('✅ 新设备切换完成(HTTP-only,无新TCP连接): ${device.deviceName}');
|
||
|
||
emit(state.copyWith(isLoading: false, selectedDevice: device));
|
||
},
|
||
);
|
||
}
|
||
|
||
/// 绑定设备
|
||
Future<void> 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<void> 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<void> 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<void> 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] 加载成功,记录数: ${records.length}');
|
||
// 将 WorkRecordEntity 转换为 Map<String, dynamic> 以兼容现有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);
|
||
}
|
||
return <String, dynamic>{
|
||
'id': record.id.toString(),
|
||
'workName': record.workName,
|
||
'imgUrl': record.imgUrl ?? '',
|
||
'jsonData': jsonDataStr,
|
||
};
|
||
}).toList();
|
||
// print('🔍 [DevicesCubit] 转换后的数据: $mappedRecords');
|
||
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<void> 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<void> loadSelectedPath(String workName) async {
|
||
emit(state.copyWith(isLoading: true));
|
||
try {
|
||
final result = await _selectWorkRecordUseCase.call(workName);
|
||
result.fold(
|
||
(failure) => emit(
|
||
state.copyWith(isLoading: false, errorMessage: failure.message),
|
||
),
|
||
(data) => emit(state.copyWith(isLoading: false, pathData: data)),
|
||
);
|
||
} catch (e) {
|
||
emit(state.copyWith(isLoading: false, errorMessage: e.toString()));
|
||
}
|
||
}
|
||
|
||
/// 保存路径数据
|
||
Future<void> 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<void> generatePath({
|
||
required ReferencePoint reference,
|
||
required int heading,
|
||
required OuterBoundary outer,
|
||
required Map<String, HoleBoundary> 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<void> startRoutePlanning(
|
||
Queue<DeviceAddPathPointModel> locationQueue,
|
||
) async {
|
||
/// print("cubit层开始路径规划");
|
||
_logger.logWithLevel('开始路径规划');
|
||
// 清空全局 Service 中的队列
|
||
// _routePlanningUseCase.clearLocationQueue();
|
||
final List<DeviceAddPathPointModel> 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<DeviceAddPathPointModel> 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<void> 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<void> 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<void> 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<DeviceEntity?> 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<void> 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<void> 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');
|
||
}
|
||
}
|
||
}
|