修复远程遥控的顶部的布局优化,

完成实现远程遥控的电压和电量和模式实时从tcp获取和展示
This commit is contained in:
2026-03-25 15:09:11 +08:00
parent ba4ced4ea8
commit 3c3b2ce4ee
5 changed files with 93 additions and 44 deletions

View File

@@ -3,11 +3,13 @@ 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';
@@ -18,7 +20,10 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
Timer? _timer;
StreamSubscription? _kickOutSub; // 新增:用于管理监听生命周期
final NetMessageDispatcher dispatcher;
StreamSubscription? _deviceStatusSub;//🔥 新增:用于监听 DeviceStat
StreamSubscription? _stringMessageSub; //
static const platform = MethodChannel('com.maibu.satabot/ping');
int _currentPing = 50;
RemoteControlCubit(this._repository, this._requestControlPermissionUseCase, this.dispatcher)
: super(
@@ -28,62 +33,85 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
),
) {
_initPacketListener();
_initStringMessageListener(); // 🔥 新增
}
// 🔥 新增:监听 DeviceStatusBloc 的状态更新
void subscribeToDeviceStatus(Stream deviceStatusStream) {
_deviceStatusSub?.cancel();
void _initStringMessageListener() {
print('>>> [RemoteControl] begin 初始化 0x02 字符串监听器');
_stringMessageSub?.cancel();
_stringMessageSub = dispatcher.onStringMessage().listen((message) async {
print('>>> [RemoteControl] 收到 0x02 字符串推送:$message');
if (message.isEmpty) {
print('⚠️ [RemoteControl] 消息为空,跳过解析');
return;
}
_deviceStatusSub = deviceStatusStream.listen((deviceState) {
try {
// 🔥 关键:从 DeviceStatusBloc 的 state 中提取数据
// 这里假设 deviceState 是 DevicesCubit 的 state
// 你需要根据实际的 DevicesCubit state 类型来调整
// 🔥 关键:使用与 DeviceStatusBloc 相同的解析方法
print('>>> [RemoteControl] 🔍 开始解析数据(使用 fromFields)...');
debugPrint('📡 [收到设备状态更新] deviceState: $deviceState');
final fields = message.trim().split(',');
print('>>> [RemoteControl] 字段数量:${fields.length}');
// 如果 DevicesCubit 的 state 中有 runningStatusModel 或类似字段
// 方式 1:直接同步整个 RunningStatusModel
if (deviceState is Map && deviceState.containsKey('runningStatusModel')) {
final newStatus = deviceState['runningStatusModel'] as RunningStatusModel;
_updateStatusFromDevice(newStatus);
if (fields.length < 18) {
print('⚠️ [RemoteControl] 字段不足:${fields.length},期望 ≥18');
return;
}
// 方式 2:如果 DevicesCubit 直接暴露 voltage, battery 等字段
else if (deviceState is Map) {
final voltage = deviceState['voltage'] as int? ?? 0;
final battery = deviceState['battery'] as int? ?? 0;
emit(state.copyWith(
voltage: voltage,
battery: battery,
));
// 🔥 使用 RunningStatusEntity.fromFields() 解析
final status = RunningStatusEntity.fromFields(fields);
// 🔥 只提取电压、电量、控制模式
final voltage = status.voltage;
final battery = status.battery;
final controlMode = status.controlMode == '3' ? '远程模式' : '本地模式';
debugPrint('📡 [同步设备状态] 电压:${voltage}V, 电量:${battery}%');
}
} catch (e) {
debugPrint('❌ [同步设备状态失败] $e');
// 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');
}
});
debugPrint('✅ [DeviceStatus] 已建立监听');
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,
voltage: int.tryParse(newStatus.voltage) ?? 0,
battery: int.tryParse(newStatus.battery) ?? 0,
// 如果需要同步其他字段,在这里添加
// controlMode: newStatus.controlMode,
));
debugPrint('✅ [更新运行状态] 电压:${newStatus.voltage}V, 电量:${newStatus.battery}%');
}
}
// 🔥 辅助方法:更新运行状态
// 1. 初始化回包监听 (如 0x12 权限)
// void _initPacketListener() {
// _repository.responseStream.listen((packet) {
@@ -243,7 +271,8 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
@override
Future<void> close() {
_timer?.cancel(); // 退出页面时务必销毁定时器
_deviceStatusSub?.cancel(); // 🔥 确保清理订阅
_kickOutSub?.cancel();
_stringMessageSub?.cancel();
return super.close();
}
@@ -333,6 +362,31 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
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; // 立即返回当前值
}

View File

@@ -17,8 +17,8 @@ class RemoteControlState extends Equatable {
final bool isEmergency;
final bool isLocked;
final int ping;
final int battery;
final int voltage;
final int battery;//电池量
final int voltage;//电压
final String permissionPlatform;
final String currentPlatform;
final bool showPermissionRequestDialog;

View File

@@ -27,7 +27,6 @@ class RemoteControlPage extends StatefulWidget {
class _RemoteControlPageState extends State<RemoteControlPage> {
String _videoStreamUrl = "";
StreamSubscription? _deviceStatusSubscription;
@override
void initState() {
super.initState();
@@ -36,17 +35,12 @@ class _RemoteControlPageState extends State<RemoteControlPage> {
// 2. 隐藏状态栏和虚拟按键
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
// 🔥 关键:订阅 DeviceStatusBloc 的状态流
WidgetsBinding.instance.addPostFrameCallback((_) {
final remoteCubit = context.read<RemoteControlCubit>();
final devicesCubit = context.read<DevicesCubit>();
remoteCubit.startControlLoop();
// 🔥 关键:订阅 DeviceStatusBloc 的状态流
remoteCubit.subscribeToDeviceStatus(devicesCubit.stream);
debugPrint('🚀 [遥控页面] 已启动控制循环和设备状态监听');
});
}
@@ -54,7 +48,6 @@ class _RemoteControlPageState extends State<RemoteControlPage> {
void dispose() {
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
_deviceStatusSubscription?.cancel();
context.read<RemoteControlCubit>().stopControlLoop();
super.dispose();
}
@@ -167,7 +160,6 @@ class _RemoteControlPageState extends State<RemoteControlPage> {
],
),
),
// 权限弹窗
BlocBuilder<RemoteControlCubit, RemoteControlState>(
buildWhen: (p, c) => p.showPermissionRequestDialog != c.showPermissionRequestDialog,

View File

@@ -110,7 +110,7 @@ class _StatusChipState extends State<StatusChip>
widget.text,
style: const TextStyle(
color: Colors.white,
fontSize: 13,
fontSize: 10,
fontWeight: FontWeight.w600,
),
),

View File

@@ -6,7 +6,10 @@ import 'package:maibu_satabot_v2/features/remote_control/presentation/bloc/remot
import 'package:maibu_satabot_v2/features/remote_control/presentation/widgets/status_chip.dart';
import 'package:cc_ui_kit/cc_ui_kit.dart';
import '../../../../core/di/injection.dart';
import '../../../../core/logging/i_logger_service.dart';
import '../../../devices/presentation/bloc/devices_cubit.dart';
import '../../data/models/running_status_model.dart';
import '../bloc/remote_control_cubit.dart';
class TopStatusBar extends StatelessWidget {
@@ -357,7 +360,7 @@ class TopStatusBar extends StatelessWidget {
Widget _buildSliderBox(String label, bool isLeft, BuildContext context) {
// 1. 核心尺寸调整:足够宽的容器解决拥挤,高度匹配状态栏
const double boxWidth = 76; // 水平宽度放大,容纳左右图标
const double boxWidth = 66; // 水平宽度放大,容纳左右图标
const double boxHeight = 32; // 高度和其他按钮保持一致
const double iconSize = 20; // 图标尺寸放大,避免过小拥挤