按 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 错误
148 lines
4.4 KiB
Dart
148 lines
4.4 KiB
Dart
/// 设备运行状态
|
||
///
|
||
/// 与下位机 `device_info.task_status` 字段对齐:
|
||
/// - `running` 运行中
|
||
/// - `pause` 暂停
|
||
/// - `idle` 空闲
|
||
enum DeviceTaskStatus { running, pause, idle }
|
||
|
||
/// 设备门状态
|
||
///
|
||
/// 与下位机 `device_info.door_status` 字段对齐:
|
||
/// - `open` 开
|
||
/// - `close` 关
|
||
enum DeviceDoorStatus { open, close }
|
||
|
||
/// 设备灯光状态
|
||
///
|
||
/// 与下位机 `device_info.light_status` / `light_control.status` 字段对齐:
|
||
/// - `on` 开
|
||
/// - `off` 关
|
||
enum DeviceLightStatus { on, off }
|
||
|
||
/// 上位机内部统一的设备状态模型
|
||
///
|
||
/// 既承载 [DeviceState](运行态监控),也承载下位机 `device_info` 上报
|
||
/// 的门状态 / 灯光状态 / 任务状态,供 UI 各处订阅展示与联动。
|
||
class DeviceState {
|
||
/// 本机内部识别的运行状态(用于驱动运行/暂停/完成流程)
|
||
final DeviceStatus status;
|
||
|
||
/// 当前运行程序名(仅运行态有效)
|
||
final String? currentProgram;
|
||
|
||
/// 当前步骤的孔位(例如 A1)
|
||
final String? currentPosition;
|
||
|
||
/// 当前步骤号
|
||
final int? currentStepNo;
|
||
|
||
/// 当前步骤名
|
||
final String? currentStepName;
|
||
|
||
/// 当前步骤剩余时间(秒)
|
||
final int? remainingSeconds;
|
||
|
||
/// 总进度 [0, 1]
|
||
final double? progress;
|
||
|
||
/// 灯光状态(来自 device_info,主动权在下位机)
|
||
final DeviceLightStatus lightStatus;
|
||
|
||
/// 门状态(来自 device_info)
|
||
final DeviceDoorStatus doorStatus;
|
||
|
||
/// 任务状态(来自 device_info)
|
||
final DeviceTaskStatus taskStatus;
|
||
|
||
/// 上次 device_info 上报的时间,便于 UI 显示「通讯正常 / 已断开」
|
||
final DateTime? lastInfoAt;
|
||
|
||
const DeviceState({
|
||
this.status = DeviceStatus.idle,
|
||
this.currentProgram,
|
||
this.currentPosition,
|
||
this.currentStepNo,
|
||
this.currentStepName,
|
||
this.remainingSeconds,
|
||
this.progress,
|
||
this.lightStatus = DeviceLightStatus.off,
|
||
this.doorStatus = DeviceDoorStatus.close,
|
||
this.taskStatus = DeviceTaskStatus.idle,
|
||
this.lastInfoAt,
|
||
});
|
||
|
||
bool get isRunning => status == DeviceStatus.running;
|
||
bool get isPaused => status == DeviceStatus.paused;
|
||
bool get isIdle => status == DeviceStatus.idle;
|
||
bool get hasError => status == DeviceStatus.error;
|
||
|
||
bool get lightingOn => lightStatus == DeviceLightStatus.on;
|
||
bool get doorOpen => doorStatus == DeviceDoorStatus.open;
|
||
|
||
/// 下位机超过该时间未上报则视为通讯异常
|
||
static const Duration infoStaleAfter = Duration(seconds: 10);
|
||
|
||
bool get infoStale {
|
||
final ts = lastInfoAt;
|
||
if (ts == null) return true;
|
||
return DateTime.now().difference(ts) > infoStaleAfter;
|
||
}
|
||
|
||
String statusText() {
|
||
switch (status) {
|
||
case DeviceStatus.running:
|
||
return '运行中';
|
||
case DeviceStatus.paused:
|
||
return '已暂停';
|
||
case DeviceStatus.error:
|
||
return '错误';
|
||
case DeviceStatus.idle:
|
||
return '未运行';
|
||
}
|
||
}
|
||
|
||
String formatRemainingTime() {
|
||
if (remainingSeconds == null) return '--:--:--';
|
||
final hours = remainingSeconds! ~/ 3600;
|
||
final minutes = (remainingSeconds! % 3600) ~/ 60;
|
||
final seconds = remainingSeconds! % 60;
|
||
return '${hours.toString().padLeft(2, '0')}:'
|
||
'${minutes.toString().padLeft(2, '0')}:'
|
||
'${seconds.toString().padLeft(2, '0')}';
|
||
}
|
||
|
||
DeviceState copyWith({
|
||
DeviceStatus? status,
|
||
String? currentProgram,
|
||
String? currentPosition,
|
||
int? currentStepNo,
|
||
String? currentStepName,
|
||
int? remainingSeconds,
|
||
double? progress,
|
||
DeviceLightStatus? lightStatus,
|
||
DeviceDoorStatus? doorStatus,
|
||
DeviceTaskStatus? taskStatus,
|
||
DateTime? lastInfoAt,
|
||
bool clearProgram = false,
|
||
bool clearProgress = false,
|
||
}) {
|
||
return DeviceState(
|
||
status: status ?? this.status,
|
||
currentProgram: clearProgram ? null : (currentProgram ?? this.currentProgram),
|
||
currentPosition: currentPosition ?? this.currentPosition,
|
||
currentStepNo: currentStepNo ?? this.currentStepNo,
|
||
currentStepName: currentStepName ?? this.currentStepName,
|
||
remainingSeconds: remainingSeconds ?? this.remainingSeconds,
|
||
progress: clearProgress ? null : (progress ?? this.progress),
|
||
lightStatus: lightStatus ?? this.lightStatus,
|
||
doorStatus: doorStatus ?? this.doorStatus,
|
||
taskStatus: taskStatus ?? this.taskStatus,
|
||
lastInfoAt: lastInfoAt ?? this.lastInfoAt,
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 简单的状态枚举(与 [DeviceState.status] 配合使用)
|
||
enum DeviceStatus { idle, running, paused, error }
|