Files
flutterApp/lib/features/remote_control/presentation/bloc/remote_control_cubit.dart
Songzex 3c3b2ce4ee 修复远程遥控的顶部的布局优化,
完成实现远程遥控的电压和电量和模式实时从tcp获取和展示
2026-03-25 15:09:11 +08:00

397 lines
13 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:ffi';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.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/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;
StreamSubscription? _stringMessageSub; //
static const platform = MethodChannel('com.maibu.satabot/ping');
int _currentPing = 50;
RemoteControlCubit(this._repository, this._requestControlPermissionUseCase, this.dispatcher)
: super(
RemoteControlState(
controlEntity: MachineControlStatusEntity(),
runningStatusModel: RunningStatusModel(),
),
) {
_initPacketListener();
_initStringMessageListener(); // 🔥 新增
}
void _initStringMessageListener() {
print('>>> [RemoteControl] begin 初始化 0x02 字符串监听器');
_stringMessageSub?.cancel();
_stringMessageSub = dispatcher.onStringMessage().listen((message) async {
print('>>> [RemoteControl] 收到 0x02 字符串推送:$message');
if (message.isEmpty) {
print('⚠️ [RemoteControl] 消息为空,跳过解析');
return;
}
try {
// 🔥 关键:使用与 DeviceStatusBloc 相同的解析方法
print('>>> [RemoteControl] 🔍 开始解析数据(使用 fromFields)...');
final fields = message.trim().split(',');
print('>>> [RemoteControl] 字段数量:${fields.length}');
if (fields.length < 18) {
print('⚠️ [RemoteControl] 字段不足:${fields.length},期望 ≥18');
return;
}
// 🔥 使用 RunningStatusEntity.fromFields() 解析
final status = RunningStatusEntity.fromFields(fields);
// 🔥 只提取电压、电量、控制模式
final voltage = status.voltage;
final battery = status.battery;
final controlMode = status.controlMode == '3' ? '远程模式' : '本地模式';
// final c = getPing('1.95.137.212'); // ⚠️ 替换为你的服务器 IP
emit(state.copyWith(
runningStatusModel: state.runningStatusModel.copyWith(
voltage: voltage.toString(), // 更新电压
battery: battery.toString(), // 更新电量
controlMode: controlMode, // 更新控制模式
),
battery: int.tryParse(battery) ?? 0,
//ping: c, // 这里直接使用异步返回的数值
));
} catch (e, stackTrace) {
print('>>> [RemoteControl] ❌ 解析失败:$e');
// print('>>> [RemoteControl] ❌ 堆栈跟踪:$stackTrace');
// print('>>> [RemoteControl] ❌ 原始消息:$message');
}
});
print('>>> [RemoteControl] ✅ 0x02 字符串监听器已建立完成');
}
// 🔥 超简单方法:传入 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}%');
}
}
// 🔥 辅助方法:更新运行状态
// 1. 初始化回包监听 (如 0x12 权限)
// void _initPacketListener() {
// _repository.responseStream.listen((packet) {
// if (packet.command == 0x12) {
// // 根据负载判断是否有权限,更新状态
// emit(state.copyWith(hasPermission: true));
// }
// });
// }
void _initPacketListener() {
print('>>> [RemoteControl] begin 初始化 0x12 监听器');
_kickOutSub?.cancel(); // 防止重复监听
_kickOutSub = dispatcher.onCommand(0x12).listen((packet) {
print('>>> [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');
final jsonMap = jsonDecode(jsonString);
// 🔥 区分两种数据格式
final requestType = jsonMap['request'];
final platform = jsonMap['platform'];
final respondData = jsonMap['respond'];
print('>>> [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');
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 端请求控制权,打开弹窗询问用户');
if (!isClosed) {
emit(state.copyWith(showPermissionRequestDialog: true));
}
} else {
print('>>> [RemoteControl] ℹ️ APP 自己的请求回显,忽略不弹窗');
}
}
// 情况 3: 异地登录通知 - {"request":"have_logged_in",...}
else if (requestType == 'have_logged_in') {
print('>>> [RemoteControl] ⚠️ 检测到异地登录,打开弹窗提示');
if (!isClosed) {
emit(state.copyWith(showPermissionRequestDialog: true));
}
}
else {
print('>>> [RemoteControl] ℹ️ 未知类型的 0x12 包,忽略');
}
} catch (e) {
print('>>> [RemoteControl] ❌ 解析失败:$e');
}
});
print('>>> [RemoteControl] ✅ 0x12 监听器已建立完成');
}
// 2. 开启 100ms 控制循环 (在进入遥控页面或点击“开始”时调用)
void startControlLoop() {
_timer?.cancel();
_timer = Timer.periodic(const Duration(milliseconds: 100), (timer) {
// 核心调用:直接把 state 里的实体丢给 repository
_repository.sendControlMachineCmd(state.controlEntity);
});
emit(state.copyWith(status: RemoteControlStatus.controlling));
}
// 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');
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');
}
void updateOriginY(int y) {
final updatedEntity = state.controlEntity.copyWith(
y: y,
);
emit(state.copyWith(controlEntity: updatedEntity));
}
void updateOriginX(int x) {
final updatedEntity = state.controlEntity.copyWith(
x: x,
);
emit(state.copyWith(controlEntity: updatedEntity));
}
// 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(); // 退出页面时务必销毁定时器
_kickOutSub?.cancel();
_stringMessageSub?.cancel();
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}');
// 可以在这里显示错误提示或重新打开弹窗
emit(state.copyWith(showPermissionRequestDialog: true));
},
(success) {
// 更新状态
print('✅ [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}');
updateFunction(lift: i);
}
/// 发送割刀指令
void sendMowerCommand(int i) {
debugPrint(' [割刀指令] ${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}');
updateFunction(ignition: i);
}
// 发送障碍物识别指令
void toggleObstacleRecognition() {
emit(state.copyWith(obstacleRecognitionFlag: !state.obstacleRecognitionFlag));
}
void toggleTopLeftExpand() {
emit(state.copyWith(topRightIsExpanded: !state.topRightIsExpanded));
}
int getPing(String host) {
print('>>> [RemoteControl] 📡 开始 ping $host...');
// 后台异步获取,不阻塞
platform.invokeMethod('ping', {
'host': host,
'count': 1,
'timeout': 2,
}).then((result) {
final success = result['success'] as bool? ?? false;
final delay = (result['delayMs'] as num?)?.toInt() ?? 100;
_currentPing = success ? delay.clamp(0, 100) : 100;
print('📶 [Ping 结果] $host - $_currentPing ms');
// 获取到新值后,再次 emit 更新 state
if (!isClosed) {
emit(state.copyWith(ping: _currentPing));
}
}).catchError((e) {
print('⚠️ [Ping 错误] $e');
_currentPing = 100;
});
return _currentPing; // 立即返回当前值
}
}