feat(device): 实现下位机 JSON 协议(data model 对齐)

按 docs/下位机交互数据模型.md 重构串口协议层:

协议层
- 新增 DeviceMessage 模型,对应 message_id/type/ack/need_ack/data
- 新增 JsonProtocolService,4 字节大端长度前缀 + UTF-8 JSON 帧
- 删除原二进制协议(serial_protocol.dart)

服务层
- 新增 DeviceMessageService,集中收发并按 type 分发
- 重写 SerialRunner 为 JsonSerialRunner,使用 create_task/control 消息

数据模型
- DeviceState 增加 doorStatus/lightStatus/taskStatus/lastInfoAt
- 新增 DeviceInfoNotifier 订阅 device_info 上行
- 灯光按钮接通 light_control 消息

测试
- 新增 device_protocol_test.dart(14 用例)
- 修复 models_test.dart 残留的 Step mixSpeed/blowSpeed 错误
This commit is contained in:
Developer
2026-06-04 13:00:21 +08:00
parent 5d28bf631b
commit 819889684f
13 changed files with 1689 additions and 122 deletions

View File

@@ -22,8 +22,6 @@ class HomePage extends ConsumerStatefulWidget {
class _HomePageState extends ConsumerState<HomePage>
with SingleTickerProviderStateMixin {
bool _lightOn = false;
final bool _ceramicSleeveInstalled = false; // TODO: 后续对接硬件传感器后改为可变状态
int _currentIndex = 0;
@override
@@ -54,13 +52,6 @@ class _HomePageState extends ConsumerState<HomePage>
// 状态栏
StatusBar(
isRunning: runState.status == RunStatus.running,
lightOn: _lightOn,
onLightToggle: () {
setState(() {
_lightOn = !_lightOn;
});
},
ceramicSleeveInstalled: _ceramicSleeveInstalled,
),
// 导航标签栏

View File

@@ -1,30 +1,28 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../core/localization/app_localizations.dart';
import '../../../core/theme/app_theme.dart';
import '../../../shared/widgets/status_indicator.dart';
import '../../device/providers/device_info_provider.dart';
/// 状态栏组件 - 明亮工业风格
/// 显示设备名称、实时时钟、系统状态、照明控制、瓷套棒状态
class StatusBar extends StatefulWidget {
/// 显示设备名称、实时时钟、系统状态、照明控制
class StatusBar extends ConsumerStatefulWidget {
final bool isRunning;
final bool lightOn;
final VoidCallback? onLightToggle;
final bool ceramicSleeveInstalled;
const StatusBar({
super.key,
this.isRunning = false,
this.lightOn = false,
this.onLightToggle,
this.ceramicSleeveInstalled = false,
});
@override
State<StatusBar> createState() => _StatusBarState();
ConsumerState<StatusBar> createState() => _StatusBarState();
}
class _StatusBarState extends State<StatusBar> {
class _StatusBarState extends ConsumerState<StatusBar> {
String _currentTime = '';
Timer? _timer;
@@ -51,9 +49,15 @@ class _StatusBarState extends State<StatusBar> {
String _twoDigits(int n) => n.toString().padLeft(2, '0');
Future<void> _onLightTap() async {
widget.onLightToggle?.call();
await ref.read(deviceInfoProvider.notifier).toggleLight();
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
final deviceInfo = ref.watch(deviceInfoProvider);
return Container(
height: 56,
@@ -86,9 +90,10 @@ class _StatusBarState extends State<StatusBar> {
],
),
const Spacer(),
_LightToggleButton(isOn: widget.lightOn, onTap: widget.onLightToggle),
const SizedBox(width: 16),
_CeramicSleeveStatus(installed: widget.ceramicSleeveInstalled),
_LightToggleButton(
isOn: deviceInfo.lightingOn,
onTap: _onLightTap,
),
const SizedBox(width: 20),
StatusIndicator(
text: widget.isRunning
@@ -114,36 +119,6 @@ class _StatusBarState extends State<StatusBar> {
}
}
class _CeramicSleeveStatus extends StatelessWidget {
final bool installed;
const _CeramicSleeveStatus({required this.installed});
@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: installed ? Colors.greenAccent : Colors.redAccent,
),
),
const SizedBox(width: 6),
Text(
installed ? '瓷套棒: 已安装' : '瓷套棒: 未安装',
style: TextStyle(
color: Colors.white.withValues(alpha: 0.9),
fontSize: 12,
),
),
],
);
}
}
class _LightToggleButton extends StatelessWidget {
final bool isOn;
final VoidCallback? onTap;