#连接库初步完成

This commit is contained in:
CC
2026-01-27 18:44:49 +08:00
parent 8c39f0daf8
commit 949743972c
82 changed files with 1187 additions and 650 deletions

View File

@@ -1,9 +1,9 @@
import 'package:dio/dio.dart';
class RemoteHttpDatasource {
class IRemoteHttpDatasource {
final Dio _dio;
RemoteHttpDatasource(this._dio);
IRemoteHttpDatasource(this._dio);
/// 示例:获取控制授权 (对应之前逻辑中的权限申请)
Future<Map<String, dynamic>> requestControlAuth(String robotId) async {

View File

@@ -1,30 +0,0 @@
import 'dart:typed_data';
import '../../../../core/protocol/machine_protocol_codec.dart';
class LawnMoverProtocolModel {
final int left;
final int right;
final int lift;
final int mower;
final int ignition;
final int emergency;
LawnMoverProtocolModel({
required this.left,
required this.right,
this.lift = 0,
this.mower = 0,
this.ignition = 0,
this.emergency = 0,
});
Uint8List toBytes() => MachineProtocolCodec.encodeRemoteControlPayload(
left: left,
right: right,
lift: lift,
mower: mower,
ignition: ignition,
emergency: emergency,
);
}

View File

@@ -0,0 +1,89 @@
import 'dart:typed_data';
import 'package:maibu_satabot_v2/core/data/base_model.dart';
import 'package:maibu_satabot_v2/features/remote_control/domain/entities/machine_control_entity.dart';
class MachineControlModel extends MachineControlEntity implements BaseModel<MachineControlEntity>{
MachineControlModel({
required super.leftSpeed,
required super.rightSpeed,
required super.chassisLift,
required super.mowerSpeed,
required super.ignitionStatus,
required super.isEmergency,
});
factory MachineControlModel.fromEntity(MachineControlEntity entity) {
return MachineControlModel(
leftSpeed: entity.leftSpeed,
rightSpeed: entity.rightSpeed,
chassisLift: entity.chassisLift,
mowerSpeed: entity.mowerSpeed,
ignitionStatus: entity.ignitionStatus,
isEmergency: entity.isEmergency,
);
}
/// 需要传入计算好的左右轮速度
Uint8List toRawPacket(Map<String, int> speeds) {
// 1. 调用 Codec:生成协议定义的 8 字节 Payload
final payload = encodeRemoteControlPayload(
left: speeds['left'] ?? 0,
right: speeds['right'] ?? 0,
lift: chassisLift,
mower: mowerSpeed,
ignition: ignitionStatus,
emergency: isEmergency ? 1 : 0,
);
// 2. 调用 Builder:封装帧头、指令、CRC、帧尾
return payload;
}
@override
MachineControlEntity toEntity() {
return MachineControlEntity(
leftSpeed: leftSpeed,
rightSpeed: rightSpeed,
chassisLift: chassisLift,
mowerSpeed: mowerSpeed,
ignitionStatus: ignitionStatus,
isEmergency: isEmergency,
);
}
@override
Map<String, dynamic> toJson() {
return {
'leftSpeed': leftSpeed,
'rightSpeed': rightSpeed,
'chassisLift': chassisLift,
'mowerSpeed': mowerSpeed,
'ignitionStatus': ignitionStatus,
'isEmergency': isEmergency,
};
}
Uint8List encodeRemoteControlPayload({
required int left,
required int right,
int lift = 0,
int mower = 0,
int ignition = 0,
int emergency = 0,
}) {
final bd = ByteData(8); // 只分配 8 字节
int pos = 0;
bd.setInt16(pos, left, Endian.little);
pos += 2;
bd.setInt16(pos, right, Endian.little);
pos += 2;
bd.setUint8(pos++, lift);
bd.setUint8(pos++, mower);
bd.setUint8(pos++, ignition);
bd.setUint8(pos++, emergency);
return bd.buffer.asUint8List();
}
}

View File

@@ -1,103 +1,132 @@
class RunningStatusModel {
final String voltage;
final String leftTargetSpeed;
final String rightTargetSpeed;
final String leftMeasureSpeed;
final String rightMeasureSpeed;
final String leftCurrent;
final String rightCurrent;
final String leftMotorTemp;
final String rightMotorTemp;
final String chipTemp;
final String yaw;
final String pitch;
final String roll;
final String satelliteCnt;
final String qual;
final String headingStatus;
final String latitude;
final String longitude;
final String time;
final String knifeCuttingSpeed;
final String controlMode;
final String workingArea;
final String battery;
import 'dart:convert';
import 'dart:typed_data';
import 'package:maibu_satabot_v2/core/data/base_model.dart';
import 'package:maibu_satabot_v2/features/remote_control/domain/entities/running_status_entity.dart';
import '../../../../core/network/enums/control_mode.dart';
class RunningStatusModel extends RunningStatusEntity
implements BaseModel<RunningStatusEntity> {
RunningStatusModel({
this.voltage = '',
this.leftTargetSpeed = '',
this.rightTargetSpeed = '',
this.leftMeasureSpeed = '',
this.rightMeasureSpeed = '',
this.leftCurrent = '',
this.rightCurrent = '',
this.leftMotorTemp = '',
this.rightMotorTemp = '',
this.chipTemp = '',
this.yaw = '',
this.pitch = '',
this.roll = '',
this.satelliteCnt = '',
this.qual = '',
this.headingStatus = '',
this.latitude = '',
this.longitude = '',
this.time = '',
this.knifeCuttingSpeed = '',
this.controlMode = '',
this.workingArea = '',
this.battery = '',
required super.voltage,
required super.leftTargetSpeed,
required super.rightTargetSpeed,
required super.leftMeasureSpeed,
required super.rightMeasureSpeed,
required super.leftCurrent,
required super.rightCurrent,
required super.leftMotorTemp,
required super.rightMotorTemp,
required super.chipTemp,
required super.yaw,
required super.pitch,
required super.roll,
required super.satelliteCnt,
required super.qual,
required super.headingStatus,
required super.latitude,
required super.longitude,
required super.time,
required super.knifeCuttingSpeed,
required super.controlMode,
required super.workingArea,
required super.battery,
required super.obstacleFlag,
});
RunningStatusModel copyWith({
String? voltage,
String? leftTargetSpeed,
String? rightTargetSpeed,
String? leftMeasureSpeed,
String? rightMeasureSpeed,
String? leftCurrent,
String? rightCurrent,
String? leftMotorTemp,
String? rightMotorTemp,
String? chipTemp,
String? yaw,
String? pitch,
String? roll,
String? satelliteCnt,
String? qual,
String? headingStatus,
String? latitude,
String? longitude,
String? time,
String? knifeCuttingSpeed,
String? controlMode,
String? workingArea,
String? battery,
}) {
@override
RunningStatusEntity toEntity() => this;
@override
Map<String, dynamic> toJson() {
return {
'voltage': voltage,
'leftTargetSpeed': leftTargetSpeed,
'rightTargetSpeed': rightTargetSpeed,
'leftMeasureSpeed': leftMeasureSpeed,
'rightMeasureSpeed': rightMeasureSpeed,
'leftCurrent': leftCurrent,
'rightCurrent': rightCurrent,
'leftMotorTemp': leftMotorTemp,
'rightMotorTemp': rightMotorTemp,
'chipTemp': chipTemp,
'yaw': yaw,
'pitch': pitch,
'roll': roll,
'satelliteCnt': satelliteCnt,
'qual': qual,
'headingStatus': headingStatus,
'latitude': latitude,
'longitude': longitude,
'time': time,
'knifeCuttingSpeed': knifeCuttingSpeed,
'controlMode': controlMode,
'workingArea': workingArea,
'battery': battery,
'obstacleFlag': obstacleFlag,
};
}
factory RunningStatusModel.fromBytes(Uint8List payload) {
// 将 Uint8List 转换为字符串 (通常是 utf8 或 ascii)
final rawString = utf8.decode(payload);
// 按逗号分割成数组
final parts = rawString.split(',');
// 辅助函数:安全转换数字,防止硬件发空字符串导致崩溃
double parseD(int index) =>
(index < parts.length && parts[index].isNotEmpty)
? double.tryParse(parts[index]) ?? 0.0
: 0.0;
int parseI(int index) => (index < parts.length && parts[index].isNotEmpty)
? int.tryParse(parts[index]) ?? 0
: 0;
String parseS(int index) => (index < parts.length) ? parts[index] : "";
ControlMode mapMode(int val) {
switch (val) {
case 1:
return ControlMode.LOCAL;
case 2:
return ControlMode.BLE;
case 3:
return ControlMode.TCP;
case 4:
return ControlMode.OTHER;
default:
return ControlMode.NONE; // 0 或其他异常值
}
}
return RunningStatusModel(
voltage: voltage ?? this.voltage,
leftTargetSpeed: leftTargetSpeed ?? this.leftTargetSpeed,
rightTargetSpeed: rightTargetSpeed ?? this.rightTargetSpeed,
leftMeasureSpeed: leftMeasureSpeed ?? this.leftMeasureSpeed,
rightMeasureSpeed: rightMeasureSpeed ?? this.rightMeasureSpeed,
leftCurrent: leftCurrent ?? this.leftCurrent,
rightCurrent: rightCurrent ?? this.rightCurrent,
leftMotorTemp: leftMotorTemp ?? this.leftMotorTemp,
rightMotorTemp: rightMotorTemp ?? this.rightMotorTemp,
chipTemp: chipTemp ?? this.chipTemp,
yaw: yaw ?? this.yaw,
pitch: pitch ?? this.pitch,
roll: roll ?? this.roll,
satelliteCnt: satelliteCnt ?? this.satelliteCnt,
qual: qual ?? this.qual,
headingStatus: headingStatus ?? this.headingStatus,
latitude: latitude ?? this.latitude,
longitude: longitude ?? this.longitude,
time: time ?? this.time,
knifeCuttingSpeed: knifeCuttingSpeed ?? this.knifeCuttingSpeed,
controlMode: controlMode ?? this.controlMode,
workingArea: workingArea ?? this.workingArea,
battery: battery ?? this.battery,
voltage: parseD(0), // 电压
leftTargetSpeed: parseI(1), // 左轮目标速度
rightTargetSpeed: parseI(2), // 右轮目标速度
leftMeasureSpeed: parseI(3), // 左轮测量速度
rightMeasureSpeed: parseI(4), // 右轮测量速度
leftCurrent: parseD(5), // 左轮电流
rightCurrent: parseD(6), // 右轮电流
leftMotorTemp: parseD(7), // 左轮温度
rightMotorTemp: parseD(8), // 右轮温度
chipTemp: parseD(9), // 芯片温度
yaw: parseD(10), // 偏航角
pitch: parseD(11), // 仰俯角
roll: parseD(12), // 翻滚角
satelliteCnt: parseI(13), // 卫星数
qual: parseI(14), // 定位质量 (0-7)
headingStatus: parseI(15), // 航向角状态
latitude: parseD(16), // 纬度
longitude: parseD(17), // 经度
time: parseS(18), // 时间字符串
knifeCuttingSpeed: parseI(19), // 割刀速度
controlMode: mapMode(20), // 控制模式 (0-4)
battery: parseD(21), // 电池电量 (0-100)
workingArea: parseD(22), // 作业面积
obstacleFlag: parseI(23), // 障碍物标志 (0/1)
);
}
}

View File

@@ -1,47 +1,65 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:maibu_satabot_v2/core/protocol/protocol_constants.dart';
import 'package:maibu_satabot_v2/core/protocol/packet_entity.dart';
import 'package:maibu_satabot_v2/features/remote_control/data/models/machine_control_model.dart';
import 'package:maibu_satabot_v2/features/remote_control/domain/entities/running_status_entity.dart';
import '../../../../core/network/protocol_decoder.dart';
import '../../../../core/network/tcp/tcp_client.dart';
import '../../../../core/protocol/machine_protocol_codec.dart';
import '../../../../core/protocol/machine_protocol_constants.dart';
import '../../domain/entities/machine_control_status_entity.dart';
import '../../../connectivity/presentation/bloc/connectivity_cubit.dart';
import '../../domain/entities/machine_control_entity.dart';
import '../../domain/repositories/remote_control_repository.dart';
import '../../domain/usecase/diff_steer_usecase.dart';
import '../models/running_status_model.dart';
class RemoteControlRepositoryImpl implements RemoteControlRepository {
final TcpClient _tcpClient;
final ConnectivityCubit _connectivityCubit;
final DiffSteerUseCase _diffSteer;
RemoteControlRepositoryImpl(this._tcpClient, this._diffSteer);
RemoteControlRepositoryImpl(
this._connectivityCubit, // 现在统一通过 Cubit 管理连接和分发
this._diffSteer,
);
@override
void changeWebViewDirection(String direction) {
// TODO: implement changeWebViewDirection
// 逻辑保持不变
}
@override
void sendControlMachineCmd(MachineControlStatusEntity status) {
// 1. 调用算法:将摇杆坐标 (x, y) 转换为左右轮电机转速
final speeds = _diffSteer.calculate(status.originX, status.originY);
Future<void> sendControlMachineCmd(MachineControlEntity entity) async {
// 1. 调用差速算法计算左右轮速度
final speeds = _diffSteer.calculate(entity.rightSpeed, entity.leftSpeed);
// 2. 调用 Codec:仅生成协议要求的 8 字节 Payload 负载数据
final payload = MachineProtocolCodec.encodeRemoteControlPayload(
left: speeds['left']!,
right: speeds['right']!,
lift: status.chassisLift,
mower: status.mowerSpeed,
ignition: status.ignitionStatus,
emergency: status.isEmergency ? 1 : 0,
);
var model = MachineControlModel.fromEntity(entity);
// 3. 调用 TcpClient:发送指令。
// TcpClient.sendRaw 会自动帮你加上 [0xAB, 0xAA] 头和 [0xAA, 0xAB] 尾
_tcpClient.sendRaw(
MachineProtocolConstants.cmdRemoteControl, // 这里通常是 0x00
payload,
var rawPacket = model.toRawPacket(speeds);
// 3. 通过连接管理器发出,内部会自动处理 [AB AA] ... [CRC] [AA AB]
// 并且会执行 flush() 确保 100ms 实时性
await _connectivityCubit.sendCommand(
ProtocolConstants.cmdRemoteControl, // 0x00
rawPacket,
);
}
/// 过滤出 0x02 状态包并转换为业务 Entity
@override
Stream<RawPacket> get responseStream => _tcpClient.packetStream;
}
Stream<RunningStatusEntity> get runningStatusStream {
return _connectivityCubit.activePacketStream
.where((p) => p.cmdType == ProtocolConstants.cmdStatusInfo) // 0x02
.map((packet) {
// 第一步:由 Model 负责处理“脏活”(字符串拆分、类型转换)
final model = RunningStatusModel.fromBytes(Uint8List.fromList(packet.payload));
// 第二步:转换为 Entity 交付给 UI 或 UseCase
return model.toEntity();
});
}
/// 暴露原始解析流 (RawPacket 在我们这里对应 Packet 类)
/// UI 或其他 Cubit 监听此流来处理机器人主动推送的消息
@override
Stream<PacketEntity> get responseStream => _connectivityCubit.activePacketStream;
}