完成优化登录的优化
完成切换和切回APP数据的及时响应 完成服务下发退出账号的命令 完成远程遥控的权限的前置确认和获取 完后远程遥控的前后左右的控制.
This commit is contained in:
@@ -51,6 +51,9 @@ import '../../features/devices/domain/usecases/save_work_record_usecase.dart';
|
||||
import '../../features/devices/domain/usecases/select_work_record_usecase.dart';
|
||||
import '../../features/devices/domain/usecases/switch_device_usecase.dart';
|
||||
import '../../features/devices/presentation/bloc/device_status_bloc.dart';
|
||||
import '../../features/remote_control/data/datasources/remote_http_datasource.dart';
|
||||
import '../../features/remote_control/data/datasources/remote_tcp_datasource.dart';
|
||||
import '../../features/remote_control/domain/usecase/remote_control_usecase.dart';
|
||||
import '../../features/remote_control/presentation/bloc/remote_control_cubit.dart';
|
||||
import '../app/app_user_cubit.dart';
|
||||
import '../network/dio_client.dart';
|
||||
@@ -100,6 +103,13 @@ Future<void> init() async {
|
||||
sl.registerLazySingleton<DeviceHttpDatasource>(
|
||||
() => DeviceHttpDatasourceImpl(sl(), sl<UserStorage>()), //
|
||||
);
|
||||
sl.registerLazySingleton<RemoteHttpDatasource>(
|
||||
() => RemoteHttpDatasource(sl(), sl<UserStorage>()),
|
||||
);
|
||||
|
||||
sl.registerLazySingleton<RemoteTcpDatasource>(
|
||||
() => RemoteTcpDatasource(sl<TcpClient>(), sl<UserStorage>()),
|
||||
);
|
||||
|
||||
/// 3. 仓库 (Repository)
|
||||
sl.registerLazySingleton<AuthRepository>(
|
||||
@@ -144,7 +154,7 @@ Future<void> init() async {
|
||||
);
|
||||
|
||||
sl.registerLazySingleton<DeviceRepository>(() => DeviceRepositoryImpl(sl()));
|
||||
sl.registerLazySingleton<RemoteControlRepository>(() => RemoteControlRepositoryImpl(sl(), sl()));
|
||||
sl.registerLazySingleton<RemoteControlRepository>(() => RemoteControlRepositoryImpl(sl(), sl() ,sl(),sl()));
|
||||
|
||||
/// 4. 用例 (UseCase)
|
||||
sl.registerLazySingleton(() => LoginUseCase(sl()));
|
||||
@@ -167,11 +177,14 @@ Future<void> init() async {
|
||||
),
|
||||
);
|
||||
|
||||
sl.registerLazySingleton<RequestControlPermissionUseCase>(() => RequestControlPermissionUseCase(sl()));
|
||||
|
||||
|
||||
/// 5. 状态管理 (Cubit/Bloc)
|
||||
sl.registerLazySingleton(() => AppUserCubit()); // AuthCubit 依赖它,必须先注册
|
||||
sl.registerLazySingleton(() => GetDeviceLocationUseCase(sl()));
|
||||
sl.registerLazySingleton(() => DevicesCubit(sl(), sl(), sl(), sl(), sl(), sl(), sl(), sl(), sl(), sl(), sl(), sl(),sl(),sl(),sl()));
|
||||
sl.registerFactory(() => RemoteControlCubit(sl()));
|
||||
sl.registerFactory(() => RemoteControlCubit(sl(),sl()));
|
||||
/* // 工厂模式(留存,每次获取新实例)
|
||||
|
||||
sl.registerFactory(() => DeviceStatusBloc(sl<NetMessageDispatcher>()));
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
import 'dart:ffi';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
import '../../../../core/storage/user_storage.dart';
|
||||
|
||||
class RemoteHttpDatasource {
|
||||
final Dio _dio;
|
||||
final UserStorage _userStorage; // 🔥 新增字段
|
||||
|
||||
RemoteHttpDatasource(this._dio);
|
||||
|
||||
RemoteHttpDatasource(this._dio, this._userStorage);
|
||||
|
||||
/// 示例:获取控制授权 (对应之前逻辑中的权限申请)
|
||||
Future<Map<String, dynamic>> requestControlAuth(String robotId) async {
|
||||
/* Future<Map<String, dynamic>> requestControlAuth(String robotId) async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
'/robot/auth/request',
|
||||
@@ -16,5 +23,36 @@ class RemoteHttpDatasource {
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
Future<bool> requestRemoteControlViaHttp( String deviceId, String currentDeviceId) async {
|
||||
final user = await _userStorage.getUser();
|
||||
if (user == null) {
|
||||
return false;
|
||||
}
|
||||
final token = user.token;
|
||||
final response = await _dio.post(
|
||||
'/forward/device/remoteControl',
|
||||
options: Options(
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer ${token}',
|
||||
},
|
||||
),
|
||||
data: {'deviceId': deviceId, 'platform': "app"});
|
||||
|
||||
try {
|
||||
final data = response.data as Map<String, dynamic>;
|
||||
final success = data['success'] as bool? ?? false;
|
||||
if (success) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('❌ [RemoteHttp] 解析响应失败:$e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../core/network/protocol_decoder.dart';
|
||||
import '../../../../core/network/tcp/tcp_client.dart';
|
||||
import '../../../../core/protocol/machine_protocol_constants.dart';
|
||||
import '../../../../core/storage/user_storage.dart';
|
||||
|
||||
/// 远程遥控 TCP 数据源
|
||||
/// 负责通过 TCP 协议发送控制指令和权限请求
|
||||
class RemoteTcpDatasource {
|
||||
final TcpClient _tcpClient;
|
||||
final UserStorage _userStorage;
|
||||
|
||||
RemoteTcpDatasource(this._tcpClient, this._userStorage);
|
||||
|
||||
/// 发送 switch_control 权限请求(对应 Android 的 requestSwitchControl)
|
||||
///
|
||||
/// 这是获取设备控制权的第一步:
|
||||
/// 1. 先发送 TCP 0x12 指令携带 switch_control 请求
|
||||
/// 2. 等待服务端响应
|
||||
/// 3. 再调用 HTTP API 确认控制权
|
||||
Future<void> sendSwitchControlRequest(String deviceName) async {
|
||||
if (!_tcpClient.isConnected) {
|
||||
debugPrint('❌ [TCP] 无法发送权限请求:Socket 未连接');
|
||||
throw Exception('TCP 未连接');
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取用户信息
|
||||
final user = await _userStorage.getUser();
|
||||
if (user == null || user.token == null) {
|
||||
debugPrint('❌ [TCP] 认证失败:用户未登录或 Token 为空,无法发送认证包');
|
||||
throw Exception('用户未登录或 Token 无效');
|
||||
}
|
||||
|
||||
final username = user.username;
|
||||
final token = user.token;
|
||||
final currentDeviceId = deviceName;
|
||||
|
||||
// 构造 JSON 请求体(参考 Android 代码)
|
||||
final request = {
|
||||
'request': 'switch_control',
|
||||
'deviceId': currentDeviceId,
|
||||
'userId': username,
|
||||
'platform': 'app',
|
||||
'token': token,
|
||||
};
|
||||
|
||||
final jsonStr = jsonEncode(request);
|
||||
final jsonBytes = utf8.encode(jsonStr);
|
||||
|
||||
debugPrint('🔑 [TCP] 发送权限请求:$jsonStr');
|
||||
|
||||
// 使用 0x12 指令发送(cmdGetAuth)
|
||||
// 注意:根据你的协议文档,这里可能是 0x04 或 0x12
|
||||
// Android 代码中使用的是 0x12
|
||||
_tcpClient.sendRaw(
|
||||
MachineProtocolConstants.cmdGetAuth, // 0x04 或 0x12,根据实际协议调整
|
||||
jsonBytes,
|
||||
);
|
||||
|
||||
debugPrint('✅ [TCP] 已发送 switch_control 权限请求 (0x${MachineProtocolConstants.cmdGetAuth.toRadixString(16)})');
|
||||
} catch (e) {
|
||||
debugPrint('❌ [TCP] 发送权限请求失败:$e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// 监听 TCP 回包流
|
||||
/// 用于接收权限响应、状态更新等
|
||||
Stream<RawPacket> get responseStream => _tcpClient.packetStream;
|
||||
|
||||
/// 检查 TCP 连接状态
|
||||
bool get isConnected => _tcpClient.isConnected;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:ffi';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
import '../../../../core/network/protocol_decoder.dart';
|
||||
import '../../../../core/network/tcp/tcp_client.dart';
|
||||
@@ -7,12 +11,17 @@ import '../../../../core/protocol/machine_protocol_constants.dart';
|
||||
import '../../domain/entities/machine_control_status_entity.dart';
|
||||
import '../../domain/repositories/remote_control_repository.dart';
|
||||
import '../../domain/usecase/diff_steer_usecase.dart';
|
||||
import '../datasources/remote_http_datasource.dart';
|
||||
import '../datasources/remote_tcp_datasource.dart';
|
||||
|
||||
class RemoteControlRepositoryImpl implements RemoteControlRepository {
|
||||
final TcpClient _tcpClient;
|
||||
final DiffSteerUseCase _diffSteer;
|
||||
final RemoteHttpDatasource _remoteHttp;
|
||||
final RemoteTcpDatasource _remoteTcp;
|
||||
|
||||
RemoteControlRepositoryImpl(this._tcpClient, this._diffSteer);
|
||||
|
||||
RemoteControlRepositoryImpl(this._tcpClient, this._diffSteer, this._remoteHttp, this._remoteTcp);
|
||||
|
||||
@override
|
||||
void changeWebViewDirection(String direction) {
|
||||
@@ -24,6 +33,11 @@ class RemoteControlRepositoryImpl implements RemoteControlRepository {
|
||||
// 1. 调用算法:将摇杆坐标 (x, y) 转换为左右轮电机转速
|
||||
final speeds = _diffSteer.calculate(status.originX, status.originY);
|
||||
|
||||
|
||||
debugPrint('🎮 [摇杆数据] originX: ${status.originX}, originY: ${status.originY}');
|
||||
debugPrint('⚙️ [电机速度] left: ${speeds['left']}, right: ${speeds['right']}');
|
||||
|
||||
|
||||
// 2. 调用 Codec:仅生成协议要求的 8 字节 Payload 负载数据
|
||||
final payload = MachineProtocolCodec.encodeRemoteControlPayload(
|
||||
left: speeds['left']!,
|
||||
@@ -33,6 +47,7 @@ class RemoteControlRepositoryImpl implements RemoteControlRepository {
|
||||
ignition: status.ignitionStatus,
|
||||
emergency: status.isEmergency ? 1 : 0,
|
||||
);
|
||||
debugPrint('📦 [发送数据] payload: ${payload}');
|
||||
|
||||
// 3. 调用 TcpClient:发送指令。
|
||||
// TcpClient.sendRaw 会自动帮你加上 [0xAB, 0xAA] 头和 [0xAA, 0xAB] 尾
|
||||
@@ -44,4 +59,16 @@ class RemoteControlRepositoryImpl implements RemoteControlRepository {
|
||||
|
||||
@override
|
||||
Stream<RawPacket> get responseStream => _tcpClient.packetStream;
|
||||
|
||||
@override
|
||||
Future<bool> requestControlPermission( String deviceName, String deviceId) async {
|
||||
try {
|
||||
await _remoteTcp.sendSwitchControlRequest(deviceName);
|
||||
debugPrint('🔑 [RemoteControl] TCP已发送权限请求 (0x04)');
|
||||
return await _remoteHttp.requestRemoteControlViaHttp(deviceName, deviceId);
|
||||
} catch (e) {
|
||||
debugPrint('❌ [RemoteControl] 权限请求失败:$e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:ffi';
|
||||
|
||||
import '../../../../core/network/protocol_decoder.dart';
|
||||
import '../../domain/entities/machine_control_status_entity.dart';
|
||||
|
||||
@@ -12,4 +14,7 @@ abstract class RemoteControlRepository {
|
||||
|
||||
/// WebView 视角控制 (逻辑层面的切换,不涉及 TCP)
|
||||
void changeWebViewDirection(String direction);
|
||||
|
||||
/// 新增:请求控制权限 (发送 0x04 指令)
|
||||
Future<bool> requestControlPermission(String deviceName, String deviceId);
|
||||
}
|
||||
|
||||
@@ -29,4 +29,8 @@ class DiffSteerUseCase {
|
||||
|
||||
int _signOperator(int num, int polarity) =>
|
||||
polarity != polarityHigh ? -num : num;
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import 'package:fpdart/fpdart.dart';
|
||||
import'package:maibu_satabot_v2/core/domain/usecases/base_usecase.dart';
|
||||
import 'package:maibu_satabot_v2/core/error/failure.dart';
|
||||
import 'package:maibu_satabot_v2/features/remote_control/domain/repositories/remote_control_repository.dart';
|
||||
|
||||
class RequestControlPermissionUseCase implements BaseUseCase<bool, RequestControlPermissionParams> {
|
||||
final RemoteControlRepository remoteControlRepository;
|
||||
|
||||
RequestControlPermissionUseCase(this.remoteControlRepository);
|
||||
|
||||
@override
|
||||
Future<Either<Failure, bool>> call(RequestControlPermissionParams params) async {
|
||||
try {
|
||||
final result = await remoteControlRepository.requestControlPermission(
|
||||
params.deviceName,
|
||||
params.deviceId,
|
||||
);
|
||||
return Right(result);
|
||||
} catch (e) {
|
||||
return Left(Failure('请求控制权限失败:$e'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RequestControlPermissionParams {
|
||||
final String deviceName;
|
||||
final String deviceId;
|
||||
|
||||
const RequestControlPermissionParams({
|
||||
required this.deviceName,
|
||||
required this.deviceId,
|
||||
});
|
||||
}
|
||||
@@ -6,12 +6,14 @@ import 'package:maibu_satabot_v2/features/remote_control/presentation/bloc/remot
|
||||
|
||||
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;
|
||||
|
||||
RemoteControlCubit(this._repository)
|
||||
RemoteControlCubit(this._repository, this._requestControlPermissionUseCase)
|
||||
: super(
|
||||
RemoteControlState(
|
||||
controlEntity: MachineControlStatusEntity(),
|
||||
@@ -61,7 +63,18 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
|
||||
emit(state.copyWith(controlEntity: updatedEntity));
|
||||
}
|
||||
|
||||
void updateOriginY(int y) {}
|
||||
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() {
|
||||
@@ -99,4 +112,35 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
|
||||
void updateEmergency(bool bool) {}
|
||||
|
||||
void respondPermission(bool bool) {}
|
||||
|
||||
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) {
|
||||
emit(state.copyWith(hasPermission: true));
|
||||
print('✅ c:$success');
|
||||
// 权限申请已发送,等待 0x12 回包更新状态
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -33,6 +33,10 @@ class _RemoteControlPageState extends State<RemoteControlPage> {
|
||||
]);
|
||||
// 2. 隐藏状态栏和虚拟按键
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
context.read<RemoteControlCubit>().startControlLoop();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -49,6 +53,11 @@ class _RemoteControlPageState extends State<RemoteControlPage> {
|
||||
final deviceState = context.watch<DevicesCubit>().state;
|
||||
final currentDevice = deviceState.selectedDevice;
|
||||
|
||||
context.read<RemoteControlCubit>().requestControlPermissionS(
|
||||
currentDevice!.deviceName,
|
||||
"app",
|
||||
);
|
||||
|
||||
if (currentDevice == null) {
|
||||
return _buildOfflineScaffold();
|
||||
}
|
||||
|
||||
@@ -31,7 +31,8 @@ class RightJoystickArea extends StatelessWidget {
|
||||
axisHint: AxisHint.leftRight, // 关键:指定为左右控制
|
||||
onValueChanged: (value) {
|
||||
// 对应 viewModel.updateOriginX(x)
|
||||
context.read<RemoteControlCubit>().updateOriginY(value.x);
|
||||
/// context.read<RemoteControlCubit>().updateOriginY(value.x);
|
||||
context.read<RemoteControlCubit>().updateOriginX(value.x);
|
||||
},
|
||||
onPress: () {
|
||||
// 对应 VibrateOnce(current, 100)
|
||||
|
||||
Reference in New Issue
Block a user