From e416416e85ba482a44582a6a3288e18ca03fc0da Mon Sep 17 00:00:00 2001 From: Songzex <2402265378@qq.com> Date: Mon, 2 Mar 2026 14:11:04 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BC=80=E5=A7=8B=E4=BD=9C=E4=B8=9A=EF=BC=8C?= =?UTF-8?q?=20=E6=9A=82=E5=81=9C=E4=BD=9C=E4=B8=9A=EF=BC=8C=20=E5=81=9C?= =?UTF-8?q?=E6=AD=A2=E4=BD=9C=E4=B8=9A=EF=BC=8C=20=E5=9B=9E=E5=A4=8D?= =?UTF-8?q?=E4=BD=9C=E4=B8=9A=EF=BC=8C=20---=E5=88=9D=E6=AD=A5=E5=BE=85?= =?UTF-8?q?=E4=BD=BF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/di/injection.dart | 17 ++- lib/core/network/tcp/tcp_client.dart | 23 +++- .../datasources/path_http_datasource.dart | 1 + .../data/models/route_plan_send_entity.dart | 38 ++++++ .../route_planning_repository_impl.dart | 116 ++++++++++++++++++ .../route_planning_repository.dart | 18 +++ .../usecases/route_planning_usecase.dart | 56 +++++++++ .../presentation/bloc/devices_cubit.dart | 62 ++++++++++ .../presentation/bloc/devices_state.dart | 8 ++ 9 files changed, 334 insertions(+), 5 deletions(-) create mode 100644 lib/features/devices/data/models/route_plan_send_entity.dart create mode 100644 lib/features/devices/data/repositories/route_planning_repository_impl.dart create mode 100644 lib/features/devices/domain/repositories/route_planning_repository.dart create mode 100644 lib/features/devices/domain/usecases/route_planning_usecase.dart diff --git a/lib/core/di/injection.dart b/lib/core/di/injection.dart index c7410a6b..72be782b 100644 --- a/lib/core/di/injection.dart +++ b/lib/core/di/injection.dart @@ -29,13 +29,16 @@ import '../../features/devices/data/datasources/path_http_datasource.dart'; import '../../features/devices/data/repositories/device_hostriry_work_impl.dart'; import '../../features/devices/data/repositories/device_repository_impl.dart'; import '../../features/devices/data/repositories/generate_path_repository_Impl.dart'; +import '../../features/devices/data/repositories/route_planning_repository_impl.dart'; import '../../features/devices/domain/repositories/device_hostrity_work_repository.dart'; import '../../features/devices/domain/repositories/path_repository.dart'; +import '../../features/devices/domain/repositories/route_planning_repository.dart'; import '../../features/devices/domain/usecases/delete_work_record_usecase.dart'; import '../../features/devices/domain/usecases/device_work_hostrirty_usecase.dart'; import '../../features/devices/domain/usecases/generate_path_usecase.dart'; import '../../features/devices/domain/usecases/get_device_location_usecase.dart'; 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/remote_control/presentation/bloc/remote_control_cubit.dart'; @@ -90,6 +93,17 @@ Future init() async { localTokenStorage: sl(), // 自动寻找已注册的 TokenStorage ), ); + + sl.registerLazySingleton( + () => RoutePlanningRepositoryImpl(tcp: sl()), // + ); + + sl.registerLazySingleton( + () => RoutePlanningUseCase(sl()), // + ); + + + //工作历史 sl.registerLazySingleton( () => DeviceHostrityWorkRepositoryImpl( @@ -114,11 +128,12 @@ Future init() async { sl.registerLazySingleton(() => SaveWorkRecordUseCase(sl())); + /// 5. 状态管理 (Cubit/Bloc) sl.registerLazySingleton(() => AppUserCubit()); // AuthCubit 依赖它,必须先注册 sl.registerLazySingleton(() => GetDeviceLocationUseCase(sl())); sl.registerLazySingleton( - () => DevicesCubit(sl(), sl(), sl(), sl(), sl(), sl(), sl(), sl(), sl(),sl()), + () => DevicesCubit(sl(), sl(), sl(), sl(), sl(), sl(), sl(), sl(), sl(),sl(),sl()), ); sl.registerFactory(() => RemoteControlCubit(sl())); diff --git a/lib/core/network/tcp/tcp_client.dart b/lib/core/network/tcp/tcp_client.dart index 9b8f669b..e181c605 100644 --- a/lib/core/network/tcp/tcp_client.dart +++ b/lib/core/network/tcp/tcp_client.dart @@ -4,7 +4,9 @@ import 'dart:io'; 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/devices/data/models/route_plan_send_entity.dart'; import '../../../features/devices/domain/entities/gps_entity.dart'; import '../../../features/devices/domain/entities/running_status_entity.dart'; import '../protocol_decoder.dart'; @@ -49,7 +51,7 @@ class TcpClient { rethrow; // 向上抛出连接异常 } } - + /// 发送数据 void send(Map data) { if (_socket == null) throw Exception("Socket not connected"); _socket!.write(jsonEncode(data)); @@ -109,12 +111,25 @@ class TcpClient { _socket = null; _controller.close(); debugPrint("TCP Disconnected"); + + } + + void sendPathPoint(RoutePlanSendEntity routePlanSendEntity) { + if (_socket == null) return; + final payload = routePlanSendEntity.toBytes(); + final packet = sendRaw(0x01, payload); // 第1种的测试 声明指令结构和类型0x01 为命令类型 + //_socket!.add(payload);//第二种的测试 + } + + + void sendDeviceStateChange(RoutePlanSendEntity routePlanSendEntity) { + if (_socket == null) return; + final payload = routePlanSendEntity.toBytes(); + _socket!.add(payload); + // final packet = sendRaw(0x02, payload); } - - - } diff --git a/lib/features/devices/data/datasources/path_http_datasource.dart b/lib/features/devices/data/datasources/path_http_datasource.dart index e1a4ff00..87799cc9 100644 --- a/lib/features/devices/data/datasources/path_http_datasource.dart +++ b/lib/features/devices/data/datasources/path_http_datasource.dart @@ -4,4 +4,5 @@ abstract class PathHttpDatasource { Future> generatePathRaw({ required Map body, }); + } diff --git a/lib/features/devices/data/models/route_plan_send_entity.dart b/lib/features/devices/data/models/route_plan_send_entity.dart new file mode 100644 index 00000000..fc91e904 --- /dev/null +++ b/lib/features/devices/data/models/route_plan_send_entity.dart @@ -0,0 +1,38 @@ +// lib/features/devices/data/models/route_plan_send_entity.dart +import 'dart:typed_data'; // ✅ 必须添加 + +class RoutePlanSendEntity { + final int commandType; + final int pointCounts; + final double targetLatitude; + final double targetLongitude; + final int speed; + + RoutePlanSendEntity({ + required this.commandType, + required this.pointCounts, + required this.targetLatitude, + required this.targetLongitude, + required this.speed, + }); + + + List toBytes() { + final bytes = []; + bytes.add(commandType); + bytes.addAll(_shortToBytes(pointCounts)); + bytes.addAll(_doubleToBytes(targetLatitude)); + bytes.addAll(_doubleToBytes(targetLongitude)); + bytes.addAll(_shortToBytes(speed)); + return bytes; + } + + List _shortToBytes(int value) { + return [(value >> 8) & 0xFF, value & 0xFF]; + } + + List _doubleToBytes(double value) { + final byteData = ByteData(8)..setFloat64(0, value); + return byteData.buffer.asUint8List(); + } +} diff --git a/lib/features/devices/data/repositories/route_planning_repository_impl.dart b/lib/features/devices/data/repositories/route_planning_repository_impl.dart new file mode 100644 index 00000000..6c8cd54e --- /dev/null +++ b/lib/features/devices/data/repositories/route_planning_repository_impl.dart @@ -0,0 +1,116 @@ +import 'dart:collection'; + +import 'package:maibu_satabot_v2/features/devices/data/models/device_add_path_point_model.dart'; +import '../../../../core/network/tcp/tcp_client.dart'; +import '../../domain/repositories/route_planning_repository.dart'; +import '../models/route_plan_send_entity.dart'; + +class RoutePlanningRepositoryImpl implements RoutePlanningRepository { + final TcpClient tcp; + late PathPlanner _planner; + + RoutePlanningRepositoryImpl({required this.tcp}) { // ✅ 加 {required} 和类型注解 + _planner = PathPlanner(tcp); + } + @override + Future startRoutePlanning(Queue locationQueue) async { + // 直接传入 DeviceAddPathPointModel 列表(无需转换) + final List locations = locationQueue.toList(); + _planner.startRoutePlanning(locations); + } + + @override + Future pauseRPWork() async { + // TODO: implement pauseRPWork + _planner.pauseRPWork(); + } + + @override + Future resumeRPWork() async { + // TODO: implement resumeRPWork + _planner.resumeRPWork(); + } + + @override + Future stopRoutePlanning() async { + // TODO: implement stopRoutePlanning + _planner.stopRoutePlanning(); + } +} + +// PathPlanner +class PathPlanner { + final Queue locationQueue = Queue(); + final TcpClient tcpClient; + bool isStart = false; + + PathPlanner(this.tcpClient); + + void sendNextLocation() { + if (isStart && locationQueue.isEmpty) { + isStart = false; + print("[track]"); + tcpClient.disconnect(); + return; + } + + final entity = locationQueue.removeFirst(); // 类型:DeviceAddPathPointModel + final routePlanSendEntity = RoutePlanSendEntity( + commandType: 0x01, + pointCounts: 1, + targetLatitude: entity.latitude, + targetLongitude: entity.longitude, + speed: 1000, + ); + + print("====Lat:${entity.latitude} ====Lng:${entity.longitude}"); + tcpClient.sendPathPoint( routePlanSendEntity); + isStart = true; + } +/// 开始 + void startRoutePlanning(List locations) { + locationQueue.addAll(locations); + isStart = true; + sendNextLocation(); + } + /// 暂停 + void pauseRPWork() { + var entity = new RoutePlanSendEntity( + commandType: 0x02, + pointCounts: 2, + targetLatitude: 0, + targetLongitude: 0, + speed: 0, + ); + tcpClient.sendDeviceStateChange(entity); + //更新APP状态 + } + /// 恢复 + void resumeRPWork() { + var entity = new RoutePlanSendEntity( + commandType: 0x02, + pointCounts: 3, + targetLatitude: 0, + targetLongitude: 0, + speed: 0, + ); + tcpClient.sendDeviceStateChange(entity); + //更新APP状态 + } + /// 停止 + void stopRoutePlanning() { + var entity = new RoutePlanSendEntity( + commandType: 0x02, + pointCounts: 0, + targetLatitude: 0, + targetLongitude: 0, + speed: 0, + ); + tcpClient.sendDeviceStateChange(entity); + //更新APP状态 + } + + +} + + diff --git a/lib/features/devices/domain/repositories/route_planning_repository.dart b/lib/features/devices/domain/repositories/route_planning_repository.dart new file mode 100644 index 00000000..f20270c4 --- /dev/null +++ b/lib/features/devices/domain/repositories/route_planning_repository.dart @@ -0,0 +1,18 @@ +// lib/features/devices/domain/repositories/route_planning_repository.dart +import 'dart:collection'; + +import 'package:collection/collection.dart'; + +import '../../data/models/device_add_path_point_model.dart'; + + +abstract class RoutePlanningRepository { + /// Start + Future startRoutePlanning(Queue locationQueue); + /// Stop + Future stopRoutePlanning(); + /// Pause + Future pauseRPWork(); + /// Resume + Future resumeRPWork(); +} diff --git a/lib/features/devices/domain/usecases/route_planning_usecase.dart b/lib/features/devices/domain/usecases/route_planning_usecase.dart new file mode 100644 index 00000000..b6270d57 --- /dev/null +++ b/lib/features/devices/domain/usecases/route_planning_usecase.dart @@ -0,0 +1,56 @@ +import 'dart:collection'; + +import 'package:fpdart/fpdart.dart'; +import '../../data/models/device_add_path_point_model.dart'; +import '../../domain/repositories/route_planning_repository.dart'; + +class RoutePlanningUseCase { + final RoutePlanningRepository repository; + + RoutePlanningUseCase(this.repository); + + Future> startRoutePlanning( + Queue locationQueue, + ) async { + try { + await repository.startRoutePlanning(locationQueue); + return right(unit); + } catch (e) { + return left(RoutePlanningFailure(message: e.toString())); + } + } + + Future> pauseRPWork() async { + try { + await repository.pauseRPWork(); + return right(unit); + } catch (e) { + return left(RoutePlanningFailure(message: e.toString())); + } + } + + Future> resumeRPWork() async { + try { + await repository.resumeRPWork(); + return right(unit); + } catch (e) { + return left(RoutePlanningFailure(message: e.toString())); + } + } + + Future> stopRoutePlanning() async { + try { + await repository.stopRoutePlanning(); + return right(unit); + } catch (e) { + return left(RoutePlanningFailure(message: e.toString())); + } + } +} + +// Failure 类型 +class RoutePlanningFailure { + final String? message; + + RoutePlanningFailure({this.message}); +} diff --git a/lib/features/devices/presentation/bloc/devices_cubit.dart b/lib/features/devices/presentation/bloc/devices_cubit.dart index 2e60710c..8f663dad 100644 --- a/lib/features/devices/presentation/bloc/devices_cubit.dart +++ b/lib/features/devices/presentation/bloc/devices_cubit.dart @@ -1,3 +1,5 @@ +import 'dart:collection'; + import 'package:flutter/rendering.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:maibu_satabot_v2/features/devices/domain/entities/device_entity.dart'; @@ -9,10 +11,12 @@ import 'package:maibu_satabot_v2/features/devices/domain/usecases/select_work_re 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 '../../data/models/device_add_path_point_model.dart'; import '../../data/models/device_work_area_param_model.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/route_planning_usecase.dart'; import 'devices_state.dart'; class DevicesCubit extends Cubit { @@ -26,6 +30,7 @@ class DevicesCubit extends Cubit { final SelectWorkRecordUseCase _selectWorkRecordUseCase; final SaveWorkRecordUseCase _saveWorkRecordUseCase; final GeneratePathUseCase _generatePathUseCase; + final RoutePlanningUseCase _routePlanningUseCase; // DevicesCubit( this.repository, @@ -38,6 +43,7 @@ class DevicesCubit extends Cubit { this._selectWorkRecordUseCase, this._saveWorkRecordUseCase, this._generatePathUseCase, + this._routePlanningUseCase ) : super(const DevicesState()); Future unbindDevice(String deviceId, String deviceName) async { @@ -239,4 +245,60 @@ class DevicesCubit extends Cubit { result.fold((failure) => emit(state.copyWith(errorMessage: failure.message)), (pathData) => emit(state.copyWith(generatedPath: pathData))); } +// 开始路径规划 + Future startRoutePlanning(Queue locationQueue) async { + emit(state.copyWith(isLoading: true)); + final result = await _routePlanningUseCase.startRoutePlanning(locationQueue); + 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(); + result.fold( + (failure) => emit(state.copyWith( + isLoading: false, + errorMessage: failure.message ?? '停止失败', + )), + (_) => emit(state.copyWith(isLoading: false)), + ); + } + + } diff --git a/lib/features/devices/presentation/bloc/devices_state.dart b/lib/features/devices/presentation/bloc/devices_state.dart index ccb868f9..4235e85d 100644 --- a/lib/features/devices/presentation/bloc/devices_state.dart +++ b/lib/features/devices/presentation/bloc/devices_state.dart @@ -8,6 +8,12 @@ enum DeviceOperationType { updateName, // 修改设备名称 } +enum AppState { + none, //无作业状态 + routePlanning, //正在规划路径 +} + + class DevicesState extends Equatable { final List devices; // 名下所有设备列表 @@ -20,6 +26,7 @@ class DevicesState extends Equatable { final DeviceOperationType operationType; // 标记当前操作类型 final List>? pathData; final List? generatedPath; // 新增字段 + final AppState appState; @@ -34,6 +41,7 @@ class DevicesState extends Equatable { this.operationType = DeviceOperationType.none, // 默认无操作 this.pathData, this.generatedPath, + this.appState = AppState.none, }); // 使用 copyWith 方便局部更新状态