开始作业,

暂停作业,
停止作业,
回复作业,
---初步待使用
This commit is contained in:
2026-03-02 14:11:04 +08:00
parent fe9b763783
commit e416416e85
9 changed files with 334 additions and 5 deletions

View File

@@ -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<void> init() async {
localTokenStorage: sl(), // 自动寻找已注册的 TokenStorage
),
);
sl.registerLazySingleton<RoutePlanningRepository>(
() => RoutePlanningRepositoryImpl(tcp: sl<TcpClient>()), //
);
sl.registerLazySingleton<RoutePlanningUseCase>(
() => RoutePlanningUseCase(sl<RoutePlanningRepository>()), //
);
//工作历史
sl.registerLazySingleton<DeviceHostrityWorkRepositoryRepository>(
() => DeviceHostrityWorkRepositoryImpl(
@@ -114,11 +128,12 @@ Future<void> init() async {
sl.registerLazySingleton<SaveWorkRecordUseCase>(() => SaveWorkRecordUseCase(sl<PathRepository>()));
/// 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()));

View File

@@ -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<String, dynamic> 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);
}
}

View File

@@ -4,4 +4,5 @@ abstract class PathHttpDatasource {
Future<List<DeviceAddPathPointModel>> generatePathRaw({
required Map<String, dynamic> body,
});
}

View File

@@ -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<int> toBytes() {
final bytes = <int>[];
bytes.add(commandType);
bytes.addAll(_shortToBytes(pointCounts));
bytes.addAll(_doubleToBytes(targetLatitude));
bytes.addAll(_doubleToBytes(targetLongitude));
bytes.addAll(_shortToBytes(speed));
return bytes;
}
List<int> _shortToBytes(int value) {
return [(value >> 8) & 0xFF, value & 0xFF];
}
List<int> _doubleToBytes(double value) {
final byteData = ByteData(8)..setFloat64(0, value);
return byteData.buffer.asUint8List();
}
}

View File

@@ -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<void> startRoutePlanning(Queue<DeviceAddPathPointModel> locationQueue) async {
// 直接传入 DeviceAddPathPointModel 列表(无需转换)
final List<DeviceAddPathPointModel> locations = locationQueue.toList();
_planner.startRoutePlanning(locations);
}
@override
Future<void> pauseRPWork() async {
// TODO: implement pauseRPWork
_planner.pauseRPWork();
}
@override
Future<void> resumeRPWork() async {
// TODO: implement resumeRPWork
_planner.resumeRPWork();
}
@override
Future<void> stopRoutePlanning() async {
// TODO: implement stopRoutePlanning
_planner.stopRoutePlanning();
}
}
// PathPlanner
class PathPlanner {
final Queue<DeviceAddPathPointModel> 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<DeviceAddPathPointModel> 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状态
}
}

View File

@@ -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<void> startRoutePlanning(Queue<DeviceAddPathPointModel> locationQueue);
/// Stop
Future<void> stopRoutePlanning();
/// Pause
Future<void> pauseRPWork();
/// Resume
Future<void> resumeRPWork();
}

View File

@@ -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<Either<RoutePlanningFailure, Unit>> startRoutePlanning(
Queue<DeviceAddPathPointModel> locationQueue,
) async {
try {
await repository.startRoutePlanning(locationQueue);
return right(unit);
} catch (e) {
return left(RoutePlanningFailure(message: e.toString()));
}
}
Future<Either<RoutePlanningFailure, Unit>> pauseRPWork() async {
try {
await repository.pauseRPWork();
return right(unit);
} catch (e) {
return left(RoutePlanningFailure(message: e.toString()));
}
}
Future<Either<RoutePlanningFailure, Unit>> resumeRPWork() async {
try {
await repository.resumeRPWork();
return right(unit);
} catch (e) {
return left(RoutePlanningFailure(message: e.toString()));
}
}
Future<Either<RoutePlanningFailure, Unit>> 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});
}

View File

@@ -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<DevicesState> {
@@ -26,6 +30,7 @@ class DevicesCubit extends Cubit<DevicesState> {
final SelectWorkRecordUseCase _selectWorkRecordUseCase;
final SaveWorkRecordUseCase _saveWorkRecordUseCase;
final GeneratePathUseCase _generatePathUseCase;
final RoutePlanningUseCase _routePlanningUseCase; //
DevicesCubit(
this.repository,
@@ -38,6 +43,7 @@ class DevicesCubit extends Cubit<DevicesState> {
this._selectWorkRecordUseCase,
this._saveWorkRecordUseCase,
this._generatePathUseCase,
this._routePlanningUseCase
) : super(const DevicesState());
Future<void> unbindDevice(String deviceId, String deviceName) async {
@@ -239,4 +245,60 @@ class DevicesCubit extends Cubit<DevicesState> {
result.fold((failure) => emit(state.copyWith(errorMessage: failure.message)), (pathData) => emit(state.copyWith(generatedPath: pathData)));
}
// 开始路径规划
Future<void> startRoutePlanning(Queue<DeviceAddPathPointModel> 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<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();
result.fold(
(failure) => emit(state.copyWith(
isLoading: false,
errorMessage: failure.message ?? '停止失败',
)),
(_) => emit(state.copyWith(isLoading: false)),
);
}
}

View File

@@ -8,6 +8,12 @@ enum DeviceOperationType {
updateName, // 修改设备名称
}
enum AppState {
none, //无作业状态
routePlanning, //正在规划路径
}
class DevicesState extends Equatable {
final List<DeviceEntity> devices; // 名下所有设备列表
@@ -20,6 +26,7 @@ class DevicesState extends Equatable {
final DeviceOperationType operationType; // 标记当前操作类型
final List<Map<String, dynamic>>? pathData;
final List<DeviceAddPathPointModel>? 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 方便局部更新状态