Files
kuaishai2/lib/features/device/services/serial_runner.dart

214 lines
6.4 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<Step> _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<Step> 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);
}
}