297 lines
6.9 KiB
Markdown
297 lines
6.9 KiB
Markdown
|
|
# 无人机机场 OSD 实时数据展示功能
|
|||
|
|
|
|||
|
|
## 📋 功能概述
|
|||
|
|
|
|||
|
|
在无人机机场详情页面添加了 **OSD 实时数据卡片**,通过 MQTT 订阅 `thing/product/${gatewaySn}/osd` topic,实时展示机场的遥测数据。
|
|||
|
|
|
|||
|
|
## 🎯 核心特性
|
|||
|
|
|
|||
|
|
### 1. 滑动窗口设计
|
|||
|
|
- **左右滑动切换**:用户可以左右滑动查看不同的 OSD 数据字段
|
|||
|
|
- **底部指示器**:显示当前页码和总页数
|
|||
|
|
- **滑动提示**:左右箭头提示用户可以滑动
|
|||
|
|
|
|||
|
|
### 2. 智能状态管理
|
|||
|
|
- **在线时**:显示实时 OSD 数据(电量、温度、风速等)
|
|||
|
|
- **离线时**:显示"机场离线,暂无实时数据"提示
|
|||
|
|
- **加载中**:显示加载动画
|
|||
|
|
|
|||
|
|
### 3. 数据字段展示
|
|||
|
|
|
|||
|
|
目前展示的 OSD 字段包括:
|
|||
|
|
|
|||
|
|
| 字段 | 图标 | 颜色规则 | 示例值 |
|
|||
|
|
|------|------|---------|--------|
|
|||
|
|
| 电量 | 🔋 battery_full | >50% 绿色, 20-50% 橙色, <20% 红色 | 85% |
|
|||
|
|
| 温度 | 🌡️ thermostat | 蓝色 | 25°C |
|
|||
|
|
| 风速 | 💨 air | 绿色 | 3.5 m/s |
|
|||
|
|
| 降雨量 | 💧 water_drop | 蓝色 | 小雨 / 12 mm |
|
|||
|
|
| 网络状态 | 📶 network_check | 灰色 | 等级 4 |
|
|||
|
|
| GPS | 🛰️ gps_fixed | 紫色 | 12 颗 |
|
|||
|
|
|
|||
|
|
## 📁 文件结构
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
lib/features/v2/device_list/
|
|||
|
|
├── presentation/
|
|||
|
|
│ ├── pages/
|
|||
|
|
│ │ └── drone_station_detail_page.dart # 详情页(集成 OSD 卡片)
|
|||
|
|
│ └── widgets/
|
|||
|
|
│ └── drone_station_osd_card.dart # OSD 实时数据卡片组件
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🔧 技术实现
|
|||
|
|
|
|||
|
|
### 1. MQTT 数据订阅
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
// 订阅机场 OSD topic
|
|||
|
|
final stationTopic = 'thing/product/$gatewaySn/osd';
|
|||
|
|
|
|||
|
|
// 监听数据流
|
|||
|
|
_subscription = _dataSource.stationOsdStream.listen((osd) {
|
|||
|
|
setState(() {
|
|||
|
|
_currentOsd = osd;
|
|||
|
|
_parseOsdFields(osd); // 解析并更新 UI
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 数据解析
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
void _parseOsdFields(DroneOsdEntity osd) {
|
|||
|
|
final data = osd.rawData;
|
|||
|
|
|
|||
|
|
_osdFields = [
|
|||
|
|
{
|
|||
|
|
'icon': Icons.battery_full_rounded,
|
|||
|
|
'label': '电量',
|
|||
|
|
'value': data['battery'] != null ? '${data['battery']}%' : '未知',
|
|||
|
|
'color': _getBatteryColor(data['battery']),
|
|||
|
|
},
|
|||
|
|
// ... 其他字段
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 滑动卡片实现
|
|||
|
|
|
|||
|
|
使用 `PageView.builder` 实现左右滑动:
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
PageView.builder(
|
|||
|
|
itemCount: _osdFields.length,
|
|||
|
|
onPageChanged: (index) {
|
|||
|
|
setState(() {
|
|||
|
|
_currentIndex = index;
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
itemBuilder: (context, index) {
|
|||
|
|
final field = _osdFields[index];
|
|||
|
|
return _buildOsdPage(field, index);
|
|||
|
|
},
|
|||
|
|
)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🎨 UI 设计
|
|||
|
|
|
|||
|
|
### 卡片布局
|
|||
|
|
```
|
|||
|
|
┌─────────────────────────────┐
|
|||
|
|
│ [图标] 电量 ◀ ▶ │
|
|||
|
|
│ │
|
|||
|
|
│ 85% │
|
|||
|
|
│ │
|
|||
|
|
│ 1 / 6 │
|
|||
|
|
│ │
|
|||
|
|
│ ● ○ ○ ○ ○ ○ │
|
|||
|
|
└─────────────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 状态指示器
|
|||
|
|
- **当前页**:长条蓝色圆点(16px)
|
|||
|
|
- **其他页**:短条灰色圆点(6px)
|
|||
|
|
|
|||
|
|
## 📊 数据映射规则
|
|||
|
|
|
|||
|
|
### 降雨量格式化
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
String _formatRainfall(dynamic rainfall) {
|
|||
|
|
if (rainfall is String) {
|
|||
|
|
switch (rainfall.toLowerCase()) {
|
|||
|
|
case 'no_rain': return '无降雨';
|
|||
|
|
case 'light_rain': return '小雨';
|
|||
|
|
case 'moderate_rain': return '中雨';
|
|||
|
|
case 'heavy_rain': return '大雨';
|
|||
|
|
default: return '$rainfall mm';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return '$rainfall mm';
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 电量颜色规则
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
Color _getBatteryColor(dynamic battery) {
|
|||
|
|
final value = battery is num ? battery.toDouble() : 0.0;
|
|||
|
|
if (value > 50) return Color(0xFF00B42A); // 绿色
|
|||
|
|
if (value > 20) return Color(0xFFFF7D00); // 橙色
|
|||
|
|
return Color(0xFFF53F3F); // 红色
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🔄 生命周期管理
|
|||
|
|
|
|||
|
|
### 启动监听
|
|||
|
|
```dart
|
|||
|
|
@override
|
|||
|
|
void initState() {
|
|||
|
|
super.initState();
|
|||
|
|
_startListening(); // 开始监听 MQTT
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 停止监听
|
|||
|
|
```dart
|
|||
|
|
@override
|
|||
|
|
void dispose() {
|
|||
|
|
_stopListening(); // 取消订阅,释放资源
|
|||
|
|
super.dispose();
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 动态更新
|
|||
|
|
```dart
|
|||
|
|
@override
|
|||
|
|
void didUpdateWidget(DroneStationOsdCard oldWidget) {
|
|||
|
|
super.didUpdateWidget(oldWidget);
|
|||
|
|
// gatewaySn 或 isOnline 变化时重新订阅
|
|||
|
|
if (oldWidget.gatewaySn != widget.gatewaySn ||
|
|||
|
|
oldWidget.isOnline != widget.isOnline) {
|
|||
|
|
_stopListening();
|
|||
|
|
_startListening();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🚀 使用方法
|
|||
|
|
|
|||
|
|
### 在详情页中集成
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
DroneStationOsdCard(
|
|||
|
|
gatewaySn: widget.station.gatewaySn,
|
|||
|
|
isOnline: detail.isOnline,
|
|||
|
|
)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 参数说明
|
|||
|
|
|
|||
|
|
| 参数 | 类型 | 说明 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| `gatewaySn` | String | 机场网关序列号(用于 MQTT topic) |
|
|||
|
|
| `isOnline` | bool | 机场是否在线(决定是否显示实时数据) |
|
|||
|
|
|
|||
|
|
## 📝 扩展建议
|
|||
|
|
|
|||
|
|
### 1. 添加更多 OSD 字段
|
|||
|
|
|
|||
|
|
在 `_parseOsdFields` 方法中添加新字段:
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
{
|
|||
|
|
'icon': Icons.new_icon,
|
|||
|
|
'label': '新字段名称',
|
|||
|
|
'value': data['newField'] != null ? '${data['newField']}' : '未知',
|
|||
|
|
'color': const Color(0xFF165DFF),
|
|||
|
|
},
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 自定义滑动方向
|
|||
|
|
|
|||
|
|
修改 `PageView` 的 `scrollDirection`:
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
PageView.builder(
|
|||
|
|
scrollDirection: Axis.vertical, // 改为上下滑动
|
|||
|
|
// ...
|
|||
|
|
)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 自动轮播
|
|||
|
|
|
|||
|
|
添加定时器自动切换卡片:
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
Timer.periodic(Duration(seconds: 3), (timer) {
|
|||
|
|
if (_osdFields.isNotEmpty) {
|
|||
|
|
setState(() {
|
|||
|
|
_currentIndex = (_currentIndex + 1) % _osdFields.length;
|
|||
|
|
});
|
|||
|
|
_pageController.animateToPage(_currentIndex, ...);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4. 手势优化
|
|||
|
|
|
|||
|
|
添加双击放大、长按复制等功能:
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
GestureDetector(
|
|||
|
|
onDoubleTap: () {
|
|||
|
|
// 双击放大
|
|||
|
|
},
|
|||
|
|
onLongPress: () {
|
|||
|
|
// 长按复制数值
|
|||
|
|
Clipboard.setData(ClipboardData(text: field['value']));
|
|||
|
|
},
|
|||
|
|
child: _buildOsdPage(field, index),
|
|||
|
|
)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## ⚠️ 注意事项
|
|||
|
|
|
|||
|
|
1. **MQTT 连接**:确保应用已连接到 MQTT 服务器
|
|||
|
|
2. **topic 格式**:必须为 `thing/product/${gatewaySn}/osd`
|
|||
|
|
3. **数据格式**:MQTT 消息 payload 必须是 JSON 格式
|
|||
|
|
4. **内存管理**:页面销毁时必须取消订阅,避免内存泄漏
|
|||
|
|
5. **在线判断**:只有机场在线时才订阅 MQTT,离线时不订阅
|
|||
|
|
|
|||
|
|
## 🐛 调试技巧
|
|||
|
|
|
|||
|
|
### 查看 MQTT 日志
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
debugPrint('🔊 [DroneStationOsdCard] 开始监听机场 OSD: ${widget.gatewaySn}');
|
|||
|
|
debugPrint('✅ [DroneStationOsdCard] 收到机场 OSD 数据');
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 检查数据解析
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
debugPrint('📊 [DroneStationOsdCard] OSD 原始数据: ${osd.rawData}');
|
|||
|
|
debugPrint('📊 [DroneStationOsdCard] 解析后字段数: ${_osdFields.length}');
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 📦 依赖项
|
|||
|
|
|
|||
|
|
- `flutter_bloc`: 状态管理
|
|||
|
|
- `mqtt_client`: MQTT 通信
|
|||
|
|
- `equatable`: 实体类比较
|
|||
|
|
- `get_it`: 依赖注入
|
|||
|
|
|
|||
|
|
## ✅ 完成清单
|
|||
|
|
|
|||
|
|
- [x] 创建 `DroneStationOsdCard` 组件
|
|||
|
|
- [x] 实现 MQTT 数据订阅
|
|||
|
|
- [x] 实现滑动窗口 UI
|
|||
|
|
- [x] 实现数据解析和格式化
|
|||
|
|
- [x] 集成到详情页
|
|||
|
|
- [x] 添加状态管理(在线/离线/加载中)
|
|||
|
|
- [x] 添加生命周期管理
|
|||
|
|
- [x] 添加调试日志
|