完成页面对接tcp推送的设备状态数据
完成页面测试解析tcp推送的设备状态数据的UI展示
This commit is contained in:
@@ -1,14 +1,25 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:maibu_satabot_v2/core/consts/http_api_consts.dart';
|
||||
import 'package:maibu_satabot_v2/features/devices/data/datasources/device_http_datasource.dart';
|
||||
import 'package:maibu_satabot_v2/features/devices/domain/entities/device_entity.dart';
|
||||
|
||||
import '../../../../../core/storage/user_storage.dart';
|
||||
import '../../../domain/entities/device_location_entity.dart';
|
||||
|
||||
class DeviceHttpDatasourceImpl implements DeviceHttpDatasource {
|
||||
final Dio dio;
|
||||
final UserStorage _userStorage; // 🔥 新增字段
|
||||
|
||||
DeviceHttpDatasourceImpl(this.dio);
|
||||
// 🔥 修改构造函数,注入 UserStorage
|
||||
DeviceHttpDatasourceImpl(this.dio, this._userStorage);
|
||||
|
||||
// 🔥 辅助方法:获取 Token
|
||||
Future<String?> _getToken() async {
|
||||
final user = await _userStorage.getUser();
|
||||
debugPrint('用户信息: $user') ;
|
||||
return user?.token;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> bindDevice(String deviceId, String deviceAlias) async {
|
||||
@@ -31,9 +42,17 @@ class DeviceHttpDatasourceImpl implements DeviceHttpDatasource {
|
||||
|
||||
@override
|
||||
Future<List<DeviceEntity>> getUserDevices(String username) async {
|
||||
final token = await _getToken();
|
||||
|
||||
var response = await dio.get(
|
||||
HttpApiConsts.getUserDevicesList,
|
||||
queryParameters: {'tenantName': username},
|
||||
options: Options(
|
||||
headers: {
|
||||
'Authorization': token != null ? 'Bearer $token' : '',
|
||||
// 如果后端不需要 Bearer 前缀,直接写 token 即可
|
||||
},
|
||||
),
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('网络请求失败:${response.statusCode}');
|
||||
@@ -58,9 +77,16 @@ class DeviceHttpDatasourceImpl implements DeviceHttpDatasource {
|
||||
|
||||
@override
|
||||
Future<int> switchDevice(String platform, String deviceId) async {
|
||||
final token = await _getToken();
|
||||
var response = await dio.post(
|
||||
HttpApiConsts.switchDevice,
|
||||
data: {'platform': platform, 'deviceId': deviceId},
|
||||
options: Options(
|
||||
headers: {
|
||||
'Authorization': token != null ? 'Bearer $token' : '',
|
||||
// 如果后端不需要 Bearer 前缀,直接写 token 即可
|
||||
},
|
||||
),
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('网络请求失败:${response.statusCode}');
|
||||
|
||||
@@ -1,29 +1,43 @@
|
||||
// lib/features/devices/domain/entities/running_status_entity.dart
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class RunningStatusEntity extends Equatable {
|
||||
final double voltage;
|
||||
final double leftTargetSpeed;
|
||||
final double rightTargetSpeed;
|
||||
final double leftMeasureSpeed;
|
||||
final double rightMeasureSpeed;
|
||||
final double leftCurrent;
|
||||
final double rightCurrent;
|
||||
final double leftMotorTemp;
|
||||
final double rightMotorTemp;
|
||||
final double chipTemp;
|
||||
final double yaw;
|
||||
final double pitch;
|
||||
final double roll;
|
||||
final int satelliteCnt;
|
||||
final int qual;
|
||||
final int headingStatus;
|
||||
final double latitude;
|
||||
final double longitude;
|
||||
final String knifeCuttingSpeed;
|
||||
final String controlMode;
|
||||
final String battery;
|
||||
final String workingArea;
|
||||
final String obstacleFlag;
|
||||
// === 基础电气信息 (字段 0-6) ===
|
||||
final double voltage; // [0] 电压 (V)
|
||||
final double leftTargetSpeed; // [1] 左轮目标速度
|
||||
final double rightTargetSpeed; // [2] 右轮目标速度
|
||||
final double leftMeasureSpeed; // [3] 左轮实测速度
|
||||
final double rightMeasureSpeed; // [4] 右轮实测速度
|
||||
final double leftCurrent; // [5] 左轮电流 (A)
|
||||
final double rightCurrent; // [6] 右轮电流 (A)
|
||||
|
||||
// === 温度信息 (字段 7-9) ===
|
||||
final double leftMotorTemp; // [7] 左电机温度 (℃)
|
||||
final double rightMotorTemp; // [8] 右电机温度 (℃)
|
||||
final double chipTemp; // [9] 芯片温度 (℃)
|
||||
|
||||
// === 姿态信息 (字段 10-12) ===
|
||||
final double yaw; // [10] 偏航角 (°)
|
||||
final double pitch; // [11] 俯仰角 (°)
|
||||
final double roll; // [12] 翻滚角 (°)
|
||||
|
||||
// === GPS 信号质量 (字段 13-15) ===
|
||||
final int satelliteCnt; // [13] 卫星数量
|
||||
final int qual; // [14] 定位质量
|
||||
final int headingStatus; // [15] 航向状态
|
||||
|
||||
// === GPS 位置信息 (字段 16-17) ===
|
||||
final double latitude; // [16] 纬度
|
||||
final double longitude; // [17] 经度
|
||||
|
||||
// === 扩展信息 (字段 18-23) ===
|
||||
final String timestamp; // [18] 时间戳 (格式:20-1-11-31 08:00:00)
|
||||
final String knifeCuttingSpeed; // [19] 割刀速度
|
||||
final String controlMode; // [20] 控制模式
|
||||
final String battery; // [21] 电量百分比
|
||||
final String workingArea; // [22] 工作面积
|
||||
final String obstacleFlag; // [23] 障碍物标志
|
||||
|
||||
const RunningStatusEntity({
|
||||
this.voltage = 0.0,
|
||||
@@ -44,13 +58,61 @@ class RunningStatusEntity extends Equatable {
|
||||
this.headingStatus = 0,
|
||||
this.latitude = 0.0,
|
||||
this.longitude = 0.0,
|
||||
this.knifeCuttingSpeed = "0",
|
||||
this.controlMode = "0",
|
||||
this.battery = "0",
|
||||
this.workingArea = "0",
|
||||
this.obstacleFlag = "0",
|
||||
this.timestamp = '',
|
||||
this.knifeCuttingSpeed = '0',
|
||||
this.controlMode = '0',
|
||||
this.battery = '0',
|
||||
this.workingArea = '0',
|
||||
this.obstacleFlag = '0',
|
||||
});
|
||||
|
||||
/// 从字段数组创建实体 (核心解析方法)
|
||||
factory RunningStatusEntity.fromFields(List<String> fields) {
|
||||
// 确保至少有 24 个字段,不足的用默认值填充
|
||||
while (fields.length < 24) {
|
||||
fields.add('');
|
||||
}
|
||||
|
||||
return RunningStatusEntity(
|
||||
// 基础电气信息
|
||||
voltage: double.tryParse(fields[0]) ?? 0.0,
|
||||
leftTargetSpeed: double.tryParse(fields[1]) ?? 0.0,
|
||||
rightTargetSpeed: double.tryParse(fields[2]) ?? 0.0,
|
||||
leftMeasureSpeed: double.tryParse(fields[3]) ?? 0.0,
|
||||
rightMeasureSpeed: double.tryParse(fields[4]) ?? 0.0,
|
||||
leftCurrent: double.tryParse(fields[5]) ?? 0.0,
|
||||
rightCurrent: double.tryParse(fields[6]) ?? 0.0,
|
||||
|
||||
// 温度信息
|
||||
leftMotorTemp: double.tryParse(fields[7]) ?? 0.0,
|
||||
rightMotorTemp: double.tryParse(fields[8]) ?? 0.0,
|
||||
chipTemp: double.tryParse(fields[9]) ?? 0.0,
|
||||
|
||||
// 姿态信息
|
||||
yaw: double.tryParse(fields[10]) ?? 0.0,
|
||||
pitch: double.tryParse(fields[11]) ?? 0.0,
|
||||
roll: double.tryParse(fields[12]) ?? 0.0,
|
||||
|
||||
// GPS 信号质量
|
||||
satelliteCnt: int.tryParse(fields[13]) ?? 0,
|
||||
qual: int.tryParse(fields[14]) ?? 0,
|
||||
headingStatus: int.tryParse(fields[15]) ?? 0,
|
||||
|
||||
// GPS 位置信息
|
||||
latitude: double.tryParse(fields[16]) ?? 0.0,
|
||||
longitude: double.tryParse(fields[17]) ?? 0.0,
|
||||
|
||||
// 扩展信息 (保持原始字符串,由调用方决定如何解析)
|
||||
timestamp: fields[18].trim(),
|
||||
knifeCuttingSpeed: fields[19].trim().isNotEmpty ? fields[19].trim() : '0',
|
||||
controlMode: fields[20].trim().isNotEmpty ? fields[20].trim() : '0',
|
||||
battery: fields[21].trim().isNotEmpty ? fields[21].trim() : '0',
|
||||
workingArea: fields[22].trim().isNotEmpty ? fields[22].trim() : '0',
|
||||
obstacleFlag: fields[23].trim().isNotEmpty ? fields[23].trim() : '0',
|
||||
);
|
||||
}
|
||||
|
||||
/// copyWith 方法 (用于局部更新)
|
||||
RunningStatusEntity copyWith({
|
||||
double? voltage,
|
||||
double? leftTargetSpeed,
|
||||
@@ -70,6 +132,7 @@ class RunningStatusEntity extends Equatable {
|
||||
int? headingStatus,
|
||||
double? latitude,
|
||||
double? longitude,
|
||||
String? timestamp,
|
||||
String? knifeCuttingSpeed,
|
||||
String? controlMode,
|
||||
String? battery,
|
||||
@@ -95,6 +158,7 @@ class RunningStatusEntity extends Equatable {
|
||||
headingStatus: headingStatus ?? this.headingStatus,
|
||||
latitude: latitude ?? this.latitude,
|
||||
longitude: longitude ?? this.longitude,
|
||||
timestamp: timestamp ?? this.timestamp,
|
||||
knifeCuttingSpeed: knifeCuttingSpeed ?? this.knifeCuttingSpeed,
|
||||
controlMode: controlMode ?? this.controlMode,
|
||||
battery: battery ?? this.battery,
|
||||
@@ -105,9 +169,35 @@ class RunningStatusEntity extends Equatable {
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
voltage, leftTargetSpeed, rightTargetSpeed, leftMeasureSpeed, rightMeasureSpeed,
|
||||
leftCurrent, rightCurrent, leftMotorTemp, rightMotorTemp, chipTemp,
|
||||
yaw, pitch, roll, satelliteCnt, qual, headingStatus,
|
||||
latitude, longitude, knifeCuttingSpeed, controlMode, battery, workingArea, obstacleFlag,
|
||||
voltage,
|
||||
leftTargetSpeed,
|
||||
rightTargetSpeed,
|
||||
leftMeasureSpeed,
|
||||
rightMeasureSpeed,
|
||||
leftCurrent,
|
||||
rightCurrent,
|
||||
leftMotorTemp,
|
||||
rightMotorTemp,
|
||||
chipTemp,
|
||||
yaw,
|
||||
pitch,
|
||||
roll,
|
||||
satelliteCnt,
|
||||
qual,
|
||||
headingStatus,
|
||||
latitude,
|
||||
longitude,
|
||||
timestamp,
|
||||
knifeCuttingSpeed,
|
||||
controlMode,
|
||||
battery,
|
||||
workingArea,
|
||||
obstacleFlag,
|
||||
];
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'RunningStatusEntity(voltage: $voltage, chipTemp: $chipTemp, '
|
||||
'latitude: $latitude, longitude: $longitude, timestamp: $timestamp)';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,77 +14,82 @@ class DeviceStatusBloc extends Bloc<DeviceStatusEvent, DeviceStatusState> {
|
||||
final NetMessageDispatcher _dispatcher;
|
||||
|
||||
DeviceStatusBloc(this._dispatcher) : super(DeviceStatusInitial()) {
|
||||
// 直接订阅 0x02 指令的字符串流
|
||||
// 订阅 TCP 数据流
|
||||
_dispatcher.onStringMessage().listen((jsonString) {
|
||||
debugPrint('📩 Bloc 收到 0x02 数据');
|
||||
add(DeviceStatusLoaded(jsonString));
|
||||
});
|
||||
|
||||
// 订阅 0x12 的 JSON 流()
|
||||
_dispatcher.onJsonMessage(0x12).listen((jsonData) {
|
||||
add(PushMessageReceived(jsonData));
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<DeviceStatusState> mapEventToState(DeviceStatusEvent event) async* {
|
||||
if (event is DeviceStatusLoaded) {
|
||||
yield* _mapDeviceStatusLoaded(event);
|
||||
} else if (event is PushMessageReceived) {
|
||||
yield* _mapPushMessageReceived(event);
|
||||
}
|
||||
}
|
||||
//
|
||||
on<DeviceStatusLoaded>(_handleDeviceStatusLoaded);
|
||||
on<PushMessageReceived>(_handlePushMessageReceived);
|
||||
|
||||
Stream<DeviceStatusState> _mapDeviceStatusLoaded(DeviceStatusLoaded event) async* {
|
||||
|
||||
// 🔥 新增:模拟测试数据(调试用)
|
||||
_testMockData();
|
||||
}
|
||||
void _testMockData() {
|
||||
// 用户提供的真实指令数据(字节数组)
|
||||
final mockBytes = [
|
||||
171, 170, 2, 48, 46, 48, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 46, 48, 48, 44, 48, 46, 48, 48, 44, 48, 46, 48, 48, 44, 48, 46, 48, 48, 44, 50, 55, 46, 52, 55, 44, 49, 56, 48, 46, 48, 48, 44, 48, 46, 48, 48, 44, 48, 46, 48, 48, 44, 52, 49, 44, 52, 44, 49, 44, 51, 50, 46, 48, 52, 49, 50, 57, 54, 56, 54, 52, 48, 48, 57, 57, 57, 44, 49, 50, 48, 46, 56, 48, 56, 57, 57, 51, 48, 56, 49, 49, 53, 56, 53, 48, 44, 50, 48, 45, 49, 45, 49, 49, 45, 51, 49, 32, 48, 56, 58, 48, 48, 58, 48, 48, 44, 48, 44, 51, 44, 54, 54, 44, 57, 53, 50, 55, 46, 48, 48, 48, 48, 44, 48, 0, 0, 0, 170, 171
|
||||
];
|
||||
|
||||
// 解码为字符串(去掉帧头 3 字节和帧尾 5 字节)
|
||||
final payloadBytes = mockBytes.sublist(3, mockBytes.length - 5);
|
||||
final mockData = String.fromCharCodes(payloadBytes);
|
||||
|
||||
debugPrint('🧪 模拟测试数据:$mockData');
|
||||
|
||||
// 发送模拟事件
|
||||
add(DeviceStatusLoaded(mockData));
|
||||
}
|
||||
// 🔥 修改:使用 Emitter 发送状态
|
||||
Future<void> _handleDeviceStatusLoaded(
|
||||
DeviceStatusLoaded event,
|
||||
Emitter<DeviceStatusState> emit,
|
||||
) async {
|
||||
try {
|
||||
final fields = event.jsonString.trim().split(' ');
|
||||
debugPrint('🔍 开始解析数据:${event.jsonString}');
|
||||
final fields = event.jsonString.trim().split(',');
|
||||
debugPrint('🔍 字段数量:${fields.length}');
|
||||
|
||||
if (fields.length < 18) {
|
||||
yield DeviceStatusError('字段不足,期望 ≥18,实际: ${fields.length}');
|
||||
emit(DeviceStatusError('字段不足,期望 ≥18,实际:${fields.length}'));
|
||||
return;
|
||||
}
|
||||
///解析设备状态
|
||||
final status = RunningStatusEntity().copyWith(
|
||||
voltage: double.tryParse(fields[0]) ?? 0.0,
|
||||
leftTargetSpeed: double.tryParse(fields[1]) ?? 0.0,
|
||||
rightTargetSpeed: double.tryParse(fields[2]) ?? 0.0,
|
||||
leftMeasureSpeed: double.tryParse(fields[3]) ?? 0.0,
|
||||
rightMeasureSpeed: double.tryParse(fields[4]) ?? 0.0,
|
||||
leftCurrent: double.tryParse(fields[5]) ?? 0.0,
|
||||
rightCurrent: double.tryParse(fields[6]) ?? 0.0,
|
||||
leftMotorTemp: double.tryParse(fields[7]) ?? 0.0,
|
||||
rightMotorTemp: double.tryParse(fields[8]) ?? 0.0,
|
||||
chipTemp: double.tryParse(fields[9]) ?? 0.0,
|
||||
yaw: double.tryParse(fields[10]) ?? 0.0,
|
||||
pitch: double.tryParse(fields[11]) ?? 0.0,
|
||||
roll: double.tryParse(fields[12]) ?? 0.0,
|
||||
satelliteCnt: int.tryParse(fields[13]) ?? 0,
|
||||
qual: int.tryParse(fields[14]) ?? 0,
|
||||
headingStatus: int.tryParse(fields[15]) ?? 0,
|
||||
latitude: double.tryParse(fields[16]) ?? 0.0,
|
||||
longitude: double.tryParse(fields[17]) ?? 0.0,
|
||||
knifeCuttingSpeed: fields.length > 19 && fields[19].isNotEmpty ? fields[19] : "0",
|
||||
controlMode: fields.length > 20 && fields[20].isNotEmpty ? fields[20] : "0",
|
||||
battery: fields.length > 21 && fields[21].isNotEmpty ? fields[21] : "0",
|
||||
workingArea: fields.length > 22 && fields[22].isNotEmpty ? fields[22] : "0",
|
||||
obstacleFlag: fields.length > 23 && fields[23].isNotEmpty ? fields[23] : "0",
|
||||
);
|
||||
///解析GPS
|
||||
|
||||
final status = RunningStatusEntity.fromFields(fields);
|
||||
debugPrint('设备运行时候状态:$status');
|
||||
final gps = GPSEntity(status.latitude, status.longitude);
|
||||
yield DeviceStatusUpdated(status, gps);
|
||||
debugPrint('GPS: ${gps.latitude}, ${gps.longitude}');
|
||||
|
||||
emit(DeviceStatusUpdated(status, gps)); // 🔥 使用 emit 发送状态
|
||||
} catch (e) {
|
||||
yield DeviceStatusError('解析设备状态失败: $e');
|
||||
debugPrint('❌ 解析异常:$e');
|
||||
emit(DeviceStatusError('解析设备运行时候状态失败:$e'));
|
||||
}
|
||||
}
|
||||
/// 解析 推送消息
|
||||
Stream<DeviceStatusState> _mapPushMessageReceived(PushMessageReceived event) async* {
|
||||
|
||||
Future<void> _handlePushMessageReceived(
|
||||
PushMessageReceived event,
|
||||
Emitter<DeviceStatusState> emit,
|
||||
) async {
|
||||
try {
|
||||
final eventStr = event.jsonData['event'] ?? '';
|
||||
final deviceId = event.jsonData['deviceId'] ?? '未知';
|
||||
debugPrint('收到推送事件: $eventStr, 设备: $deviceId');
|
||||
|
||||
// 可扩展:yield PushMessageState(event.jsonData)
|
||||
debugPrint('收到推送事件:$eventStr, 设备:$deviceId');
|
||||
} catch (e) {
|
||||
yield DeviceStatusError('解析推送消息失败: $e');
|
||||
emit(DeviceStatusError('解析推送消息失败:$e'));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,3 +21,4 @@ class PushMessageReceived extends DeviceStatusEvent {
|
||||
@override
|
||||
List<Object?> get props => [jsonData];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user