调整硬件appstate 保持与硬件库层面定义一致

This commit is contained in:
zhengxuan.zhang
2026-05-06 16:48:09 +08:00
parent 7c0f9dab73
commit 1ef876db2c
10 changed files with 280 additions and 80 deletions
+184 -51
View File
@@ -1,29 +1,40 @@
using Prism.Events;
using Prism.Mvvm;
using System;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
using XP.Common.Logging.Interfaces;
using XP.Hardware.MotionControl.Abstractions;
using XP.Hardware.MotionControl.Abstractions.Enums;
using XP.Hardware.MotionControl.Abstractions.Events;
using XP.Hardware.MotionControl.Services;
using XP.Hardware.RaySource.Services;
using XplorePlane.Models;
namespace XplorePlane.Services.AppState
{
/// <summary>
/// 全局应用状态管理服务实现。
/// 继承 BindableBase 以支持 WPF 数据绑定,使用 Interlocked.Exchange 保证线程安全写入,
/// 通过 Dispatcher.BeginInvoke 将事件调度到 UI 线程。
/// Global application state service.
/// Motion state is synchronized from the motion hardware service layer and
/// mapped into the legacy business model for compatibility.
/// </summary>
public class AppStateService : BindableBase, IAppStateService
{
private readonly Dispatcher _dispatcher;
private readonly IRaySourceService _raySourceService;
private readonly IMotionSystem _motionSystem;
private readonly IMotionControlService _motionControlService;
private readonly IEventAggregator _eventAggregator;
private readonly ILoggerService _logger;
private readonly SubscriptionToken _axisStatusChangedToken;
private readonly SubscriptionToken _geometryUpdatedToken;
private bool _disposed;
private GeometryData _latestGeometry;
// ── 状态字段(通过 Interlocked.Exchange 原子替换)──
private MotionState _motionState = MotionState.Default;
private RaySourceState _raySourceState = RaySourceState.Default;
private DetectorState _detectorState = DetectorState.Default;
private SystemState _systemState = SystemState.Default;
@@ -34,24 +45,16 @@ namespace XplorePlane.Services.AppState
// ── 类型化状态变更事件 ──
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;
@@ -62,17 +65,34 @@ namespace XplorePlane.Services.AppState
public AppStateService(
IRaySourceService raySourceService,
IMotionSystem motionSystem,
IMotionControlService motionControlService,
IEventAggregator eventAggregator,
ILoggerService logger)
{
ArgumentNullException.ThrowIfNull(raySourceService);
ArgumentNullException.ThrowIfNull(motionSystem);
ArgumentNullException.ThrowIfNull(motionControlService);
ArgumentNullException.ThrowIfNull(eventAggregator);
ArgumentNullException.ThrowIfNull(logger);
_raySourceService = raySourceService;
_motionSystem = motionSystem;
_motionControlService = motionControlService;
_eventAggregator = eventAggregator;
_logger = logger.ForModule<AppStateService>();
_dispatcher = Application.Current.Dispatcher;
_dispatcher = Application.Current?.Dispatcher ?? Dispatcher.CurrentDispatcher;
_geometryUpdatedToken = _eventAggregator
.GetEvent<GeometryUpdatedEvent>()
.Subscribe(OnGeometryUpdated);
_axisStatusChangedToken = _eventAggregator
.GetEvent<AxisStatusChangedEvent>()
.Subscribe(OnAxisStatusChanged);
SubscribeToExistingServices();
_logger.Info("AppStateService 已初始化");
_logger.Info("AppStateService initialized");
}
// ── 状态更新方法 ──
@@ -80,17 +100,30 @@ namespace XplorePlane.Services.AppState
public void UpdateMotionState(MotionState newState)
{
ArgumentNullException.ThrowIfNull(newState);
if (_disposed) { _logger.Warn("AppStateService 已释放,忽略 UpdateMotionState 调用"); return; }
if (_disposed)
{
_logger.Warn("AppStateService is disposed, ignoring UpdateMotionState");
return;
}
var old = Interlocked.Exchange(ref _motionState, newState);
if (ReferenceEquals(old, newState)) return;
RaiseOnDispatcher(old, newState, MotionStateChanged, nameof(MotionState));
// Keep the legacy API surface, but let the hardware service layer
// remain the source of truth whenever a fresh hardware snapshot is available.
if (TryRefreshMotionStateFromHardware("UpdateMotionState"))
{
return;
}
SetMotionState(newState);
}
public void UpdateRaySourceState(RaySourceState newState)
{
ArgumentNullException.ThrowIfNull(newState);
if (_disposed) { _logger.Warn("AppStateService 已释放,忽略 UpdateRaySourceState 调用"); return; }
if (_disposed)
{
_logger.Warn("AppStateService is disposed, ignoring UpdateRaySourceState");
return;
}
var old = Interlocked.Exchange(ref _raySourceState, newState);
if (ReferenceEquals(old, newState)) return;
@@ -100,7 +133,11 @@ namespace XplorePlane.Services.AppState
public void UpdateDetectorState(DetectorState newState)
{
ArgumentNullException.ThrowIfNull(newState);
if (_disposed) { _logger.Warn("AppStateService 已释放,忽略 UpdateDetectorState 调用"); return; }
if (_disposed)
{
_logger.Warn("AppStateService is disposed, ignoring UpdateDetectorState");
return;
}
var old = Interlocked.Exchange(ref _detectorState, newState);
if (ReferenceEquals(old, newState)) return;
@@ -110,7 +147,11 @@ namespace XplorePlane.Services.AppState
public void UpdateSystemState(SystemState newState)
{
ArgumentNullException.ThrowIfNull(newState);
if (_disposed) { _logger.Warn("AppStateService 已释放,忽略 UpdateSystemState 调用"); return; }
if (_disposed)
{
_logger.Warn("AppStateService is disposed, ignoring UpdateSystemState");
return;
}
var old = Interlocked.Exchange(ref _systemState, newState);
if (ReferenceEquals(old, newState)) return;
@@ -120,7 +161,11 @@ namespace XplorePlane.Services.AppState
public void UpdateCameraState(CameraState newState)
{
ArgumentNullException.ThrowIfNull(newState);
if (_disposed) { _logger.Warn("AppStateService 已释放,忽略 UpdateCameraState 调用"); return; }
if (_disposed)
{
_logger.Warn("AppStateService is disposed, ignoring UpdateCameraState");
return;
}
var old = Interlocked.Exchange(ref _cameraState, newState);
if (ReferenceEquals(old, newState)) return;
@@ -130,21 +175,26 @@ namespace XplorePlane.Services.AppState
public void UpdateCalibrationMatrix(CalibrationMatrix newMatrix)
{
ArgumentNullException.ThrowIfNull(newMatrix);
if (_disposed) { _logger.Warn("AppStateService 已释放,忽略 UpdateCalibrationMatrix 调用"); return; }
if (_disposed)
{
_logger.Warn("AppStateService is disposed, ignoring UpdateCalibrationMatrix");
return;
}
var old = Interlocked.Exchange(ref _calibrationMatrix, newMatrix);
if (ReferenceEquals(old, newMatrix)) return;
_dispatcher.BeginInvoke(() =>
{
RaisePropertyChanged(nameof(CalibrationMatrix));
});
_dispatcher.BeginInvoke(() => RaisePropertyChanged(nameof(CalibrationMatrix)));
}
public void UpdateLinkedViewState(LinkedViewState newState)
{
ArgumentNullException.ThrowIfNull(newState);
if (_disposed) { _logger.Warn("AppStateService 已释放,忽略 UpdateLinkedViewState 调用"); return; }
if (_disposed)
{
_logger.Warn("AppStateService is disposed, ignoring UpdateLinkedViewState");
return;
}
var old = Interlocked.Exchange(ref _linkedViewState, newState);
if (ReferenceEquals(old, newState)) return;
@@ -154,7 +204,11 @@ namespace XplorePlane.Services.AppState
public void UpdateRecipeExecutionState(RecipeExecutionState newState)
{
ArgumentNullException.ThrowIfNull(newState);
if (_disposed) { _logger.Warn("AppStateService 已释放,忽略 UpdateRecipeExecutionState 调用"); return; }
if (_disposed)
{
_logger.Warn("AppStateService is disposed, ignoring UpdateRecipeExecutionState");
return;
}
var old = Interlocked.Exchange(ref _recipeExecutionState, newState);
if (ReferenceEquals(old, newState)) return;
@@ -168,11 +222,11 @@ namespace XplorePlane.Services.AppState
var matrix = _calibrationMatrix;
if (matrix is null)
{
_logger.Warn("CalibrationMatrix 未设置,无法执行画面联动 (pixelX={PixelX}, pixelY={PixelY})", pixelX, pixelY);
_logger.Warn("CalibrationMatrix is not configured, cannot execute linked view request (pixelX={PixelX}, pixelY={PixelY})", pixelX, pixelY);
UpdateSystemState(SystemState with
{
HasError = true,
ErrorMessage = "CalibrationMatrix 未设置,无法执行画面联动"
ErrorMessage = "CalibrationMatrix is not configured, cannot execute linked view request"
});
return;
}
@@ -191,10 +245,38 @@ namespace XplorePlane.Services.AppState
});
}
// ── 内部辅助方法 ──
public void Dispose()
{
if (_disposed) return;
_disposed = true;
private void RaiseOnDispatcher<T>(T oldVal, T newVal,
EventHandler<StateChangedEventArgs<T>> handler, string propertyName)
if (_axisStatusChangedToken is not null)
{
_eventAggregator.GetEvent<AxisStatusChangedEvent>().Unsubscribe(_axisStatusChangedToken);
}
if (_geometryUpdatedToken is not null)
{
_eventAggregator.GetEvent<GeometryUpdatedEvent>().Unsubscribe(_geometryUpdatedToken);
}
MotionStateChanged = null;
RaySourceStateChanged = null;
DetectorStateChanged = null;
SystemStateChanged = null;
CameraStateChanged = null;
LinkedViewStateChanged = null;
RecipeExecutionStateChanged = null;
LinkedViewRequested = null;
_logger.Info("AppStateService disposed");
}
private void RaiseOnDispatcher<T>(
T oldVal,
T newVal,
EventHandler<StateChangedEventArgs<T>> handler,
string propertyName)
{
_dispatcher.BeginInvoke(() =>
{
@@ -205,34 +287,85 @@ namespace XplorePlane.Services.AppState
}
catch (Exception ex)
{
_logger.Error(ex, "状态变更事件处理器抛出异常 (property={PropertyName})", propertyName);
_logger.Error(ex, "State changed handler failed (property={PropertyName})", propertyName);
}
});
}
private void SubscribeToExistingServices()
{
_logger.Info("AppStateService 已准备好接收外部服务状态更新");
if (TryRefreshMotionStateFromHardware("initialization"))
{
_logger.Info("AppStateService subscribed to motion hardware state");
return;
}
_logger.Warn("AppStateService could not initialize motion state from hardware");
}
// ── Dispose ──
public void Dispose()
private void OnAxisStatusChanged(AxisStatusChangedData _)
{
if (_disposed) return;
_disposed = true;
TryRefreshMotionStateFromHardware("axis-status-changed");
}
// 清除所有事件订阅
MotionStateChanged = null;
RaySourceStateChanged = null;
DetectorStateChanged = null;
SystemStateChanged = null;
CameraStateChanged = null;
LinkedViewStateChanged = null;
RecipeExecutionStateChanged = null;
LinkedViewRequested = null;
private void OnGeometryUpdated(GeometryData geometry)
{
if (_disposed) return;
_logger.Info("AppStateService 已释放");
_latestGeometry = geometry;
TryRefreshMotionStateFromHardware("geometry-updated");
}
private bool TryRefreshMotionStateFromHardware(string reason)
{
try
{
if (_latestGeometry is null)
{
var geometry = _motionControlService.GetCurrentGeometry();
_latestGeometry = new GeometryData(geometry.FOD, geometry.FDD, geometry.Magnification);
}
SetMotionState(BuildMotionStateSnapshot(_latestGeometry));
return true;
}
catch (Exception ex)
{
_logger.Warn("Failed to refresh motion state from hardware during {Reason}: {Message}", reason, ex.Message);
_logger.Error(ex, "Motion state refresh exception during {Reason}", reason);
return false;
}
}
private MotionState BuildMotionStateSnapshot(GeometryData geometry)
{
var stageX = _motionSystem.GetLinearAxis(AxisId.StageX);
var stageY = _motionSystem.GetLinearAxis(AxisId.StageY);
var sourceZ = _motionSystem.GetLinearAxis(AxisId.SourceZ);
var detectorZ = _motionSystem.GetLinearAxis(AxisId.DetectorZ);
var detectorSwing = _motionSystem.GetRotaryAxis(RotaryAxisId.DetectorSwing);
return new MotionState(
XM: stageX.ActualPosition,
YM: stageY.ActualPosition,
ZT: sourceZ.ActualPosition,
ZD: detectorZ.ActualPosition,
TiltD: detectorSwing.ActualAngle,
Dist: geometry?.FDD ?? 0,
XMSpeed: 0,
YMSpeed: 0,
ZTSpeed: 0,
ZDSpeed: 0,
TiltDSpeed: 0,
DistSpeed: 0);
}
private void SetMotionState(MotionState newState)
{
var old = Interlocked.Exchange(ref _motionState, newState);
if (ReferenceEquals(old, newState)) return;
RaiseOnDispatcher(old, newState, MotionStateChanged, nameof(MotionState));
}
}
}
}
@@ -1,4 +1,4 @@
using Microsoft.Win32;
using Microsoft.Win32;
using Prism.Commands;
using Prism.Mvvm;
using System;
@@ -1,4 +1,4 @@
using Microsoft.Win32;
using Microsoft.Win32;
using Prism.Events;
using Prism.Commands;
using Prism.Mvvm;
+1 -1
View File
@@ -1,4 +1,4 @@
using Microsoft.Win32;
using Microsoft.Win32;
using Prism.Commands;
using Prism.Events;
using Prism.Ioc;
+1 -1
View File
@@ -1,4 +1,4 @@
<Window
<Window
x:Class="XplorePlane.Views.Cnc.CncEditorWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -1,4 +1,4 @@
<UserControl
<UserControl
x:Class="XplorePlane.Views.PipelineEditorView"
x:Name="RootControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+1 -1
View File
@@ -1,4 +1,4 @@
<UserControl
<UserControl
x:Class="XplorePlane.Views.ImagePanelView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+1 -1
View File
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
+1 -1
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup />
</Project>