Files
feature-next-arch/lib/features/remote_control/presentation/bloc/remote_control_cubit.dart

528 lines
20 KiB
Dart
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:dart_ping/dart_ping.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:maibu_satabot_v2/features/devices/presentation/bloc/device_status_bloc.dart';
import 'package:maibu_satabot_v2/features/devices/presentation/bloc/device_status_state.dart';
import 'package:maibu_satabot_v2/features/remote_control/data/models/running_status_model.dart';
import 'package:maibu_satabot_v2/features/remote_control/presentation/bloc/remote_control_state.dart';
import '../../../../core/logging/i_logger_service.dart';
import '../../../../core/network/net_message_dispatcher.dart';
import '../../../devices/domain/entities/running_status_entity.dart';
import '../../domain/entities/machine_control_status_entity.dart';
import '../../domain/repositories/remote_control_repository.dart';
import '../../domain/usecase/remote_control_usecase.dart';
class RemoteControlCubit extends Cubit<RemoteControlState> {
final RemoteControlRepository _repository;
final RequestControlPermissionUseCase _requestControlPermissionUseCase;
Timer? _timer;
StreamSubscription? _kickOutSub; // 新增:用于管理监听生命周期
final NetMessageDispatcher dispatcher;
final DeviceStatusBloc deviceStatusBloc; // 🔥 注入 DeviceStatusBloc
StreamSubscription? _deviceStatusSub; // 🔥 订阅 DeviceStatusBloc 的状态流
static const platform = MethodChannel('com.maibu.satabot/ping');
int _currentPing = 50;
final ILoggerService _logger = GetIt.I<ILoggerService>();
// 🔥 参考Android版:使用成员变量存储摇杆值,避免state竞态
int _currentOriginX = 0;
int _currentOriginY = 0;
RemoteControlCubit(
this._repository,
this._requestControlPermissionUseCase,
this.dispatcher,
this.deviceStatusBloc, // 🔥 注入
) : super(
RemoteControlState(
controlEntity: MachineControlStatusEntity(),
runningStatusModel: RunningStatusModel(),
),
) {
_initPacketListener();
_initDeviceStatusListener(); // 🔥 改为订阅 DeviceStatusBloc
}
// 🔥 新增:订阅 DeviceStatusBloc 的状态流,而不是直接监听 TCP
void _initDeviceStatusListener() {
_logger.logWithLevel('>>> [RemoteControl] begin 订阅 DeviceStatusBloc 状态流');
_deviceStatusSub?.cancel();
_deviceStatusSub = deviceStatusBloc.stream.listen((deviceState) async {
if (deviceState is DeviceStatusUpdated) {
final voltage = deviceState.status.voltage;
final battery = deviceState.status.battery;
final controlMode = deviceState.status.controlMode == '3' ? '远程模式' : '本地模式';
final c = getNetworkDelay();
emit(state.copyWith(
runningStatusModel: state.runningStatusModel.copyWith(
voltage: voltage.toString(),
battery: battery.toString(),
controlMode: controlMode,
),
battery: int.tryParse(battery) ?? 0,
ping: await c,
));
_logger.logWithLevel('✅ [RemoteControl] 从 DeviceStatusBloc 收到更新: 电压=$voltage, 电量=$battery, 模式=$controlMode');
}
});
_logger.logWithLevel('>>> [RemoteControl] ✅ DeviceStatusBloc 订阅已建立完成');
}
// 🔥 超简单方法:传入 IP,得到 ping 值
// 🔥 辅助方法:更新运行状态
void _updateStatusFromDevice(RunningStatusModel newStatus) {
debugPrint('✅ [_updateStatusFromDevice] 收到运行状态更新:$newStatus');
if (!isClosed) {
emit(state.copyWith(
runningStatusModel: newStatus,
voltage: int.tryParse(newStatus.voltage) ?? 0,
battery: int.tryParse(newStatus.battery) ?? 0,
));
// debugPrint('✅ [更新运行状态] 电压:${newStatus.voltage}V, 电量:${newStatus.battery}%');
_logger.logWithLevel('✅ [更新运行状态] 电压:${newStatus.voltage}V, 电量:${newStatus.battery}%');
}
}
// 🔥 辅助方法:更新运行状态
// 1. 初始化回包监听 (如 0x12 权限)
// void _initPacketListener() {
// _repository.responseStream.listen((packet) {
// if (packet.command == 0x12) {
// // 根据负载判断是否有权限,更新状态
// emit(state.copyWith(hasPermission: true));
// }
// });
// }
Future<void> _initPacketListener() async {
//print('>>> [RemoteControl] begin 初始化 0x12 监听器');
_logger.logWithLevel('>>> [RemoteControl] begin 0x12 监听器');
_kickOutSub?.cancel(); // 防止重复监听
_kickOutSub = dispatcher.onCommand(0x12).listen((packet) {
//print('>>> [RemoteControl] 收到 0x12 原始包,payload 长度=${packet.payload.length}');
_logger.logWithLevel('>>> [RemoteControl] 收到 0x12 原始包,payload 长度=${packet.payload.length}');
try {
// 🔥 关键:手动去掉最后 2 个 CRC 字节
String jsonString;
if (packet.payload.length > 2) {
jsonString = utf8.decode(packet.payload.sublist(0, packet.payload.length - 2));
} else {
jsonString = utf8.decode(packet.payload);
}
//print('>>> [RemoteControl] 去除 CRC 后的 JSON: $jsonString');
_logger.logWithLevel('>>> [RemoteControl] 去除 CRC 后的 JSON: $jsonString');
final jsonMap = jsonDecode(jsonString);
// 🔥 区分两种数据格式
final requestType = jsonMap['request'];
final platform = jsonMap['platform'];
final respondData = jsonMap['respond'];
//print('>>> [RemoteControl] 📋 requestType=$requestType, platform=$platform');
_logger.logWithLevel('>>> [RemoteControl] 📋 requestType=$requestType, platform=$platform');
// 情况 1: 响应格式 - {"respond":{"switchResult":true,"deviceId":"...","holder":"you"}}
if (respondData != null && respondData is Map) {
final switchResult = respondData['switchResult'];
//print('>>> [RemoteControl] 📊 收到切换结果响应:switchResult = $switchResult');
_logger.logWithLevel('>>> [RemoteControl] 📊 收到切换结果响应:switchResult = $switchResult');
if (!isClosed) {
if (switchResult == true) {
// 切换成功,当前 APP 失去控制权
emit(state.copyWith(hasPermission: true, showPermissionRequestDialog: false));
print('>>> [RemoteControl] ✅ 权限已切');
} else {
// 切换失败或拒绝,保持当前状态
emit(state.copyWith(showPermissionRequestDialog: false));
print('>>> [RemoteControl] ❌ 权限切换失败/被拒绝');
}
}
}
// 情况 2: 请求格式 - {"request":"switch_control","deviceId":"...","platform":"web",...}
else if (requestType == 'switch_control') {
// 🔥 关键判断:只有当是其他平台(web)请求时才弹窗
if (platform != null && platform.toString().toLowerCase() != 'app') {
//print('>>> [RemoteControl] 🚨 $platform 端请求控制权,打开弹窗询问用户');
_logger.logWithLevel('>>> [RemoteControl] 🚨 $platform 端请求控制权,打开弹窗询问用户');
if (!isClosed) {
emit(state.copyWith(showPermissionRequestDialog: true));
}
} else {
//print('>>> [RemoteControl] ℹ️ APP 自己的请求回显,忽略不弹窗');
_logger.logWithLevel('>>> [RemoteControl] ℹ️ APP 自己的请求回显,忽略不弹窗');
}
}
// 情况 3: 异地登录通知 - {"request":"have_logged_in",...}
else if (requestType == 'have_logged_in') {
//print('>>> [RemoteControl] ⚠️ 检测到异地登录,打开弹窗提示');
_logger.logWithLevel('>>> [RemoteControl] ⚠️ 检测到异地登录,打开弹窗提示');
if (!isClosed) {
emit(state.copyWith(showPermissionRequestDialog: true));
}
}
else {
// print('>>> [RemoteControl] ℹ️ 未知类型的 0x12 包,忽略');
_logger.logWithLevel('>>> [RemoteControl] ℹ️ 未知类型的 0x12 包,忽略');
}
} catch (e) {
//print('>>> [RemoteControl] ❌ 解析失败:$e');
_logger.logWithLevel('>>> [RemoteControl] ❌ 解析失败:$e');
}
});
final c = getNetworkDelay();
emit(state.copyWith(
ping: await c, // 这里直接使用异步返回的数值
));
//print('>>> [RemoteControl] ✅ 0x12 监听器已建立完成');
_logger.logWithLevel('>>> [RemoteControl] ✅ 0x12 监听器已建立完成');
}
// 2. 开启 100ms 控制循环 (在进入遥控页面或点击“开始”时调用)
/* void startControlLoop() {
_timer?.cancel();
_timer = Timer.periodic(const Duration(milliseconds: 100), (timer) {
// 🔥 关键修复:每次循环都重新读取最新的 state
final currentEntity = state.controlEntity;
// 🔥 安全检查:无权限或急停时不发送
if (!state.hasPermission || state.isEmergency) {
debugPrint('⚠️ [定时器] 跳过发送 - hasPermission=${state.hasPermission}, isEmergency=${state.isEmergency}');
return;
}
debugPrint('⏰ [定时器] 发送控制指令 - originX=${currentEntity.originX}, originY=${currentEntity.originY}');
_logger.logWithLevel('真实的发送的实体 - originX: ${currentEntity.originX}, originY: ${currentEntity.originY}');
_repository.sendControlMachineCmd(currentEntity);
});
emit(state.copyWith(status: RemoteControlStatus.controlling));
}*/
void startControlLoop() {
_timer?.cancel();
_timer = Timer.periodic(const Duration(milliseconds: 100), (timer) {
if (isClosed) {
timer.cancel();
return;
}
// 🔥 参考Android版:直接读取成员变量,避免state竞态
final snapshot = MachineControlStatusEntity(
originX: _currentOriginX,
originY: _currentOriginY,
chassisLift: state.controlEntity.chassisLift,
mowerSpeed: state.controlEntity.mowerSpeed,
ignitionStatus: state.controlEntity.ignitionStatus,
isEmergency: state.isEmergency,
);
// 常规安全检查
if (!state.hasPermission) {
debugPrint('⚠️ [定时器] 无权限,跳过发送');
return;
}
if (state.isEmergency) {
debugPrint('🚨 [定时器] 急停状态,跳过发送');
return;
}
_logger.logWithLevel('[定时器] 发送控制指令 - originX=${snapshot.originX}, originY=${snapshot.originY}');
debugPrint('📤 [定时器] 准备发送 - originX=${snapshot.originX}, originY=${snapshot.originY}');
_repository.sendControlMachineCmd(snapshot);
});
}
//通过方法拿最新 state
MachineControlStatusEntity _getCurrentControlEntity() {
// 🔥 关键修复:必须copyWith创建新对象,避免引用竞态条件
final entity = state.controlEntity;
_logger.logWithLevel('真实的发送的实体 - originX: ${entity.originX}, originY: ${entity.originY}');
return entity.copyWith(); // 返回副本,不是引用
}
// 3. 更新摇杆数据
void updateJoystick(double x, double y) {
final updatedEntity = state.controlEntity.copyWith(
x: x.toInt(),
y: y.toInt(),
);
emit(state.copyWith(controlEntity: updatedEntity));
}
// 4. 更新功能开关 (比如割刀速度、灯光、点火等)
void updateFunction({int? mower, int? lift, int? ignition, bool? emergency}) {
// debugPrint('🔧 [updateFunction] 调用 - mower: $mower, lift: $lift, ignition: $ignition, emergency: $emergency');
_logger.logWithLevel('🔧 [updateFunction] 调用 - mower: $mower, lift: $lift, ignition: $ignition, emergency: $emergency');
final updatedEntity = state.controlEntity.copyWith(
mower: mower,
lift: lift,
ignition: ignition,
emergency: emergency,
);
//emit(state.copyWith(controlEntity: updatedEntity));
emit(state.copyWith(
controlEntity: updatedEntity,
isEmergency: emergency ?? state.isEmergency,
));
debugPrint('✅ [updateFunction] 状态已更新并 emit');
_logger.logWithLevel('✅ [updateFunction] 状态已更新并 emit');
}
void updateOriginY(int y) {
debugPrint('📥 [updateOriginY] 被调用 - y=$y');
// 🔥 参考Android版:直接更新成员变量
_currentOriginY = y;
// 同时更新state(用于UI显示)
final updatedEntity = state.controlEntity.copyWith(y: y);
emit(state.copyWith(controlEntity: updatedEntity));
debugPrint('📝 [updateOriginY] state已更新 - originY=$y');
}
void updateOriginX(int x) {
debugPrint('📥 [updateOriginX] 被调用 - x=$x');
// 🔥 参考Android版:直接更新成员变量
_currentOriginX = x;
// 同时更新state(用于UI显示)
final updatedEntity = state.controlEntity.copyWith(x: x);
emit(state.copyWith(controlEntity: updatedEntity));
debugPrint('📝 [updateOriginX] state已更新 - originX=$x');
}
/// 🔥 安全方法:同时清零双轴,确保只发送一次完全停止指令
Future<void> stopAllMovement() async {
debugPrint('🛑 [stopAllMovement] 开始执行 - 当前成员变量: originX=$_currentOriginX, originY=$_currentOriginY');
// 🔥 参考Android版:直接清零成员变量
_currentOriginX = 0;
_currentOriginY = 0;
// 🔥 关键修复:先暂停定时器,防止定时器在停止期间发送旧的运动指令
_timer?.cancel();
_timer = null; // 🔥 彻底清空,防止重复启动
final updatedEntity = state.controlEntity.copyWith(x: 0, y: 0);
// 一次性更新双轴(即使无权限也要更新本地state)
emit(state.copyWith(controlEntity: updatedEntity));
// 🔥 安全底线:停止指令必须无视权限强制发送!
debugPrint('🛑 [stopAllMovement] 双轴归零,发送停止指令 - originX=0, originY=0');
await _sendStopCommandRepeatedly(updatedEntity); // ✅ 等待所有指令发送完成
// 🔥 恢复定时器
await Future.delayed(const Duration(milliseconds: 200));
startControlLoop();
debugPrint('✅ [stopAllMovement] 执行完成');
}
/// 🔥 统一方法:连续发送30次停止指令,彻底清空TCP缓冲区
Future<void> _sendStopCommandRepeatedly(MachineControlStatusEntity stopEntity) async {
debugPrint('🛑 [紧急停止] 开始连续发送30次停止指令 - originX=${stopEntity.originX}, originY=${stopEntity.originY}');
// 🔥 注意:_lastStopTime 已经在 stopAllMovement() 中设置了,这里不需要再设置
int sentCount = 0;
// 🔥 关键修复:每次发送间隔5ms,避免TCP合并/丢弃
for (int i = 0; i < 20; i++) {
_repository.sendControlMachineCmd(stopEntity);
sentCount++;
if (i % 5 == 0) {
debugPrint('🛑 [紧急停止] 已发送第${i + 1}次');
}
if (i < 19) {
await Future.delayed(const Duration(milliseconds: 5));
}
}
// 🔥 延迟后再发送10次(双重保险,对抗网络抖动)
await Future.delayed(const Duration(milliseconds: 100));
for (int i = 0; i < 10; i++) {
_repository.sendControlMachineCmd(stopEntity);
sentCount++;
if (i < 9) {
await Future.delayed(const Duration(milliseconds: 5));
}
}
debugPrint('✅ [紧急停止] 所有30次停止指令已发出');
}
// 5. 停止控制循环
void stopControlLoop() {
_timer?.cancel();
_timer = null;
emit(state.copyWith(status: RemoteControlStatus.initial));
}
void toggleLock() {
emit(state.copyWith(isLocked: !state.isLocked));
}
void togglePermissionDialog(bool show) {
emit(state.copyWith(showPermissionRequestDialog: show));
}
void requestControlPermission() {
// 1. 关闭弹窗
emit(state.copyWith(showPermissionRequestDialog: false));
}
void toggleLeftPip() => emit(state.copyWith(showLeftPip: !state.showLeftPip));
void toggleRightPip() => emit(state.copyWith(showRightPip: !state.showRightPip));
@override
Future<void> close() {
_timer?.cancel();
_deviceStatusSub?.cancel(); // 🔥 取消订阅 DeviceStatusBloc
return super.close();
}
void updateChassisLift(int i) {}
void updateEmergency(bool bool) {
debugPrint('🚨 [急停] ${bool ? "触发急停!" : "解除急停"}');
updateFunction(emergency: bool);
}
void respondPermission(bool bool, String deviceId) {
emit(state.copyWith(showPermissionRequestDialog: false));
// 2. 发送响应到服务端
_repository.respondPermission(bool, deviceId);
// 3. 根据用户选择更新控制状态
if (bool) {
// 用户同意 → APP 失去控制权,Web 端获得控制权
emit(state.copyWith(hasPermission: false));
//路由到首页home
} else {
// 用户拒绝 → APP 继续保持控制权
emit(state.copyWith(hasPermission: true));
}
}
void requestControlPermissionS(String deviceName, String deviceId) async {
// 1. 关闭弹窗
emit(state.copyWith(showPermissionRequestDialog: false));
// 2. 调用 UseCase
final result = await _requestControlPermissionUseCase(
RequestControlPermissionParams(
deviceName: deviceName,
deviceId: deviceId,
),
);
// 3. 处理结果
result.fold(
(failure) {
//print('❌ [RemoteControl] 请求控制权限失败:${failure.message}');
_logger.logWithLevel('❌ [RemoteControl] 请求控制权限失败:${failure.message}');
// 可以在这里显示错误提示或重新打开弹窗
emit(state.copyWith(showPermissionRequestDialog: true));
},
(success) {
// 更新状态
//print('✅ [RemoteControl] 请求控制权限成功:$success');
_logger.logWithLevel('✅ [RemoteControl] 请求控制权限成功:$success');
///处理result
if(success){
emit(state.copyWith(hasPermission: true));
}else{
emit(state.copyWith(hasPermission: false));
}
print('✅ c:$success');
// 权限申请已发送,等待 0x12 回包更新状态
},
);
}
/// 发送底盘指令
void sendChassisCommand(int i) {
// debugPrint(' [底盘指令] ${i}');
_logger.logWithLevel(' [底盘指令] ${i}');
updateFunction(lift: i);
}
/// 发送割刀指令
void sendMowerCommand(int i) {
// debugPrint(' [割刀指令] ${i}');
_logger.logWithLevel(' [割刀指令] ${i}');
updateFunction(mower: i);
}
/// 发送点火指令
void sendFireCommand(int i) {
//void updateFunction({int? mower, int? lift, int? ignition, bool? emergency})
// updateFunction(mower:0, lift: 0, ignition: i, emergency: false);
debugPrint(' [点火指令] ${i}');
_logger.logWithLevel(' [点火指令] ${i}');
updateFunction(ignition: i);
}
// 发送障碍物识别指令
void toggleObstacleRecognition() {
emit(state.copyWith(obstacleRecognitionFlag: !state.obstacleRecognitionFlag));
}
void toggleTopLeftExpand() {
emit(state.copyWith(topRightIsExpanded: !state.topRightIsExpanded));
}
Future<int> getNetworkDelay() async {
try {
// 直接 Ping 你的服务器 IP
final ping = Ping('1.95.137.212', count: 1, timeout: 1);
// 等待一次结果
final data = await ping.stream.first;
if (data.response != null && data.response!.time != null) {
// 返回和 cmd 一样的毫秒值
return data.response!.time!.inMilliseconds;
} else {
return 9999;
}
} catch (e) {
return 9999;
}
}
//app退出远程遥控界面释放权限
Future<bool> releasePermission(String platform) async {
return await _repository.releasePermission(platform);
}
}