按 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 错误
124 lines
3.9 KiB
Dart
124 lines
3.9 KiB
Dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import '../models/device_state.dart';
|
|
import '../services/device_message.dart';
|
|
import '../services/device_message_service.dart';
|
|
import 'serial_provider.dart';
|
|
|
|
/// 设备信息通知器
|
|
///
|
|
/// 订阅 [DeviceMessageService] 中的 `device_info` 上行消息,
|
|
/// 维护门状态 / 灯状态 / 任务状态,供 UI 直接读取。
|
|
class DeviceInfoNotifier extends StateNotifier<DeviceState> {
|
|
final DeviceMessageService _msgService;
|
|
void Function()? _cancelSub;
|
|
|
|
DeviceInfoNotifier(this._msgService) : super(const DeviceState()) {
|
|
_cancelSub = _msgService.subscribe(
|
|
DeviceMessageType.deviceInfo,
|
|
_onDeviceInfo,
|
|
);
|
|
// 串口断开时无需主动清空 lastInfoAt —— infoStale 会根据
|
|
// 当前时间与 lastInfoAt 的差值自动判断为「通讯异常」。
|
|
}
|
|
|
|
void _onDeviceInfo(DeviceMessage msg) {
|
|
final data = msg.data;
|
|
final light = _parseLight(data['light_status']);
|
|
final door = _parseDoor(data['door_status']);
|
|
final task = _parseTask(data['task_status']);
|
|
state = state.copyWith(
|
|
lightStatus: light,
|
|
doorStatus: door,
|
|
taskStatus: task,
|
|
lastInfoAt: DateTime.now(),
|
|
);
|
|
}
|
|
|
|
/// 主动查询设备信息(发送 need_ack=true 的 create_task 之外的 device_info 请求,
|
|
/// 具体取决于下位机协议约定;目前仅作占位)。
|
|
Future<void> queryDeviceInfo() async {
|
|
if (!_msgService.canSend) return;
|
|
final id = _msgService.nextId();
|
|
await _msgService.send(DeviceMessage.request(
|
|
messageId: id,
|
|
type: DeviceMessageType.deviceInfo,
|
|
data: const <String, dynamic>{},
|
|
needAck: true,
|
|
));
|
|
}
|
|
|
|
/// 发送灯光控制消息
|
|
///
|
|
/// 主动切换后,下位机通常会通过 device_info 上报新的 light_status
|
|
/// 覆盖本地状态。
|
|
Future<bool> toggleLight() async {
|
|
if (!_msgService.canSend) return false;
|
|
final next = state.lightStatus == DeviceLightStatus.on ? 'off' : 'on';
|
|
final id = _msgService.nextId();
|
|
final ok = await _msgService.send(DeviceMessage.request(
|
|
messageId: id,
|
|
type: DeviceMessageType.lightControl,
|
|
data: {'status': next},
|
|
needAck: true,
|
|
));
|
|
if (ok) {
|
|
// 乐观更新;下位机的 device_info 上报会作为最终一致来源
|
|
state = state.copyWith(
|
|
lightStatus: next == 'on' ? DeviceLightStatus.on : DeviceLightStatus.off,
|
|
);
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
/// 显式设定灯光
|
|
Future<bool> setLight(bool on) async {
|
|
if (!_msgService.canSend) return false;
|
|
if (on && state.lightStatus == DeviceLightStatus.on) return true;
|
|
if (!on && state.lightStatus == DeviceLightStatus.off) return true;
|
|
final id = _msgService.nextId();
|
|
final ok = await _msgService.send(DeviceMessage.request(
|
|
messageId: id,
|
|
type: DeviceMessageType.lightControl,
|
|
data: {'status': on ? 'on' : 'off'},
|
|
needAck: true,
|
|
));
|
|
if (ok) {
|
|
state = state.copyWith(
|
|
lightStatus: on ? DeviceLightStatus.on : DeviceLightStatus.off,
|
|
);
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_cancelSub?.call();
|
|
_cancelSub = null;
|
|
super.dispose();
|
|
}
|
|
|
|
// -- 解析 -------------------------------------------------------------
|
|
|
|
static DeviceLightStatus _parseLight(dynamic v) =>
|
|
v == 'on' ? DeviceLightStatus.on : DeviceLightStatus.off;
|
|
|
|
static DeviceDoorStatus _parseDoor(dynamic v) =>
|
|
v == 'open' ? DeviceDoorStatus.open : DeviceDoorStatus.close;
|
|
|
|
static DeviceTaskStatus _parseTask(dynamic v) {
|
|
return switch (v) {
|
|
'running' => DeviceTaskStatus.running,
|
|
'pause' => DeviceTaskStatus.pause,
|
|
_ => DeviceTaskStatus.idle,
|
|
};
|
|
}
|
|
}
|
|
|
|
/// 设备信息 Provider
|
|
final deviceInfoProvider =
|
|
StateNotifierProvider<DeviceInfoNotifier, DeviceState>((ref) {
|
|
final service = ref.watch(deviceMessageServiceProvider);
|
|
return DeviceInfoNotifier(service);
|
|
});
|