#0039 全局数据结构设计

This commit is contained in:
zhengxuan.zhang
2026-03-18 20:14:08 +08:00
parent c6144fae89
commit 67898edc3f
19 changed files with 1490 additions and 16 deletions
+24
View File
@@ -6,6 +6,8 @@ using System.Windows;
using XplorePlane.Views;
using XplorePlane.ViewModels;
using XplorePlane.Services;
using XplorePlane.Services.AppState;
using XplorePlane.Services.Recipe;
using Prism.Ioc;
using Prism.DryIoc;
using Prism.Modularity;
@@ -88,6 +90,22 @@ namespace XplorePlane
Log.Information("XplorePlane 应用程序退出");
Log.Information("========================================");
// 释放全局状态服务资源
try
{
var bootstrapper = AppBootstrapper.Instance;
if (bootstrapper != null)
{
var appStateService = bootstrapper.Container.Resolve<IAppStateService>();
appStateService?.Dispose();
Log.Information("全局状态服务资源已释放");
}
}
catch (Exception ex)
{
Log.Error(ex, "全局状态服务资源释放失败");
}
// 释放射线源资源
try
{
@@ -179,6 +197,12 @@ namespace XplorePlane
containerRegistry.RegisterSingleton<IPipelineExecutionService, PipelineExecutionService>();
containerRegistry.RegisterSingleton<IPipelinePersistenceService, PipelinePersistenceService>();
// 注册全局状态服务(单例)
containerRegistry.RegisterSingleton<IAppStateService, AppStateService>();
// 注册检测配方服务(单例)
containerRegistry.RegisterSingleton<IRecipeService, RecipeService>();
// 注册流水线 ViewModel(每次解析创建新实例)
containerRegistry.Register<PipelineEditorViewModel>();
containerRegistry.Register<OperatorToolboxViewModel>();
Binary file not shown.

After

Width:  |  Height:  |  Size: 279 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

+44
View File
@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace XplorePlane.Models
{
/// <summary>检测配方中的单个步骤</summary>
public record RecipeStep(
int StepIndex,
MotionState MotionState,
RaySourceState RaySourceState,
DetectorState DetectorState,
PipelineModel Pipeline
);
/// <summary>检测配方(CNC 自动编程)</summary>
public record InspectionRecipe(
Guid Id,
string Name,
DateTime CreatedAt,
DateTime UpdatedAt,
IReadOnlyList<RecipeStep> Steps
)
{
/// <summary>追加步骤,返回新的 InspectionRecipe</summary>
public InspectionRecipe AddStep(RecipeStep step) =>
this with
{
Steps = Steps.Append(step).ToList().AsReadOnly(),
UpdatedAt = DateTime.UtcNow
};
}
/// <summary>配方执行状态(不可变)</summary>
public record RecipeExecutionState(
int CurrentStepIndex,
int TotalSteps,
RecipeExecutionStatus Status,
string CurrentRecipeName
)
{
public static readonly RecipeExecutionState Default = new(0, 0, RecipeExecutionStatus.Idle, string.Empty);
}
}
+120
View File
@@ -0,0 +1,120 @@
using System;
namespace XplorePlane.Models
{
// ── Enumerations ──────────────────────────────────────────────────
/// <summary>系统操作模式</summary>
public enum OperationMode
{
Idle, // 空闲
Scanning, // 扫描
CTAcquire, // CT 采集
RecipeRun // 配方执行中
}
/// <summary>配方执行状态</summary>
public enum RecipeExecutionStatus
{
Idle, // 空闲
Running, // 运行中
Paused, // 暂停
Completed, // 已完成
Error // 出错
}
// ── State Records ─────────────────────────────────────────────────
/// <summary>运动控制状态(不可变)</summary>
public record MotionState(
double XM, // X 轴位置 (μm)
double YM, // Y 轴位置 (μm)
double ZT, // Z 上轴位置 (μm)
double ZD, // Z 下轴位置 (μm)
double TiltD, // 倾斜角度 (m°)
double Dist, // 距离 (μm)
double XMSpeed, // X 轴速度 (μm/s)
double YMSpeed, // Y 轴速度 (μm/s)
double ZTSpeed, // Z 上轴速度 (μm/s)
double ZDSpeed, // Z 下轴速度 (μm/s)
double TiltDSpeed, // 倾斜速度 (m°/s)
double DistSpeed // 距离速度 (μm/s)
)
{
public static readonly MotionState Default = new(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
}
/// <summary>射线源状态(不可变)</summary>
public record RaySourceState(
bool IsOn, // 开关状态
double Voltage, // 电压 (kV)
double Power // 功率 (W)
)
{
public static readonly RaySourceState Default = new(false, 0, 0);
}
/// <summary>探测器状态(不可变)</summary>
public record DetectorState(
bool IsConnected, // 连接状态
bool IsAcquiring, // 是否正在采集
double FrameRate, // 当前帧率 (fps)
string Resolution // 分辨率描述,如 "2048x2048"
)
{
public static readonly DetectorState Default = new(false, false, 0, string.Empty);
}
/// <summary>系统级状态(不可变)</summary>
public record SystemState(
OperationMode OperationMode, // 当前操作模式
bool HasError, // 是否存在系统错误
string ErrorMessage // 错误描述
)
{
public static readonly SystemState Default = new(OperationMode.Idle, false, string.Empty);
}
/// <summary>摄像头视频流状态(不可变)</summary>
public record CameraState(
bool IsConnected, // 连接状态
bool IsStreaming, // 是否正在推流
object CurrentFrame, // 当前帧数据引用(BitmapSource 或 byte[]Frozen
int Width, // 分辨率宽
int Height, // 分辨率高
double FrameRate // 帧率 (fps)
)
{
public static readonly CameraState Default = new(false, false, null, 0, 0, 0);
}
/// <summary>物理坐标</summary>
public record PhysicalPosition(double X, double Y, double Z);
/// <summary>图像标定矩阵,像素坐标 → 物理坐标映射</summary>
public record CalibrationMatrix(
double M11, double M12, double M13, // 3x3 仿射变换矩阵
double M21, double M22, double M23,
double M31, double M32, double M33
)
{
/// <summary>将像素坐标转换为物理坐标</summary>
public (double X, double Y, double Z) Transform(double pixelX, double pixelY)
{
double x = M11 * pixelX + M12 * pixelY + M13;
double y = M21 * pixelX + M22 * pixelY + M23;
double z = M31 * pixelX + M32 * pixelY + M33;
return (x, y, z);
}
}
/// <summary>画面联动状态(不可变)</summary>
public record LinkedViewState(
PhysicalPosition TargetPosition, // 目标物理坐标
bool IsExecuting, // 联动是否正在执行
DateTime LastRequestTime // 最近一次联动请求时间
)
{
public static readonly LinkedViewState Default = new(new PhysicalPosition(0, 0, 0), false, DateTime.MinValue);
}
}
@@ -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;
}
}
}
+3 -2
View File
@@ -34,8 +34,9 @@
2026.3.18
----------------------
1、全局数据结构的考虑与设计
2、将计划窗体默认隐藏,只有CNC状态下展开
1、全局数据结构的考虑与设计(多个窗体可以调用公共的数据,如射线源状态,探测器状态,运动位置,图像等)
2、将计划窗体默认隐藏,只有CNC状态下展开
3、日志该用XP.Common库和多语言的学习