Files
feature-tenant/lib/features/remote_control/presentation/bloc/remote_control_cubit.dart
Songzex 354f7d6290 优化了tcp实时更新对UI线程的压力和对用弹窗的重复弹窗的影响。
更换了路径规划的的经纬度的字段名和取操作的名字更换。
2026-06-08 13:35:36 +08:00

968 lines
36 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 'dart:math';
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/domain/entities/device_entity.dart';
import 'package:maibu_satabot_v2/features/devices/domain/repositories/device_repository.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/domain/usecase/request_control_permission_usecase.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;
final DeviceRepository _deviceRepository; // 🔥 注入设备仓库
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;
// 🔥 权限请求处理标志位 - 防止竞态条件,避免重复显示弹窗
bool _isProcessingPermissionRequest = false;
// 🔥 模拟数据推送定时器
// Timer? _simulationTimer;
// 🔥 获取带时间戳的日志前缀
String _getTimePrefix() {
final now = DateTime.now();
return '[${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}:${now.second.toString().padLeft(2, '0')}.${now.millisecond.toString().padLeft(3, '0')}]';
}
RemoteControlCubit(
this._repository,
this._requestControlPermissionUseCase,
this._deviceRepository, // 🔥 注入
this.dispatcher,
this.deviceStatusBloc, // 🔥 注入
) : super(
RemoteControlState(
controlEntity: MachineControlStatusEntity(),
runningStatusModel: RunningStatusModel(),
),
) {
_initPacketListener();
_initDeviceStatusListener(); // 🔥 改为订阅 DeviceStatusBloc
}
// 🔥 新增:订阅 DeviceStatusBloc 的状态流,而不是直接监听TCP
// 类全局变量
DateTime? _lastUiUpdateTime;
String? _cacheVoltage;
String? _cacheBattery;
String? _cacheCtrlMode;
int? _cachePing;
void _initDeviceStatusListener() {
_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 = await getNetworkDelay();
_cacheVoltage = voltage.toString();
_cacheBattery = battery.toString();
_cacheCtrlMode = controlMode;
_cachePing = c;
// 500ms节流,不到时间不刷新UI
final now = DateTime.now();
if (_lastUiUpdateTime != null &&
now.difference(_lastUiUpdateTime!) <
const Duration(milliseconds: 500)) {
return;
}
_lastUiUpdateTime = now;
// 间隔达标,统一一次刷新UI
emit(
state.copyWith(
runningStatusModel: state.runningStatusModel.copyWith(
voltage: _cacheVoltage,
battery: _cacheBattery,
controlMode: _cacheCtrlMode,
),
battery: int.tryParse(_cacheBattery ?? '') ?? 0,
ping: _cachePing,
// 🔥 标记为设备状态更新
updateType: 'device_status',
),
);
}
});
}
// 🔥 超简单方法:传入 IP,得到 ping 值
// 🔥 模拟设备状态更新 - 用于测试
// void simulateDeviceStatusUpdate({
// String? voltage,
// String? battery,
// String? controlMode,
// int? ping,
// // 🔥 是否随机生成数据
// bool random = false,
// }) {
// // 如果开启随机模式,生成随机数据
// final rand = Random();
/* if (random) {
voltage = (22.0 + rand.nextDouble() * 4.0).toStringAsFixed(
1,
); // 22.0-26.0V
battery = (rand.nextInt(100) + 1).toString(); // 1-100%
controlMode = rand.nextBool() ? '远程模式' : '本地模式';
ping = rand.nextInt(150) + 20; // 20-170ms
} */
/* debugPrint(
'🔧 [模拟设备状态更新] voltage=$voltage V, battery=$battery%, controlMode=$controlMode, ping=$ping ms',
); */
// 更新缓存
/* if (voltage != null) _cacheVoltage = voltage;
if (battery != null) _cacheBattery = battery;
if (controlMode != null) _cacheCtrlMode = controlMode;
if (ping != null) _cachePing = ping;
// 直接触发状态更新(跳过节流,立即更新)
emit(
state.copyWith(
runningStatusModel: state.runningStatusModel.copyWith(
voltage: _cacheVoltage,
battery: _cacheBattery,
controlMode: _cacheCtrlMode,
),
battery: int.tryParse(_cacheBattery ?? '') ?? 0,
ping: _cachePing,
// 🔥 标记为设备状态更新
updateType: 'device_status',
),
);
} */
// 🔥 开始模拟设备状态推送(随机数据)
/* void startSimulation({int intervalMs = 500}) {
// 如果已经在运行,先停止
stopSimulation();
// debugPrint('🔔 [模拟推送] 开始模拟设备状态推送,间隔:${intervalMs}ms');
// 立即发送一次初始数据
simulateDeviceStatusUpdate(random: true);
// 定时推送随机数据
_simulationTimer = Timer.periodic(Duration(milliseconds: intervalMs), (
timer,
) {
if (!isClosed) {
simulateDeviceStatusUpdate(random: true);
} else {
stopSimulation();
}
});
} */
// 🔥 停止模拟设备状态推送
/* void stopSimulation() {
if (_simulationTimer != null) {
_simulationTimer!.cancel();
_simulationTimer = null;
debugPrint('🔔 [模拟推送] 已停止模拟设备状态推送');
}
} */
// 🔥 辅助方法:更新运行状态
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 {
final logPrefix = '${_getTimePrefix()} 🔍 [RemoteControl] [0x12监听器]';
debugPrint('$logPrefix =========================================');
debugPrint('$logPrefix 开始初始化 0x12 监听器');
_logger.logWithLevel('$logPrefix 开始初始化 0x12 监听器', shouldLog: true);
_kickOutSub?.cancel(); // 防止重复监听
_kickOutSub = dispatcher.onCommand(0x12).listen((packet) {
final timeNow = _getTimePrefix();
debugPrint('$timeNow 🔍 [RemoteControl] [0x12监听器] ✅ 收到 0x12 原始包');
/* debugPrint(
'$timeNow 🔍 [RemoteControl] [0x12监听器] payload长度: ${packet.payload.length}',
);*/
_logger.logWithLevel(
'$timeNow 🔍 [RemoteControl] [0x12监听器] 收到 0x12 原始包,payload长度=${packet.payload.length}',
shouldLog: true,
);
// 先解析JSON判断是否为响应格式
String jsonStringForCheck;
try {
if (packet.payload.length > 2) {
jsonStringForCheck = utf8.decode(
packet.payload.sublist(0, packet.payload.length - 2),
);
} else {
jsonStringForCheck = utf8.decode(packet.payload);
}
final jsonMap = jsonDecode(jsonStringForCheck);
final respondData = jsonMap['respond'];
// 如果是响应格式,继续处理(更新权限状态)
// 如果是请求格式且弹窗已显示,忽略
if (respondData == null && state.showPermissionRequestDialog) {
debugPrint('$timeNow 🔍 [RemoteControl] [0x12监听器] ⚠️ 弹窗已显示(状态),忽略请求');
return;
}
} catch (e) {
// 解析失败,继续处理
}
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);
}
debugPrint('$timeNow 🔍 [RemoteControl] [0x12监听器] JSON内容: $jsonString');
_logger.logWithLevel(
'$timeNow 🔍 [RemoteControl] [0x12监听器] 去除CRC后的JSON: $jsonString',
shouldLog: true,
);
final jsonMap = jsonDecode(jsonString);
// 🔥 区分两种数据格式
final requestType = jsonMap['request'];
final platform = jsonMap['platform'];
final respondData = jsonMap['respond'];
debugPrint(
'$timeNow 🔍 [RemoteControl] [0x12监听器] requestType: $requestType, platform: $platform, hasRespond: ${respondData != null}',
);
// 情况 1: 响应格式 - {"respond":{"switchResult":true,"deviceId":"...","holder":"you"}}
if (respondData != null && respondData is Map) {
// 🔥 收到响应格式,更新权限状态
final switchResult = respondData['switchResult'];
/* debugPrint('$timeNow 🔍 [RemoteControl] [0x12监听器] 📊 收到切换结果响应');
debugPrint(
'$timeNow 🔍 [RemoteControl] [0x12监听器] switchResult: $switchResult',
);*/
_logger.logWithLevel(
'$timeNow 🔍 [RemoteControl] [0x12监听器] 📊 收到切换结果响应 - switchResult: $switchResult',
shouldLog: true,
);
if (!isClosed) {
if (switchResult == true) {
// 切换成功,当前 APP 获得控制权
debugPrint('$timeNow 🔍 [RemoteControl] [0x12监听器] ✅ APP获得控制权');
debugPrint(
'$timeNow 🔍 [RemoteControl] [0x12监听器] 设置 hasPermission = true',
);
emit(state.copyWith(hasPermission: true));
} else {
// 切换失败或拒绝,APP 失去控制权
debugPrint('$timeNow 🔍 [RemoteControl] [0x12监听器] ❌ APP失去控制权');
debugPrint(
'$timeNow 🔍 [RemoteControl] [0x12监听器] 设置 hasPermission = false',
);
emit(
state.copyWith(
hasPermission: false,
// 🔥 修复:不强制关闭弹窗,让弹窗由用户操作控制
),
);
}
}
}
// 情况 2: 请求格式 - {"request":"switch_control","deviceId":"...","platform":"web",...}
else if (requestType == 'switch_control') {
final requestDeviceId = jsonMap['deviceId'];
final webLogPrefix =
'${_getTimePrefix()} 🚨 [RemoteControl] [Web端权限请求]';
// 🔥 新增:记录收到请求的时间,便于排查是否为后端持续推送
debugPrint(
'$webLogPrefix ⚡️⚡️⚡️ 收到Web端权限请求 - ${DateTime.now().toString()}',
);
_logger.logWithLevel(
'$webLogPrefix ⚡️⚡️⚡️ 收到Web端权限请求 - ${DateTime.now().toString()}',
shouldLog: true,
);
debugPrint('$webLogPrefix =========================================');
debugPrint('$webLogPrefix 收到 switch_control 请求');
debugPrint('$webLogPrefix platform: $platform');
debugPrint('$webLogPrefix requestDeviceId: $requestDeviceId');
_logger.logWithLevel(
'$webLogPrefix 收到 switch_control 请求 - platform: $platform, deviceId: $requestDeviceId',
shouldLog: true,
);
// 🔥 关键判断:只有当是其他平台(web)请求时才弹窗
if (platform != null && platform.toString().toLowerCase() != 'app') {
// 🔥 验证设备ID是否与当前控制的 targetDevice 一致
final currentDeviceId = state.targetDevice?.deviceName;
debugPrint('$webLogPrefix 当前控制设备ID: $currentDeviceId');
if (currentDeviceId != null && requestDeviceId == currentDeviceId) {
debugPrint('$webLogPrefix 设备ID匹配');
// 🔥 只有当弹窗还没显示时才弹出,防止重复弹窗叠加
if (state.showPermissionRequestDialog) {
debugPrint('$webLogPrefix ⚠️ 弹窗已显示,忽略重复请求');
_logger.logWithLevel(
'$webLogPrefix ⚠️ 弹窗已显示,忽略重复请求',
shouldLog: true,
);
return;
}
debugPrint('$webLogPrefix 弹出权限请求对话框');
_logger.logWithLevel(
'$webLogPrefix 设备ID匹配,弹出权限请求对话框',
shouldLog: true,
);
if (!isClosed) {
emit(
state.copyWith(
showPermissionRequestDialog: true,
requestingDeviceId: requestDeviceId?.toString(),
requestingPlatform: platform.toString(),
// 🔥 标记为弹窗状态更新
updateType: 'permission_dialog',
),
);
}
} else {
debugPrint('$webLogPrefix ⚠️ 设备ID不匹配,忽略');
debugPrint(
'$webLogPrefix currentDeviceId: $currentDeviceId, requestDeviceId: $requestDeviceId',
);
_logger.logWithLevel(
'$webLogPrefix ⚠️ 设备ID不匹配,忽略请求',
shouldLog: true,
);
}
} else {
debugPrint('$webLogPrefix ℹ️ APP自己的请求回显或platform为空,忽略不弹窗');
_logger.logWithLevel(
'$webLogPrefix ℹ️ APP自己的请求回显,忽略',
shouldLog: true,
);
}
debugPrint('$webLogPrefix =========================================');
}
// 情况 3: 异地登录通知 - {"request":"have_logged_in",...}
else if (requestType == 'have_logged_in') {
debugPrint('${_getTimePrefix()} ⚠️ [RemoteControl] 检测到异地登录');
_logger.logWithLevel(
'${_getTimePrefix()} ⚠️ [RemoteControl] 检测到异地登录,打开弹窗提示',
shouldLog: true,
);
// 🔥 只有当弹窗还没显示时才弹出
if (!isClosed && !state.showPermissionRequestDialog) {
emit(state.copyWith(showPermissionRequestDialog: true));
}
} else {
debugPrint('${_getTimePrefix()} ℹ️ [RemoteControl] 未知类型的 0x12 包,忽略');
_logger.logWithLevel(
'${_getTimePrefix()} ℹ️ [RemoteControl] 未知类型的 0x12 包,忽略',
shouldLog: true,
);
}
} catch (e) {
debugPrint('${_getTimePrefix()} ❌ [RemoteControl] 解析失败:$e');
_logger.logWithLevel(
'${_getTimePrefix()} ❌ [RemoteControl] 解析失败:$e',
shouldLog: true,
);
}
});
}
// 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();
final logPrefix = '⏰ [RemoteControl] [控制循环]';
//debugPrint('$logPrefix =========================================');
//debugPrint('$logPrefix 启动控制循环,间隔: 100ms');
///debugPrint('$logPrefix =========================================');
// 🔥 启动模拟设备状态推送(用于测试)
// startSimulation();
_timer = Timer.periodic(const Duration(milliseconds: 100), (timer) {
if (isClosed) {
// debugPrint('$logPrefix ❌ Cubit已关闭,取消定时器');
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('$logPrefix ⚠️ 无权限,跳过发送 - hasPermission=false');
return;
}
if (state.isEmergency) {
// debugPrint('$logPrefix 🚨 急停状态,跳过发送 - isEmergency=true');
return;
}
// 发送控制指令
//debugPrint(
// '$logPrefix 📤 发送控制指令: originX=${snapshot.originX}, originY=${snapshot.originY}, mower=${snapshot.mowerSpeed}, lift=${snapshot.chassisLift}, ignition=${snapshot.ignitionStatus}, emergency=${snapshot.isEmergency}',
// );
_repository.sendControlMachineCmd(snapshot);
//debugPrint('$logPrefix ✅ 控制指令已发送');
});
}
//通过方法拿最新 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] 执行完成');
}
/// 🔥 统一方法:连续发送10次停止指令,彻底清空TCP缓冲区
Future<void> _sendStopCommandRepeatedly(
MachineControlStatusEntity stopEntity,
) async {
// debugPrint(
// '🛑 [紧急停止] 开始连续发送10次停止指令- 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('>>> [紧急停止] 所有20次停止指令已发出');
}
// 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 agreed, String deviceId) {
// 1. 关闭弹窗(立即关闭,防止重复点击)
emit(state.copyWith(showPermissionRequestDialog: false));
// 2. 发送响应到服务器
try {
_repository.respondPermission(agreed, deviceId);
} catch (e) {
debugPrint(' ❌ TCP权限响应指令发送失败: $e');
debugPrint(' ❌ 错误类型: ${e.runtimeType}');
_logger.logWithLevel(' TCP权限响应指令发送失败 $e', shouldLog: true);
rethrow;
}
// 3. 根据用户选择更新控制状态
if (agreed) {
emit(
state.copyWith(
hasPermission: false,
showPermissionRequestDialog: false,
),
);
} else {
// 用户拒绝 APP 继续保持控制权
// 🔥 必须同时设置 showPermissionRequestDialog: false,防止状态回退
emit(
state.copyWith(hasPermission: true, showPermissionRequestDialog: false),
);
}
debugPrint('${_getTimePrefix()} ====权限弹窗响应结束=====');
}
Future<void> requestControlPermissionS(
String deviceName,
String deviceId, {
String source = '自动',
}) async {
final timePrefix = _getTimePrefix();
final logPrefix = '$timePrefix 🔑 [RemoteControl] [请求权限接口-$source]';
debugPrint('$logPrefix =========================================');
debugPrint('$logPrefix 开始请求控制权');
debugPrint('$logPrefix deviceName: $deviceName');
debugPrint('$logPrefix platform: $deviceId');
debugPrint(
'$logPrefix 当前状态 hasPermission=${state.hasPermission}, showDialog=${state.showPermissionRequestDialog}',
);
_logger.logWithLevel(
'$logPrefix 开始请求控制权- deviceName: $deviceName, platform: $deviceId',
shouldLog: true,
);
// 1. 只有当弹窗不是因Web 端请求权限而显示时,才关闭弹窗
// 避免 Web 端请求权限的弹窗被自动关闭(一闪而过的问题)
if (state.requestingPlatform == null) {
debugPrint('$logPrefix 关闭权限请求弹窗 (非Web端触发)');
emit(state.copyWith(showPermissionRequestDialog: false));
} else {
debugPrint('$logPrefix 保留弹窗 (Web端请求触发)');
}
// 2. 调用 UseCase 获取 HTTP 返回的完整权限信息
debugPrint('$logPrefix 调用 HTTP 接口查询权限状态..');
final result = await _requestControlPermissionUseCase(
RequestControlPermissionParams(
deviceName: deviceName,
deviceId: deviceId,
),
);
// 3. 处理结果
result.fold(
(failure) {
final failLogPrefix =
'${_getTimePrefix()} 🔑 [RemoteControl] [请求权限接口-$source]';
debugPrint('$failLogPrefix APP请求控制权限失败: ${failure.message}');
_logger.logWithLevel(
'$failLogPrefix APP请求控制权限失败: ${failure.message}',
shouldLog: true,
);
// 🔥 修复:HTTP请求失败时不要自动打开弹窗,避免形成循环
// emit(state.copyWith(showPermissionRequestDialog: true));
debugPrint('$failLogPrefix HTTP请求失败,不自动打开弹窗');
},
(permissionInfo) async {
final bool hasPermission =
permissionInfo['hasPermission'] as bool? ?? false;
final String? owner = permissionInfo['owner'] as String?;
final successLogPrefix =
'${_getTimePrefix()} 🔑 [RemoteControl] [请求权限接口-$source]';
debugPrint(
'$successLogPrefix APP HTTP返回 - hasPermission=$hasPermission, owner=$owner',
);
_logger.logWithLevel(
'$successLogPrefix APP HTTP返回 - hasPermission=$hasPermission, owner=$owner',
shouldLog: true,
);
// 🔥 关键逻辑: 如果没有权限 或owner为null,则发送TCP 请求
if (!hasPermission || owner == null) {
debugPrint('$successLogPrefix ⚠️ APP无权限或owner为null,发送TCP请求...');
_logger.logWithLevel(
'$successLogPrefix ⚠️ APP无权限或owner为null,发送TCP请求',
shouldLog: true,
);
// 🔥 发送TCP 0x12 权限请求指令
_repository.sendTcpPermissionRequest(deviceName);
// 等待 TCP 回包(通过监听器更新状态
debugPrint('$successLogPrefix 📡 TCP请求已发送,等待回包确认');
} else {
debugPrint('$successLogPrefix APP已有权限,直接更新UI');
_logger.logWithLevel(
'$successLogPrefix APP已有权限,直接更新UI',
shouldLog: true,
);
debugPrint(
'$successLogPrefix 当前 hasPermission 状态: ${state.hasPermission}',
);
// 🔥 正确的状态更新:使用最新状态
if (state.hasPermission != true) {
debugPrint('$successLogPrefix 🔄 更新 hasPermission = true');
emit(state.copyWith(hasPermission: true));
debugPrint('$successLogPrefix ✅ hasPermission 状态已更新为 true');
} else {
debugPrint('$successLogPrefix ⚠️ hasPermission 已是 true,无需更新');
// 强制触发UI刷新:通过临时改变其他属性
emit(state.copyWith(ping: state.ping + 1));
emit(state.copyWith(ping: state.ping));
}
}
},
);
debugPrint(
'${_getTimePrefix()} 🔑 [RemoteControl] [请求权限接口-$source] =========================================',
);
}
/// 🔥 重置弹窗状态 - 在弹窗关闭后调用(已简化,不再需要标志位)
void resetPermissionCoolDown() {
// 标志位已移除,此方法保留以保持向后兼容性
}
Future<void> confirmPermissionResponse(
String deviceName,
String platform,
bool agreed,
) async {
final timePrefix = _getTimePrefix();
final logPrefix = '$timePrefix 🔑 [RemoteControl] [权限确认]';
debugPrint('$logPrefix ========进入TCP发送=================================');
debugPrint('$logPrefix ⚡️ confirmPermissionResponse 方法被调用');
debugPrint('$logPrefix 用户操作: ${agreed ? "同意" : "拒绝"}');
debugPrint('$logPrefix deviceName: $deviceName');
debugPrint('$logPrefix platform: $platform');
respondPermission(agreed, deviceName);
// 2. 调用 HTTP 接口获取最终权限状态
debugPrint('$logPrefix 📡 调用 HTTP 接口确认最终权限状态..');
/* final result = await _requestControlPermissionUseCase(
RequestControlPermissionParams(
deviceName: deviceName,
deviceId: platform,
),
);*/
// 3. 根据 HTTP 返回的真实权限状态更新UI
/* result.fold(
(failure) {
final failLogPrefix = '${_getTimePrefix()} 🔑 [RemoteControl] [权限确认]';
debugPrint('$failLogPrefix APP HTTP请求失败: ${failure.message}');
debugPrint('$failLogPrefix ⚠️ 保持当前状态不变');
},
(permissionInfo) {
final bool hasPermission =
permissionInfo['hasPermission'] as bool? ?? false;
final String? owner = permissionInfo['owner'] as String?;
final successLogPrefix =
'${_getTimePrefix()} 🔑 [RemoteControl] [权限确认]';
debugPrint(
'$successLogPrefix APP HTTP返回真实权限状态 hasPermission=$hasPermission, owner=$owner',
);
debugPrint(
'$successLogPrefix 🔄 正在更新UI - hasPermission: ${state.hasPermission} -> $hasPermission',
);
// 🔥 直接用HTTP 返回的权限状态覆盖
// emit(state.copyWith(hasPermission: hasPermission));
debugPrint(
'$successLogPrefix 📊 UI已同步完成- 当前 hasPermission=$hasPermission',
);
},
);*/
debugPrint(
'${_getTimePrefix()} 🔑 [RemoteControl] [权限确认] =========================================',
);
}
/// 发送底盘指令
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);
}
/// 🔥 设置待控制的设备(从机器人列表点击进入时调用)
void setTargetDevice(DeviceEntity device) {
// debugPrint('🎯 [RemoteControl] 设置待控制设备 ${device.deviceName}');
// _logger.logWithLevel('🎯 [RemoteControl] 设置待控制设备 ${device.deviceName}');
// 🔥 通知后端订阅该设备
_deviceRepository.switchDevice("app", device.deviceName).then((result) {
result.fold(
(failure) {
// debugPrint('>>> [RemoteControl] 切换设备失败: ${failure.message}');
// _logger.logWithLevel('>>> [RemoteControl] 切换设备失败: ${failure.message}');
},
(success) {
// debugPrint('>>> [RemoteControl] 切换设备成功, code: $success');
// _logger.logWithLevel('>>> [RemoteControl] 切换设备成功, code: $success');
},
);
});
emit(state.copyWith(targetDevice: device));
}
/// 🔥 清除待控制设备(退出远程控制页时调用)
void clearTargetDevice() {
// debugPrint('🧹 [RemoteControl] 清除待控制设备');
// _logger.logWithLevel('🧹 [RemoteControl] 清除待控制设备');
emit(state.copyWith(targetDevice: null));
}
}