Files
XplorePlane/XplorePlane/Doc/硬件层及UI集成技术路线.md
T
2026-05-06 23:55:28 +08:00

24 KiB
Raw Blame History

硬件层及 UI 集成技术路线

整体架构

采用 DLL 直接引用 + Prism DI 容器手动注册 + AutoWireViewModel 自动装配 的集成方式。

DLL 提供完整的 MVVM 三层(View / ViewModel / Service),主项目负责 DI 注册和 XAML 布局嵌入,数据通过注入的服务接口和 Prism EventAggregator 在两侧流动。


分层说明

1. DLL 引用层

XP.Hardware.RaySource.dll 放置于 Libs/Hardware/ 目录,通过 .csproj<Reference HintPath> 引用。

DLL 内部包含:

类型 名称 说明
UserControl RaySourceOperateView 射线源操作界面
ViewModel RaySourceOperateViewModel 对应 ViewModel
服务接口/实现 IRaySourceService / RaySourceService 射线源业务逻辑
工厂 IRaySourceFactory / RaySourceFactory 策略工厂,支持 Comet 160kv、Hamamatsu160kv
服务 IFilamentLifetimeService 灯丝寿命管理
配置模型 RaySourceConfig 从 App.config 加载的配置

2. DI 注册层(App.xaml.cs → AppBootstrapper

主项目在 RegisterTypes()手动注册 DLL 内所有服务,未走 Prism 的 ConfigureModuleCatalog 自动模块加载,目的是避免模块加载顺序问题,确保 DryIoc 容器在 Shell 创建前已具备所有依赖。

// 注册 ViewModel(供 ViewModelLocator 自动装配)
containerRegistry.Register<XP.Hardware.RaySource.ViewModels.RaySourceOperateViewModel>();

// 注册配置(从 App.config 读取 RaySource:xxx 键值)
var raySourceConfig = XP.Hardware.RaySource.Config.ConfigLoader.LoadConfig();
containerRegistry.RegisterInstance(raySourceConfig);

// 注册核心服务(全部单例)
containerRegistry.RegisterSingleton<IRaySourceFactory, RaySourceFactory>();
containerRegistry.RegisterSingleton<IRaySourceService, RaySourceService>();
containerRegistry.RegisterSingleton<IFilamentLifetimeService, FilamentLifetimeService>();

3. XAML 嵌入层(MainWindow.xaml

通过 XML 命名空间直接引用 DLL 中的 View:

xmlns:views1="clr-namespace:XP.Hardware.RaySource.Views;assembly=XP.Hardware.RaySource"

在主窗口右侧面板顶部(Grid.Row="0",固定高度 250px)放置控件:

<views1:RaySourceOperateView Grid.Row="0" Grid.ColumnSpan="2" />

控件内部已设置 prism:ViewModelLocator.AutoWireViewModel="True",Prism 按命名约定自动从 DI 容器解析 RaySourceOperateViewModel 并绑定为 DataContext。


4. 数据传递路线

数据流分四条路径:

路径 A:配置数据(启动时,单向下行)

App.config (RaySource:xxx 键值)
  → ConfigLoader.LoadConfig()
    → RaySourceConfig 实例
      → 注入到 RaySourceService / RaySourceOperateViewModel

App.config 中的关键配置项:

<add key="RaySource:PlcIpAddress" value="192.168.1.100" />
<add key="RaySource:MinVoltage" value="20" />
<add key="RaySource:MaxVoltage" value="225" />
<add key="RaySource:StatusPollingInterval" value="500" />
<add key="RaySource:EnableAutoStatusMonitoring" value="true" />

路径 B:用户操作(UI → DLL 服务层)

RaySourceOperateView(按钮点击)
  → RaySourceOperateViewModelCommand 绑定)
    → IRaySourceService.SetVoltageAsync() / TurnOnAsync() / ...
      → IXRaySource(具体策略实现,如 Comet225)
        → 硬件通讯(B&R PVI / BR.AN.PviServices.dll

路径 C:状态回传(DLL 服务层 → UI)

硬件状态轮询(StatusPollingInterval = 500ms
  → RaySourceService 内部更新
    → RaySourceOperateViewModel 属性变更(INotifyPropertyChanged
      → RaySourceOperateView 数据绑定自动刷新

同时:
  → AppStateService 订阅 IRaySourceService 事件
    → 更新 RaySourceState(IsOn, Voltage, Power)
      → Dispatcher.BeginInvoke 调度到 UI 线程
        → 其他 ViewModel 通过 IAppStateService 读取全局射线源状态

RaySourceState 为不可变 record,定义于 Models/StateModels.cs

public record RaySourceState(
    bool IsOn,       // 开关状态
    double Voltage,  // 电压 (kV)
    double Power     // 功率 (W)
)
{
    public static readonly RaySourceState Default = new(false, 0, 0);
}

路径 D:跨模块事件通讯(Prism EventAggregator

DLL 内部发布事件(XP.Hardware.RaySource.Abstractions.Events):
  XrayStateChangedEvent    — 射线开关状态变化
  StatusUpdatedEvent       — 实时电压/电流数据
  ErrorOccurredEvent       — 错误通知
  OperationResultEvent     — 操作结果回调

主项目任意 ViewModel 订阅示例:
  _eventAggregator.GetEvent<StatusUpdatedEvent>().Subscribe(data => { ... });

5. 完整依赖关系

RaySourceOperateViewDLL 中的 UserControl
  └─ AutoWire → RaySourceOperateViewModelDLL,主项目注册到 DI
                   ├─ IRaySourceService        ← 单例,DLL 实现
                   ├─ RaySourceConfig           ← App.config 加载
                   ├─ IFilamentLifetimeService  ← 单例,DLL 实现
                   ├─ IEventAggregator          ← Prism 内置
                   ├─ ILoggerService            ← 主项目 LoggerServiceAdapter 适配 Serilog
                   └─ ILocalizationService      ← 主项目注册,DLL 消费

6. 资源释放

App.OnExit() 中显式从容器解析并释放资源:

var appStateService = bootstrapper.Container.Resolve<IAppStateService>();
appStateService?.Dispose();

var raySourceService = bootstrapper.Container.Resolve<IRaySourceService>();
raySourceService?.Dispose();

确保硬件连接在应用退出时正确断开。

7.


硬件层 → AppState → UI 状态同步机制

整体结论

硬件子系统 同步方式 AppState 订阅 状态是否自动同步
运动控制(Motion 事件驱动 + 轮询 已订阅 GeometryUpdatedEvent / AxisStatusChangedEvent 自动同步
射线源(RaySource 事件驱动 已订阅 StatusUpdatedEvent / RaySourceStatusChangedEvent / VariablesConnectedEvent 自动同步
探测器(Detector 事件驱动 已订阅 StatusChangedEvent / ImageCapturedEvent 自动同步
相机(Camera ViewModel 直连 + AppState 同步 连接/采集状态通过 SyncCameraStateToAppState() 推送 连接状态自动同步(帧数据不经过 AppState)

一、运动控制 — 完整链路

1.1 数据流

PLC 硬件(B&R
  └─ IPlcService.IsConnected(轮询前置检查)
       └─ MotionControlService.OnPollingTick()   [System.Threading.Timer,周期 = PollingInterval ms]
            ├─ _motionSystem.UpdateAllStatus()    ← 从 PLC 读取所有轴实际位置
            ├─ GetCurrentGeometry()               ← 正算 FOD / FDD / Magnification
            ├─ 发布 GeometryUpdatedEvent          ─┐
            └─ 发布 AxisStatusChangedEvent         ─┤
                                                    ↓
                                         AppStateService(构造时订阅两个事件)
                                           ├─ OnGeometryUpdated()
                                           └─ OnAxisStatusChanged()
                                                └─ TryRefreshMotionStateFromHardware()
                                                     └─ BuildMotionStateSnapshot()
                                                          ├─ 读取所有轴 ActualPosition / ActualAngle
                                                          └─ SetMotionState()
                                                               └─ 触发 MotionStateChanged 事件
                                                                    └─ ViewModel 绑定自动刷新 UI

1.2 关键实现细节

轮询启动MotionControlService.StartPolling() 需在应用启动时显式调用,否则轮询不会运行。

PLC 未连接时的保护

// MotionControlService.OnPollingTick()
if (!_plcService.IsConnected) return;  // 直接跳过,不报错

连续错误降频

if (_pollErrorCount > 3)
{
    if (++_pollErrorCount % 50 != 0) return;  // 每 50 次才尝试一次,防止日志刷屏
}

AppStateService 初始化时的首次刷新

// AppStateService 构造函数末尾
private void SubscribeToExistingServices()
{
    if (TryRefreshMotionStateFromHardware("initialization"))
        _logger.Info("AppStateService subscribed to motion hardware state");
    else
        _logger.Warn("AppStateService could not initialize motion state from hardware");
}

PLC 未连接时 warn 但不崩溃,等待后续轮询事件触发再同步。

UpdateMotionState() 的特殊行为

public void UpdateMotionState(MotionState newState)
{
    // 优先从硬件层拉取最新快照,忽略外部传入值
    if (TryRefreshMotionStateFromHardware("UpdateMotionState"))
        return;

    // 硬件不可用时才使用传入值(降级路径)
    SetMotionState(newState);
}

硬件连接后,外部调用 UpdateMotionState() 实际上会被硬件快照覆盖,硬件层始终是 MotionState 的唯一真实来源。

1.3 事件清单

事件 发布方 订阅方 触发时机
GeometryUpdatedEvent MotionControlService AppStateServiceMotionControlViewModelAxisControlViewModel 每次轮询 tick
AxisStatusChangedEvent MotionControlService AppStateServiceMotionControlViewModelAxisControlViewModel 轴状态发生变化时
MotionErrorEvent MotionControlService 轴状态变为 Error / Alarm
DoorStatusChangedEvent MotionControlService MotionControlViewModel 安全门状态变化
DoorInterlockChangedEvent MotionControlService 联锁状态变化
GeometryApplyRequestEvent DebugWindow MotionControlViewModel 调试窗口发起几何反算请求

二、射线源 — 已打通

2.1 当前状态

AppStateService 已在构造时订阅 StatusUpdatedEventRaySourceStatusChangedEventVariablesConnectedEventRaySourceState 自动同步。

XP.Hardware.RaySource 发布的事件(AppState 层订阅情况):
  ├─ StatusUpdatedEvent          ← ✅ 已订阅(主路径),映射为 RaySourceState
  ├─ RaySourceStatusChangedEvent ← ✅ 已订阅(补充路径),快速同步 IsOn 状态
  ├─ VariablesConnectedEvent     ← ✅ 已订阅,断开时重置 RaySourceState 为默认值
  └─ OperationResultEvent        ← 未订阅(操作结果由 ViewModel 层直接处理)

2.2 数据流

射线源硬件(B&R PVI
  └─ IRaySourceServiceXP.Hardware.RaySource
       ├─ 发布 StatusUpdatedEventSystemStatusData500ms 轮询)
       │    └─ AppStateService.OnRaySourceStatusUpdated()  [BackgroundThread]
       │         ├─ IsOn  = data.IsXRayOn
       │         ├─ Voltage = data.ActualVoltage (kV)
       │         ├─ Power = ActualVoltage × ActualCurrent / 1000 (W)
       │         └─ UpdateRaySourceState() → 触发 RaySourceStateChanged → ViewModel 刷新 UI
       │
       ├─ 发布 RaySourceStatusChangedEventRaySourceStatus 枚举)
       │    └─ AppStateService.OnRaySourceStatusChanged()  [BackgroundThread]
       │         └─ 仅在 IsOn 变化时更新,保留当前 Voltage/Power 值
       │
       └─ 发布 VariablesConnectedEventbool
            └─ AppStateService.OnRaySourceVariablesConnected()  [BackgroundThread]
                 └─ 断开时:UpdateRaySourceState(RaySourceState.Default)

2.3 关键实现细节

双路径设计StatusUpdatedEvent 是主路径,携带完整的电压、电流、开关状态;RaySourceStatusChangedEvent 是补充路径,仅在 StatusUpdatedEvent 尚未到达时快速同步 IsOn 字段,避免覆盖精确数据:

// OnRaySourceStatusChanged — 仅当 IsOn 状态发生变化时才更新
if (current.IsOn == isOn) return;
UpdateRaySourceState(new RaySourceState(IsOn: isOn, Voltage: current.Voltage, Power: current.Power));

功率计算Power(W) = ActualVoltage(kV) × ActualCurrent(μA) / 1000

断连重置PVI 变量断开时将 RaySourceState 重置为 Default(false, 0, 0),防止 UI 显示过期数据。

Dispose 时取消订阅:三个 token 均在 Dispose() 中取消。


三、探测器 — 已打通

3.1 当前状态

AppStateService 已在构造时订阅 StatusChangedEventImageCapturedEvent,探测器状态和最新帧均自动同步。

XP.Hardware.Detector 发布的事件(AppState 层订阅情况):
  ├─ StatusChangedEvent      ← ✅ 已订阅,映射为 DetectorState
  ├─ ImageCapturedEvent      ← ✅ 已订阅,缓存为 LatestDetectorFramevolatile
  ├─ CorrectionCompletedEvent ← 未订阅(无需同步到 AppState
  └─ ErrorOccurredEvent      ← 未订阅(无需同步到 AppState)

3.2 数据流

探测器硬件
  └─ IDetectorServiceXP.Hardware.Detector
       ├─ 发布 StatusChangedEventDetectorStatus 枚举)
       │    └─ AppStateService.OnDetectorStatusChanged()  [BackgroundThread]
       │         ├─ 映射 DetectorStatus → DetectorState(IsConnected, IsAcquiring, ...)
       │         ├─ UpdateDetectorState()
       │         │    └─ 触发 DetectorStateChanged 事件 → ViewModel 刷新 UI
       │         └─ 若从已连接变为断开:发布 DetectorDisconnectedEvent
       │              ├─ CncExecutionService 订阅 → 取消当前 CNC 执行
       │              └─ ViewportPanelViewModel 订阅 → 弹出断连警告对话框
       │
       └─ 发布 ImageCapturedEventImageCapturedEventArgs
            └─ AppStateService.OnDetectorImageCaptured()  [BackgroundThread]
                 └─ volatile 写 _latestDetectorFrame
                      └─ 上层通过 LatestDetectorFrame 属性按需读取(CNC 执行、图像处理等)

3.3 关键实现细节

订阅均在后台线程,避免阻塞采集链路:

_detectorStatusChangedToken = _eventAggregator
    .GetEvent<StatusChangedEvent>()
    .Subscribe(OnDetectorStatusChanged, ThreadOption.BackgroundThread);

_detectorImageCapturedToken = _eventAggregator
    .GetEvent<ImageCapturedEvent>()
    .Subscribe(OnDetectorImageCaptured, ThreadOption.BackgroundThread);

断连检测OnDetectorStatusChanged 在更新状态前记录 wasConnected,状态更新后若检测到从已连接变为断开,则发布 DetectorDisconnectedEvent

bool wasConnected = _detectorState?.IsConnected ?? false;
// ... 更新状态 ...
if (wasConnected && !isConnected)
{
    _eventAggregator.GetEvent<DetectorDisconnectedEvent>().Publish();
    _logger.Warn("探测器已断连,发布 DetectorDisconnectedEvent");
}

最新帧缓存LatestDetectorFramevolatile 字段,任意线程可安全读取,无需加锁:

private volatile ImageCapturedEventArgs _latestDetectorFrame;
public ImageCapturedEventArgs LatestDetectorFrame => _latestDetectorFrame;

Dispose 时取消订阅

_eventAggregator.GetEvent<StatusChangedEvent>().Unsubscribe(_detectorStatusChangedToken);
_eventAggregator.GetEvent<ImageCapturedEvent>().Unsubscribe(_detectorImageCapturedToken);

3.4 DetectorDisconnectedEvent 下游链路

DetectorDisconnectedEvent 是无载荷 Prism 事件,定义于 XplorePlane/Events/DetectorDisconnectedEvent.cs

订阅方 线程选项 行为
CncExecutionService BackgroundThread 取消 _executionCts,中止当前 CNC 执行
ViewportPanelViewModel UIThread 若 CNC 正在运行,弹出 MessageBox 警告

四、相机 — 已打通(连接状态同步)

4.1 当前状态

相机图像流数据量大、帧率高,不适合经过 AppState 的不可变 record 替换机制,因此相机的图像帧仍由 NavigationPropertyPanelViewModel 直接持有并渲染。但连接状态和采集状态现已通过 UpdateCameraState() 同步到 AppStateService,供其他模块查询。

ICameraController 事件(NavigationPropertyPanelViewModel 直接订阅):
  ├─ ImageGrabbed     ← 直接渲染到 CameraImageSource(不经过 AppState,避免高频刷新)
  ├─ GrabError        ← 更新 CameraStatusText
  └─ ConnectionLost   ← 更新 IsCameraConnected + 同步 CameraState ✅

4.2 数据流

ICameraControllerXP.Camera
  └─ NavigationPropertyPanelViewModel(直接持有 _camera
       ├─ ConnectCamera() / OnCameraReady()
       │    └─ IsCameraConnected = true → SyncCameraStateToAppState()
       │         └─ AppStateService.UpdateCameraState(IsConnected=true, IsStreaming=false, ...)
       │
       ├─ DisconnectCamera()
       │    └─ IsCameraConnected = false → SyncCameraStateToAppState()
       │         └─ AppStateService.UpdateCameraState(IsConnected=false, IsStreaming=false, ...)
       │
       ├─ StartGrab() / StopGrab()
       │    └─ IsCameraGrabbing = true/false → SyncCameraStateToAppState()
       │         └─ AppStateService.UpdateCameraState(IsStreaming=true/false, ...)
       │
       └─ OnCameraConnectionLost()
            └─ IsCameraConnected = false → SyncCameraStateToAppState()
                 └─ AppStateService.UpdateCameraState(IsConnected=false, IsStreaming=false, ...)

4.3 关键实现细节

SyncCameraStateToAppState() 是统一的同步入口,在所有状态变更节点调用:

private void SyncCameraStateToAppState()
{
    _appStateService.UpdateCameraState(new CameraState(
        IsConnected: IsCameraConnected,
        IsStreaming: IsCameraGrabbing,
        CurrentFrame: null,          // 帧数据不经过 AppState,避免高频触发 UI 刷新
        Width: IsCameraConnected ? ImageWidth : 0,
        Height: IsCameraConnected ? ImageHeight : 0,
        FrameRate: 0));
}

设计决策CurrentFrame 始终为 null,帧数据仍由 ViewModel 直接持有。CameraState 只承载连接/采集状态和图像尺寸,供其他模块(如 CNC 执行、状态栏)查询相机是否可用。

DI 注册NavigationPropertyPanelViewModel 已通过 RegisterSingleton<NavigationPropertyPanelViewModel>() 注册,DryIoc 自动解析新增的 IAppStateService 构造函数参数,无需修改注册代码。


五、状态同步全景图

┌─────────────────────────────────────────────────────────────────────┐
│                         硬件层                                       │
│                                                                     │
│  PLC ──→ MotionControlService ──→ GeometryUpdatedEvent    ─────┐   │
│                               └──→ AxisStatusChangedEvent  ────┤   │
│                                                                 │   │
│  RaySource ──→ IRaySourceService ──→ StatusUpdatedEvent    ────┤   │
│                                  └──→ RaySourceStatusChangedEvent ─┤│
│                                  └──→ VariablesConnectedEvent  ────┤│
│                                                                 │   │
│  Detector ──→ IDetectorService ──→ StatusChangedEvent      ────┤   │
│                               └──→ ImageCapturedEvent      ────┤   │
│                                                                 │   │
│  Camera ──→ ICameraController ──→ ConnectionLost / Grab    ────┘   │
│                                   (via SyncCameraStateToAppState)   │
└─────────────────────────────────────────────────────────────────────┘
                    │ 全部已接通 ✅
                    ↓
┌─────────────────────────────────────────────────────────────────────┐
│                       AppStateService                               │
│                                                                     │
│  MotionState         ← 自动同步(轮询 + 事件驱动)        ✅          │
│  RaySourceState      ← 自动同步(StatusUpdatedEvent 等)  ✅          │
│  DetectorState       ← 自动同步(StatusChangedEvent    ✅          │
│  LatestDetectorFrame ← 自动缓存(ImageCapturedEvent    ✅          │
│  CameraState         ← 连接/采集状态同步(帧数据不经过)  ✅          │
└─────────────────────────────────────────────────────────────────────┘
                    │
                    ↓ PropertyChanged / StateChangedEvent
┌─────────────────────────────────────────────────────────────────────┐
│                         ViewModel 层                                │
│                                                                     │
│  MotionControlViewModel      ← 同时订阅 GeometryUpdatedEvent(双路) │
│  CncNodeViewModel            ← 通过 IAppStateService 读取快照        │
│  CncEditorViewModel          ← 通过 IAppStateService 读取快照        │
│  ViewportPanelViewModel      ← 订阅 DetectorStateChanged            │
│                                 订阅 DetectorDisconnectedEvent       │
│  CncExecutionService         ← 订阅 DetectorDisconnectedEvent        │
│  NavigationPropertyPanelViewModel ← 直连 ICameraController          │
│                                      + 推送 CameraState 到 AppState  │
└─────────────────────────────────────────────────────────────────────┘

六、待办事项

优先级 项目 涉及文件 状态
已完成 射线源事件订阅,打通 RaySourceState 自动同步 AppStateService.cs 已完成
已完成 探测器事件订阅,打通 DetectorState 自动同步 + LatestDetectorFrame 缓存 AppStateService.cs 已完成
已完成 探测器断连事件(DetectorDisconnectedEvent)下游链路 CncExecutionService.csViewportPanelViewModel.cs 已完成
已完成 手动模式下实时按钮切换回 LiveDetector 模式 MainViewportService.cs 已完成
确认 StartPolling() 在应用启动流程中被调用 App.xaml.cs / AppBootstrapper 已确认
评估是否将相机连接状态纳入 CameraState 统一管理 NavigationPropertyPanelViewModel.csAppStateService.cs 已完成