修复探测器的订阅与获取
This commit is contained in:
@@ -169,3 +169,258 @@ raySourceService?.Dispose();
|
||||
```
|
||||
|
||||
确保硬件连接在应用退出时正确断开。
|
||||
|
||||
### 7.
|
||||
|
||||
---
|
||||
|
||||
# 硬件层 → AppState → UI 状态同步机制
|
||||
|
||||
## 整体结论
|
||||
|
||||
| 硬件子系统 | 同步方式 | AppState 订阅 | 状态是否自动同步 |
|
||||
|-----------|---------|--------------|----------------|
|
||||
| 运动控制(Motion) | 事件驱动 + 轮询 | ✅ 已订阅 `GeometryUpdatedEvent` / `AxisStatusChangedEvent` | ✅ 自动同步 |
|
||||
| 射线源(RaySource) | 事件定义存在 | ❌ 未订阅任何事件 | ⚠️ 需手动推送 |
|
||||
| 探测器(Detector) | 事件定义存在 | ❌ 未订阅任何事件 | ⚠️ 需手动推送 |
|
||||
| 相机(Camera) | ViewModel 直连 | ❌ 不经过 AppState | ⚠️ `CameraState` 为空壳 |
|
||||
|
||||
---
|
||||
|
||||
## 一、运动控制 — 完整链路
|
||||
|
||||
### 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 未连接时的保护**:
|
||||
```csharp
|
||||
// MotionControlService.OnPollingTick()
|
||||
if (!_plcService.IsConnected) return; // 直接跳过,不报错
|
||||
```
|
||||
|
||||
**连续错误降频**:
|
||||
```csharp
|
||||
if (_pollErrorCount > 3)
|
||||
{
|
||||
if (++_pollErrorCount % 50 != 0) return; // 每 50 次才尝试一次,防止日志刷屏
|
||||
}
|
||||
```
|
||||
|
||||
**AppStateService 初始化时的首次刷新**:
|
||||
```csharp
|
||||
// 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()` 的特殊行为**:
|
||||
```csharp
|
||||
public void UpdateMotionState(MotionState newState)
|
||||
{
|
||||
// 优先从硬件层拉取最新快照,忽略外部传入值
|
||||
if (TryRefreshMotionStateFromHardware("UpdateMotionState"))
|
||||
return;
|
||||
|
||||
// 硬件不可用时才使用传入值(降级路径)
|
||||
SetMotionState(newState);
|
||||
}
|
||||
```
|
||||
硬件连接后,外部调用 `UpdateMotionState()` 实际上会被硬件快照覆盖,硬件层始终是 `MotionState` 的唯一真实来源。
|
||||
|
||||
### 1.3 事件清单
|
||||
|
||||
| 事件 | 发布方 | 订阅方 | 触发时机 |
|
||||
|------|--------|--------|---------|
|
||||
| `GeometryUpdatedEvent` | `MotionControlService` | `AppStateService`、`MotionControlViewModel`、`AxisControlViewModel` | 每次轮询 tick |
|
||||
| `AxisStatusChangedEvent` | `MotionControlService` | `AppStateService`、`MotionControlViewModel`、`AxisControlViewModel` | 轴状态发生变化时 |
|
||||
| `MotionErrorEvent` | `MotionControlService` | — | 轴状态变为 Error / Alarm |
|
||||
| `DoorStatusChangedEvent` | `MotionControlService` | `MotionControlViewModel` | 安全门状态变化 |
|
||||
| `DoorInterlockChangedEvent` | `MotionControlService` | — | 联锁状态变化 |
|
||||
| `GeometryApplyRequestEvent` | DebugWindow | `MotionControlViewModel` | 调试窗口发起几何反算请求 |
|
||||
|
||||
---
|
||||
|
||||
## 二、射线源 — 断链(待补全)
|
||||
|
||||
### 2.1 现状
|
||||
|
||||
`AppStateService` 持有 `IRaySourceService` 引用,但**没有订阅任何射线源事件**。`RaySourceState` 只能通过外部显式调用 `UpdateRaySourceState()` 手动推送,硬件状态变化不会自动反映到 AppState。
|
||||
|
||||
```
|
||||
XP.Hardware.RaySource 发布的事件(均无人在 AppState 层订阅):
|
||||
├─ StatusUpdatedEvent ← 实时电压 / 电流 / 功率数据
|
||||
├─ RaySourceStatusChangedEvent ← 三态状态(On / Off / Fault)
|
||||
├─ VariablesConnectedEvent ← PLC 变量连接状态
|
||||
└─ OperationResultEvent ← 操作执行结果
|
||||
```
|
||||
|
||||
### 2.2 当前 `RaySourceState` 的写入路径
|
||||
|
||||
目前只有两处会更新 `RaySourceState`:
|
||||
|
||||
1. `CncProgramService.CreateReferencePointNode()` — 创建参考点时读取一次快照
|
||||
2. 任何直接调用 `appStateService.UpdateRaySourceState()` 的地方(目前代码中无此调用)
|
||||
|
||||
### 2.3 修复方案
|
||||
|
||||
在 `AppStateService` 构造时订阅 `StatusUpdatedEvent`,将实时数据映射到 `RaySourceState`:
|
||||
|
||||
```csharp
|
||||
// AppStateService 构造函数中补充
|
||||
_raySourceStatusToken = _eventAggregator
|
||||
.GetEvent<StatusUpdatedEvent>()
|
||||
.Subscribe(OnRaySourceStatusUpdated);
|
||||
|
||||
// 处理方法
|
||||
private void OnRaySourceStatusUpdated(SystemStatusData data)
|
||||
{
|
||||
if (_disposed) return;
|
||||
UpdateRaySourceState(new RaySourceState(
|
||||
IsOn: data.IsOn,
|
||||
Voltage: data.Voltage,
|
||||
Power: data.Power));
|
||||
}
|
||||
```
|
||||
|
||||
同时在 `Dispose()` 中取消订阅,并在 `AppStateService` 的字段和 `Dispose` 方法中补充对应的 `SubscriptionToken`。
|
||||
|
||||
---
|
||||
|
||||
## 三、探测器 — 断链(待补全)
|
||||
|
||||
### 3.1 现状
|
||||
|
||||
与射线源情况相同,`AppStateService` 没有订阅任何探测器事件,`DetectorState` 是静态默认值。
|
||||
|
||||
```
|
||||
XP.Hardware.Detector 发布的事件(均无人在 AppState 层订阅):
|
||||
├─ StatusChangedEvent ← 探测器连接 / 采集状态变化
|
||||
├─ ImageCapturedEvent ← 图像采集完成
|
||||
├─ CorrectionCompletedEvent ← 校正完成
|
||||
└─ ErrorOccurredEvent ← 错误发生
|
||||
```
|
||||
|
||||
### 3.2 修复方案
|
||||
|
||||
在 `AppStateService` 构造时订阅 `StatusChangedEvent`:
|
||||
|
||||
```csharp
|
||||
_detectorStatusToken = _eventAggregator
|
||||
.GetEvent<XP.Hardware.Detector.Abstractions.Events.StatusChangedEvent>()
|
||||
.Subscribe(OnDetectorStatusChanged);
|
||||
|
||||
private void OnDetectorStatusChanged(DetectorStatus status)
|
||||
{
|
||||
if (_disposed) return;
|
||||
UpdateDetectorState(new DetectorState(
|
||||
IsConnected: status.IsConnected,
|
||||
IsAcquiring: status.IsAcquiring,
|
||||
FrameRate: status.FrameRate,
|
||||
Resolution: status.Resolution));
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、相机 — 独立直连,不经过 AppState
|
||||
|
||||
### 4.1 现状
|
||||
|
||||
相机完全绕过 `AppStateService`,由 `NavigationPropertyPanelViewModel` 直接持有 `ICamera` 引用并订阅硬件事件:
|
||||
|
||||
```
|
||||
NavigationPropertyPanelViewModel
|
||||
└─ 直接持有 ICamera 引用
|
||||
├─ _camera.ImageGrabbed += OnCameraImageGrabbed
|
||||
├─ _camera.GrabError += OnCameraGrabError
|
||||
└─ _camera.ConnectionLost += OnCameraConnectionLost
|
||||
└─ IsCameraConnected 属性直接驱动 UI 命令可用性
|
||||
```
|
||||
|
||||
`AppStateService.CameraState` 字段目前是空壳,没有任何地方向它写入真实数据。
|
||||
|
||||
### 4.2 架构说明
|
||||
|
||||
相机的独立直连是有意为之的设计——相机图像流数据量大、帧率高,不适合经过 AppState 的不可变 record 替换机制。当前图像通过 `ManualImageLoadedEvent` 在模块间传递,与状态管理解耦。
|
||||
|
||||
如需将相机连接状态纳入 AppState 统一管理(例如供其他模块查询),可在 `OnCameraConnectionLost` / `ConnectCamera` 中补充调用 `_appStateService.UpdateCameraState()`,仅同步连接状态,不同步图像帧。
|
||||
|
||||
---
|
||||
|
||||
## 五、状态同步全景图
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ 硬件层 │
|
||||
│ │
|
||||
│ PLC ──→ MotionControlService ──→ GeometryUpdatedEvent ─────┐ │
|
||||
│ └──→ AxisStatusChangedEvent ────┤ │
|
||||
│ │ │
|
||||
│ RaySource ──→ IRaySourceService ──→ StatusUpdatedEvent ─ ─ ┤ │
|
||||
│ └──→ RaySourceStatusChangedEvent ─┤│
|
||||
│ │ │
|
||||
│ Detector ──→ IDetectorService ──→ StatusChangedEvent ─ ─ ┤ │
|
||||
│ │ │
|
||||
│ Camera ──→ ICamera ──→ ImageGrabbed / ConnectionLost ─ ─ ┘ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
│ 已接通 ✅ │ 断链 ⚠️
|
||||
↓ ↓
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ AppStateService │
|
||||
│ │
|
||||
│ MotionState ← 自动同步(轮询 + 事件驱动) ✅ │
|
||||
│ RaySourceState ← 需手动推送 / 待补订阅 ⚠️ │
|
||||
│ DetectorState ← 需手动推送 / 待补订阅 ⚠️ │
|
||||
│ CameraState ← 空壳,相机走独立直连路径 ⚠️ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
↓ PropertyChanged / StateChangedEvent
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ ViewModel 层 │
|
||||
│ │
|
||||
│ MotionControlViewModel ← 同时订阅 GeometryUpdatedEvent(双路) │
|
||||
│ CncNodeViewModel ← 通过 IAppStateService 读取快照 │
|
||||
│ CncEditorViewModel ← 通过 IAppStateService 读取快照 │
|
||||
│ NavigationPropertyPanelViewModel ← 直连 ICamera(独立路径) │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、待办事项
|
||||
|
||||
| 优先级 | 项目 | 涉及文件 |
|
||||
|--------|------|---------|
|
||||
| 高 | 补充射线源事件订阅,打通 `RaySourceState` 自动同步 | `AppStateService.cs` |
|
||||
| 高 | 补充探测器事件订阅,打通 `DetectorState` 自动同步 | `AppStateService.cs` |
|
||||
| 中 | 确认 `StartPolling()` 在应用启动流程中被调用 | `App.xaml.cs` / `AppBootstrapper` |
|
||||
| 低 | 评估是否将相机连接状态纳入 `CameraState` 统一管理 | `NavigationPropertyPanelViewModel.cs`、`AppStateService.cs` |
|
||||
|
||||
Reference in New Issue
Block a user