优化了tcp实时更新对UI线程的压力和对用弹窗的重复弹窗的影响。
更换了路径规划的的经纬度的字段名和取操作的名字更换。
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
# Uncomment this line to define a global platform for your project
|
||||
# platform :ios, '13.0'
|
||||
source 'https://github.com/volcengine/volcengine-specs.git'
|
||||
source 'https://github.com/volcengine/volcengine-specs.git'error: Classes can only extend other classes. (extends_non_class at [maibu_satabot_v2] lib\features\v2\device_list\domain\entities\video_stream_entity.dart:2)error: Classes can only extend other classes. (extends_non_class at [maibu_satabot_v2] lib\features\v2\device_list\domain\entities\video_stream_entity.dart:2)you
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
|
||||
@@ -62,7 +62,10 @@ class AuthCubit extends Cubit<AuthState> {
|
||||
|
||||
if (user != null) {
|
||||
// 🔥 冷启动时重新初始化 TCP 连接
|
||||
await tcp.initializeTcp(host: TCPConsts.TCP_IP, port: TCPConsts.TCP_PORT);
|
||||
await tcp.initializeTcp(
|
||||
host: TCPConsts.TCP_IP,
|
||||
port: TCPConsts.TCP_PORT,
|
||||
);
|
||||
|
||||
// 2. 同步全局 App 状态
|
||||
appCubit.setAuth(user);
|
||||
|
||||
@@ -21,6 +21,12 @@ class DeviceStatusBloc extends Bloc<DeviceStatusEvent, DeviceStatusState> {
|
||||
// 🔥 保存订阅引用,用于管理生命周期
|
||||
StreamSubscription? _tcpSubscription;
|
||||
|
||||
// 🔥 节流相关:500ms节流控制0x02数据推送频率
|
||||
Timer? _throttleTimer;
|
||||
static const _throttleDuration = Duration(milliseconds: 500);
|
||||
RunningStatusEntity? _cachedStatus;
|
||||
GPSEntity? _cachedGps;
|
||||
|
||||
DeviceStatusBloc(this._dispatcher, {TcpClient? client})
|
||||
: tcpClient = client ?? GetIt.I<TcpClient>(),
|
||||
super(DeviceStatusInitial()) {
|
||||
@@ -95,6 +101,7 @@ class DeviceStatusBloc extends Bloc<DeviceStatusEvent, DeviceStatusState> {
|
||||
|
||||
if (fields.length < 18) {
|
||||
//debugPrint('⚠️ [DeviceStatusBloc] 字段不足:${fields.length},期望≥18');
|
||||
// 🔥 错误不节流,立即emit以便UI显示错误
|
||||
if (!isClosed) {
|
||||
emit(DeviceStatusError('字段不足,期望≥18,实际:${fields.length}'));
|
||||
}
|
||||
@@ -104,13 +111,8 @@ class DeviceStatusBloc extends Bloc<DeviceStatusEvent, DeviceStatusState> {
|
||||
final status = RunningStatusEntity.fromFields(fields);
|
||||
final gps = GPSEntity(status.latitude, status.longitude);
|
||||
|
||||
//debugPrint('✅ [DeviceStatusBloc] 直接解析成功,更新状态:Lat=${gps.latitude}, Lng=${gps.longitude}');
|
||||
// _logger.log('✅ [DeviceStatusBloc] 直接解析成功,更新状态');
|
||||
|
||||
// 🔥 关键修复:BLoC有Equatable去重机制,必须创建新对象才能触发UI更新
|
||||
if (!isClosed) {
|
||||
// 创建全新的status和gps对象,绕过Equatable去重
|
||||
final newStatus = RunningStatusEntity(
|
||||
// 🔥 缓存最新数据用于节流发射
|
||||
_cachedStatus = RunningStatusEntity(
|
||||
voltage: status.voltage,
|
||||
leftTargetSpeed: status.leftTargetSpeed,
|
||||
rightTargetSpeed: status.rightTargetSpeed,
|
||||
@@ -136,13 +138,17 @@ class DeviceStatusBloc extends Bloc<DeviceStatusEvent, DeviceStatusState> {
|
||||
workingArea: status.workingArea,
|
||||
obstacleFlag: status.obstacleFlag,
|
||||
);
|
||||
final newGps = GPSEntity(status.latitude, status.longitude);
|
||||
// debugPrint('📤 [DeviceStatusBloc] emit DeviceStatusUpdated - 电压:${status.voltage}, 电量:${status.battery}, 模式:${status.controlMode}');
|
||||
emit(DeviceStatusUpdated(newStatus, newGps));
|
||||
}
|
||||
_cachedGps = GPSEntity(status.latitude, status.longitude);
|
||||
|
||||
// 🔥 节流:取消之前的timer,重新计时500ms
|
||||
_throttleTimer?.cancel();
|
||||
_throttleTimer = Timer(_throttleDuration, () {
|
||||
_emitCachedStatus();
|
||||
});
|
||||
} catch (e, stack) {
|
||||
//debugPrint('❌ [DeviceStatusBloc] 直接解析异常:$e\n$stack');
|
||||
// _logger.log('❌ [DeviceStatusBloc] 直接解析异常:$e');
|
||||
// 🔥 解析错误不节流,立即emit以便UI显示错误
|
||||
if (!isClosed) {
|
||||
emit(DeviceStatusError('解析失败:$e'));
|
||||
}
|
||||
@@ -156,10 +162,23 @@ class DeviceStatusBloc extends Bloc<DeviceStatusEvent, DeviceStatusState> {
|
||||
_logger.log('✅ [DeviceStatusBloc] 直接TCP监听器已建立完成');
|
||||
}
|
||||
|
||||
// 🔥 节流发射:500ms到期后发射缓存的最新数据
|
||||
void _emitCachedStatus() {
|
||||
if (_cachedStatus != null && _cachedGps != null && !isClosed) {
|
||||
// debugPrint('📤 [DeviceStatusBloc] 🔥节流发射 - 电压:${_cachedStatus!.voltage}, 电量:${_cachedStatus!.battery}');
|
||||
emit(DeviceStatusUpdated(_cachedStatus!, _cachedGps!));
|
||||
}
|
||||
}
|
||||
|
||||
// 🔥 重置时仅清空状态
|
||||
Future<void> _handleReset(DeviceStatusReset event, Emitter<DeviceStatusState> emit) async {
|
||||
Future<void> _handleReset(
|
||||
DeviceStatusReset event,
|
||||
Emitter<DeviceStatusState> emit,
|
||||
) async {
|
||||
debugPrint('🔄 [DeviceStatusBloc] 收到重置事件:清空状态 - 当前状态:${state.runtimeType}');
|
||||
_logger.log('🔄 [DeviceStatusBloc] 收到重置事件:清空状态 - 当前状态:${state.runtimeType}');
|
||||
_logger.log(
|
||||
'🔄 [DeviceStatusBloc] 收到重置事件:清空状态 - 当前状态:${state.runtimeType}',
|
||||
);
|
||||
|
||||
// 只 emit 初始状态,让 UI 清除旧设备的数据
|
||||
emit(DeviceStatusInitial());
|
||||
@@ -214,6 +233,11 @@ class DeviceStatusBloc extends Bloc<DeviceStatusEvent, DeviceStatusState> {
|
||||
debugPrint('🚫 [DeviceStatusBloc] 页面退出,仅取消TCP订阅(不关闭BLoC)');
|
||||
_logger.log('🚫 [DeviceStatusBloc] 页面退出,仅取消TCP订阅(不关闭BLoC)');
|
||||
//_tcpSubscription?.cancel();
|
||||
// 🔥 清理节流timer和缓存
|
||||
_throttleTimer?.cancel();
|
||||
_throttleTimer = null;
|
||||
_cachedStatus = null;
|
||||
_cachedGps = null;
|
||||
// 🔥 关键修复:不调用 super.close(),保持 BLoC 活跃
|
||||
return Future.value();
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@ import '../../../remote_control/domain/repositories/remote_control_repository.da
|
||||
import 'permission_request_event.dart';
|
||||
import 'permission_request_state.dart';
|
||||
|
||||
class PermissionRequestBloc extends Bloc<PermissionRequestEvent, PermissionRequestState> {
|
||||
class PermissionRequestBloc
|
||||
extends Bloc<PermissionRequestEvent, PermissionRequestState> {
|
||||
final NetMessageDispatcher _dispatcher;
|
||||
final RemoteControlRepository _repository;
|
||||
final ILoggerService _logger = GetIt.I<ILoggerService>();
|
||||
@@ -18,9 +19,11 @@ class PermissionRequestBloc extends Bloc<PermissionRequestEvent, PermissionReque
|
||||
// 🔥 保存订阅引用,用于管理生命周期
|
||||
StreamSubscription? _permissionSubscription;
|
||||
|
||||
PermissionRequestBloc(this._dispatcher, this._repository) : super(const PermissionRequestInitial()) {
|
||||
PermissionRequestBloc(this._dispatcher, this._repository)
|
||||
: super(const PermissionRequestInitial()) {
|
||||
// 🔥 核心:直接在构造函数中建立 0x12 监听(通过 NetMessageDispatcher)
|
||||
_initPermissionListener();
|
||||
// 🔥 注意:RemoteControlCubit 已经在处理 0x12 权限请求,这里不再重复监听
|
||||
// _initPermissionListener();
|
||||
|
||||
// 事件处理
|
||||
on<PermissionRequestReceived>(_handleRequestReceived);
|
||||
@@ -28,100 +31,53 @@ class PermissionRequestBloc extends Bloc<PermissionRequestEvent, PermissionReque
|
||||
}
|
||||
|
||||
// 🔥 通过 NetMessageDispatcher 监听 TCP 0x12 指令,解析权限请求
|
||||
// 🔥 已禁用:RemoteControlCubit 统一处理权限请求,避免重复弹窗
|
||||
void _initPermissionListener() {
|
||||
debugPrint('🔗 [PermissionRequestBloc] 初始化 0x12 权限监听器(通过 NetMessageDispatcher)');
|
||||
_logger.logWithLevel('🔗 [PermissionRequestBloc] 初始化 0x12 权限监听器', shouldLog: true);
|
||||
debugPrint(
|
||||
'🔗 [PermissionRequestBloc] 初始化 0x12 权限监听器(通过 NetMessageDispatcher)',
|
||||
);
|
||||
_logger.logWithLevel(
|
||||
'🔗 [PermissionRequestBloc] 初始化 0x12 权限监听器',
|
||||
shouldLog: true,
|
||||
);
|
||||
|
||||
_permissionSubscription = _dispatcher.onCommand(0x12).listen((packet) {
|
||||
// 🔥 已禁用:权限请求统一由 RemoteControlCubit 处理,避免重复弹窗
|
||||
// _permissionSubscription = _dispatcher.onCommand(0x12).listen((packet) {
|
||||
// ...
|
||||
// });
|
||||
|
||||
debugPrint('🔍 [PermissionRequestBloc] ✅✅✅ 0x12 包被监听到了!');
|
||||
_logger.logWithLevel('>>> [PermissionRequestBloc] ✅✅✅ 0x12 包被监听到了!', shouldLog: true);
|
||||
|
||||
try {
|
||||
_logger.logWithLevel('>>> [PermissionRequestBloc] ✅ 收到 0x12 原始包', shouldLog: true);
|
||||
|
||||
// 🔥 关键:手动去掉最后 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('>>> [PermissionRequestBloc] 收到 0x12: $jsonString');
|
||||
_logger.logWithLevel('>>> [PermissionRequestBloc] 收到 0x12: $jsonString', shouldLog: true);
|
||||
|
||||
final jsonMap = jsonDecode(jsonString);
|
||||
final requestType = jsonMap['request'];
|
||||
final platform = jsonMap['platform'];
|
||||
final respondData = jsonMap['respond'];
|
||||
|
||||
_logger.logWithLevel('>>> [PermissionRequestBloc] 解析结果: requestType=$requestType, platform=$platform', shouldLog: true);
|
||||
|
||||
// 情况 1: 响应格式(忽略)
|
||||
if (respondData != null && respondData is Map) {
|
||||
debugPrint('>>> [PermissionRequestBloc] 📊 收到响应格式,忽略');
|
||||
_logger.logWithLevel('>>> [PermissionRequestBloc] 📊 收到响应格式,忽略', shouldLog: true);
|
||||
return;
|
||||
}
|
||||
|
||||
// 情况 2: 请求格式 - switch_control
|
||||
if (requestType == 'switch_control') {
|
||||
// 🔥 关键判断:只有当是其他平台(web)请求时才弹窗
|
||||
if (platform != null && platform.toString().toLowerCase() != 'app') {
|
||||
debugPrint('>>> [PermissionRequestBloc] 🚨 $platform 端请求控制权');
|
||||
_logger.logWithLevel('>>> [PermissionRequestBloc] 🚨 $platform 端请求控制权', shouldLog: true);
|
||||
|
||||
if (!isClosed) {
|
||||
_logger.logWithLevel('>>> [PermissionRequestBloc] 📤 准备 add PermissionRequestReceived 事件', shouldLog: true);
|
||||
add(PermissionRequestReceived(
|
||||
platform: platform.toString(),
|
||||
requestType: requestType,
|
||||
));
|
||||
} else {
|
||||
_logger.logWithLevel('>>> [PermissionRequestBloc] ⚠️ Bloc 已关闭,无法添加事件', shouldLog: true);
|
||||
}
|
||||
} else {
|
||||
debugPrint('>>> [PermissionRequestBloc] ℹ️ APP 自己的请求回显,忽略');
|
||||
_logger.logWithLevel('>>> [PermissionRequestBloc] ℹ️ APP 自己的请求回显,忽略', shouldLog: true);
|
||||
}
|
||||
}
|
||||
// 情况 3: 异地登录通知
|
||||
else if (requestType == 'have_logged_in') {
|
||||
debugPrint('>>> [PermissionRequestBloc] ⚠️ 检测到异地登录');
|
||||
_logger.logWithLevel('>>> [PermissionRequestBloc] ⚠️ 检测到异地登录', shouldLog: true);
|
||||
|
||||
if (!isClosed) {
|
||||
_logger.logWithLevel('>>> [PermissionRequestBloc] 📤 准备 add PermissionRequestReceived 事件(异地登录)', shouldLog: true);
|
||||
add(PermissionRequestReceived(
|
||||
platform: 'unknown',
|
||||
requestType: requestType,
|
||||
));
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('>>> [PermissionRequestBloc] ❌ 解析失败:$e');
|
||||
_logger.logWithLevel('>>> [PermissionRequestBloc] ❌ 解析失败:$e', shouldLog: true);
|
||||
}
|
||||
});
|
||||
|
||||
debugPrint('✅ [PermissionRequestBloc] 0x12 监听器已建立完成');
|
||||
_logger.logWithLevel('✅ [PermissionRequestBloc] 0x12 监听器已建立完成', shouldLog: true);
|
||||
debugPrint(
|
||||
'🔍 [PermissionRequestBloc] ⚠️ 权限监听已禁用,统一由 RemoteControlCubit 处理',
|
||||
);
|
||||
_logger.logWithLevel(
|
||||
'>>> [PermissionRequestBloc] ⚠️ 权限监听已禁用,统一由 RemoteControlCubit 处理',
|
||||
shouldLog: true,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _handleRequestReceived(
|
||||
PermissionRequestReceived event,
|
||||
Emitter<PermissionRequestState> emit,
|
||||
) async {
|
||||
debugPrint('📤 [PermissionRequestBloc] emit DialogVisible - platform=${event.platform}');
|
||||
_logger.logWithLevel('📤 [PermissionRequestBloc] emit DialogVisible - platform=${event.platform}', shouldLog: true);
|
||||
debugPrint(
|
||||
'📤 [PermissionRequestBloc] emit DialogVisible - platform=${event.platform}',
|
||||
);
|
||||
_logger.logWithLevel(
|
||||
'📤 [PermissionRequestBloc] emit DialogVisible - platform=${event.platform}',
|
||||
shouldLog: true,
|
||||
);
|
||||
|
||||
emit(PermissionRequestDialogVisible(
|
||||
emit(
|
||||
PermissionRequestDialogVisible(
|
||||
platform: event.platform,
|
||||
requestType: event.requestType,
|
||||
));
|
||||
),
|
||||
);
|
||||
|
||||
_logger.logWithLevel('📤 [PermissionRequestBloc] ✅ 已 emit DialogVisible 状态', shouldLog: true);
|
||||
_logger.logWithLevel(
|
||||
'📤 [PermissionRequestBloc] ✅ 已 emit DialogVisible 状态',
|
||||
shouldLog: true,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _handleDialogDismissed(
|
||||
@@ -133,7 +89,9 @@ class PermissionRequestBloc extends Bloc<PermissionRequestEvent, PermissionReque
|
||||
|
||||
// 🔥 发送响应到机器人(和远程遥控页面一样)
|
||||
if (event.deviceId.isNotEmpty) {
|
||||
debugPrint('📤 [PermissionRequestBloc] 发送权限响应: agree=${event.agree}, deviceId=${event.deviceId}');
|
||||
debugPrint(
|
||||
'📤 [PermissionRequestBloc] 发送权限响应: agree=${event.agree}, deviceId=${event.deviceId}',
|
||||
);
|
||||
_repository.respondPermission(event.agree, event.deviceId);
|
||||
}
|
||||
|
||||
@@ -151,7 +109,10 @@ class PermissionRequestBloc extends Bloc<PermissionRequestEvent, PermissionReque
|
||||
/// 🔥 公开方法:重新初始化监听器(TCP 重连后调用)
|
||||
void reinitListener() {
|
||||
debugPrint('🔄 [PermissionRequestBloc] 重新初始化监听器');
|
||||
_logger.logWithLevel('🔄 [PermissionRequestBloc] 重新初始化监听器', shouldLog: true);
|
||||
_logger.logWithLevel(
|
||||
'🔄 [PermissionRequestBloc] 重新初始化监听器',
|
||||
shouldLog: true,
|
||||
);
|
||||
_permissionSubscription?.cancel();
|
||||
_initPermissionListener();
|
||||
}
|
||||
|
||||
@@ -1472,13 +1472,13 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
List<LatLng> newOuterPoints = [];
|
||||
for (var item in pathList) {
|
||||
double? lat = safeToDouble(item['lat']);
|
||||
double? lon = safeToDouble(item['lon']);
|
||||
double? lon = safeToDouble(item['lng'] ?? item['lon']);
|
||||
if (lat != null && lon != null) {
|
||||
newPathPoints.add(convertWGS84ToGCJ02(lat, lon));
|
||||
}
|
||||
}
|
||||
for (var item in outerList) {
|
||||
double? lng = safeToDouble(item['lng']);
|
||||
double? lng = safeToDouble(item['lng'] ?? item['lon']);
|
||||
double? lat = safeToDouble(item['lat']);
|
||||
if (lng != null && lat != null) {
|
||||
newOuterPoints.add(convertWGS84ToGCJ02(lat, lng));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:dart_ping/dart_ping.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
@@ -39,13 +40,12 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
|
||||
int _currentOriginX = 0;
|
||||
int _currentOriginY = 0;
|
||||
|
||||
// 🔥 权限请求冷却期机制 - 防止Web端持续发送请求导致弹窗不断显示
|
||||
DateTime? _lastPermissionResponseTime; // 记录上次响应权限请求的时间
|
||||
static const _coolDownDuration = Duration(seconds: 5); // 冷却期5秒
|
||||
|
||||
// 🔥 同步锁 - 防止状态更新期间接收新请求导致重复弹窗
|
||||
// 🔥 权限请求处理标志位 - 防止竞态条件,避免重复显示弹窗
|
||||
bool _isProcessingPermissionRequest = false;
|
||||
|
||||
// 🔥 模拟数据推送定时器
|
||||
// Timer? _simulationTimer;
|
||||
|
||||
// 🔥 获取带时间戳的日志前缀
|
||||
String _getTimePrefix() {
|
||||
final now = DateTime.now();
|
||||
@@ -69,40 +69,138 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
|
||||
}
|
||||
|
||||
// 🔥 新增:订阅 DeviceStatusBloc 的状态流,而不是直接监听TCP
|
||||
// 类全局变量
|
||||
DateTime? _lastUiUpdateTime;
|
||||
String? _cacheVoltage;
|
||||
String? _cacheBattery;
|
||||
String? _cacheCtrlMode;
|
||||
int? _cachePing;
|
||||
|
||||
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 = await getNetworkDelay();
|
||||
|
||||
final c = 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: voltage.toString(),
|
||||
battery: battery.toString(),
|
||||
controlMode: controlMode,
|
||||
voltage: _cacheVoltage,
|
||||
battery: _cacheBattery,
|
||||
controlMode: _cacheCtrlMode,
|
||||
),
|
||||
battery: int.tryParse(battery) ?? 0,
|
||||
ping: await c,
|
||||
battery: int.tryParse(_cacheBattery ?? '') ?? 0,
|
||||
ping: _cachePing,
|
||||
// 🔥 标记为设备状态更新
|
||||
updateType: 'device_status',
|
||||
),
|
||||
);
|
||||
|
||||
// // _logger.logWithLevel('✅ [RemoteControl] 从 DeviceStatusBloc 收到更新: 电压=$voltage, 电量=$battery, 模式=$controlMode');
|
||||
}
|
||||
});
|
||||
|
||||
// _logger.logWithLevel('>>> [RemoteControl] ✅ DeviceStatusBloc 订阅已建立完成');
|
||||
}
|
||||
|
||||
// 🔥 超简单方法:传入 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');
|
||||
@@ -141,14 +239,37 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
|
||||
_kickOutSub = dispatcher.onCommand(0x12).listen((packet) {
|
||||
final timeNow = _getTimePrefix();
|
||||
debugPrint('$timeNow 🔍 [RemoteControl] [0x12监听器] ✅ 收到 0x12 原始包');
|
||||
debugPrint(
|
||||
/* 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;
|
||||
@@ -179,11 +300,13 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
|
||||
|
||||
// 情况 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监听器] 📊 收到切换结果响应');
|
||||
debugPrint(
|
||||
'$timeNow 🔍 [RemoteControl] [0x12监听器] switchResult: $switchResult',
|
||||
);
|
||||
);*/
|
||||
_logger.logWithLevel(
|
||||
'$timeNow 🔍 [RemoteControl] [0x12监听器] 📊 收到切换结果响应 - switchResult: $switchResult',
|
||||
shouldLog: true,
|
||||
@@ -218,6 +341,15 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
|
||||
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');
|
||||
@@ -235,8 +367,17 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
|
||||
|
||||
if (currentDeviceId != null && requestDeviceId == currentDeviceId) {
|
||||
debugPrint('$webLogPrefix 设备ID匹配');
|
||||
// 🔥 添加防重复检查:只有当弹窗还没显示时才弹出
|
||||
if (!state.showPermissionRequestDialog) {
|
||||
|
||||
// 🔥 只有当弹窗还没显示时才弹出,防止重复弹窗叠加
|
||||
if (state.showPermissionRequestDialog) {
|
||||
debugPrint('$webLogPrefix ⚠️ 弹窗已显示,忽略重复请求');
|
||||
_logger.logWithLevel(
|
||||
'$webLogPrefix ⚠️ 弹窗已显示,忽略重复请求',
|
||||
shouldLog: true,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
debugPrint('$webLogPrefix 弹出权限请求对话框');
|
||||
_logger.logWithLevel(
|
||||
'$webLogPrefix 设备ID匹配,弹出权限请求对话框',
|
||||
@@ -248,16 +389,11 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
|
||||
showPermissionRequestDialog: true,
|
||||
requestingDeviceId: requestDeviceId?.toString(),
|
||||
requestingPlatform: platform.toString(),
|
||||
// 🔥 标记为弹窗状态更新
|
||||
updateType: 'permission_dialog',
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
debugPrint('$webLogPrefix ⚠️ 弹窗已显示,忽略重复请求');
|
||||
_logger.logWithLevel(
|
||||
'$webLogPrefix ⚠️ 弹窗已显示,忽略重复请求',
|
||||
shouldLog: true,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
debugPrint('$webLogPrefix ⚠️ 设备ID不匹配,忽略');
|
||||
debugPrint(
|
||||
@@ -284,7 +420,8 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
|
||||
'${_getTimePrefix()} ⚠️ [RemoteControl] 检测到异地登录,打开弹窗提示',
|
||||
shouldLog: true,
|
||||
);
|
||||
if (!isClosed) {
|
||||
// 🔥 只有当弹窗还没显示时才弹出
|
||||
if (!isClosed && !state.showPermissionRequestDialog) {
|
||||
emit(state.copyWith(showPermissionRequestDialog: true));
|
||||
}
|
||||
} else {
|
||||
@@ -331,6 +468,9 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
|
||||
//debugPrint('$logPrefix 启动控制循环,间隔: 100ms');
|
||||
///debugPrint('$logPrefix =========================================');
|
||||
|
||||
// 🔥 启动模拟设备状态推送(用于测试)
|
||||
// startSimulation();
|
||||
|
||||
_timer = Timer.periodic(const Duration(milliseconds: 100), (timer) {
|
||||
if (isClosed) {
|
||||
// debugPrint('$logPrefix ❌ Cubit已关闭,取消定时器');
|
||||
@@ -538,27 +678,16 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
|
||||
}
|
||||
|
||||
void respondPermission(bool agreed, String deviceId) {
|
||||
|
||||
|
||||
|
||||
// 🔥 记录响应时间,开启冷却期
|
||||
_lastPermissionResponseTime = DateTime.now();
|
||||
//debugPrint('$logPrefix 开启权限请求冷却期,持续${_coolDownDuration.inSeconds}秒');
|
||||
|
||||
// 1. 关闭弹窗(立即关闭,防止重复点击)
|
||||
emit(state.copyWith(showPermissionRequestDialog: false));
|
||||
|
||||
// 2. 发送响应到服务器
|
||||
//debugPrint('$logPrefix 📤 发送权限响应命令到服务器');
|
||||
try {
|
||||
// 🔥 关键调用:发送 TCP 指令
|
||||
debugPrint('{_coolDownDuration.inSeconds}秒');
|
||||
_repository.respondPermission(agreed, deviceId);
|
||||
} catch (e) {
|
||||
debugPrint(' ❌ TCP权限响应指令发送失败: $e');
|
||||
debugPrint(' ❌ 错误类型: ${e.runtimeType}');
|
||||
_logger.logWithLevel(' TCP权限响应指令发送失败 $e', shouldLog: true);
|
||||
// 即使发送失败,也要更新状态
|
||||
rethrow;
|
||||
}
|
||||
|
||||
@@ -577,9 +706,7 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
|
||||
state.copyWith(hasPermission: true, showPermissionRequestDialog: false),
|
||||
);
|
||||
}
|
||||
debugPrint(
|
||||
'${_getTimePrefix()} ====权限弹窗响应结束=====',
|
||||
);
|
||||
debugPrint('${_getTimePrefix()} ====权限弹窗响应结束=====');
|
||||
}
|
||||
|
||||
Future<void> requestControlPermissionS(
|
||||
@@ -625,12 +752,13 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
|
||||
final failLogPrefix =
|
||||
'${_getTimePrefix()} 🔑 [RemoteControl] [请求权限接口-$source]';
|
||||
debugPrint('$failLogPrefix APP请求控制权限失败: ${failure.message}');
|
||||
debugPrint('$failLogPrefix 重新打开权限请求弹窗');
|
||||
_logger.logWithLevel(
|
||||
'$failLogPrefix APP请求控制权限失败: ${failure.message}',
|
||||
shouldLog: true,
|
||||
);
|
||||
emit(state.copyWith(showPermissionRequestDialog: true));
|
||||
// 🔥 修复:HTTP请求失败时不要自动打开弹窗,避免形成循环
|
||||
// emit(state.copyWith(showPermissionRequestDialog: true));
|
||||
debugPrint('$failLogPrefix HTTP请求失败,不自动打开弹窗');
|
||||
},
|
||||
(permissionInfo) async {
|
||||
final bool hasPermission =
|
||||
@@ -689,7 +817,11 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
|
||||
);
|
||||
}
|
||||
|
||||
/// 🔥 权限弹窗确认后调用- 发送TCP响应 + HTTP确认最终权限状态
|
||||
/// 🔥 重置弹窗状态 - 在弹窗关闭后调用(已简化,不再需要标志位)
|
||||
void resetPermissionCoolDown() {
|
||||
// 标志位已移除,此方法保留以保持向后兼容性
|
||||
}
|
||||
|
||||
Future<void> confirmPermissionResponse(
|
||||
String deviceName,
|
||||
String platform,
|
||||
@@ -702,6 +834,7 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
|
||||
debugPrint('$logPrefix 用户操作: ${agreed ? "同意" : "拒绝"}');
|
||||
debugPrint('$logPrefix deviceName: $deviceName');
|
||||
debugPrint('$logPrefix platform: $platform');
|
||||
|
||||
respondPermission(agreed, deviceName);
|
||||
|
||||
// 2. 调用 HTTP 接口获取最终权限状态
|
||||
@@ -734,7 +867,7 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
|
||||
'$successLogPrefix 🔄 正在更新UI - hasPermission: ${state.hasPermission} -> $hasPermission',
|
||||
);
|
||||
// 🔥 直接用HTTP 返回的权限状态覆盖
|
||||
emit(state.copyWith(hasPermission: hasPermission));
|
||||
// emit(state.copyWith(hasPermission: hasPermission));
|
||||
debugPrint(
|
||||
'$successLogPrefix 📊 UI已同步完成- 当前 hasPermission=$hasPermission',
|
||||
);
|
||||
|
||||
@@ -28,8 +28,11 @@ class RemoteControlState extends Equatable {
|
||||
final bool obstacleRecognitionFlag; // 障碍物识别标志位(这是UI显示的)
|
||||
|
||||
final String obstacleFlag; //障碍物标志位
|
||||
final DeviceEntity? targetDevice; // 🔥 待控制的设备
|
||||
final RunningStatusModel runningStatusModel;
|
||||
final DeviceEntity? targetDevice;
|
||||
|
||||
// 🔥 状态更新类型标记 - 用于区分是设备状态更新还是弹窗状态更新
|
||||
final String? updateType;
|
||||
|
||||
const RemoteControlState({
|
||||
this.status = RemoteControlStatus.initial,
|
||||
@@ -53,6 +56,7 @@ class RemoteControlState extends Equatable {
|
||||
this.topRightIsExpanded = false,
|
||||
this.obstacleRecognitionFlag = true,
|
||||
this.targetDevice,
|
||||
this.updateType,
|
||||
});
|
||||
|
||||
// 便利 UI 更新部分属性
|
||||
@@ -78,7 +82,7 @@ class RemoteControlState extends Equatable {
|
||||
bool? topRightIsExpanded,
|
||||
bool? obstacleRecognitionFlag,
|
||||
DeviceEntity? targetDevice,
|
||||
|
||||
String? updateType,
|
||||
}) {
|
||||
return RemoteControlState(
|
||||
status: status ?? this.status,
|
||||
@@ -101,9 +105,10 @@ class RemoteControlState extends Equatable {
|
||||
runningStatusModel: runningStatusModel ?? this.runningStatusModel,
|
||||
obstacleFlag: obstacleFlag ?? this.obstacleFlag,
|
||||
topRightIsExpanded: topRightIsExpanded ?? this.topRightIsExpanded,
|
||||
obstacleRecognitionFlag: obstacleRecognitionFlag ?? this.obstacleRecognitionFlag,
|
||||
obstacleRecognitionFlag:
|
||||
obstacleRecognitionFlag ?? this.obstacleRecognitionFlag,
|
||||
targetDevice: targetDevice ?? this.targetDevice,
|
||||
|
||||
updateType: updateType, // 不使用 ??,因为我们想要 null 的时候就是 null
|
||||
);
|
||||
}
|
||||
|
||||
@@ -130,8 +135,6 @@ class RemoteControlState extends Equatable {
|
||||
topRightIsExpanded,
|
||||
obstacleRecognitionFlag,
|
||||
targetDevice,
|
||||
updateType,
|
||||
];
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -33,6 +33,7 @@ class _RemoteControlPageState extends State<RemoteControlPage> {
|
||||
DevicesCubit? _devicesCubit;
|
||||
StreamSubscription? _permissionSubscription; // 🔥 权限监听订阅
|
||||
bool _isShowingPermissionDialog = false; // 🔥 防止弹窗重复显示
|
||||
bool? _lastPermissionDialogState; // 🔥 记录上次弹窗状态,检测状态变化
|
||||
bool _isLoadingPermission = true; // 🔥 标记是否正在加载权限状态
|
||||
|
||||
@override
|
||||
@@ -44,10 +45,28 @@ class _RemoteControlPageState extends State<RemoteControlPage> {
|
||||
// 🔥 监听权限弹窗状态变化 (只订阅一次)
|
||||
if (_permissionSubscription == null) {
|
||||
_permissionSubscription = _cubit.stream.listen((state) {
|
||||
// 🔥 加强防重复逻辑:只在状态真正变化且不在显示弹窗时才显示
|
||||
if (mounted &&
|
||||
// 🔥 关键修复:只有当状态更新类型是弹窗更新时才处理弹窗逻辑
|
||||
// 设备状态更新(500ms)不会触发弹窗显示
|
||||
if (state.updateType != 'permission_dialog') {
|
||||
// 如果是设备状态更新,只重置状态记录,不显示弹窗
|
||||
if (!state.showPermissionRequestDialog) {
|
||||
_lastPermissionDialogState = false;
|
||||
}
|
||||
return; // 跳过设备状态更新
|
||||
}
|
||||
|
||||
// 🔥 只有当状态从 false 变为 true 时才显示弹窗
|
||||
// 使用三个条件确保不会重复显示
|
||||
final wasFalseBefore =
|
||||
_lastPermissionDialogState == null ||
|
||||
_lastPermissionDialogState == false;
|
||||
final shouldShow =
|
||||
state.showPermissionRequestDialog &&
|
||||
!_isShowingPermissionDialog) {
|
||||
!_isShowingPermissionDialog &&
|
||||
wasFalseBefore;
|
||||
|
||||
if (mounted && shouldShow) {
|
||||
_lastPermissionDialogState = true; // 记录当前状态
|
||||
_isShowingPermissionDialog = true;
|
||||
debugPrint('🔔 [权限弹窗] 检测到 showPermissionRequestDialog=true,准备显示弹窗');
|
||||
// 使用微任务确保标志位已设置
|
||||
@@ -55,10 +74,17 @@ class _RemoteControlPageState extends State<RemoteControlPage> {
|
||||
_showPermissionDialog(state).then((_) {
|
||||
debugPrint('🔔 [权限弹窗] 弹窗已关闭,重置标志位');
|
||||
_isShowingPermissionDialog = false; // 弹窗关闭后重置标志
|
||||
_lastPermissionDialogState = false; // 重置状态记录
|
||||
});
|
||||
});
|
||||
} else if (!state.showPermissionRequestDialog) {
|
||||
// 状态变为 false 时,重置记录
|
||||
_lastPermissionDialogState = false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 🔥 如果已经订阅,重置状态记录,确保下次能正确检测状态变化
|
||||
_lastPermissionDialogState = _cubit.state.showPermissionRequestDialog;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,8 +411,18 @@ class _RemoteControlPageState extends State<RemoteControlPage> {
|
||||
);
|
||||
}
|
||||
|
||||
// 🔥 关闭弹窗(在 TCP 发送完成后)
|
||||
// 🔥 关键修复:先重置弹窗标志位,再关闭弹窗
|
||||
// 避免 Navigator.pop() 后上下文失效导致后续代码不执行
|
||||
context.read<RemoteControlCubit>().resetPermissionCoolDown();
|
||||
context.read<RemoteControlCubit>().emit(
|
||||
context.read<RemoteControlCubit>().state.copyWith(
|
||||
showPermissionRequestDialog: false,
|
||||
),
|
||||
);
|
||||
|
||||
// 🔥 关闭弹窗(在状态重置之后)
|
||||
Navigator.pop(dialogContext);
|
||||
debugPrint('🔔 [权限弹窗] 弹窗已关闭,重置标志位');
|
||||
},
|
||||
child: Text(
|
||||
AppLocalizations.of(
|
||||
@@ -434,9 +470,7 @@ class _RemoteControlPageState extends State<RemoteControlPage> {
|
||||
debugPrint('🔑 [权限弹窗] 用户同意,调用 confirmPermissionResponse');
|
||||
final targetDevice = remoteCubit.state.targetDevice;
|
||||
if (targetDevice != null) {
|
||||
debugPrint(
|
||||
'✅ [权限弹窗] targetDevice 不为空',
|
||||
);
|
||||
debugPrint('✅ [权限弹窗] targetDevice 不为空');
|
||||
await remoteCubit.confirmPermissionResponse(
|
||||
targetDevice.deviceName,
|
||||
'app',
|
||||
@@ -449,8 +483,18 @@ class _RemoteControlPageState extends State<RemoteControlPage> {
|
||||
);
|
||||
}
|
||||
|
||||
// 🔥 关闭弹窗(在 TCP 发送完成后)
|
||||
// 🔥 关键修复:先重置弹窗标志位,再关闭弹窗
|
||||
// 避免 Navigator.pop() 后上下文失效导致后续代码不执行
|
||||
context.read<RemoteControlCubit>().resetPermissionCoolDown();
|
||||
context.read<RemoteControlCubit>().emit(
|
||||
context.read<RemoteControlCubit>().state.copyWith(
|
||||
showPermissionRequestDialog: false,
|
||||
),
|
||||
);
|
||||
|
||||
// 🔥 关闭弹窗(在状态重置之后)
|
||||
Navigator.pop(dialogContext);
|
||||
debugPrint('🔔 [权限弹窗] 弹窗已关闭,重置标志位');
|
||||
},
|
||||
child: Text(
|
||||
AppLocalizations.of(
|
||||
|
||||
Reference in New Issue
Block a user