运动控制:新增射线源与探测器 Z 轴联动移动功能,支持同步位移控制,新增实体摇杆实现 PLC 摇杆状态监控和事件发布,IMotionSystem 接口新增 Joystick 属性,更新文档说明联动功能使用方法。
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
using Prism.Events;
|
||||
|
||||
namespace XP.Hardware.MotionControl.Abstractions.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// 实体摇杆激活状态变化事件 | Physical Joystick Active Status Changed Event
|
||||
/// </summary>
|
||||
public class JoystickActiveEvent : PubSubEvent<bool>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using XP.Hardware.MotionControl.Abstractions.Enums;
|
||||
|
||||
namespace XP.Hardware.MotionControl.Abstractions
|
||||
{
|
||||
/// <summary>
|
||||
/// 摇杆接口 | Joystick Interface
|
||||
/// 定义实体摇杆输入状态的读取能力 | Defines physical joystick input status reading capability
|
||||
/// </summary>
|
||||
public interface IJoystick
|
||||
{
|
||||
/// <summary>实体摇杆输入是否激活 | Whether physical joystick input is active</summary>
|
||||
bool IsJoystickActive { get; }
|
||||
|
||||
/// <summary>从 PLC 更新状态 | Update status from PLC</summary>
|
||||
void UpdateStatus();
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,9 @@ namespace XP.Hardware.MotionControl.Abstractions
|
||||
/// <summary>安全门实例 | Safety door instance</summary>
|
||||
ISafetyDoor SafetyDoor { get; }
|
||||
|
||||
/// <summary>摇杆实例 | Joystick instance</summary>
|
||||
IJoystick Joystick { get; }
|
||||
|
||||
/// <summary>轴复位实例 | Axis reset instance</summary>
|
||||
IAxisReset AxisReset { get; }
|
||||
|
||||
|
||||
@@ -0,0 +1,449 @@
|
||||
# AxisControlView PLC 通信机制 | AxisControlView PLC Communication
|
||||
|
||||
## 概述 | Overview
|
||||
|
||||
AxisControlView 是运动控制模块的核心用户控件,通过 AxisControlViewModel 与 PLC 进行双向通信。本文档详细说明摇杆 Jog 操作、轴位置输入框操作以及射线源/探测器Z轴联动状态下的 PLC 信号下发机制。
|
||||
|
||||
## 通信架构 | Communication Architecture
|
||||
|
||||
```
|
||||
AxisControlView (XAML)
|
||||
↓ (数据绑定)
|
||||
AxisControlViewModel (C#)
|
||||
↓ (调用)
|
||||
IMotionControlService
|
||||
↓ (调用)
|
||||
ISignalDataService
|
||||
↓ (写入队列)
|
||||
PLC (DB31, WriteCommon 组)
|
||||
```
|
||||
|
||||
## 1. 摇杆 Jog 操作 | Joystick Jog Operation
|
||||
|
||||
### 1.1 双轴摇杆(圆形)| Dual Axis Joystick (Circular)
|
||||
|
||||
**左键拖拽(默认模式)**:
|
||||
- X 轴输出 → StageX Jog(正向/反向)
|
||||
- Y 轴输出 → StageY Jog(正向/反向)
|
||||
|
||||
**右键拖拽(默认模式)**:
|
||||
- X 轴输出 → DetectorSwing Jog(正向/反向)
|
||||
- Y 轴输出 → StageRotation/FixtureRotation Jog(正向/反向)
|
||||
|
||||
**信号下发流程**:
|
||||
|
||||
```csharp
|
||||
// 1. 摇杆输出变化触发 HandleDualJoystickOutput()
|
||||
private void HandleDualJoystickOutput()
|
||||
{
|
||||
switch (DualJoystickActiveButton)
|
||||
{
|
||||
case MouseButtonType.Left:
|
||||
UpdateLinearJog(AxisId.StageX, DualJoystickOutputX);
|
||||
UpdateLinearJog(AxisId.StageY, DualJoystickOutputY);
|
||||
break;
|
||||
case MouseButtonType.Right:
|
||||
UpdateRotaryJog(RotaryAxisId.DetectorSwing, DualJoystickOutputX);
|
||||
var rotationAxisId = GetEnabledRotationAxisId();
|
||||
if (rotationAxisId.HasValue)
|
||||
UpdateRotaryJog(rotationAxisId.Value, DualJoystickOutputY);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. UpdateLinearJog/UpdateRotaryJog 处理 Jog 启动/速度更新/停止
|
||||
private void UpdateLinearJog(AxisId axisId, double output)
|
||||
{
|
||||
if (output != 0)
|
||||
{
|
||||
var speedPercent = Math.Abs(output) * 100;
|
||||
var positive = output > 0;
|
||||
|
||||
if (!_linearJogActive[axisId])
|
||||
{
|
||||
// 首次非零:设置速度并启动 Jog
|
||||
_motionControlService.SetJogSpeed(axisId, speedPercent);
|
||||
_motionControlService.JogStart(axisId, positive);
|
||||
_linearJogActive[axisId] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 已在 Jog:仅更新速度
|
||||
_motionControlService.SetJogSpeed(axisId, speedPercent);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 松开鼠标:停止 Jog
|
||||
if (_linearJogActive[axisId])
|
||||
{
|
||||
_motionControlService.JogStop(axisId);
|
||||
_linearJogActive[axisId] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. IMotionControlService 调用 ISignalDataService.EnqueueWrite 写入 PLC
|
||||
public MotionResult JogStart(AxisId axisId, bool positive)
|
||||
{
|
||||
var signalName = positive ? MotionSignalNames.SourceZ_JogPos : MotionSignalNames.SourceZ_JogNeg;
|
||||
var result = _signalService.EnqueueWrite(signalName, true);
|
||||
return result ? MotionResult.Ok() : MotionResult.Fail("Jog 启动写入失败");
|
||||
}
|
||||
```
|
||||
|
||||
**PLC 信号地址**:
|
||||
|
||||
| 轴 | 信号名 | 地址 | 说明 |
|
||||
|----|--------|------|------|
|
||||
| StageX | `MC_StageX_JogPos` | 42 | 正向 Jog(0:缺省, 1:点动) |
|
||||
| StageX | `MC_StageX_JogNeg` | 43 | 反向 Jog(0:缺省, 1:点动) |
|
||||
| StageY | `MC_StageY_JogPos` | 54 | 正向 Jog(0:缺省, 1:点动) |
|
||||
| StageY | `MC_StageY_JogNeg` | 55 | 反向 Jog(0:缺省, 1:点动) |
|
||||
| DetectorSwing | `MC_DetSwing_JogPos` | 66 | 正向 Jog(0:缺省, 1:点动) |
|
||||
| DetectorSwing | `MC_DetSwing_JogNeg` | 67 | 反向 Jog(0:缺省, 1:点动) |
|
||||
| StageRotation | `MC_StageRot_JogPos` | 78 | 正向 Jog(0:缺省, 1:点动) |
|
||||
| StageRotation | `MC_StageRot_JogNeg` | 79 | 反向 Jog(0:缺省, 1:点动) |
|
||||
| FixtureRotation | `MC_FixRot_JogPos` | 90 | 正向 Jog(0:缺省, 1:点动) |
|
||||
| FixtureRotation | `MC_FixRot_JogNeg` | 91 | 反向 Jog(0:缺省, 1:点动) |
|
||||
|
||||
### 1.2 单轴摇杆(腰圆)| Single Axis Joystick (Oval)
|
||||
|
||||
**左键拖拽(默认模式)**:
|
||||
- Y 轴输出 → SourceZ Jog(正向/反向)
|
||||
- 若 SZDZLock=true,同时 → DetectorZ Jog(正向/反向)
|
||||
|
||||
**右键拖拽(默认模式)**:
|
||||
- Y 轴输出 → DetectorZ Jog(正向/反向)
|
||||
- 若 SZDZLock=true,同时 → SourceZ Jog(正向/反向)
|
||||
|
||||
**信号下发流程**:
|
||||
|
||||
```csharp
|
||||
private void HandleSingleJoystickOutput()
|
||||
{
|
||||
switch (SingleJoystickActiveButton)
|
||||
{
|
||||
case MouseButtonType.Left:
|
||||
UpdateLinearJog(AxisId.SourceZ, SingleJoystickOutputY);
|
||||
if (_szdzLock)
|
||||
UpdateLinearJog(AxisId.DetectorZ, SingleJoystickOutputY);
|
||||
break;
|
||||
case MouseButtonType.Right:
|
||||
UpdateLinearJog(AxisId.DetectorZ, SingleJoystickOutputY);
|
||||
if (_szdzLock)
|
||||
UpdateLinearJog(AxisId.SourceZ, SingleJoystickOutputY);
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**PLC 信号地址**:
|
||||
|
||||
| 轴 | 信号名 | 地址 | 说明 |
|
||||
|----|--------|------|------|
|
||||
| SourceZ | `MC_SourceZ_JogPos` | 18 | 正向 Jog(0:缺省, 1:点动) |
|
||||
| SourceZ | `MC_SourceZ_JogNeg` | 19 | 反向 Jog(0:缺省, 1:点动) |
|
||||
| DetectorZ | `MC_DetZ_JogPos` | 30 | 正向 Jog(0:缺省, 1:点动) |
|
||||
| DetectorZ | `MC_DetZ_JogNeg` | 31 | 反向 Jog(0:缺省, 1:点动) |
|
||||
|
||||
### 1.3 Jog 速度控制 | Jog Speed Control
|
||||
|
||||
摇杆输出值(-1.0 ~ 1.0)映射为速度百分比(0% ~ 100%):
|
||||
|
||||
```csharp
|
||||
var speedPercent = Math.Abs(output) * 100; // 0% ~ 100%
|
||||
```
|
||||
|
||||
**速度计算流程**:
|
||||
|
||||
1. **MotionControlService.SetJogSpeed** 计算实际速度:
|
||||
```csharp
|
||||
var actualSpeed = _config.DefaultVelocity * speedPercent / 100.0;
|
||||
// 例如:DefaultVelocity=100, speedPercent=50 → actualSpeed=50 mm/s
|
||||
```
|
||||
|
||||
2. **PlcLinearAxis.SetJogSpeed** 将实际速度写入 PLC:
|
||||
```csharp
|
||||
_signalService.EnqueueWrite(_speedSignal, (float)speedPercent);
|
||||
// 注意:参数名 speedPercent 实际接收的是 actualSpeed(已计算的实际速度值)
|
||||
```
|
||||
|
||||
**PLC 信号地址**:
|
||||
|
||||
| 轴 | 信号名 | 地址 | 说明 |
|
||||
|----|--------|------|------|
|
||||
| SourceZ | `MC_SourceZ_Speed` | 14 | 速度值(mm/s,由 DefaultVelocity * speedPercent / 100 计算) |
|
||||
| DetectorZ | `MC_DetZ_Speed` | 26 | 速度值(mm/s,由 DefaultVelocity * speedPercent / 100 计算) |
|
||||
| StageX | `MC_StageX_Speed` | 38 | 速度值(mm/s,由 DefaultVelocity * speedPercent / 100 计算) |
|
||||
| StageY | `MC_StageY_Speed` | 50 | 速度值(mm/s,由 DefaultVelocity * speedPercent / 100 计算) |
|
||||
| DetectorSwing | `MC_DetSwing_Speed` | 62 | 速度值(度/s,由 DefaultVelocity * speedPercent / 100 计算) |
|
||||
| StageRotation | `MC_StageRot_Speed` | 74 | 速度值(度/s,由 DefaultVelocity * speedPercent / 100 计算) |
|
||||
| FixtureRotation | `MC_FixRot_Speed` | 86 | 速度值(度/s,由 DefaultVelocity * speedPercent / 100 计算) |
|
||||
|
||||
**示例**:
|
||||
- `DefaultVelocity = 100` mm/s
|
||||
- 摇杆输出 30% → PLC 接收速度 = 100 * 30% = 30 mm/s
|
||||
- 摇杆输出 100% → PLC 接收速度 = 100 * 100% = 100 mm/s
|
||||
|
||||
## 2. 轴位置输入框操作 | Axis Position Input Box Operation
|
||||
|
||||
### 2.1 编辑状态管理 | Editing State Management
|
||||
|
||||
- **GotFocus**:`SetEditing(propertyName, true)` → 冻结实时更新
|
||||
- **LostFocus/Escape**:`CancelEditing(propertyName)` → 恢复实时更新,显示实际值
|
||||
- **Enter**:`ConfirmPosition(propertyName)` → 发送目标位置移动命令
|
||||
|
||||
### 2.2 移动命令下发流程 | Move Command Flow
|
||||
|
||||
```csharp
|
||||
public void ConfirmPosition(string propertyName)
|
||||
{
|
||||
var value = GetPropertyValue(propertyName);
|
||||
SafeRun(() =>
|
||||
{
|
||||
MotionResult result;
|
||||
// 如果射线源与探测器Z轴联动,且操作的是SourceZ或DetectorZ,则同时发送两个轴
|
||||
if (SZDZLock && (propertyName == nameof(SourceZPosition) || propertyName == nameof(DetectorZPosition)))
|
||||
{
|
||||
result = SendSourceDetectorZMoveCommand(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = SendMoveCommand(propertyName, value);
|
||||
}
|
||||
if (result.Success)
|
||||
_logger.Info("目标位置已发送:{Property}={Value}", propertyName, value);
|
||||
else
|
||||
_logger.Warn("目标位置发送失败:{Property}={Value},原因={Reason}", propertyName, value, result.ErrorMessage);
|
||||
});
|
||||
_editingFlags[propertyName] = false;
|
||||
}
|
||||
|
||||
private MotionResult SendMoveCommand(string propertyName, double value)
|
||||
{
|
||||
switch (propertyName)
|
||||
{
|
||||
case nameof(StageXPosition):
|
||||
return _motionControlService.MoveToTarget(AxisId.StageX, value);
|
||||
case nameof(SourceZPosition):
|
||||
return _motionControlService.MoveToTarget(AxisId.SourceZ, value);
|
||||
// ... 其他轴
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 单轴移动 | Single Axis Move
|
||||
|
||||
```csharp
|
||||
public MotionResult MoveToTarget(AxisId axisId, double targetPosition, int? speed = null)
|
||||
{
|
||||
var axis = _motionSystem.GetLinearAxis(axisId);
|
||||
var actualSpeed = speed ?? _config.DefaultVelocity;
|
||||
|
||||
// 边界检查(使用配置中的 Min/Max)
|
||||
if (targetPosition < config.Min || targetPosition > config.Max)
|
||||
return MotionResult.Fail("目标位置超出范围");
|
||||
|
||||
// 写入目标位置和速度
|
||||
var targetResult = _signalService.EnqueueWrite(signalName, targetPosition);
|
||||
var speedResult = _signalService.EnqueueWrite(speedSignalName, actualSpeed);
|
||||
|
||||
// 启动移动(写入目标位置会自动触发移动)
|
||||
return targetResult && speedResult ? MotionResult.Ok() : MotionResult.Fail("移动命令写入失败");
|
||||
}
|
||||
```
|
||||
|
||||
**PLC 信号地址**:
|
||||
|
||||
| 轴 | 目标位置信号 | 地址 | 速度信号 | 地址 |
|
||||
|----|-------------|------|---------|------|
|
||||
| SourceZ | `MC_SourceZ_Target` | 10 | `MC_SourceZ_Speed` | 14 |
|
||||
| DetectorZ | `MC_DetZ_Target` | 22 | `MC_DetZ_Speed` | 26 |
|
||||
| StageX | `MC_StageX_Target` | 34 | `MC_StageX_Speed` | 38 |
|
||||
| StageY | `MC_StageY_Target` | 46 | `MC_StageY_Speed` | 50 |
|
||||
| DetectorSwing | `MC_DetSwing_Target` | 58 | `MC_DetSwing_Speed` | 62 |
|
||||
| StageRotation | `MC_StageRot_Target` | 70 | `MC_StageRot_Speed` | 74 |
|
||||
| FixtureRotation | `MC_FixRot_Target` | 82 | `MC_FixRot_Speed` | 86 |
|
||||
|
||||
### 2.4 步进移动 | Step Move
|
||||
|
||||
上下箭头键触发 `StepPosition(propertyName, delta)`:
|
||||
|
||||
```csharp
|
||||
public void StepPosition(string propertyName, double delta)
|
||||
{
|
||||
var currentValue = GetPropertyValue(propertyName);
|
||||
var newValue = currentValue + delta;
|
||||
SetPropertyValue(propertyName, newValue);
|
||||
|
||||
// 直接发送移动命令,不进入编辑冻结
|
||||
if (SZDZLock && (propertyName == nameof(SourceZPosition) || propertyName == nameof(DetectorZPosition)))
|
||||
{
|
||||
result = SendSourceDetectorZMoveCommand(newValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = SendMoveCommand(propertyName, newValue);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 射线源/探测器Z轴联动 | Source-Detector Z-axis Linkage
|
||||
|
||||
### 3.1 联动使能 | Linkage Enable
|
||||
|
||||
点击"SZDZLock"按钮切换联动状态:
|
||||
|
||||
```csharp
|
||||
private void ExecuteSZDZLock()
|
||||
{
|
||||
SZDZLock = !SZDZLock;
|
||||
var result = _motionControlService.SetSourceDetectorZLinkage(SZDZLock);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
// 设置失败,恢复状态
|
||||
SZDZLock = !SZDZLock;
|
||||
MessageBox.Show(result.ErrorMessage);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**PLC 信号**:
|
||||
|
||||
| 信号名 | 地址 | 类型 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| `MC_SourceDetZ_Linkage_Enable` | 101 | bool | 联动使能(true=启用, false=禁用) |
|
||||
|
||||
### 3.2 联动移动 | Linkage Move
|
||||
|
||||
当 SZDZLock=true 时,移动 SourceZ 或 DetectorZ 会同时移动两个轴,保持位移量相同:
|
||||
|
||||
```csharp
|
||||
private MotionResult SendSourceDetectorZMoveCommand(double targetValue)
|
||||
{
|
||||
// 1. 检查联动是否在配置中启用
|
||||
if (!_config.SourceDetectorZLinkage.Enabled)
|
||||
return MotionResult.Fail("射线源与探测器Z轴联动未启用");
|
||||
|
||||
// 2. 检查两个轴是否都在空闲状态
|
||||
var sourceZAxis = _motionSystem.GetLinearAxis(AxisId.SourceZ);
|
||||
var detectorZAxis = _motionSystem.GetLinearAxis(AxisId.DetectorZ);
|
||||
|
||||
if (sourceZAxis.Status == AxisStatus.Moving)
|
||||
return MotionResult.Fail("射线源Z轴正在运动中,无法联动移动");
|
||||
|
||||
if (detectorZAxis.Status == AxisStatus.Moving)
|
||||
return MotionResult.Fail("探测器Z轴正在运动中,无法联动移动");
|
||||
|
||||
// 3. 计算位移量和目标位置
|
||||
var currentSourceZ = sourceZAxis.ActualPosition;
|
||||
var currentDetectorZ = detectorZAxis.ActualPosition;
|
||||
var sourceDelta = targetValue - currentSourceZ;
|
||||
var targetDetectorZ = currentDetectorZ + sourceDelta;
|
||||
|
||||
// 4. 边界检查(使用配置中的 Min/Max)
|
||||
if (targetValue < sourceConfig.Min || targetValue > sourceConfig.Max)
|
||||
return MotionResult.Fail("射线源Z轴目标位置超出范围");
|
||||
|
||||
if (targetDetectorZ < detectorConfig.Min || targetDetectorZ > detectorConfig.Max)
|
||||
return MotionResult.Fail("探测器Z轴目标位置超出范围");
|
||||
|
||||
// 5. 同时发送两个轴的移动命令
|
||||
var sourceResult = sourceZAxis.MoveToTarget(targetValue, _config.DefaultVelocity);
|
||||
var detectorResult = detectorZAxis.MoveToTarget(targetDetectorZ, _config.DefaultVelocity);
|
||||
|
||||
return sourceResult.Success && detectorResult.Success
|
||||
? MotionResult.Ok()
|
||||
: (sourceResult.Success ? detectorResult : sourceResult);
|
||||
}
|
||||
```
|
||||
|
||||
**PLC 信号下发**:
|
||||
|
||||
1. **SourceZ 移动**:
|
||||
- `MC_SourceZ_Target` = targetValue(地址 10)
|
||||
- `MC_SourceZ_Speed` = DefaultVelocity(地址 14)
|
||||
|
||||
2. **DetectorZ 移动**:
|
||||
- `MC_DetZ_Target` = currentDetectorZ + (targetValue - currentSourceZ)(地址 22)
|
||||
- `MC_DetZ_Speed` = DefaultVelocity(地址 26)
|
||||
|
||||
### 3.3 联动 Jog | Linkage Jog
|
||||
|
||||
当 SZDZLock=true 时,单轴摇杆的左右键会同时控制两个轴:
|
||||
|
||||
```csharp
|
||||
private void HandleSingleJoystickOutput()
|
||||
{
|
||||
switch (SingleJoystickActiveButton)
|
||||
{
|
||||
case MouseButtonType.Left:
|
||||
UpdateLinearJog(AxisId.SourceZ, SingleJoystickOutputY);
|
||||
if (_szdzLock)
|
||||
UpdateLinearJog(AxisId.DetectorZ, SingleJoystickOutputY);
|
||||
break;
|
||||
case MouseButtonType.Right:
|
||||
UpdateLinearJog(AxisId.DetectorZ, SingleJoystickOutputY);
|
||||
if (_szdzLock)
|
||||
UpdateLinearJog(AxisId.SourceZ, SingleJoystickOutputY);
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**PLC 信号下发**:
|
||||
|
||||
- **SourceZ Jog**:
|
||||
- `MC_SourceZ_JogPos`/`MC_SourceZ_JogNeg` = true(地址 18/19)
|
||||
- `MC_SourceZ_Speed` = speedPercent(地址 14)
|
||||
|
||||
- **DetectorZ Jog**:
|
||||
- `MC_DetZ_JogPos`/`MC_DetZ_JogNeg` = true(地址 30/31)
|
||||
- `MC_DetZ_Speed` = speedPercent(地址 26)
|
||||
|
||||
## 4. 虚拟摇杆使能 | Virtual Joystick Enable
|
||||
|
||||
点击"使能开关"按钮切换虚拟摇杆使能状态:
|
||||
|
||||
```csharp
|
||||
private void ExecuteToggleEnable()
|
||||
{
|
||||
IsJoystickEnabled = !IsJoystickEnabled;
|
||||
_motionControlService.SetVirtualJoystickEnable(IsJoystickEnabled);
|
||||
}
|
||||
```
|
||||
|
||||
**PLC 信号**:
|
||||
|
||||
| 信号名 | 地址 | 类型 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| `MC_VirtualJoystick_Enable` | 111 | bool | 虚拟摇杆使能(true=启用, false=禁用) |
|
||||
|
||||
## 5. 信号写入队列机制 | Signal Write Queue Mechanism
|
||||
|
||||
所有 PLC 写入操作通过 `ISignalDataService.EnqueueWrite()` 进入写入队列:
|
||||
|
||||
```csharp
|
||||
public interface ISignalDataService
|
||||
{
|
||||
bool EnqueueWrite(string signalName, object value);
|
||||
bool EnqueueWriteBatch(Dictionary<string, object> writes);
|
||||
}
|
||||
```
|
||||
|
||||
**队列处理流程**:
|
||||
|
||||
1. ViewModel 调用 `EnqueueWrite(signalName, value)`
|
||||
2. 信号数据服务将写入请求加入队列
|
||||
3. PLC 服务在轮询周期(默认 100ms)中批量写入
|
||||
4. 写入成功后从队列移除
|
||||
|
||||
**优势**:
|
||||
|
||||
- 批量写入减少 PLC 通讯次数
|
||||
- 避免频繁写入导致的通讯拥塞
|
||||
- 统一的错误处理和重试机制
|
||||
|
||||
---
|
||||
@@ -21,7 +21,80 @@ protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
|
||||
|
||||
---
|
||||
|
||||
## 2. 获取轴状态和位置 | Reading Axis Status and Position
|
||||
## 2. 界面集成 | UI Integration
|
||||
|
||||
### AxisControlView 控件使用 | AxisControlView Usage
|
||||
|
||||
`AxisControlView` 是运动控制模块的核心用户控件,提供完整的轴控制和调试功能。
|
||||
|
||||
**XAML 引用 | XAML Reference**
|
||||
|
||||
```xml
|
||||
<UserControl x:Class="YourView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:motion="clr-namespace:XP.Hardware.MotionControl.Views;assembly=XP.Hardware.MotionControl">
|
||||
|
||||
<Grid>
|
||||
<!-- 基本使用 | Basic usage -->
|
||||
<motion:AxisControlView />
|
||||
|
||||
<!-- 设置最小宽度 | Set minimum width -->
|
||||
<motion:AxisControlView MinWidth="400" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
```
|
||||
|
||||
**自动 ViewModel 绑定 | Auto ViewModel Binding**
|
||||
|
||||
控件使用 Prism 的 `ViewModelLocator.AutoWireViewModel="True"` 自动绑定 `AxisControlViewModel`,无需手动设置 `DataContext`。
|
||||
|
||||
**ViewModel 属性说明 | ViewModel Properties**
|
||||
|
||||
| 属性 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `StageXPosition` | double | 载物台 X 轴位置(mm),范围由配置决定 |
|
||||
| `StageYPosition` | double | 载物台 Y 轴位置(mm),范围由配置决定 |
|
||||
| `SourceZPosition` | double | 射线源 Z 轴位置(mm),范围由配置决定 |
|
||||
| `DetectorZPosition` | double | 探测器 Z 轴位置(mm),范围由配置决定 |
|
||||
| `DetectorSwingAngle` | double | 探测器摆动角度(度),范围 -45~45 |
|
||||
| `StageRotationAngle` | double | 载物台旋转角度(度),范围 -360~360 |
|
||||
| `FixtureRotationAngle` | double | 夹具旋转角度(度),范围 -90~90 |
|
||||
| `IsJoystickEnabled` | bool | 摇杆使能开关(默认 false) |
|
||||
| `SwapMouseButtons` | bool | 摇杆左右键功能切换(默认 false) |
|
||||
| `SZDZLock` | bool | 射线源/探测器Z轴锁定移动(默认 false) |
|
||||
| `HasSavedPositions` | bool | 是否有已保存的位置快照 |
|
||||
|
||||
**交互方式 | Interaction Modes**
|
||||
|
||||
1. **手动输入位置**
|
||||
- 直接编辑 RadNumericUpDown 控件
|
||||
- Enter 键确认并移动轴
|
||||
- Escape 键取消修改,恢复原值
|
||||
|
||||
2. **单轴摇杆(SourceZ/DetectorZ)**
|
||||
- 左键拖拽:正向 Jog
|
||||
- 右键拖拽:反向 Jog
|
||||
- 松开鼠标:停止 Jog
|
||||
|
||||
3. **双轴摇杆(StageX/Y + Rotation)**
|
||||
- 默认模式(SwapMouseButtons=false):
|
||||
- 左键拖拽:控制 StageX/Y(X轴=左右,Y轴=上下)
|
||||
- 右键拖拽:控制 StageRotation(X轴=旋转)
|
||||
- 切换模式(SwapMouseButtons=true):
|
||||
- 左键拖拽:控制 StageRotation(X轴=旋转)
|
||||
- 右键拖拽:控制 DetectorSwing(X轴=摆动,Y轴=摆动)
|
||||
|
||||
4. **快捷按钮**
|
||||
- **使能开关**:启用/禁用摇杆控制(图标切换)
|
||||
- **摇杆模式**:切换左右键功能(图标切换)
|
||||
- **SZDZ锁定**:锁定 SourceZ/DetectorZ 同步移动(图标切换)
|
||||
- **保存位置**:保存当前所有轴位置到快照
|
||||
- **恢复位置**:恢复到上次保存的位置快照
|
||||
|
||||
---
|
||||
|
||||
## 3. 获取轴状态和位置 | Reading Axis Status and Position
|
||||
|
||||
通过 DI 注入 `IMotionSystem`:
|
||||
|
||||
@@ -154,7 +227,7 @@ public class YourController
|
||||
|
||||
---
|
||||
|
||||
## 4. 几何计算 | Geometry Calculation
|
||||
## 5. 几何计算 | Geometry Calculation
|
||||
|
||||
```csharp
|
||||
public class YourGeometryUser
|
||||
@@ -207,7 +280,7 @@ public class YourGeometryUser
|
||||
|
||||
---
|
||||
|
||||
## 5. 事件订阅 | Event Subscription
|
||||
## 6. 事件订阅 | Event Subscription
|
||||
|
||||
通过 Prism `IEventAggregator` 被动接收状态变化:
|
||||
|
||||
@@ -260,35 +333,86 @@ public class YourMonitor
|
||||
|
||||
---
|
||||
|
||||
## 6. PLC 信号定义 | PLC Signal Definitions
|
||||
## 7. PLC 信号定义 | PLC Signal Definitions
|
||||
|
||||
信号名称硬编码在 `MotionSignalNames.cs` 中,信号地址定义在 `PlcAddrDfn.xml`。
|
||||
信号名称硬编码在 `MotionSignalNames.cs` 中,信号地址定义在 `PlcAddrDfn.xml`(位于 `XplorePlane/bin/Debug/net8.0-windows/win-x64/`)。
|
||||
|
||||
每个轴包含以下信号(以 SourceZ 为例):
|
||||
### 7.1 写入信号(WriteCommon,DB31)
|
||||
|
||||
| 信号名 | 方向 | 类型 | 说明 |
|
||||
| 信号名 | 类型 | 地址 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| `MC_SourceZ_Pos` | Read | single | 实际位置 |
|
||||
| `MC_SourceZ_Target` | Write | single | 目标位置 |
|
||||
| `MC_SourceZ_Speed` | Write | single | 运动速度 |
|
||||
| `MC_SourceZ_JogPos` | Write | byte | 正向 Jog |
|
||||
| `MC_SourceZ_JogNeg` | Write | byte | 反向 Jog |
|
||||
| `MC_SourceZ_Home` | Write | byte | 回零 |
|
||||
| `MC_SourceZ_Stop` | Write | byte | 停止 |
|
||||
| `MC_SourceZ_Target` | single | 10 | 射线源Z目标位置(mm) |
|
||||
| `MC_SourceZ_Speed` | single | 14 | 射线源Z运动速度 |
|
||||
| `MC_SourceZ_JogPos` | byte | 18 | 射线源Z正向Jog |
|
||||
| `MC_SourceZ_JogNeg` | byte | 19 | 射线源Z反向Jog |
|
||||
| `MC_SourceZ_Home` | byte | 20 | 射线源Z回零 |
|
||||
| `MC_SourceZ_Stop` | byte | 21 | 射线源Z停止 |
|
||||
| `MC_DetZ_Target` | single | 22 | 探测器Z目标位置(mm) |
|
||||
| `MC_DetZ_Speed` | single | 26 | 探测器Z运动速度 |
|
||||
| `MC_DetZ_JogPos` | byte | 30 | 探测器Z正向Jog |
|
||||
| `MC_DetZ_JogNeg` | byte | 31 | 探测器Z反向Jog |
|
||||
| `MC_DetZ_Home` | byte | 32 | 探测器Z回零 |
|
||||
| `MC_DetZ_Stop` | byte | 33 | 探测器Z停止 |
|
||||
| `MC_StageX_Target` | single | 34 | 载物台X目标位置(mm) |
|
||||
| `MC_StageX_Speed` | single | 38 | 载物台X运动速度 |
|
||||
| `MC_StageX_JogPos` | byte | 42 | 载物台X正向Jog |
|
||||
| `MC_StageX_JogNeg` | byte | 43 | 载物台X反向Jog |
|
||||
| `MC_StageX_Home` | byte | 44 | 载物台X回零 |
|
||||
| `MC_StageX_Stop` | byte | 45 | 载物台X停止 |
|
||||
| `MC_StageY_Target` | single | 46 | 载物台Y目标位置(mm) |
|
||||
| `MC_StageY_Speed` | single | 50 | 载物台Y运动速度 |
|
||||
| `MC_StageY_JogPos` | byte | 54 | 载物台Y正向Jog |
|
||||
| `MC_StageY_JogNeg` | byte | 55 | 载物台Y反向Jog |
|
||||
| `MC_StageY_Home` | byte | 56 | 载物台Y回零 |
|
||||
| `MC_StageY_Stop` | byte | 57 | 载物台Y停止 |
|
||||
| `MC_DetSwing_Target` | single | 58 | 探测器摆动目标角度(度) |
|
||||
| `MC_DetSwing_Speed` | single | 62 | 探测器摆动运动速度 |
|
||||
| `MC_DetSwing_JogPos` | byte | 66 | 探测器摆动正向Jog |
|
||||
| `MC_DetSwing_JogNeg` | byte | 67 | 探测器摆动反向Jog |
|
||||
| `MC_DetSwing_Home` | byte | 68 | 探测器摆动回零 |
|
||||
| `MC_DetSwing_Stop` | byte | 69 | 探测器摆动停止 |
|
||||
| `MC_StageRot_Target` | single | 70 | 载物台旋转目标角度(度) |
|
||||
| `MC_StageRot_Speed` | single | 74 | 载物台旋转运动速度 |
|
||||
| `MC_StageRot_JogPos` | byte | 78 | 载物台旋转正向Jog |
|
||||
| `MC_StageRot_JogNeg` | byte | 79 | 载物台旋转反向Jog |
|
||||
| `MC_StageRot_Home` | byte | 80 | 载物台旋转回零 |
|
||||
| `MC_StageRot_Stop` | byte | 81 | 载物台旋转停止 |
|
||||
| `MC_FixRot_Target` | single | 82 | 夹具旋转目标角度(度) |
|
||||
| `MC_FixRot_Speed` | single | 86 | 夹具旋转运动速度 |
|
||||
| `MC_FixRot_JogPos` | byte | 90 | 夹具旋转正向Jog |
|
||||
| `MC_FixRot_JogNeg` | byte | 91 | 夹具旋转反向Jog |
|
||||
| `MC_FixRot_Home` | byte | 92 | 夹具旋转回零 |
|
||||
| `MC_FixRot_Stop` | byte | 93 | 夹具旋转停止 |
|
||||
| `MC_Door_Open` | byte | 94 | 安全门开门 |
|
||||
| `MC_Door_Close` | byte | 95 | 安全门关门 |
|
||||
| `MC_Door_Stop` | byte | 96 | 安全门停止 |
|
||||
| `MC_SourceDetZ_Linkage_Enable` | bool | 101 | 射线源与探测器Z轴联动使能 |
|
||||
| `MC_VirtualJoystick_Enable` | bool | 111 | 虚拟摇杆使能 |
|
||||
|
||||
安全门信号:
|
||||
### 7.2 读取信号(ReadCommon,DB31)
|
||||
|
||||
| 信号名 | 方向 | 说明 |
|
||||
|--------|------|------|
|
||||
| `MC_Door_Open` | Write | 开门 |
|
||||
| `MC_Door_Close` | Write | 关门 |
|
||||
| `MC_Door_Stop` | Write | 停门 |
|
||||
| `MC_Door_Status` | Read | 门状态(int: 0-6) |
|
||||
| `MC_Door_Interlock` | Read | 联锁信号 |
|
||||
| 信号名 | 类型 | 地址 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| `MC_SourceZ_Pos` | single | 100 | 射线源Z实际位置(mm) |
|
||||
| `MC_DetZ_Pos` | single | 104 | 探测器Z实际位置(mm) |
|
||||
| `MC_StageX_Pos` | single | 108 | 载物台X实际位置(mm) |
|
||||
| `MC_StageY_Pos` | single | 112 | 载物台Y实际位置(mm) |
|
||||
| `MC_DetSwing_Angle` | single | 116 | 探测器摆动实际角度(度) |
|
||||
| `MC_StageRot_Angle` | single | 120 | 载物台旋转实际角度(度) |
|
||||
| `MC_FixRot_Angle` | single | 124 | 夹具旋转实际角度(度) |
|
||||
| `MC_Door_Status` | byte | 128 | 安全门状态(0:未知,1:开门中,2:已开,3:关门中,4:已关,5:已锁定,6:故障) |
|
||||
| `MC_Door_Interlock` | byte | 130 | 安全门联锁信号(0:无联锁,10:联锁有效) |
|
||||
| `MC_Joystick_Active` | bool | 110 | 实体摇杆输入激活 |
|
||||
|
||||
### 7.3 信号类型说明 | Signal Type Notes
|
||||
|
||||
- **single**:32位浮点数(4字节)
|
||||
- **byte**:8位无符号整数(1字节)
|
||||
- **bool**:布尔值(1字节,0=false, 非0=true)
|
||||
|
||||
---
|
||||
|
||||
## 7. 安全机制 | Safety Mechanisms
|
||||
## 8. 安全机制 | Safety Mechanisms
|
||||
|
||||
| 机制 | 说明 |
|
||||
|------|------|
|
||||
@@ -303,7 +427,7 @@ public class YourMonitor
|
||||
|
||||
---
|
||||
|
||||
## 8. 轮询机制 | Polling Mechanism
|
||||
## 9. 轮询机制 | Polling Mechanism
|
||||
|
||||
`MotionControlService` 使用 `System.Threading.Timer` 以配置的 `PollingInterval`(默认 100ms)周期执行:
|
||||
|
||||
@@ -319,4 +443,4 @@ public class YourMonitor
|
||||
|
||||
---
|
||||
|
||||
**最后更新 | Last Updated**: 2026-04-14
|
||||
**最后更新 | Last Updated**: 2026-05-08
|
||||
|
||||
@@ -14,7 +14,7 @@ XP.Hardware.MotionControl 是 XplorePlane 平面CT工业检测系统的核心运
|
||||
- 安全防护门控制(联锁检查、状态机)
|
||||
- FOD/FDD/放大倍率几何正算与反算
|
||||
- 多轴联动移动(原子性边界检查)
|
||||
- Jog 点动调试(MouseDown/MouseUp 安全控制)
|
||||
- Jog 点动调试(虚拟摇杆控制)
|
||||
- 100ms 周期 PLC 状态轮询
|
||||
- 基于 Prism EventAggregator 的跨模块事件通讯
|
||||
- 可配置的轴启用/禁用(FixtureRotation 等可选轴)
|
||||
@@ -23,6 +23,56 @@ XP.Hardware.MotionControl 是 XplorePlane 平面CT工业检测系统的核心运
|
||||
|
||||
---
|
||||
|
||||
## 界面组件 | UI Components
|
||||
|
||||
### AxisControlView(核心界面控件)
|
||||
|
||||
`AxisControlView.xaml` 是运动控制模块的核心用户控件,提供完整的轴控制和调试功能:
|
||||
|
||||
```
|
||||
XP.Hardware.MotionControl/Views/
|
||||
├── AxisControlView.xaml # 核心轴控制面板(350px+ UserControl)
|
||||
└── AxisControlView.xaml.cs
|
||||
```
|
||||
|
||||
**功能组成 | Features**
|
||||
|
||||
| 区域 | 组件 | 功能说明 |
|
||||
|------|------|----------|
|
||||
| **标题栏** | TextBlock | 显示"运动控制"标题 |
|
||||
| **左侧** | RadNumericUpDown | 7个轴位置输入框(StageX/Y/SourceZ/DetectorZ/DetectorSwing/StageRotation/FixtureRotation) |
|
||||
| **右侧** | VirtualJoystick | 单轴摇杆(Z轴)+ 双轴摇杆(XY+旋转) |
|
||||
| **底部** | RadButton | 使能开关、摇杆模式切换、SZDZ锁定、保存/恢复位置 |
|
||||
|
||||
**ViewModel 绑定 | ViewModel Bindings**
|
||||
|
||||
| 属性 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `StageXPosition` / `StageYPosition` | double | 载物台 X/Y 轴位置(mm) |
|
||||
| `SourceZPosition` / `DetectorZPosition` | double | 射线源/探测器 Z 轴位置(mm) |
|
||||
| `DetectorSwingAngle` | double | 探测器摆动角度(度) |
|
||||
| `StageRotationAngle` | double | 载物台旋转角度(度) |
|
||||
| `FixtureRotationAngle` | double | 夹具旋转角度(度) |
|
||||
| `IsJoystickEnabled` | bool | 摇杆使能开关 |
|
||||
| `SwapMouseButtons` | bool | 摇杆左右键功能切换 |
|
||||
| `SZDZLock` | bool | 射线源/探测器Z轴锁定移动 |
|
||||
| `SavedPositions` | SavedPositions | 已保存的位置快照 |
|
||||
|
||||
**交互方式 | Interaction Modes**
|
||||
|
||||
1. **手动输入**:直接编辑 RadNumericUpDown,Enter 确认,Escape 取消
|
||||
2. **单轴摇杆**:拖拽单轴摇杆控制 SourceZ/DetectorZ 轴 Jog
|
||||
3. **双轴摇杆**:
|
||||
- 左键拖拽:控制 StageX/Y 轴(X轴=左右,Y轴=上下)
|
||||
- 右键拖拽:控制 StageRotation/DetectorSwing 轴(X轴=旋转,Y轴=摆动)
|
||||
4. **快捷按钮**:
|
||||
- 使能开关:启用/禁用摇杆控制
|
||||
- 摇杆模式:切换左右键功能(左键XY/右键旋转 vs 左键旋转/右键摆动)
|
||||
- SZDZ锁定:锁定 SourceZ/DetectorZ 同步移动
|
||||
- 保存/恢复:保存当前所有轴位置或恢复到上次保存位置
|
||||
|
||||
---
|
||||
|
||||
## 框架架构 | Architecture
|
||||
|
||||
```
|
||||
@@ -56,17 +106,18 @@ XP.Hardware.MotionControl/
|
||||
│ ├── IMotionControlService.cs # 业务服务接口
|
||||
│ ├── MotionControlService.cs # 服务实现(轮询、事件、日志)
|
||||
│ └── GeometryCalculator.cs # 几何计算器
|
||||
├── ViewModels/ # 视图模型
|
||||
│ ├── AxisControlViewModel.cs # AxisControlView ViewModel
|
||||
│ └── MotionControlViewModel.cs # MotionControlView ViewModel
|
||||
├── Config/ # 配置层
|
||||
│ ├── MotionControlConfig.cs # 配置实体
|
||||
│ ├── ConfigLoader.cs # 配置加载器
|
||||
│ └── MotionSignalNames.cs # PLC 信号名称常量
|
||||
├── ViewModels/ # 视图模型
|
||||
│ ├── MotionControlViewModel.cs # 操作面板 ViewModel
|
||||
│ └── MotionDebugViewModel.cs # 调试窗口 ViewModel
|
||||
├── Views/ # WPF 视图
|
||||
│ ├── MotionControlView.xaml # 操作面板(350px UserControl)
|
||||
│ ├── MotionDebugWindow.xaml # Jog 调试窗口
|
||||
│ └── MotionControlView.xaml.cs
|
||||
│ ├── AxisControlView.xaml # 核心轴控制面板(350px+)
|
||||
│ ├── AxisControlView.xaml.cs
|
||||
│ ├── MotionControlView.xaml # 简化版操作面板
|
||||
│ └── MotionDebugWindow.xaml # Jog 调试窗口
|
||||
├── Module/
|
||||
│ └── MotionControlModule.cs # Prism 模块注册
|
||||
├── Resources/ # 多语言资源
|
||||
@@ -123,17 +174,22 @@ XP.Hardware.MotionControl/
|
||||
| `MotionControl:DetectorZ:Min/Max/Origin` | 0/600/0 | 探测器Z轴范围和原点(mm) |
|
||||
| `MotionControl:StageX:Min/Max/Origin` | -150/150/0 | 载物台X轴范围和原点(mm) |
|
||||
| `MotionControl:StageY:Min/Max/Origin` | -150/150/0 | 载物台Y轴范围和原点(mm) |
|
||||
| `MotionControl:DetectorSwing:Min/Max/Enabled` | -45/45/true | 探测器摆动范围和启用 |
|
||||
| `MotionControl:StageRotation:Min/Max/Enabled` | -360/360/true | 载物台旋转范围和启用 |
|
||||
| `MotionControl:FixtureRotation:Min/Max/Enabled` | -90/90/false | 夹具旋转范围和启用 |
|
||||
| `MotionControl:DetectorSwing:Min/Max/Origin/Enabled` | -45/45/0/true | 探测器摆动范围、原点和启用 |
|
||||
| `MotionControl:StageRotation:Min/Max/Origin/Enabled` | -360/360/0/true | 载物台旋转范围、原点和启用 |
|
||||
| `MotionControl:FixtureRotation:Min/Max/Origin/Enabled` | -90/90/0/false | 夹具旋转范围、原点和启用 |
|
||||
| `MotionControl:Geometry:SourceZOrigin` | 0 | 射线源Z原点偏移(mm) |
|
||||
| `MotionControl:Geometry:DetectorZOrigin` | 600 | 探测器Z原点偏移(mm) |
|
||||
| `MotionControl:Geometry:StageRotationCenterZ` | 300 | 旋转中心Z坐标(mm) |
|
||||
| `MotionControl:Geometry:StageRotationCenterZ` | 0 | 旋转中心Z坐标(mm,固定值) |
|
||||
| `MotionControl:Geometry:SwingPivotOffset` | 0 | 探测器摆动旋转中心Z偏移(mm) |
|
||||
| `MotionControl:Geometry:SwingRadius` | 0 | 探测器摆动半径(mm) |
|
||||
| `MotionControl:PollingInterval` | 100 | 轮询周期(ms) |
|
||||
| `MotionControl:DefaultVelocity` | 100 | 默认速度 |
|
||||
| `MotionControl:SourceDetectorZLinkage:Enabled` | false | 射线源与探测器Z轴联动使能 |
|
||||
| `MotionControl:SourceDetectorZLinkage:TriggerThreshold` | 1.0 | 联动触发的位置变化阈值(mm) |
|
||||
| `MotionControl:SourceDetectorZLinkage:SpeedPercent` | 100 | 联动移动速度百分比(0-100) |
|
||||
|
||||
PLC 信号名称硬编码在 `MotionSignalNames.cs` 中,信号定义在 `PlcAddrDfn.xml` 的 ReadCommon/WriteCommon 组。
|
||||
|
||||
---
|
||||
|
||||
**最后更新 | Last Updated**: 2026-04-14
|
||||
**最后更新 | Last Updated**: 2026-05-08
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using XP.Hardware.MotionControl.Abstractions;
|
||||
using XP.Hardware.MotionControl.Config;
|
||||
using XP.Hardware.Plc.Abstractions;
|
||||
|
||||
namespace XP.Hardware.MotionControl.Implementations
|
||||
{
|
||||
/// <summary>
|
||||
/// 基于 PLC 的实体摇杆实现 | PLC-based Physical Joystick Implementation
|
||||
/// 信号名称硬编码在 MotionSignalNames 中 | Signal names hardcoded in MotionSignalNames
|
||||
/// </summary>
|
||||
public class PlcJoystick : IJoystick
|
||||
{
|
||||
private readonly ISignalDataService _signalService;
|
||||
private bool _isJoystickActive;
|
||||
|
||||
public PlcJoystick(ISignalDataService signalService)
|
||||
{
|
||||
_signalService = signalService ?? throw new ArgumentNullException(nameof(signalService));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsJoystickActive => _isJoystickActive;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void UpdateStatus()
|
||||
{
|
||||
// 读取实体摇杆输入激活信号 | Read physical joystick input active signal
|
||||
_isJoystickActive = _signalService.GetValueByName<byte>(MotionSignalNames.Joystick_Active) == 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ namespace XP.Hardware.MotionControl.Implementations
|
||||
private readonly Dictionary<AxisId, ILinearAxis> _linearAxes = new();
|
||||
private readonly Dictionary<RotaryAxisId, IRotaryAxis> _rotaryAxes = new();
|
||||
private readonly ISafetyDoor _safetyDoor;
|
||||
private readonly IJoystick _joystick;
|
||||
private readonly IAxisReset _axisReset;
|
||||
|
||||
public PlcMotionSystem(MotionControlConfig config, ISignalDataService signalService)
|
||||
@@ -70,6 +71,9 @@ namespace XP.Hardware.MotionControl.Implementations
|
||||
// 创建安全门 | Create safety door
|
||||
_safetyDoor = new PlcSafetyDoor(signalService);
|
||||
|
||||
// 创建摇杆 | Create joystick
|
||||
_joystick = new PlcJoystick(signalService);
|
||||
|
||||
// 创建轴复位 | Create axis reset
|
||||
_axisReset = new PlcAxisReset(signalService);
|
||||
}
|
||||
@@ -77,6 +81,8 @@ namespace XP.Hardware.MotionControl.Implementations
|
||||
/// <inheritdoc/>
|
||||
public ISafetyDoor SafetyDoor => _safetyDoor;
|
||||
/// <inheritdoc/>
|
||||
public IJoystick Joystick => _joystick;
|
||||
/// <inheritdoc/>
|
||||
public IAxisReset AxisReset => _axisReset;
|
||||
/// <inheritdoc/>
|
||||
public IReadOnlyDictionary<AxisId, ILinearAxis> LinearAxes => _linearAxes;
|
||||
@@ -103,6 +109,7 @@ namespace XP.Hardware.MotionControl.Implementations
|
||||
foreach (var axis in _linearAxes.Values) axis.UpdateStatus();
|
||||
foreach (var axis in _rotaryAxes.Values) axis.UpdateStatus();
|
||||
_safetyDoor.UpdateStatus();
|
||||
_joystick.UpdateStatus();
|
||||
_axisReset.UpdateStatus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,6 +131,9 @@ namespace XP.Hardware.MotionControl.Services
|
||||
var oldDoorStatus = _motionSystem.SafetyDoor.Status;
|
||||
var oldInterlocked = _motionSystem.SafetyDoor.IsInterlocked;
|
||||
|
||||
// 缓存旧的摇杆状态 | Cache old joystick status
|
||||
var oldJoystickActive = _motionSystem.Joystick.IsJoystickActive;
|
||||
|
||||
// 更新所有轴和门状态 | Update all axis and door status
|
||||
_motionSystem.UpdateAllStatus();
|
||||
|
||||
@@ -185,6 +188,15 @@ namespace XP.Hardware.MotionControl.Services
|
||||
_logger.Info("门联锁状态变化:{IsInterlocked} | Door interlock status changed: {IsInterlocked}",
|
||||
_motionSystem.SafetyDoor.IsInterlocked);
|
||||
}
|
||||
|
||||
// 检测实体摇杆状态变化 | Detect physical joystick status changes
|
||||
if (oldJoystickActive != _motionSystem.Joystick.IsJoystickActive)
|
||||
{
|
||||
_eventAggregator.GetEvent<JoystickActiveEvent>()
|
||||
.Publish(_motionSystem.Joystick.IsJoystickActive);
|
||||
_logger.Info("实体摇杆状态变化:{IsJoystickActive} | Physical joystick status changed: {IsJoystickActive}",
|
||||
_motionSystem.Joystick.IsJoystickActive);
|
||||
}
|
||||
// 轮询成功,重置错误计数 | Poll succeeded, reset error count
|
||||
_pollErrorCount = 0;
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@ namespace XP.Hardware.MotionControl.ViewModels
|
||||
// 订阅事件 | Subscribe to events
|
||||
_eventAggregator.GetEvent<GeometryUpdatedEvent>().Subscribe(OnGeometryUpdated, ThreadOption.UIThread);
|
||||
_eventAggregator.GetEvent<AxisStatusChangedEvent>().Subscribe(OnAxisStatusChanged, ThreadOption.UIThread);
|
||||
_eventAggregator.GetEvent<JoystickActiveEvent>().Subscribe(OnJoystickActiveChanged, ThreadOption.UIThread);
|
||||
|
||||
// 初始化时主动刷新一次轴位置 | Refresh axis positions on initialization
|
||||
try
|
||||
@@ -265,6 +266,10 @@ namespace XP.Hardware.MotionControl.ViewModels
|
||||
/// <summary>PLC 连接状态 | PLC connection status</summary>
|
||||
public bool IsPlcConnected { get => _isPlcConnected; set => SetProperty(ref _isPlcConnected, value); }
|
||||
|
||||
private bool _isJoystickActive;
|
||||
/// <summary>实体摇杆是否激活 | Whether physical joystick is active</summary>
|
||||
public bool IsJoystickActive { get => _isJoystickActive; set => SetProperty(ref _isJoystickActive, value); }
|
||||
|
||||
private string _errorMessage;
|
||||
/// <summary>错误提示信息 | Error message</summary>
|
||||
public string ErrorMessage { get => _errorMessage; set => SetProperty(ref _errorMessage, value); }
|
||||
@@ -302,6 +307,16 @@ namespace XP.Hardware.MotionControl.ViewModels
|
||||
|
||||
#region 事件回调 | Event Callbacks
|
||||
|
||||
/// <summary>
|
||||
/// 实体摇杆激活状态变化回调 | Physical joystick active status changed callback
|
||||
/// </summary>
|
||||
/// <param name="isActive">实体摇杆是否激活 | Whether physical joystick is active</param>
|
||||
private void OnJoystickActiveChanged(bool isActive)
|
||||
{
|
||||
IsJoystickActive = isActive;
|
||||
_logger.Debug("实体摇杆状态更新:{IsActive} | Physical joystick status updated: {IsActive}", isActive);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 几何参数更新回调,刷新轴实际位置 | Geometry updated callback, refresh axis actual positions
|
||||
/// </summary>
|
||||
@@ -566,6 +581,20 @@ namespace XP.Hardware.MotionControl.ViewModels
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 同步联动状态到 PLC(供视图加载时调用)| Sync linkage state to PLC (called when view loads)
|
||||
/// </summary>
|
||||
/// <returns>操作结果 | Operation result</returns>
|
||||
public MotionResult SetSZDZLinkageToPlc()
|
||||
{
|
||||
return _motionControlService.SetSourceDetectorZLinkage(SZDZLock);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取日志服务实例 | Get logger service instance
|
||||
/// </summary>
|
||||
public ILoggerService Logger => _logger;
|
||||
#endregion
|
||||
|
||||
#region 摇杆 Jog 映射逻辑 | Joystick Jog Mapping Logic
|
||||
@@ -798,7 +827,16 @@ namespace XP.Hardware.MotionControl.ViewModels
|
||||
var value = GetPropertyValue(propertyName);
|
||||
SafeRun(() =>
|
||||
{
|
||||
var result = SendMoveCommand(propertyName, value);
|
||||
MotionResult result;
|
||||
// 如果射线源与探测器Z轴联动,且操作的是SourceZ或DetectorZ,则同时发送两个轴
|
||||
if (SZDZLock && (propertyName == nameof(SourceZPosition) || propertyName == nameof(DetectorZPosition)))
|
||||
{
|
||||
result = SendSourceDetectorZMoveCommand(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = SendMoveCommand(propertyName, value);
|
||||
}
|
||||
if (result.Success)
|
||||
_logger.Info("目标位置已发送:{Property}={Value} | Target position sent: {Property}={Value}", propertyName, value);
|
||||
else
|
||||
@@ -831,7 +869,16 @@ namespace XP.Hardware.MotionControl.ViewModels
|
||||
SetPropertyValue(propertyName, newValue);
|
||||
SafeRun(() =>
|
||||
{
|
||||
var result = SendMoveCommand(propertyName, newValue);
|
||||
MotionResult result;
|
||||
// 如果射线源与探测器Z轴联动,且操作的是SourceZ或DetectorZ,则同时步进两个轴
|
||||
if (SZDZLock && (propertyName == nameof(SourceZPosition) || propertyName == nameof(DetectorZPosition)))
|
||||
{
|
||||
result = SendSourceDetectorZMoveCommand(newValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = SendMoveCommand(propertyName, newValue);
|
||||
}
|
||||
if (!result.Success)
|
||||
_logger.Warn("步进移动失败:{Property},原因={Reason} | Step move failed: {Property}, reason={Reason}", propertyName, result.ErrorMessage);
|
||||
});
|
||||
@@ -866,6 +913,96 @@ namespace XP.Hardware.MotionControl.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送射线源与探测器Z轴联动移动命令 | Send Source-Detector Z-axis linkage move command
|
||||
/// 当SZDZLock=true时,保持两个轴的位移量相同
|
||||
/// When SZDZLock=true, keeps the same displacement for both axes
|
||||
/// </summary>
|
||||
/// <param name="targetValue">目标位置 | Target position</param>
|
||||
/// <returns>操作结果 | Operation result</returns>
|
||||
private MotionResult SendSourceDetectorZMoveCommand(double targetValue)
|
||||
{
|
||||
// 1. 检查联动是否在配置中启用 | Check if linkage is enabled in config
|
||||
if (!_config.SourceDetectorZLinkage.Enabled)
|
||||
{
|
||||
_logger.Warn("射线源与探测器Z轴联动未在配置中启用 | Source-Detector Z-axis linkage not enabled in config");
|
||||
return MotionResult.Fail("射线源与探测器Z轴联动未启用 | Source-Detector Z-axis linkage not enabled");
|
||||
}
|
||||
|
||||
// 2. 获取两个轴并检查是否都在空闲状态 | Get both axes and check if both are idle
|
||||
var sourceZAxis = _motionSystem.GetLinearAxis(AxisId.SourceZ);
|
||||
var detectorZAxis = _motionSystem.GetLinearAxis(AxisId.DetectorZ);
|
||||
|
||||
if (sourceZAxis.Status == AxisStatus.Moving)
|
||||
{
|
||||
_logger.Warn("射线源Z轴正在运动中,拒绝联动移动命令 | Source Z-axis is moving, linkage move command rejected");
|
||||
return MotionResult.Fail("射线源Z轴正在运动中,无法联动移动 | Source Z-axis is moving, linkage move rejected");
|
||||
}
|
||||
|
||||
if (detectorZAxis.Status == AxisStatus.Moving)
|
||||
{
|
||||
_logger.Warn("探测器Z轴正在运动中,拒绝联动移动命令 | Detector Z-axis is moving, linkage move command rejected");
|
||||
return MotionResult.Fail("探测器Z轴正在运动中,无法联动移动 | Detector Z-axis is moving, linkage move rejected");
|
||||
}
|
||||
|
||||
// 3. 计算位移量和目标位置 | Calculate displacement and target positions
|
||||
var currentSourceZ = sourceZAxis.ActualPosition;
|
||||
var currentDetectorZ = detectorZAxis.ActualPosition;
|
||||
var sourceDelta = targetValue - currentSourceZ;
|
||||
var targetDetectorZ = currentDetectorZ + sourceDelta;
|
||||
|
||||
// 4. 边界检查(使用配置中的 Min/Max)| Boundary check (use Min/Max from config)
|
||||
if (_config.LinearAxes.TryGetValue(AxisId.SourceZ, out var sourceConfig) &&
|
||||
_config.LinearAxes.TryGetValue(AxisId.DetectorZ, out var detectorConfig))
|
||||
{
|
||||
var errors = new List<string>();
|
||||
|
||||
if (targetValue < sourceConfig.Min || targetValue > sourceConfig.Max)
|
||||
{
|
||||
errors.Add($"射线源Z轴目标位置 {targetValue} 超出范围 [{sourceConfig.Min}, {sourceConfig.Max}] | " +
|
||||
$"Source Z-axis target {targetValue} out of range [{sourceConfig.Min}, {sourceConfig.Max}]");
|
||||
}
|
||||
|
||||
if (targetDetectorZ < detectorConfig.Min || targetDetectorZ > detectorConfig.Max)
|
||||
{
|
||||
errors.Add($"探测器Z轴目标位置 {targetDetectorZ} 超出范围 [{detectorConfig.Min}, {detectorConfig.Max}] | " +
|
||||
$"Detector Z-axis target {targetDetectorZ} out of range [{detectorConfig.Min}, {detectorConfig.Max}]");
|
||||
}
|
||||
|
||||
if (errors.Count > 0)
|
||||
{
|
||||
var allErrors = string.Join("; ", errors);
|
||||
_logger.Warn("联动移动边界检查失败:{Errors} | Linkage move boundary check failed: {Errors}", allErrors);
|
||||
return MotionResult.Fail(allErrors);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(null, "未找到射线源Z或探测器Z轴配置 | Source Z or Detector Z axis config not found");
|
||||
return MotionResult.Fail("轴配置缺失 | Axis config missing");
|
||||
}
|
||||
|
||||
// 5. 所有检查通过,同时发送两个轴的移动命令 | All checks passed, send move commands to both axes simultaneously
|
||||
var sourceResult = sourceZAxis.MoveToTarget(targetValue, _config.DefaultVelocity);
|
||||
var detectorResult = detectorZAxis.MoveToTarget(targetDetectorZ, _config.DefaultVelocity);
|
||||
|
||||
// 6. 返回结果(任一失败都记录日志)| Return result (log if any fails)
|
||||
if (!sourceResult.Success)
|
||||
{
|
||||
_logger.Warn("射线源Z轴移动失败:{Reason} | Source Z-axis move failed: {Reason}", sourceResult.ErrorMessage);
|
||||
return sourceResult;
|
||||
}
|
||||
|
||||
if (!detectorResult.Success)
|
||||
{
|
||||
_logger.Warn("探测器Z轴移动失败:{Reason} | Detector Z-axis move failed: {Reason}", detectorResult.ErrorMessage);
|
||||
return detectorResult;
|
||||
}
|
||||
|
||||
_logger.Info("射线源与探测器Z轴联动移动成功 | Source-Detector Z-axis linkage move successful");
|
||||
return sourceResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据属性名称获取当前绑定值 | Get current bound value by property name
|
||||
/// </summary>
|
||||
|
||||
@@ -18,6 +18,9 @@ namespace XP.Hardware.MotionControl.Views
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// 监听视图 Loaded 事件,在视图加载时同步联动状态到 PLC | Listen to view Loaded event, sync linkage state to PLC on view load
|
||||
Loaded += AxisControlView_Loaded;
|
||||
|
||||
// 监听摇杆只读依赖属性变化,推送到 ViewModel | Listen to joystick read-only DP changes and push to ViewModel
|
||||
var dualOutputXDesc = DependencyPropertyDescriptor.FromProperty(VirtualJoystick.OutputXProperty, typeof(VirtualJoystick));
|
||||
var dualOutputYDesc = DependencyPropertyDescriptor.FromProperty(VirtualJoystick.OutputYProperty, typeof(VirtualJoystick));
|
||||
@@ -230,5 +233,25 @@ namespace XP.Hardware.MotionControl.Views
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 视图加载事件 | View Loaded Event
|
||||
|
||||
private void AxisControlView_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 视图加载时同步联动状态到 PLC | Sync linkage state to PLC when view is loaded
|
||||
var viewModel = ViewModel;
|
||||
if (viewModel != null && viewModel.SZDZLock)
|
||||
{
|
||||
// 调用服务设置联动 | Call service to set linkage
|
||||
var result = viewModel.SetSZDZLinkageToPlc();
|
||||
if (!result.Success)
|
||||
{
|
||||
// 记录日志但不阻塞加载 | Log but don't block loading
|
||||
viewModel.Logger.Warn("视图加载时同步联动状态失败:{Reason} | Failed to sync linkage state on view load: {Reason}", result.ErrorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user