#0039 全局数据结构设计
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using Prism.Mvvm;
|
||||
using Serilog;
|
||||
using XP.Hardware.RaySource.Services;
|
||||
using XplorePlane.Models;
|
||||
|
||||
namespace XplorePlane.Services.AppState
|
||||
{
|
||||
/// <summary>
|
||||
/// 全局应用状态管理服务实现。
|
||||
/// 继承 BindableBase 以支持 WPF 数据绑定,使用 Interlocked.Exchange 保证线程安全写入,
|
||||
/// 通过 Dispatcher.BeginInvoke 将事件调度到 UI 线程。
|
||||
/// </summary>
|
||||
public class AppStateService : BindableBase, IAppStateService
|
||||
{
|
||||
private readonly Dispatcher _dispatcher;
|
||||
private readonly IRaySourceService _raySourceService;
|
||||
private readonly ILogger _logger;
|
||||
private bool _disposed;
|
||||
|
||||
// ── 状态字段(通过 Interlocked.Exchange 原子替换)──
|
||||
private MotionState _motionState = MotionState.Default;
|
||||
private RaySourceState _raySourceState = RaySourceState.Default;
|
||||
private DetectorState _detectorState = DetectorState.Default;
|
||||
private SystemState _systemState = SystemState.Default;
|
||||
private CameraState _cameraState = CameraState.Default;
|
||||
private CalibrationMatrix _calibrationMatrix;
|
||||
private LinkedViewState _linkedViewState = LinkedViewState.Default;
|
||||
private RecipeExecutionState _recipeExecutionState = RecipeExecutionState.Default;
|
||||
|
||||
// ── 类型化状态变更事件 ──
|
||||
public event EventHandler<StateChangedEventArgs<MotionState>> MotionStateChanged;
|
||||
public event EventHandler<StateChangedEventArgs<RaySourceState>> RaySourceStateChanged;
|
||||
public event EventHandler<StateChangedEventArgs<DetectorState>> DetectorStateChanged;
|
||||
public event EventHandler<StateChangedEventArgs<SystemState>> SystemStateChanged;
|
||||
public event EventHandler<StateChangedEventArgs<CameraState>> CameraStateChanged;
|
||||
public event EventHandler<StateChangedEventArgs<LinkedViewState>> LinkedViewStateChanged;
|
||||
public event EventHandler<StateChangedEventArgs<RecipeExecutionState>> RecipeExecutionStateChanged;
|
||||
public event EventHandler<LinkedViewRequestEventArgs> LinkedViewRequested;
|
||||
|
||||
// ── 状态属性(只读)──
|
||||
public MotionState MotionState => _motionState;
|
||||
public RaySourceState RaySourceState => _raySourceState;
|
||||
public DetectorState DetectorState => _detectorState;
|
||||
public SystemState SystemState => _systemState;
|
||||
public CameraState CameraState => _cameraState;
|
||||
public CalibrationMatrix CalibrationMatrix => _calibrationMatrix;
|
||||
public LinkedViewState LinkedViewState => _linkedViewState;
|
||||
public RecipeExecutionState RecipeExecutionState => _recipeExecutionState;
|
||||
|
||||
public AppStateService(
|
||||
IRaySourceService raySourceService,
|
||||
ILogger logger)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(raySourceService);
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
|
||||
_raySourceService = raySourceService;
|
||||
_logger = logger;
|
||||
_dispatcher = Application.Current.Dispatcher;
|
||||
|
||||
SubscribeToExistingServices();
|
||||
_logger.Information("AppStateService 已初始化");
|
||||
}
|
||||
|
||||
// ── 状态更新方法 ──
|
||||
|
||||
public void UpdateMotionState(MotionState newState)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(newState);
|
||||
if (_disposed) { _logger.Warning("AppStateService 已释放,忽略 UpdateMotionState 调用"); return; }
|
||||
|
||||
var old = Interlocked.Exchange(ref _motionState, newState);
|
||||
if (ReferenceEquals(old, newState)) return;
|
||||
RaiseOnDispatcher(old, newState, MotionStateChanged, nameof(MotionState));
|
||||
}
|
||||
|
||||
public void UpdateRaySourceState(RaySourceState newState)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(newState);
|
||||
if (_disposed) { _logger.Warning("AppStateService 已释放,忽略 UpdateRaySourceState 调用"); return; }
|
||||
|
||||
var old = Interlocked.Exchange(ref _raySourceState, newState);
|
||||
if (ReferenceEquals(old, newState)) return;
|
||||
RaiseOnDispatcher(old, newState, RaySourceStateChanged, nameof(RaySourceState));
|
||||
}
|
||||
|
||||
public void UpdateDetectorState(DetectorState newState)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(newState);
|
||||
if (_disposed) { _logger.Warning("AppStateService 已释放,忽略 UpdateDetectorState 调用"); return; }
|
||||
|
||||
var old = Interlocked.Exchange(ref _detectorState, newState);
|
||||
if (ReferenceEquals(old, newState)) return;
|
||||
RaiseOnDispatcher(old, newState, DetectorStateChanged, nameof(DetectorState));
|
||||
}
|
||||
|
||||
public void UpdateSystemState(SystemState newState)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(newState);
|
||||
if (_disposed) { _logger.Warning("AppStateService 已释放,忽略 UpdateSystemState 调用"); return; }
|
||||
|
||||
var old = Interlocked.Exchange(ref _systemState, newState);
|
||||
if (ReferenceEquals(old, newState)) return;
|
||||
RaiseOnDispatcher(old, newState, SystemStateChanged, nameof(SystemState));
|
||||
}
|
||||
|
||||
public void UpdateCameraState(CameraState newState)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(newState);
|
||||
if (_disposed) { _logger.Warning("AppStateService 已释放,忽略 UpdateCameraState 调用"); return; }
|
||||
|
||||
var old = Interlocked.Exchange(ref _cameraState, newState);
|
||||
if (ReferenceEquals(old, newState)) return;
|
||||
RaiseOnDispatcher(old, newState, CameraStateChanged, nameof(CameraState));
|
||||
}
|
||||
|
||||
public void UpdateCalibrationMatrix(CalibrationMatrix newMatrix)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(newMatrix);
|
||||
if (_disposed) { _logger.Warning("AppStateService 已释放,忽略 UpdateCalibrationMatrix 调用"); return; }
|
||||
|
||||
var old = Interlocked.Exchange(ref _calibrationMatrix, newMatrix);
|
||||
if (ReferenceEquals(old, newMatrix)) return;
|
||||
|
||||
_dispatcher.BeginInvoke(() =>
|
||||
{
|
||||
RaisePropertyChanged(nameof(CalibrationMatrix));
|
||||
});
|
||||
}
|
||||
|
||||
public void UpdateLinkedViewState(LinkedViewState newState)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(newState);
|
||||
if (_disposed) { _logger.Warning("AppStateService 已释放,忽略 UpdateLinkedViewState 调用"); return; }
|
||||
|
||||
var old = Interlocked.Exchange(ref _linkedViewState, newState);
|
||||
if (ReferenceEquals(old, newState)) return;
|
||||
RaiseOnDispatcher(old, newState, LinkedViewStateChanged, nameof(LinkedViewState));
|
||||
}
|
||||
|
||||
public void UpdateRecipeExecutionState(RecipeExecutionState newState)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(newState);
|
||||
if (_disposed) { _logger.Warning("AppStateService 已释放,忽略 UpdateRecipeExecutionState 调用"); return; }
|
||||
|
||||
var old = Interlocked.Exchange(ref _recipeExecutionState, newState);
|
||||
if (ReferenceEquals(old, newState)) return;
|
||||
RaiseOnDispatcher(old, newState, RecipeExecutionStateChanged, nameof(RecipeExecutionState));
|
||||
}
|
||||
|
||||
// ── 画面联动 ──
|
||||
|
||||
public void RequestLinkedView(double pixelX, double pixelY)
|
||||
{
|
||||
var matrix = _calibrationMatrix;
|
||||
if (matrix is null)
|
||||
{
|
||||
_logger.Warning("CalibrationMatrix 未设置,无法执行画面联动 (pixelX={PixelX}, pixelY={PixelY})", pixelX, pixelY);
|
||||
UpdateSystemState(SystemState with
|
||||
{
|
||||
HasError = true,
|
||||
ErrorMessage = "CalibrationMatrix 未设置,无法执行画面联动"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
var (x, y, z) = matrix.Transform(pixelX, pixelY);
|
||||
var newLinkedState = new LinkedViewState(
|
||||
new PhysicalPosition(x, y, z),
|
||||
IsExecuting: true,
|
||||
LastRequestTime: DateTime.UtcNow);
|
||||
|
||||
UpdateLinkedViewState(newLinkedState);
|
||||
|
||||
_dispatcher.BeginInvoke(() =>
|
||||
{
|
||||
LinkedViewRequested?.Invoke(this, new LinkedViewRequestEventArgs(x, y, z));
|
||||
});
|
||||
}
|
||||
|
||||
// ── 内部辅助方法 ──
|
||||
|
||||
private void RaiseOnDispatcher<T>(T oldVal, T newVal,
|
||||
EventHandler<StateChangedEventArgs<T>> handler, string propertyName)
|
||||
{
|
||||
_dispatcher.BeginInvoke(() =>
|
||||
{
|
||||
RaisePropertyChanged(propertyName);
|
||||
try
|
||||
{
|
||||
handler?.Invoke(this, new StateChangedEventArgs<T>(oldVal, newVal));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "状态变更事件处理器抛出异常 (property={PropertyName})", propertyName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void SubscribeToExistingServices()
|
||||
{
|
||||
// IRaySourceService 是基于命令的服务(没有直接的状态变更事件),
|
||||
// 状态同步由外部调用者通过 UpdateRaySourceState 完成。
|
||||
// TODO: 当 IRaySourceService 添加状态变更事件时,在此处订阅并同步到 RaySourceState
|
||||
_logger.Information("AppStateService 已准备好接收外部服务状态更新");
|
||||
}
|
||||
|
||||
// ── Dispose ──
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
|
||||
// 清除所有事件订阅
|
||||
MotionStateChanged = null;
|
||||
RaySourceStateChanged = null;
|
||||
DetectorStateChanged = null;
|
||||
SystemStateChanged = null;
|
||||
CameraStateChanged = null;
|
||||
LinkedViewStateChanged = null;
|
||||
RecipeExecutionStateChanged = null;
|
||||
LinkedViewRequested = null;
|
||||
|
||||
_logger.Information("AppStateService 已释放");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using XplorePlane.Models;
|
||||
|
||||
namespace XplorePlane.Services.AppState
|
||||
{
|
||||
/// <summary>
|
||||
/// 全局应用状态管理服务接口。
|
||||
/// 以单例模式注册,聚合所有硬件和系统状态,支持 WPF 数据绑定和类型化事件订阅。
|
||||
/// </summary>
|
||||
public interface IAppStateService : INotifyPropertyChanged, IDisposable
|
||||
{
|
||||
// ── 状态属性(只读,通过 Update 方法更新)──
|
||||
|
||||
MotionState MotionState { get; }
|
||||
RaySourceState RaySourceState { get; }
|
||||
DetectorState DetectorState { get; }
|
||||
SystemState SystemState { get; }
|
||||
CameraState CameraState { get; }
|
||||
CalibrationMatrix CalibrationMatrix { get; }
|
||||
LinkedViewState LinkedViewState { get; }
|
||||
RecipeExecutionState RecipeExecutionState { get; }
|
||||
|
||||
// ── 状态更新方法(线程安全,可从任意线程调用)──
|
||||
|
||||
void UpdateMotionState(MotionState newState);
|
||||
void UpdateRaySourceState(RaySourceState newState);
|
||||
void UpdateDetectorState(DetectorState newState);
|
||||
void UpdateSystemState(SystemState newState);
|
||||
void UpdateCameraState(CameraState newState);
|
||||
void UpdateCalibrationMatrix(CalibrationMatrix newMatrix);
|
||||
void UpdateLinkedViewState(LinkedViewState newState);
|
||||
void UpdateRecipeExecutionState(RecipeExecutionState newState);
|
||||
|
||||
// ── 画面联动 ──
|
||||
|
||||
/// <summary>将像素坐标通过 CalibrationMatrix 转换为物理坐标并发布 LinkedViewRequest</summary>
|
||||
void RequestLinkedView(double pixelX, double pixelY);
|
||||
|
||||
// ── 类型化状态变更事件 ──
|
||||
|
||||
event EventHandler<StateChangedEventArgs<MotionState>> MotionStateChanged;
|
||||
event EventHandler<StateChangedEventArgs<RaySourceState>> RaySourceStateChanged;
|
||||
event EventHandler<StateChangedEventArgs<DetectorState>> DetectorStateChanged;
|
||||
event EventHandler<StateChangedEventArgs<SystemState>> SystemStateChanged;
|
||||
event EventHandler<StateChangedEventArgs<CameraState>> CameraStateChanged;
|
||||
event EventHandler<StateChangedEventArgs<LinkedViewState>> LinkedViewStateChanged;
|
||||
event EventHandler<StateChangedEventArgs<RecipeExecutionState>> RecipeExecutionStateChanged;
|
||||
|
||||
// ── 画面联动请求事件 ──
|
||||
|
||||
event EventHandler<LinkedViewRequestEventArgs> LinkedViewRequested;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
|
||||
namespace XplorePlane.Services.AppState
|
||||
{
|
||||
/// <summary>类型化状态变更事件参数,携带旧值和新值</summary>
|
||||
public sealed class StateChangedEventArgs<T> : EventArgs
|
||||
{
|
||||
public T OldValue { get; }
|
||||
public T NewValue { get; }
|
||||
|
||||
public StateChangedEventArgs(T oldValue, T newValue)
|
||||
{
|
||||
OldValue = oldValue;
|
||||
NewValue = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>画面联动请求事件参数,携带目标物理坐标</summary>
|
||||
public sealed class LinkedViewRequestEventArgs : EventArgs
|
||||
{
|
||||
public double TargetX { get; }
|
||||
public double TargetY { get; }
|
||||
public double TargetZ { get; }
|
||||
|
||||
public LinkedViewRequestEventArgs(double targetX, double targetY, double targetZ)
|
||||
{
|
||||
TargetX = targetX;
|
||||
TargetY = targetY;
|
||||
TargetZ = targetZ;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using XplorePlane.Models;
|
||||
|
||||
namespace XplorePlane.Services.Recipe
|
||||
{
|
||||
/// <summary>检测配方管理服务接口,负责配方的创建、序列化/反序列化和执行编排</summary>
|
||||
public interface IRecipeService
|
||||
{
|
||||
/// <summary>创建空的检测配方</summary>
|
||||
InspectionRecipe CreateRecipe(string name);
|
||||
|
||||
/// <summary>从当前全局状态快照 + PipelineModel 记录一个步骤</summary>
|
||||
RecipeStep RecordCurrentStep(InspectionRecipe recipe, PipelineModel pipeline);
|
||||
|
||||
/// <summary>序列化配方到 JSON 文件</summary>
|
||||
Task SaveAsync(InspectionRecipe recipe, string filePath);
|
||||
|
||||
/// <summary>从 JSON 文件反序列化配方</summary>
|
||||
Task<InspectionRecipe> LoadAsync(string filePath);
|
||||
|
||||
/// <summary>按步骤顺序执行配方</summary>
|
||||
Task ExecuteAsync(InspectionRecipe recipe, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>暂停当前配方执行(当前步骤完成后停止)</summary>
|
||||
void Pause();
|
||||
|
||||
/// <summary>恢复已暂停的配方执行</summary>
|
||||
void Resume();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using XplorePlane.Models;
|
||||
using XplorePlane.Services.AppState;
|
||||
|
||||
namespace XplorePlane.Services.Recipe
|
||||
{
|
||||
/// <summary>
|
||||
/// 检测配方管理服务实现。
|
||||
/// 负责配方的创建、序列化/反序列化和执行编排,通过 ManualResetEventSlim 支持暂停/恢复。
|
||||
/// </summary>
|
||||
public class RecipeService : IRecipeService
|
||||
{
|
||||
private readonly IAppStateService _appStateService;
|
||||
private readonly IPipelineExecutionService _pipelineExecutionService;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ManualResetEventSlim _pauseHandle = new(true); // initially signaled (not paused)
|
||||
|
||||
private static readonly JsonSerializerOptions _jsonOptions = new()
|
||||
{
|
||||
WriteIndented = true
|
||||
};
|
||||
|
||||
public RecipeService(
|
||||
IAppStateService appStateService,
|
||||
IPipelineExecutionService pipelineExecutionService,
|
||||
ILogger logger)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(appStateService);
|
||||
ArgumentNullException.ThrowIfNull(pipelineExecutionService);
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
|
||||
_appStateService = appStateService;
|
||||
_pipelineExecutionService = pipelineExecutionService;
|
||||
_logger = logger;
|
||||
|
||||
_logger.Information("RecipeService 已初始化");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public InspectionRecipe CreateRecipe(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
throw new ArgumentException("配方名称不能为空", nameof(name));
|
||||
|
||||
var recipe = new InspectionRecipe(
|
||||
Id: Guid.NewGuid(),
|
||||
Name: name,
|
||||
CreatedAt: DateTime.UtcNow,
|
||||
UpdatedAt: DateTime.UtcNow,
|
||||
Steps: new List<RecipeStep>().AsReadOnly());
|
||||
|
||||
_logger.Information("已创建配方: {RecipeName}, Id={RecipeId}", name, recipe.Id);
|
||||
return recipe;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public RecipeStep RecordCurrentStep(InspectionRecipe recipe, PipelineModel pipeline)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(recipe);
|
||||
ArgumentNullException.ThrowIfNull(pipeline);
|
||||
|
||||
var motionState = _appStateService.MotionState;
|
||||
var raySourceState = _appStateService.RaySourceState;
|
||||
var detectorState = _appStateService.DetectorState;
|
||||
|
||||
var step = new RecipeStep(
|
||||
StepIndex: recipe.Steps.Count,
|
||||
MotionState: motionState,
|
||||
RaySourceState: raySourceState,
|
||||
DetectorState: detectorState,
|
||||
Pipeline: pipeline);
|
||||
|
||||
_logger.Information("已记录配方步骤: StepIndex={StepIndex}, Recipe={RecipeName}",
|
||||
step.StepIndex, recipe.Name);
|
||||
return step;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task SaveAsync(InspectionRecipe recipe, string filePath)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(recipe);
|
||||
ArgumentNullException.ThrowIfNull(filePath);
|
||||
|
||||
// 路径安全性验证:检查路径遍历
|
||||
if (filePath.Contains("..", StringComparison.Ordinal))
|
||||
throw new UnauthorizedAccessException($"文件路径包含不安全的路径遍历字符: {filePath}");
|
||||
|
||||
var json = JsonSerializer.Serialize(recipe, _jsonOptions);
|
||||
await File.WriteAllTextAsync(filePath, json).ConfigureAwait(false);
|
||||
|
||||
_logger.Information("已保存配方到文件: {FilePath}, Recipe={RecipeName}", filePath, recipe.Name);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<InspectionRecipe> LoadAsync(string filePath)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(filePath);
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
throw new FileNotFoundException($"配方文件不存在: {filePath}", filePath);
|
||||
|
||||
string json;
|
||||
try
|
||||
{
|
||||
json = await File.ReadAllTextAsync(filePath).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex) when (ex is not FileNotFoundException)
|
||||
{
|
||||
throw new InvalidDataException($"无法读取配方文件: {filePath}", ex);
|
||||
}
|
||||
|
||||
InspectionRecipe recipe;
|
||||
try
|
||||
{
|
||||
recipe = JsonSerializer.Deserialize<InspectionRecipe>(json, _jsonOptions);
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
throw new InvalidDataException($"配方文件 JSON 格式无效: {filePath}", ex);
|
||||
}
|
||||
|
||||
if (recipe is null)
|
||||
throw new InvalidDataException($"配方文件反序列化结果为 null: {filePath}");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(recipe.Name))
|
||||
throw new InvalidDataException($"配方文件数据不完整,缺少名称: {filePath}");
|
||||
|
||||
if (recipe.Steps is null)
|
||||
throw new InvalidDataException($"配方文件数据不完整,缺少步骤列表: {filePath}");
|
||||
|
||||
_logger.Information("已加载配方: {FilePath}, Recipe={RecipeName}, Steps={StepCount}",
|
||||
filePath, recipe.Name, recipe.Steps.Count);
|
||||
return recipe;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task ExecuteAsync(InspectionRecipe recipe, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(recipe);
|
||||
|
||||
_logger.Information("开始执行配方: {RecipeName}, 共 {StepCount} 步", recipe.Name, recipe.Steps.Count);
|
||||
|
||||
// 重置暂停状态
|
||||
_pauseHandle.Set();
|
||||
|
||||
_appStateService.UpdateRecipeExecutionState(new RecipeExecutionState(
|
||||
CurrentStepIndex: 0,
|
||||
TotalSteps: recipe.Steps.Count,
|
||||
Status: RecipeExecutionStatus.Running,
|
||||
CurrentRecipeName: recipe.Name));
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < recipe.Steps.Count; i++)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// 暂停检查:等待恢复信号
|
||||
_pauseHandle.Wait(cancellationToken);
|
||||
|
||||
var step = recipe.Steps[i];
|
||||
|
||||
_appStateService.UpdateRecipeExecutionState(new RecipeExecutionState(
|
||||
CurrentStepIndex: i,
|
||||
TotalSteps: recipe.Steps.Count,
|
||||
Status: RecipeExecutionStatus.Running,
|
||||
CurrentRecipeName: recipe.Name));
|
||||
|
||||
_logger.Information("执行配方步骤 {StepIndex}/{TotalSteps}: Recipe={RecipeName}",
|
||||
i + 1, recipe.Steps.Count, recipe.Name);
|
||||
|
||||
try
|
||||
{
|
||||
await ExecuteStepAsync(step, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw; // 让取消异常向上传播
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "配方步骤 {StepIndex} 执行失败: Recipe={RecipeName}", i, recipe.Name);
|
||||
|
||||
_appStateService.UpdateRecipeExecutionState(new RecipeExecutionState(
|
||||
CurrentStepIndex: i,
|
||||
TotalSteps: recipe.Steps.Count,
|
||||
Status: RecipeExecutionStatus.Error,
|
||||
CurrentRecipeName: recipe.Name));
|
||||
|
||||
_appStateService.UpdateSystemState(new SystemState(
|
||||
OperationMode: OperationMode.Idle,
|
||||
HasError: true,
|
||||
ErrorMessage: $"配方 '{recipe.Name}' 步骤 {i} 执行失败: {ex.Message}"));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_appStateService.UpdateRecipeExecutionState(new RecipeExecutionState(
|
||||
CurrentStepIndex: recipe.Steps.Count,
|
||||
TotalSteps: recipe.Steps.Count,
|
||||
Status: RecipeExecutionStatus.Completed,
|
||||
CurrentRecipeName: recipe.Name));
|
||||
|
||||
_logger.Information("配方执行完成: {RecipeName}", recipe.Name);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
_logger.Information("配方执行已取消: {RecipeName}", recipe.Name);
|
||||
|
||||
_appStateService.UpdateRecipeExecutionState(new RecipeExecutionState(
|
||||
CurrentStepIndex: 0,
|
||||
TotalSteps: recipe.Steps.Count,
|
||||
Status: RecipeExecutionStatus.Idle,
|
||||
CurrentRecipeName: recipe.Name));
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Pause()
|
||||
{
|
||||
_pauseHandle.Reset();
|
||||
|
||||
var current = _appStateService.RecipeExecutionState;
|
||||
if (current.Status == RecipeExecutionStatus.Running)
|
||||
{
|
||||
_appStateService.UpdateRecipeExecutionState(current with
|
||||
{
|
||||
Status = RecipeExecutionStatus.Paused
|
||||
});
|
||||
}
|
||||
|
||||
_logger.Information("配方执行已暂停");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Resume()
|
||||
{
|
||||
var current = _appStateService.RecipeExecutionState;
|
||||
if (current.Status == RecipeExecutionStatus.Paused)
|
||||
{
|
||||
_appStateService.UpdateRecipeExecutionState(current with
|
||||
{
|
||||
Status = RecipeExecutionStatus.Running
|
||||
});
|
||||
}
|
||||
|
||||
_pauseHandle.Set();
|
||||
_logger.Information("配方执行已恢复");
|
||||
}
|
||||
|
||||
// ── 内部辅助方法 ──
|
||||
|
||||
private async Task ExecuteStepAsync(RecipeStep step, CancellationToken cancellationToken)
|
||||
{
|
||||
// 更新运动状态
|
||||
_appStateService.UpdateMotionState(step.MotionState);
|
||||
|
||||
// 更新射线源状态
|
||||
_appStateService.UpdateRaySourceState(step.RaySourceState);
|
||||
|
||||
// 更新探测器状态
|
||||
_appStateService.UpdateDetectorState(step.DetectorState);
|
||||
|
||||
// TODO: 通过 IPipelineExecutionService 执行 Pipeline
|
||||
// IPipelineExecutionService.ExecutePipelineAsync 需要 IEnumerable<PipelineNodeViewModel> 和 BitmapSource,
|
||||
// 而 RecipeStep 持有 PipelineModel。此处记录日志作为占位,待 ViewModel 层适配后完善。
|
||||
_logger.Information("步骤 {StepIndex}: 已更新设备状态,Pipeline '{PipelineName}' 执行待实现",
|
||||
step.StepIndex, step.Pipeline?.Name ?? "N/A");
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user