import 'dart:async'; import '../../programs/models/program.dart'; import '../../programs/models/step.dart'; import 'device_log.dart'; import 'device_message.dart'; import 'device_message_service.dart'; import 'runner_interface.dart'; import 'task_payload.dart'; /// JSON 协议运行器 /// /// 与下位机的程序运行相关通信(create_task / control)通过 /// [DeviceMessageService] 完成;运行过程中下位机可通过 ack 消息确认动作, /// 步骤进度仍由下位机主动上报(具体协议待硬件侧确认)。 /// /// 当前实现: /// 1. start → 发送 `create_task` 消息(need_ack=true),收到 ack 后切到 running; /// 2. pause → 发送 `control{status:pause}`,切到 paused; /// 3. resume → 发送 `control{status:continue}`,切到 running; /// 4. stop → 发送 `control{status:stop}`,切到 idle。 /// /// 为兜底下位机不主动上报完成的情况,保留本地倒计时 + 步骤推进。 class JsonSerialRunner implements Runner { @override RunnerStatus status = RunnerStatus.idle; final DeviceMessageService _msg; // ignore: unused_field 持有当前运行的程序引用,便于调试与未来扩展 Program? _program; List _steps = const []; RunnerCallbacks? _callbacks; void Function()? _cancelAckSub; Timer? _ticker; int _currentStepIndex = 0; int _remainingSeconds = 0; String? _pendingCreateTaskId; String? _pendingControlId; JsonSerialRunner({required DeviceMessageService messageService}) : _msg = messageService { _cancelAckSub = _msg.subscribe(DeviceMessageType.createTask, _onCreateTaskAck); _msg.subscribe(DeviceMessageType.control, _onControlAck); } @override void start(Program program, List steps, RunnerCallbacks callbacks) { if (status == RunnerStatus.running) { callbacks.onError?.call('已有程序在运行中'); return; } if (steps.isEmpty) { callbacks.onError?.call('程序步骤为空'); status = RunnerStatus.error; return; } _program = program; _steps = List.unmodifiable(steps); _callbacks = callbacks; _currentStepIndex = 0; _remainingSeconds = _stepTotalSeconds(steps[0]); final payload = TaskPayload.fromProgram(program, steps); final messageId = _msg.nextId(); _pendingCreateTaskId = messageId; final msg = payload.toMessage(messageId, needAck: true); DeviceLog.info('Runner.start: program="${program.name}" steps=${steps.length} ' 'temperature=${program.temperature} airflow=${program.airflowTime}'); _msg.send(msg).then((ok) { if (!ok) { _pendingCreateTaskId = null; status = RunnerStatus.error; _callbacks?.onError?.call('下发任务失败:串口写入错误'); } }); } @override void pause() { if (status != RunnerStatus.running) return; DeviceLog.info('Runner.pause'); _sendControl('pause'); _stopLocalTicker(); status = RunnerStatus.paused; } @override void resume() { if (status != RunnerStatus.paused) return; DeviceLog.info('Runner.resume'); _sendControl('continue'); status = RunnerStatus.running; _startLocalTicker(); } @override void stop() { if (status == RunnerStatus.idle) return; DeviceLog.info('Runner.stop'); _sendControl('stop'); _teardown(); status = RunnerStatus.idle; } @override RunnerStatus getStatus() => status; @override void dispose() { _teardown(); _cancelAckSub?.call(); _cancelAckSub = null; } // -- 私有方法 --------------------------------------------------------- void _sendControl(String statusValue) { final messageId = _msg.nextId(); _pendingControlId = messageId; final msg = DeviceMessage.request( messageId: messageId, type: DeviceMessageType.control, data: {'status': statusValue}, needAck: true, ); _msg.send(msg); } void _onCreateTaskAck(DeviceMessage ack) { if (ack.ack != _pendingCreateTaskId) return; _pendingCreateTaskId = null; DeviceLog.info('Runner received create_task ack: id=${ack.messageId}'); // ack 即视为下位机已接受任务,进入 running 状态 if (status == RunnerStatus.idle || status == RunnerStatus.error) { status = RunnerStatus.running; _startLocalTicker(); } } void _onControlAck(DeviceMessage ack) { if (ack.ack != _pendingControlId) return; _pendingControlId = null; DeviceLog.info('Runner received control ack: id=${ack.messageId}'); // control ack 不修改状态,状态机在调用 pause/resume/stop 时已切过 } // -- 本地兜底倒计时 --------------------------------------------------- void _startLocalTicker() { _ticker?.cancel(); _ticker = Timer.periodic(const Duration(seconds: 1), (_) { if (status != RunnerStatus.running) return; if (_steps.isEmpty) return; _remainingSeconds--; if (_remainingSeconds <= 0) { _currentStepIndex++; if (_currentStepIndex >= _steps.length) { DeviceLog.info('Runner 本地倒计时完成 (timeout fallback)'); _stopLocalTicker(); status = RunnerStatus.completed; _callbacks?.onComplete?.call(); return; } _remainingSeconds = _stepTotalSeconds(_steps[_currentStepIndex]); } final well = _steps[_currentStepIndex.clamp(0, _steps.length - 1)] .position; _callbacks?.onProgress?.call( _currentStepIndex, _remainingSeconds, _calculateProgress(_currentStepIndex, _remainingSeconds), well, ); }); } void _stopLocalTicker() { _ticker?.cancel(); _ticker = null; } void _teardown() { _stopLocalTicker(); _program = null; _steps = const []; _callbacks = null; _pendingCreateTaskId = null; _pendingControlId = null; } int _stepTotalSeconds(Step s) { final t = s.mixTime + s.magnetTime + s.blowTime; return t == 0 ? 5 : t; } double _calculateProgress(int stepIndex, int remaining) { if (_steps.isEmpty) return 0; var total = 0; for (final s in _steps) { total += _stepTotalSeconds(s); } if (total <= 0) return 0; var elapsed = 0; for (var i = 0; i < stepIndex && i < _steps.length; i++) { elapsed += _stepTotalSeconds(_steps[i]); } final cur = _stepTotalSeconds(_steps[stepIndex.clamp(0, _steps.length - 1)]); elapsed += (cur - remaining).clamp(0, cur); return (elapsed / total).clamp(0.0, 1.0); } }