修复运动控制问题:ExecuteToggleEnable 在 PLC 写入失败或异常时回滚 IsJoystickEnabled 状态;OnPlcConnectionChanged 中改为 new List<>(dict.Keys) 遍历副本,避免潜在的集合修改问题;SendSourceDetectorZMoveCommand 新增 sourcePropertyName 参数,根据用户编辑的是 SourceZ 还是 DetectorZ 来决定 delta 计算方向;SavePositionsCommand 增加 () => IsPlcConnected 条件,PLC 未连接时禁止保存。

补充射线源和PLC类库所需配置和信号地址定义文件。
This commit is contained in:
QI Mingxuan
2026-05-11 10:53:45 +08:00
parent f4a2856b92
commit e9596b013a
11 changed files with 9656 additions and 467 deletions
@@ -1,449 +0,0 @@
# 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 | 正向 Jog0:缺省, 1:点动) |
| StageX | `MC_StageX_JogNeg` | 43 | 反向 Jog0:缺省, 1:点动) |
| StageY | `MC_StageY_JogPos` | 54 | 正向 Jog0:缺省, 1:点动) |
| StageY | `MC_StageY_JogNeg` | 55 | 反向 Jog0:缺省, 1:点动) |
| DetectorSwing | `MC_DetSwing_JogPos` | 66 | 正向 Jog0:缺省, 1:点动) |
| DetectorSwing | `MC_DetSwing_JogNeg` | 67 | 反向 Jog0:缺省, 1:点动) |
| StageRotation | `MC_StageRot_JogPos` | 78 | 正向 Jog0:缺省, 1:点动) |
| StageRotation | `MC_StageRot_JogNeg` | 79 | 反向 Jog0:缺省, 1:点动) |
| FixtureRotation | `MC_FixRot_JogPos` | 90 | 正向 Jog0:缺省, 1:点动) |
| FixtureRotation | `MC_FixRot_JogNeg` | 91 | 反向 Jog0:缺省, 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 | 正向 Jog0:缺省, 1:点动) |
| SourceZ | `MC_SourceZ_JogNeg` | 19 | 反向 Jog0:缺省, 1:点动) |
| DetectorZ | `MC_DetZ_JogPos` | 30 | 正向 Jog0:缺省, 1:点动) |
| DetectorZ | `MC_DetZ_JogNeg` | 31 | 反向 Jog0:缺省, 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 通讯次数
- 避免频繁写入导致的通讯拥塞
- 统一的错误处理和重试机制
---
@@ -0,0 +1,803 @@
# AxisControlView PLC 通信机制 | AxisControlView PLC Communication
## 概述 | Overview
AxisControlView 是运动控制模块的核心用户控件,通过 AxisControlViewModel 与 PLC 进行双向通信。本文档详细说明摇杆 Jog 操作、轴位置输入框操作、射线源/探测器Z轴联动、安全机制以及辅助功能的 PLC 信号下发机制。
## 通信架构 | Communication Architecture
```
AxisControlView (XAML)
↓ (数据绑定)
AxisControlViewModel (C#)
↓ (调用)
IMotionControlService
↓ (调用)
IMotionSystem → PlcLinearAxis / PlcRotaryAxis
↓ (调用)
ISignalDataService.EnqueueWrite()
↓ (写入队列)
PLC (DB31, WriteCommon 组)
```
**关键分层说明**
- `AxisControlViewModel` 调用 `IMotionControlService` 的业务方法
- `IMotionControlService` 委托给 `IMotionSystem` 获取轴实例(`PlcLinearAxis` / `PlcRotaryAxis`
- 轴实例内部通过 `ISignalDataService.EnqueueWrite()` 写入 PLC 信号
- ViewModel 不直接操作信号,所有信号写入由轴实现层完成
## 1. 摇杆 Jog 操作 | Joystick Jog Operation
### 1.1 双轴摇杆(圆形)| Dual Axis Joystick (Circular)
**左键拖拽(默认模式)**
- X 轴输出 → StageX Jog(正向/反向)
- Y 轴输出 → StageY Jog(正向/反向)
**右键拖拽(默认模式)**
- X 轴输出 → DetectorSwing Jog(正向/反向)
- Y 轴输出 → StageRotation/FixtureRotation Jog(正向/反向)
> 注:当 `SwapMouseButtons=true` 时,左右键功能互换。
**信号下发流程**
```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 处理 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. MotionControlService.JogStart 委托给轴实现
public MotionResult JogStart(AxisId axisId, bool positive)
{
var axis = _motionSystem.GetLinearAxis(axisId);
// Homing 状态检查
if (axis.Status == AxisStatus.Homing)
return MotionResult.Fail("轴正在回零,拒绝 Jog 命令");
return axis.JogStart(positive);
}
// 4. PlcLinearAxis.JogStart 写入 PLC 信号
public override MotionResult JogStart(bool positive)
{
if (Status == AxisStatus.Homing)
return MotionResult.Fail("轴正在回零,拒绝 Jog 命令");
var signal = positive ? _jogPosSignal : _jogNegSignal;
_signalService.EnqueueWrite(signal, true);
return MotionResult.Ok();
}
```
**旋转轴 Jog 流程类似**`UpdateRotaryJog``SetJogRotarySpeed``JogRotaryStart`),额外包含禁用轴检查。
**PLC 信号地址(直线轴 Jog**
| 轴 | 信号名 | 地址 | 说明 |
|----|--------|------|------|
| SourceZ | `MC_SourceZ_JogPos` | 18 | 正向 Jog0:缺省, 1:点动) |
| SourceZ | `MC_SourceZ_JogNeg` | 19 | 反向 Jog0:缺省, 1:点动) |
| DetectorZ | `MC_DetZ_JogPos` | 30 | 正向 Jog0:缺省, 1:点动) |
| DetectorZ | `MC_DetZ_JogNeg` | 31 | 反向 Jog0:缺省, 1:点动) |
| StageX | `MC_StageX_JogPos` | 42 | 正向 Jog0:缺省, 1:点动) |
| StageX | `MC_StageX_JogNeg` | 43 | 反向 Jog0:缺省, 1:点动) |
| StageY | `MC_StageY_JogPos` | 54 | 正向 Jog0:缺省, 1:点动) |
| StageY | `MC_StageY_JogNeg` | 55 | 反向 Jog0:缺省, 1:点动) |
**PLC 信号地址(旋转轴 Jog**
| 轴 | 信号名 | 地址 | 说明 |
|----|--------|------|------|
| DetectorSwing | `MC_DetSwing_JogPos` | 66 | 正向 Jog0:缺省, 1:点动) |
| DetectorSwing | `MC_DetSwing_JogNeg` | 67 | 反向 Jog0:缺省, 1:点动) |
| StageRotation | `MC_StageRot_JogPos` | 78 | 正向 Jog0:缺省, 1:点动) |
| StageRotation | `MC_StageRot_JogNeg` | 79 | 反向 Jog0:缺省, 1:点动) |
| FixtureRotation | `MC_FixRot_JogPos` | 90 | 正向 Jog0:缺省, 1:点动) |
| FixtureRotation | `MC_FixRot_JogNeg` | 91 | 反向 Jog0:缺省, 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;
}
}
```
### 1.3 按键释放自动停止 | Button Release Auto-Stop
当摇杆按键从非 None 变为 None 时,自动停止所有关联轴的 Jog:
```csharp
// DualJoystickActiveButton setter 中
if (value == MouseButtonType.None && oldValue != MouseButtonType.None)
StopDualJoystickAxes();
// StopDualJoystickAxes 停止所有双轴摇杆关联轴
private void StopDualJoystickAxes()
{
UpdateLinearJog(AxisId.StageX, 0);
UpdateLinearJog(AxisId.StageY, 0);
UpdateRotaryJog(RotaryAxisId.DetectorSwing, 0);
var rotationAxisId = GetEnabledRotationAxisId();
if (rotationAxisId.HasValue)
UpdateRotaryJog(rotationAxisId.Value, 0);
}
```
单轴摇杆同理(`StopSingleJoystickAxes` 停止 SourceZ 和 DetectorZ)。
### 1.4 Jog 速度控制 | Jog Speed Control
摇杆输出值(-1.0 ~ 1.0)映射为速度百分比(0% ~ 100%):
```csharp
var speedPercent = Math.Abs(output) * 100; // 0% ~ 100%
```
**速度计算流程**
1. **MotionControlService.SetJogSpeed** 计算实际速度:
```csharp
public MotionResult SetJogSpeed(AxisId axisId, double speedPercent)
{
var axis = _motionSystem.GetLinearAxis(axisId);
var actualSpeed = _config.DefaultVelocity * speedPercent / 100.0;
return axis.SetJogSpeed(actualSpeed);
}
```
2. **PlcLinearAxis.SetJogSpeed** 将实际速度写入 PLC
```csharp
public override MotionResult SetJogSpeed(double speedPercent)
{
// 注意:参数名为 speedPercent,但实际接收的是已计算的速度值(mm/s 或 度/s)
_signalService.EnqueueWrite(_speedSignal, (float)speedPercent);
return MotionResult.Ok();
}
```
**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 / 100 = 30 mm/s
- 摇杆输出 100% → PLC 接收速度 = 100 × 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(StageYPosition):
return _motionControlService.MoveToTarget(AxisId.StageY, value);
case nameof(SourceZPosition):
return _motionControlService.MoveToTarget(AxisId.SourceZ, value);
case nameof(DetectorZPosition):
return _motionControlService.MoveToTarget(AxisId.DetectorZ, value);
case nameof(DetectorSwingAngle):
return _motionControlService.MoveRotaryToTarget(RotaryAxisId.DetectorSwing, value);
case nameof(StageRotationAngle):
return _motionControlService.MoveRotaryToTarget(RotaryAxisId.StageRotation, value);
case nameof(FixtureRotationAngle):
return _motionControlService.MoveRotaryToTarget(RotaryAxisId.FixtureRotation, value);
default:
return MotionResult.Fail($"未知的属性名称:{propertyName}");
}
}
```
### 2.3 单轴移动 | Single Axis Move
```csharp
// MotionControlService.MoveToTarget — 业务层
public MotionResult MoveToTarget(AxisId axisId, double target, double? speed = null)
{
var axis = _motionSystem.GetLinearAxis(axisId);
// 运动中防重入检查
if (axis.Status == AxisStatus.Moving)
return MotionResult.Fail("轴正在运动中,拒绝重复命令");
// 委托给轴实现(轴内部包含边界检查)
return axis.MoveToTarget(target, speed ?? _config.DefaultVelocity);
}
// PlcLinearAxis.MoveToTarget — 轴实现层
public override MotionResult MoveToTarget(double target, double? speed = null)
{
if (!ValidateTarget(target))
return MotionResult.Fail($"目标位置 {target} 超出范围 [{_min}, {_max}]");
if (Status == AxisStatus.Moving)
return MotionResult.Fail("轴正在运动中,拒绝重复命令");
if (speed.HasValue)
_signalService.EnqueueWrite(_speedSignal, (float)speed.Value);
_signalService.EnqueueWrite(_writeSignal, (float)target);
Status = AxisStatus.Moving;
return MotionResult.Ok();
}
```
**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);
SafeRun(() =>
{
MotionResult result;
if (SZDZLock && (propertyName == nameof(SourceZPosition) || propertyName == nameof(DetectorZPosition)))
{
result = SendSourceDetectorZMoveCommand(newValue);
}
else
{
result = SendMoveCommand(propertyName, newValue);
}
if (!result.Success)
_logger.Warn("步进移动失败:{Property},原因={Reason}", propertyName, result.ErrorMessage);
});
}
```
## 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, ...);
}
}
// MotionControlService.SetSourceDetectorZLinkage
public MotionResult SetSourceDetectorZLinkage(bool enabled)
{
var config = _config.SourceDetectorZLinkage;
if (!config.Enabled)
return MotionResult.Fail("射线源与探测器Z轴联动未启用");
var result = _signalService.EnqueueWrite(MotionSignalNames.SourceDetZ_Linkage_Enable, (bool)enabled);
return result ? MotionResult.Ok() : MotionResult.Fail("联动使能写入失败");
}
```
另外提供公开方法 `SetSZDZLinkageToPlc()` 供外部直接同步当前联动状态到 PLC:
```csharp
public MotionResult SetSZDZLinkageToPlc()
{
return _motionControlService.SetSourceDetectorZLinkage(SZDZLock);
}
```
**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 (_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轴目标位置超出范围");
if (targetDetectorZ < detectorConfig.Min || targetDetectorZ > detectorConfig.Max)
errors.Add("探测器Z轴目标位置超出范围");
if (errors.Count > 0)
return MotionResult.Fail(string.Join("; ", errors));
}
// 5. 同时发送两个轴的移动命令(通过轴实现层写入 PLC)
var sourceResult = sourceZAxis.MoveToTarget(targetValue, _config.DefaultVelocity);
var detectorResult = detectorZAxis.MoveToTarget(targetDetectorZ, _config.DefaultVelocity);
if (!sourceResult.Success) return sourceResult;
if (!detectorResult.Success) return detectorResult;
return MotionResult.Ok();
}
```
**PLC 信号下发**
1. **SourceZ 移动**
- `MC_SourceZ_Speed` = DefaultVelocity(地址 14
- `MC_SourceZ_Target` = targetValue(地址 10
2. **DetectorZ 移动**
- `MC_DetZ_Speed` = DefaultVelocity(地址 26
- `MC_DetZ_Target` = currentDetectorZ + (targetValue - currentSourceZ)(地址 22
### 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_Speed` = actualSpeed(地址 14
- `MC_SourceZ_JogPos`/`MC_SourceZ_JogNeg` = true(地址 18/19
- **DetectorZ Jog**
- `MC_DetZ_Speed` = actualSpeed(地址 26
- `MC_DetZ_JogPos`/`MC_DetZ_JogNeg` = true(地址 30/31
## 4. 虚拟摇杆使能 | Virtual Joystick Enable
点击"使能开关"按钮切换虚拟摇杆使能状态:
```csharp
private void ExecuteToggleEnable()
{
IsJoystickEnabled = !IsJoystickEnabled;
var result = _motionControlService.SetVirtualJoystickEnable(IsJoystickEnabled);
if (!result.Success)
{
MessageBox.Show(result.ErrorMessage, ...);
}
}
// MotionControlService.SetVirtualJoystickEnable
public MotionResult SetVirtualJoystickEnable(bool enabled)
{
var result = _signalService.EnqueueWrite(MotionSignalNames.VirtualJoystick_Enable, (bool)enabled);
return result ? MotionResult.Ok() : MotionResult.Fail("虚拟摇杆使能写入失败");
}
```
**PLC 信号**
| 信号名 | 地址 | 类型 | 说明 |
|--------|------|------|------|
| `MC_VirtualJoystick_Enable` | 111 | bool | 虚拟摇杆使能(true=启用, false=禁用) |
## 5. 鼠标按键交换 | Mouse Button Swap
点击"交换按键"按钮切换摇杆左右键功能映射:
```csharp
private void ExecuteToggleSwapMouseButtons()
{
SwapMouseButtons = !SwapMouseButtons;
}
```
此功能为纯 UI 层逻辑,不涉及 PLC 信号写入。当 `SwapMouseButtons=true` 时,摇杆控件内部交换左右键的功能映射。
## 6. 保存/恢复轴位置 | Save/Restore Axis Positions
### 6.1 保存当前位置 | Save Current Positions
```csharp
private void ExecuteSavePositions()
{
_savedPositions = new SavedPositions
{
StageX = StageXPosition,
StageY = StageYPosition,
SourceZ = SourceZPosition,
DetectorZ = DetectorZPosition,
DetectorSwing = DetectorSwingAngle,
StageRotation = StageRotationAngle,
FixtureRotation = FixtureRotationAngle
};
}
```
### 6.2 恢复保存的位置 | Restore Saved Positions
恢复时会同时向所有轴发送移动命令:
```csharp
private void ExecuteRestorePositions()
{
if (_savedPositions == null) return;
// 恢复到输入框
StageXPosition = _savedPositions.StageX;
StageYPosition = _savedPositions.StageY;
SourceZPosition = _savedPositions.SourceZ;
DetectorZPosition = _savedPositions.DetectorZ;
DetectorSwingAngle = _savedPositions.DetectorSwing;
StageRotationAngle = _savedPositions.StageRotation;
FixtureRotationAngle = _savedPositions.FixtureRotation;
// 发送移动命令(通过 MotionControlService → PlcLinearAxis/PlcRotaryAxis → PLC
_motionControlService.MoveToTarget(AxisId.StageX, _savedPositions.StageX);
_motionControlService.MoveToTarget(AxisId.StageY, _savedPositions.StageY);
_motionControlService.MoveToTarget(AxisId.SourceZ, _savedPositions.SourceZ);
_motionControlService.MoveToTarget(AxisId.DetectorZ, _savedPositions.DetectorZ);
_motionControlService.MoveRotaryToTarget(RotaryAxisId.DetectorSwing, _savedPositions.DetectorSwing);
_motionControlService.MoveRotaryToTarget(RotaryAxisId.StageRotation, _savedPositions.StageRotation);
_motionControlService.MoveRotaryToTarget(RotaryAxisId.FixtureRotation, _savedPositions.FixtureRotation);
}
```
**PLC 信号下发**:恢复时向所有 7 个轴写入目标位置和速度信号(参见第 2.3 节信号地址表)。
## 7. 安全参数 | Safety Parameters
ViewModel 提供以下安全参数属性:
| 属性 | 说明 |
|------|------|
| `SafetyHeight` | 探测器安全高度限定值 |
| `CalibrationValue` | 校准自动计算值 |
```csharp
public void ConfirmSafetyHeight()
{
_logger.Info("探测器安全高度限定值已保存:{Value}", SafetyHeight);
}
public void ConfirmCalibrationValue()
{
_logger.Info("校准自动计算值已保存:{Value}", CalibrationValue);
}
```
> 注:当前实现仅记录日志,未写入 PLC 信号。后续可能扩展为写入 PLC 安全参数区域。
## 8. PLC 断连安全处理 | PLC Disconnection Safety
当 PLC 连接断开时,ViewModel 自动执行以下安全操作:
```csharp
private void OnPlcConnectionChanged()
{
IsPlcConnected = _plcService.IsConnected;
if (!_plcService.IsConnected)
{
// 1. 停止所有活跃的 Jog 操作(标记为停止)
foreach (var axisId in _linearJogActive.Keys)
if (_linearJogActive[axisId])
_linearJogActive[axisId] = false;
foreach (var axisId in _rotaryJogActive.Keys)
if (_rotaryJogActive[axisId])
_rotaryJogActive[axisId] = false;
// 2. 禁用摇杆
IsJoystickEnabled = false;
// 3. 显示连接断开提示
ErrorMessage = LocalizationHelper.Get("MC_PlcNotConnected");
}
else
{
// PLC 重连:清除错误信息(不自动启用摇杆,需用户手动开启)
ErrorMessage = null;
}
}
```
**安全策略**
- PLC 断连时立即标记所有 Jog 为停止状态
- 禁用虚拟摇杆,防止用户误操作
- PLC 重连后不自动恢复使能,需用户手动确认后开启
## 9. 实体摇杆状态 | Physical Joystick Status
通过 `JoystickActiveEvent` 事件订阅实体摇杆的激活状态:
```csharp
_eventAggregator.GetEvent<JoystickActiveEvent>().Subscribe(OnJoystickActiveChanged, ThreadOption.UIThread);
private void OnJoystickActiveChanged(bool isActive)
{
IsJoystickActive = isActive;
}
```
**PLC 信号(读取)**
| 信号名 | 说明 |
|--------|------|
| `MC_Joystick_Active` | 实体摇杆输入激活状态(由 PLC 轮询读取) |
## 10. 旋转轴可见性控制 | Rotary Axis Visibility Control
根据配置动态控制旋转轴输入框的可见性:
```csharp
DetectorSwingVisibility = _config.RotaryAxes.ContainsKey(RotaryAxisId.DetectorSwing)
&& _config.RotaryAxes[RotaryAxisId.DetectorSwing].Enabled
? Visibility.Visible : Visibility.Collapsed;
StageRotationVisibility = _config.RotaryAxes.ContainsKey(RotaryAxisId.StageRotation)
&& _config.RotaryAxes[RotaryAxisId.StageRotation].Enabled
? Visibility.Visible : Visibility.Collapsed;
FixtureRotationVisibility = _config.RotaryAxes.ContainsKey(RotaryAxisId.FixtureRotation)
&& _config.RotaryAxes[RotaryAxisId.FixtureRotation].Enabled
? Visibility.Visible : Visibility.Collapsed;
```
此功能为纯 UI 层逻辑,不涉及 PLC 信号。
## 11. 信号写入队列机制 | Signal Write Queue Mechanism
所有 PLC 写入操作通过 `ISignalDataService.EnqueueWrite()` 进入写入队列:
```csharp
public interface ISignalDataService
{
bool EnqueueWrite(string signalName, object value);
bool EnqueueWriteBatch(Dictionary<string, object> writes);
T GetValueByName<T>(string signalName);
}
```
**队列处理流程**
1. 轴实现层(`PlcLinearAxis`/`PlcRotaryAxis`)调用 `EnqueueWrite(signalName, value)`
2. 信号数据服务将写入请求加入队列
3. PLC 服务在轮询周期(默认 100ms)中批量写入
4. 写入成功后从队列移除
**优势**
- 批量写入减少 PLC 通讯次数
- 避免频繁写入导致的通讯拥塞
- 统一的错误处理和重试机制
## 12. 完整 PLC 信号名称常量 | Complete PLC Signal Name Constants
定义在 `MotionSignalNames.cs` 中:
| 分类 | 常量名 | 信号名 | 方向 |
|------|--------|--------|------|
| 联动 | `SourceDetZ_Linkage_Enable` | `MC_SourceDetZ_Linkage_Enable` | 写入 |
| 使能 | `VirtualJoystick_Enable` | `MC_VirtualJoystick_Enable` | 写入 |
| 状态 | `Joystick_Active` | `MC_Joystick_Active` | 读取 |
| SourceZ | `SourceZ_Pos` | `MC_SourceZ_Pos` | 读取 |
| SourceZ | `SourceZ_Target` | `MC_SourceZ_Target` | 写入 |
| SourceZ | `SourceZ_Speed` | `MC_SourceZ_Speed` | 写入 |
| SourceZ | `SourceZ_JogPos` | `MC_SourceZ_JogPos` | 写入 |
| SourceZ | `SourceZ_JogNeg` | `MC_SourceZ_JogNeg` | 写入 |
| SourceZ | `SourceZ_Home` | `MC_SourceZ_Home` | 写入 |
| SourceZ | `SourceZ_Stop` | `MC_SourceZ_Stop` | 写入 |
| DetectorZ | `DetZ_Pos` | `MC_DetZ_Pos` | 读取 |
| DetectorZ | `DetZ_Target` | `MC_DetZ_Target` | 写入 |
| DetectorZ | `DetZ_Speed` | `MC_DetZ_Speed` | 写入 |
| DetectorZ | `DetZ_JogPos` | `MC_DetZ_JogPos` | 写入 |
| DetectorZ | `DetZ_JogNeg` | `MC_DetZ_JogNeg` | 写入 |
| DetectorZ | `DetZ_Home` | `MC_DetZ_Home` | 写入 |
| DetectorZ | `DetZ_Stop` | `MC_DetZ_Stop` | 写入 |
| StageX | `StageX_Pos` | `MC_StageX_Pos` | 读取 |
| StageX | `StageX_Target` | `MC_StageX_Target` | 写入 |
| StageX | `StageX_Speed` | `MC_StageX_Speed` | 写入 |
| StageX | `StageX_JogPos` | `MC_StageX_JogPos` | 写入 |
| StageX | `StageX_JogNeg` | `MC_StageX_JogNeg` | 写入 |
| StageX | `StageX_Home` | `MC_StageX_Home` | 写入 |
| StageX | `StageX_Stop` | `MC_StageX_Stop` | 写入 |
| StageY | `StageY_Pos` | `MC_StageY_Pos` | 读取 |
| StageY | `StageY_Target` | `MC_StageY_Target` | 写入 |
| StageY | `StageY_Speed` | `MC_StageY_Speed` | 写入 |
| StageY | `StageY_JogPos` | `MC_StageY_JogPos` | 写入 |
| StageY | `StageY_JogNeg` | `MC_StageY_JogNeg` | 写入 |
| StageY | `StageY_Home` | `MC_StageY_Home` | 写入 |
| StageY | `StageY_Stop` | `MC_StageY_Stop` | 写入 |
| DetSwing | `DetSwing_Angle` | `MC_DetSwing_Angle` | 读取 |
| DetSwing | `DetSwing_Target` | `MC_DetSwing_Target` | 写入 |
| DetSwing | `DetSwing_Speed` | `MC_DetSwing_Speed` | 写入 |
| DetSwing | `DetSwing_JogPos` | `MC_DetSwing_JogPos` | 写入 |
| DetSwing | `DetSwing_JogNeg` | `MC_DetSwing_JogNeg` | 写入 |
| DetSwing | `DetSwing_Home` | `MC_DetSwing_Home` | 写入 |
| DetSwing | `DetSwing_Stop` | `MC_DetSwing_Stop` | 写入 |
| StageRot | `StageRot_Angle` | `MC_StageRot_Angle` | 读取 |
| StageRot | `StageRot_Target` | `MC_StageRot_Target` | 写入 |
| StageRot | `StageRot_Speed` | `MC_StageRot_Speed` | 写入 |
| StageRot | `StageRot_JogPos` | `MC_StageRot_JogPos` | 写入 |
| StageRot | `StageRot_JogNeg` | `MC_StageRot_JogNeg` | 写入 |
| StageRot | `StageRot_Home` | `MC_StageRot_Home` | 写入 |
| StageRot | `StageRot_Stop` | `MC_StageRot_Stop` | 写入 |
| FixRot | `FixRot_Angle` | `MC_FixRot_Angle` | 读取 |
| FixRot | `FixRot_Target` | `MC_FixRot_Target` | 写入 |
| FixRot | `FixRot_Speed` | `MC_FixRot_Speed` | 写入 |
| FixRot | `FixRot_JogPos` | `MC_FixRot_JogPos` | 写入 |
| FixRot | `FixRot_JogNeg` | `MC_FixRot_JogNeg` | 写入 |
| FixRot | `FixRot_Home` | `MC_FixRot_Home` | 写入 |
| FixRot | `FixRot_Stop` | `MC_FixRot_Stop` | 写入 |
| 安全门 | `Door_Open` | `MC_Door_Open` | 写入 |
| 安全门 | `Door_Close` | `MC_Door_Close` | 写入 |
| 安全门 | `Door_Stop` | `MC_Door_Stop` | 写入 |
| 安全门 | `Door_Status` | `MC_Door_Status` | 读取 |
| 安全门 | `Door_Interlock` | `MC_Door_Interlock` | 读取 |
| 轴复位 | `Axis_Reset` | `MC_Axis_Reset` | 写入 |
| 轴复位 | `Axis_ResetDone` | `MC_Axis_ResetDone` | 读取 |
---
@@ -83,7 +83,7 @@ namespace XP.Hardware.MotionControl.ViewModels
// 初始化命令 | Initialize commands
ToggleEnableCommand = new DelegateCommand(ExecuteToggleEnable, () => IsPlcConnected);
ToggleSwapMouseButtonsCommand = new DelegateCommand(ExecuteToggleSwapMouseButtons);
SavePositionsCommand = new DelegateCommand(ExecuteSavePositions);
SavePositionsCommand = new DelegateCommand(ExecuteSavePositions, () => IsPlcConnected);
RestorePositionsCommand = new DelegateCommand(ExecuteRestorePositions, () => _savedPositions != null && IsPlcConnected);
// 射线源与探测器Z轴联动命令 | Source-Detector Z-axis linkage command
SZDZLockCommand = new DelegateCommand(() => SafeRun(ExecuteSZDZLock), () => IsPlcConnected);
@@ -125,11 +125,7 @@ namespace XP.Hardware.MotionControl.ViewModels
public double DualJoystickOutputX
{
get => _dualJoystickOutputX;
set
{
if (SetProperty(ref _dualJoystickOutputX, value))
HandleDualJoystickOutput();
}
set => SetProperty(ref _dualJoystickOutputX, value);
}
private double _dualJoystickOutputY;
@@ -430,7 +426,7 @@ namespace XP.Hardware.MotionControl.ViewModels
if (!_plcService.IsConnected)
{
// PLC 断开:停止所有活跃的 Jog 操作 | PLC disconnected: stop all active jog operations
foreach (var axisId in _linearJogActive.Keys)
foreach (var axisId in new List<AxisId>(_linearJogActive.Keys))
{
if (_linearJogActive[axisId])
{
@@ -438,7 +434,7 @@ namespace XP.Hardware.MotionControl.ViewModels
_logger.Debug("PLC 断开,直线轴 Jog 已标记停止:{AxisId} | PLC disconnected, linear axis jog marked stopped: {AxisId}", axisId);
}
}
foreach (var axisId in _rotaryJogActive.Keys)
foreach (var axisId in new List<RotaryAxisId>(_rotaryJogActive.Keys))
{
if (_rotaryJogActive[axisId])
{
@@ -484,12 +480,16 @@ namespace XP.Hardware.MotionControl.ViewModels
if (!result.Success)
{
// 写入失败,回滚状态 | Write failed, rollback state
IsJoystickEnabled = !IsJoystickEnabled;
MessageBox.Show(result.ErrorMessage, XP.Common.Localization.LocalizationHelper.Get("MC_Error"),
MessageBoxButton.OK, MessageBoxImage.Warning);
}
}
catch (Exception ex)
{
// 异常时回滚状态 | Rollback state on exception
IsJoystickEnabled = !IsJoystickEnabled;
MessageBox.Show(ex.Message, XP.Common.Localization.LocalizationHelper.Get("MC_Error"),
MessageBoxButton.OK, MessageBoxImage.Error);
}
@@ -831,7 +831,7 @@ namespace XP.Hardware.MotionControl.ViewModels
// 如果射线源与探测器Z轴联动,且操作的是SourceZ或DetectorZ,则同时发送两个轴
if (SZDZLock && (propertyName == nameof(SourceZPosition) || propertyName == nameof(DetectorZPosition)))
{
result = SendSourceDetectorZMoveCommand(value);
result = SendSourceDetectorZMoveCommand(value, propertyName);
}
else
{
@@ -873,7 +873,7 @@ namespace XP.Hardware.MotionControl.ViewModels
// 如果射线源与探测器Z轴联动,且操作的是SourceZ或DetectorZ,则同时步进两个轴
if (SZDZLock && (propertyName == nameof(SourceZPosition) || propertyName == nameof(DetectorZPosition)))
{
result = SendSourceDetectorZMoveCommand(newValue);
result = SendSourceDetectorZMoveCommand(newValue, propertyName);
}
else
{
@@ -920,7 +920,7 @@ namespace XP.Hardware.MotionControl.ViewModels
/// </summary>
/// <param name="targetValue">目标位置 | Target position</param>
/// <returns>操作结果 | Operation result</returns>
private MotionResult SendSourceDetectorZMoveCommand(double targetValue)
private MotionResult SendSourceDetectorZMoveCommand(double targetValue, string sourcePropertyName = null)
{
// 1. 检查联动是否在配置中启用 | Check if linkage is enabled in config
if (!_config.SourceDetectorZLinkage.Enabled)
@@ -945,11 +945,27 @@ namespace XP.Hardware.MotionControl.ViewModels
return MotionResult.Fail("探测器Z轴正在运动中,无法联动移动 | Detector Z-axis is moving, linkage move rejected");
}
// 3. 计算位移量和目标位置 | Calculate displacement and target positions
// 3. 根据编辑来源计算位移量和目标位置 | Calculate displacement and targets based on edit source
var currentSourceZ = sourceZAxis.ActualPosition;
var currentDetectorZ = detectorZAxis.ActualPosition;
var sourceDelta = targetValue - currentSourceZ;
var targetDetectorZ = currentDetectorZ + sourceDelta;
double targetSourceZ;
double targetDetectorZ;
if (sourcePropertyName == nameof(DetectorZPosition))
{
// 用户编辑的是探测器Z:以探测器Z为基准计算位移 | User edited DetectorZ: calculate delta from DetectorZ
var delta = targetValue - currentDetectorZ;
targetDetectorZ = targetValue;
targetSourceZ = currentSourceZ + delta;
}
else
{
// 用户编辑的是射线源Z(默认):以射线源Z为基准计算位移 | User edited SourceZ (default): calculate delta from SourceZ
var delta = targetValue - currentSourceZ;
targetSourceZ = targetValue;
targetDetectorZ = currentDetectorZ + delta;
}
// 4. 边界检查(使用配置中的 Min/Max| Boundary check (use Min/Max from config)
if (_config.LinearAxes.TryGetValue(AxisId.SourceZ, out var sourceConfig) &&
@@ -957,10 +973,10 @@ namespace XP.Hardware.MotionControl.ViewModels
{
var errors = new List<string>();
if (targetValue < sourceConfig.Min || targetValue > sourceConfig.Max)
if (targetSourceZ < sourceConfig.Min || targetSourceZ > sourceConfig.Max)
{
errors.Add($"射线源Z轴目标位置 {targetValue} 超出范围 [{sourceConfig.Min}, {sourceConfig.Max}] | " +
$"Source Z-axis target {targetValue} out of range [{sourceConfig.Min}, {sourceConfig.Max}]");
errors.Add($"射线源Z轴目标位置 {targetSourceZ} 超出范围 [{sourceConfig.Min}, {sourceConfig.Max}] | " +
$"Source Z-axis target {targetSourceZ} out of range [{sourceConfig.Min}, {sourceConfig.Max}]");
}
if (targetDetectorZ < detectorConfig.Min || targetDetectorZ > detectorConfig.Max)
@@ -983,7 +999,7 @@ namespace XP.Hardware.MotionControl.ViewModels
}
// 5. 所有检查通过,同时发送两个轴的移动命令 | All checks passed, send move commands to both axes simultaneously
var sourceResult = sourceZAxis.MoveToTarget(targetValue, _config.DefaultVelocity);
var sourceResult = sourceZAxis.MoveToTarget(targetSourceZ, _config.DefaultVelocity);
var detectorResult = detectorZAxis.MoveToTarget(targetDetectorZ, _config.DefaultVelocity);
// 6. 返回结果(任一失败都记录日志)| Return result (log if any fails)
@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Config>
<Group ID="WriteCommon" DBNumber="31">
<Signal Name="SoftLive" Type="byte" StartAddr="0" IndexOrLength="" Remark="软件心跳,01周期变化表示PLC存活" />
<Signal Name="MC_SourceZ_Target" Type="single" StartAddr="10" IndexOrLength="" Remark="射线源Z目标位置" />
<Signal Name="MC_SourceZ_Speed" Type="single" StartAddr="14" IndexOrLength="" Remark="射线源Z运动速度" />
<Signal Name="MC_SourceZ_JogPos" Type="byte" StartAddr="18" IndexOrLength="" Remark="射线源Z正向Jog0:缺省,1:正向点动" />
<Signal Name="MC_SourceZ_JogNeg" Type="byte" StartAddr="19" IndexOrLength="" Remark="射线源Z反向Jog0:缺省,1:反向点动" />
<Signal Name="MC_SourceZ_Home" Type="byte" StartAddr="20" IndexOrLength="" Remark="射线源Z回零,0:缺省,1:触发回零" />
<Signal Name="MC_SourceZ_Stop" Type="byte" StartAddr="21" IndexOrLength="" Remark="射线源Z停止,0:缺省,1:触发停止" />
<Signal Name="MC_DetZ_Target" Type="single" StartAddr="22" IndexOrLength="" Remark="探测器Z目标位置" />
<Signal Name="MC_DetZ_Speed" Type="single" StartAddr="26" IndexOrLength="" Remark="探测器Z运动速度" />
<Signal Name="MC_DetZ_JogPos" Type="byte" StartAddr="30" IndexOrLength="" Remark="探测器Z正向Jog0:缺省,1:正向点动" />
<Signal Name="MC_DetZ_JogNeg" Type="byte" StartAddr="31" IndexOrLength="" Remark="探测器Z反向Jog0:缺省,1:反向点动" />
<Signal Name="MC_DetZ_Home" Type="byte" StartAddr="32" IndexOrLength="" Remark="探测器Z回零,0:缺省,1:触发回零" />
<Signal Name="MC_DetZ_Stop" Type="byte" StartAddr="33" IndexOrLength="" Remark="探测器Z停止,0:缺省,1:触发停止" />
<Signal Name="MC_StageX_Target" Type="single" StartAddr="34" IndexOrLength="" Remark="载物台X目标位置" />
<Signal Name="MC_StageX_Speed" Type="single" StartAddr="38" IndexOrLength="" Remark="载物台X运动速度" />
<Signal Name="MC_StageX_JogPos" Type="byte" StartAddr="42" IndexOrLength="" Remark="载物台X正向Jog0:缺省,1:正向点动" />
<Signal Name="MC_StageX_JogNeg" Type="byte" StartAddr="43" IndexOrLength="" Remark="载物台X反向Jog0:缺省,1:反向点动" />
<Signal Name="MC_StageX_Home" Type="byte" StartAddr="44" IndexOrLength="" Remark="载物台X回零,0:缺省,1:触发回零" />
<Signal Name="MC_StageX_Stop" Type="byte" StartAddr="45" IndexOrLength="" Remark="载物台X停止,0:缺省,1:触发停止" />
<Signal Name="MC_StageY_Target" Type="single" StartAddr="46" IndexOrLength="" Remark="载物台Y目标位置" />
<Signal Name="MC_StageY_Speed" Type="single" StartAddr="50" IndexOrLength="" Remark="载物台Y运动速度" />
<Signal Name="MC_StageY_JogPos" Type="byte" StartAddr="54" IndexOrLength="" Remark="载物台Y正向Jog0:缺省,1:正向点动" />
<Signal Name="MC_StageY_JogNeg" Type="byte" StartAddr="55" IndexOrLength="" Remark="载物台Y反向Jog0:缺省,1:反向点动" />
<Signal Name="MC_StageY_Home" Type="byte" StartAddr="56" IndexOrLength="" Remark="载物台Y回零,0:缺省,1:触发回零" />
<Signal Name="MC_StageY_Stop" Type="byte" StartAddr="57" IndexOrLength="" Remark="载物台Y停止,0:缺省,1:触发停止" />
<Signal Name="MC_DetSwing_Target" Type="single" StartAddr="58" IndexOrLength="" Remark="探测器摆动目标角度" />
<Signal Name="MC_DetSwing_Speed" Type="single" StartAddr="62" IndexOrLength="" Remark="探测器摆动运动速度" />
<Signal Name="MC_DetSwing_JogPos" Type="byte" StartAddr="66" IndexOrLength="" Remark="探测器摆动正向Jog,0:缺省,1:正向点动" />
<Signal Name="MC_DetSwing_JogNeg" Type="byte" StartAddr="67" IndexOrLength="" Remark="探测器摆动反向Jog,0:缺省,1:反向点动" />
<Signal Name="MC_DetSwing_Home" Type="byte" StartAddr="68" IndexOrLength="" Remark="探测器摆动回零,0:缺省,1:触发回零" />
<Signal Name="MC_DetSwing_Stop" Type="byte" StartAddr="69" IndexOrLength="" Remark="探测器摆动停止,0:缺省,1:触发停止" />
<Signal Name="MC_StageRot_Target" Type="single" StartAddr="70" IndexOrLength="" Remark="载物台旋转目标角度" />
<Signal Name="MC_StageRot_Speed" Type="single" StartAddr="74" IndexOrLength="" Remark="载物台旋转运动速度" />
<Signal Name="MC_StageRot_JogPos" Type="byte" StartAddr="78" IndexOrLength="" Remark="载物台旋转正向Jog,0:缺省,1:正向点动" />
<Signal Name="MC_StageRot_JogNeg" Type="byte" StartAddr="79" IndexOrLength="" Remark="载物台旋转反向Jog,0:缺省,1:反向点动" />
<Signal Name="MC_StageRot_Home" Type="byte" StartAddr="80" IndexOrLength="" Remark="载物台旋转回零,0:缺省,1:触发回零" />
<Signal Name="MC_StageRot_Stop" Type="byte" StartAddr="81" IndexOrLength="" Remark="载物台旋转停止,0:缺省,1:触发停止" />
<Signal Name="MC_FixRot_Target" Type="single" StartAddr="82" IndexOrLength="" Remark="夹具旋转目标角度" />
<Signal Name="MC_FixRot_Speed" Type="single" StartAddr="86" IndexOrLength="" Remark="夹具旋转运动速度" />
<Signal Name="MC_FixRot_JogPos" Type="byte" StartAddr="90" IndexOrLength="" Remark="夹具旋转正向Jog,0:缺省,1:正向点动" />
<Signal Name="MC_FixRot_JogNeg" Type="byte" StartAddr="91" IndexOrLength="" Remark="夹具旋转反向Jog,0:缺省,1:反向点动" />
<Signal Name="MC_FixRot_Home" Type="byte" StartAddr="92" IndexOrLength="" Remark="夹具旋转回零,0:缺省,1:触发回零" />
<Signal Name="MC_FixRot_Stop" Type="byte" StartAddr="93" IndexOrLength="" Remark="夹具旋转停止,0:缺省,1:触发停止" />
<Signal Name="MC_Door_Open" Type="byte" StartAddr="94" IndexOrLength="" Remark="安全门开门,0:缺省,1:触发开门" />
<Signal Name="MC_Door_Close" Type="byte" StartAddr="95" IndexOrLength="" Remark="安全门关门,0:缺省,1:触发关门" />
<Signal Name="MC_Door_Stop" Type="byte" StartAddr="96" IndexOrLength="" Remark="安全门停止,0:缺省,1:触发停止" />
<Signal Name="MC_VirtualJoystick_Enable" Type="bool" StartAddr="111" IndexOrLength="" Remark="虚拟摇杆使能"/>
<Signal Name="MC_SourceDetZ_Linkage_Enable" Type="bool" StartAddr="101" IndexOrLength="" Remark="联动使能"/>
</Group>
<Group ID="ReadCommon" DBNumber="31">
<Signal Name="PlcLive" Type="byte" StartAddr="200" IndexOrLength="" Remark="PLC心跳,01周期变化表示PLC存活" />
<Signal Name="PlcAlarm" Type="byte" StartAddr="201" IndexOrLength="" Remark="系统报警,0:缺省,10:有报警" />
<Signal Name="MC_SourceZ_Pos" Type="single" StartAddr="100" IndexOrLength="" Remark="射线源Z实际位置" />
<Signal Name="MC_DetZ_Pos" Type="single" StartAddr="104" IndexOrLength="" Remark="探测器Z实际位置" />
<Signal Name="MC_StageX_Pos" Type="single" StartAddr="108" IndexOrLength="" Remark="载物台X实际位置" />
<Signal Name="MC_StageY_Pos" Type="single" StartAddr="112" IndexOrLength="" Remark="载物台Y实际位置" />
<Signal Name="MC_DetSwing_Angle" Type="single" StartAddr="116" IndexOrLength="" Remark="探测器摆动实际角度" />
<Signal Name="MC_StageRot_Angle" Type="single" StartAddr="120" IndexOrLength="" Remark="载物台旋转实际角度" />
<Signal Name="MC_FixRot_Angle" Type="single" StartAddr="124" IndexOrLength="" Remark="夹具旋转实际角度" />
<Signal Name="MC_Door_Status" Type="byte" StartAddr="128" IndexOrLength="" Remark="安全门状态,0:未知,1:正在开门,2:已开,3:正在关门,4:已关,5:已锁定,6:故障" />
<Signal Name="MC_Door_Interlock" Type="byte" StartAddr="130" IndexOrLength="" Remark="安全门联锁信号,0:缺省(无联锁),10:联锁有效(禁止开门)" />
<Signal Name="MC_Joystick_Active" Type="bool" StartAddr="110" IndexOrLength="" Remark="实体摇杆输入"/>
</Group>
<Group ID="Status" DBNumber="4100">
<Signal Name="ScanMode" Type="byte" StartAddr="201" IndexOrLength="" Remark="扫描模式,0:缺省(空闲),具体值由PLC定义" />
</Group>
</Config>
@@ -0,0 +1,90 @@
[sys1____]
"SerialNumber"
[geo_dm__#Cougar]
"Name9"
"Value9"
"Name10"
"Value10"
"Name19"
"Value19"
"Name21"
"Value21"
"Name24"
"Value24"
"Name29"
"Value29"
"Name30"
"Value30"
"Name48"
"Value48"
[geo_dm__#Cheetah]
"Name9"
"Value9"
"Name10"
"Value10"
"Name12"
"Value12"
"Name13"
"Value13"
"Name22"
"Value22"
"Name24"
"Value24"
"Name27"
"Value27"
"Name48"
"Value48"
[joyspeed]
"Speed0"
"Speed1"
"Speed2"
"Speed3"
"Speed4"
"Speed5"
"Speed6"
"Speed7"
"Speed8"
"Speed9"
[loadpos_]
"SafePos[3]"
[para_1__]
"PosSW_End"
"NegSW_End"
"RefPosition"
[para_2__]
"PosSW_End"
"NegSW_End"
"RefPosition"
[para_3__]
"PosSW_End"
"NegSW_End"
"RefPosition"
[para_4__]
"PosSW_End"
"NegSW_End"
"RefPosition"
"RefPositionEnd"
[para_5__]
"PosSW_End"
"NegSW_End"
"RefPosition"
[para_6__]
"PosSW_End"
"NegSW_End"
"RefPosition"
"DistPerTurn"
[para_7__]
"PosSW_End"
"NegSW_End"
"RefPosition"
[para_8__]
"PosSW_End"
"NegSW_End"
"RefPosition"
[para_9__]
"PosSW_End"
"NegSW_End"
"RefPosition"
[dm_door_]
"DoorOpenSwitchAvail#410"
EOF
@@ -0,0 +1,93 @@
[MODEDM#all]
"everything"
"beginExceptions"
"Version"
"endExceptions"
!*************************************************************
[TARGETDM#all]
"everything"
"beginExceptions"
"Version"
"endExceptions"
!*************************************************************
[TUBEDM#all]
"everything"
"beginExceptions"
"Version"
"endExceptions"
!*************************************************************
[HSGDM#all]
"everything"
"beginExceptions"
"Version"
"endExceptions"
!*************************************************************
[SYSDM#all]
"everything"
"beginExceptions"
"Version"
"SoftwareVersionPLC"
"SysConfig"
"SoftwareVersion"
"endExceptions"
!*************************************************************
[AMPDM#all]
"everything"
"beginExceptions"
"Version"
"endExceptions"
!*************************************************************
[VACDM#all]
"everything"
"beginExceptions"
"Version_vacuum_system"
"endExceptions"
!*************************************************************
[CENTDM#all]
"everything"
"beginExceptions"
"Version"
"endExceptions"
!*************************************************************
[FOCUSDM#all]
"everything"
"beginExceptions"
"Version"
"endExceptions"
!*************************************************************
[CONSDM#all]
"everything"
"beginExceptions"
"Version"
"endExceptions"
!*************************************************************
[TREGDM#all]
"everything"
"beginExceptions"
"Version"
"endExceptions"
!*************************************************************
[CONDDM#all]
"everything"
"beginExceptions"
"Version"
"endExceptions"
!*************************************************************
[FILDM#all]
"everything"
"beginExceptions"
"Version"
"endExceptions"
!*************************************************************
[TUBEHEADDM#all]
"everything"
"beginExceptions"
"version"
"endExceptions"
!*************************************************************
[INITDM#all]
"everything"
"beginExceptions"
"version"
"endExceptions"
EOF
@@ -0,0 +1,6 @@
// Flag, ob die Trace-Meldungen asynchron zu protokolliern sind: 1 für asynchron, 0 für synchron.
1
// Kategorie Konfigurationen: <Kategorie-Name> <Enabled[0/1]>
FXDriver 0
FXDriverPviXCreateResponse 0
FXDriverUpgradeCP1483ToCP1583 0
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,294 @@
[XRaySystemErrors]
ErrorNumber0=101
ErrorString0="Data module init error"
ErrorNumber1=102
ErrorString1="Data module sys error"
ErrorNumber2=103
ErrorString2="Data module amp error"
ErrorNumber3=301
ErrorString3="Timeout during filament adjusting"
ErrorNumber4=302
ErrorString4="Timeout during warmup"
ErrorNumber5=303
ErrorString5="Error door control"
ErrorNumber6=401
ErrorString6="Defective warn lamp 1"
ErrorNumber7=402
ErrorString7="Defective warn lamp 2"
ErrorNumber8=403
ErrorString8="Defective warn lamp 3"
ErrorNumber9=404
ErrorString9="Cooling defect"
ErrorNumber10=405
ErrorString10="Error COM2"
ErrorNumber11=406
ErrorString12="Error COM2"
ErrorNumber13=407
ErrorString13="Error COM2"
ErrorNumber14=408
ErrorString14="Error COM2"
ErrorNumber15=601
ErrorString15="Defective warn lamp 1"
ErrorNumber16=602
ErrorString16="Defective warn lamp 2"
ErrorNumber17=603
ErrorString17="Defective warn lamp 3"
ErrorNumber18=604
ErrorString18="Cooling defect"
ErrorNumber19=605
ErrorString19="Error COM2"
[HSGErrors]
ErrorNumber0=101
ErrorString0="Data module error"
ErrorNumber1=201
ErrorString1="Underflow of actual accelerating voltage"
ErrorNumber2=202
ErrorString2="Overflow of actual accelerating voltage"
ErrorNumber3=203
ErrorString3="Underflow of actual tube current"
ErrorNumber4=204
ErrorString4="Overflow of actual tube current"
ErrorNumber5=205
ErrorString5="Underflow of actual filament current"
ErrorNumber6=206
ErrorString6="Overflow of actual filament current"
ErrorNumber7=301
ErrorString7="Timeout acceleration voltage"
ErrorNumber8=302
ErrorString8="Timeout tube current"
ErrorNumber9=303
ErrorString9="Timeout filament adjust"
ErrorNumber10=402
ErrorString10="High voltage generator overload"
ErrorNumber11=403
ErrorString11="No filament current"
ErrorNumber12=501
ErrorString12="Overvlow of nominal filament current"
ErrorNumber13=502
ErrorString13="Overflow or underflow of nominal accelerating voltage"
ErrorNumber14=503
ErrorString14="Overflow of nominal tube current"
ErrorNumber15=510
ErrorString15="Error during filament adjust step 30"
ErrorNumber16=520
ErrorString16="Nominal filament current smaller than filament offset"
ErrorNumber17=524
ErrorString17="Filament working current exceeds limit during filament adjust"
ErrorNumber18=525
ErrorString18="Filament working current exceeds limit during filament adjust"
ErrorNumber19=526
ErrorString19="Nominal filament current smaller than filament offset during filament adjust"
ErrorNumber19=527
ErrorString19="Nominal filament current smaller than filament offset during filament adjust"
[TubeErrors]
ErrorNumber0=101
ErrorString0="Data module error"
ErrorNumber1=102
ErrorString1="Data module focus table created"
ErrorNumber2=103
ErrorString2="Focus data module write error"
ErrorNumber3=104
ErrorString3="Focus data module error"
ErrorNumber4=105
ErrorString4="Focus table data module info error"
ErrorNumber5=106
ErrorString5="Focus table data module read error"
ErrorNumber6=107
ErrorString6="Condensor data module error"
ErrorNumber7=108
ErrorString7="Data module condensor table created"
ErrorNumber8=109
ErrorString8="Condensor table data module info error"
ErrorNumber9=110
ErrorString9="Condensor table data module read error"
ErrorNumber10=111
ErrorString10="Centering 1 / Centering 2 / Centering 3 data module error"
ErrorNumber11=114
ErrorString11="Centering table 1 data module error"
ErrorNumber12=115
ErrorString12="Centering table 2 data module error"
ErrorNumber13=116
ErrorString13="Centering table 3 data module error"
ErrorNumber14=117
ErrorString14="Conditioning data module error"
ErrorNumber15=118
ErrorString15="Read focus data module error"
ErrorNumber16=119
ErrorString16="Write focus data module error"
ErrorNumber17=120
ErrorString17="Read default focus data module error"
ErrorNumber18=121
ErrorString18="Write default focus data module error"
ErrorNumber19=122
ErrorString19="Read condensor data module error"
ErrorNumber20=123
ErrorString20="Write condensor data module error"
ErrorNumber21=124
ErrorString21="Read default condensor data module error"
ErrorNumber22=125
ErrorString22="Write default condensor data module error"
ErrorNumber23=126
ErrorString23="Read centering 1 data module error"
ErrorNumber24=127
ErrorString24="Read centering 2 data module error"
ErrorNumber25=128
ErrorString25="Read centering 3 data module error"
ErrorNumber26=129
ErrorString26="Write centering 1 data module error"
ErrorNumber27=130
ErrorString27="Write centering 2 data module error"
ErrorNumber28=131
ErrorString28="Write centering 3 data module error"
ErrorNumber29=132
ErrorString29="Read default centering 1 data module error"
ErrorNumber30=133
ErrorString30="Read default centering 2 data module error"
ErrorNumber31=134
ErrorString31="Read default centering 3 data module error"
ErrorNumber32=135
ErrorString32="Write default centering 1 data module error"
ErrorNumber33=136
ErrorString33="Write default centering 2 data module error"
ErrorNumber34=137
ErrorString34="Write default centering 3 data module error"
ErrorNumber35=140
ErrorString35="Shutter data module error"
ErrorNumber36=141
ErrorString36="Data module shutter created"
ErrorNumber37=142
ErrorString37="Shutter table data module info error"
ErrorNumber38=143
ErrorString38="Shutter table data module read error"
ErrorNumber39=144
ErrorString39="Scan data module error"
ErrorNumber40=145
ErrorString40="Scan table data module error"
ErrorNumber41=146
ErrorString41="Ti_Reg data module error"
ErrorNumber42=147
ErrorString42="dm_ken (focus) data module error"
ErrorNumber43=148
ErrorString43="dm_ken (condensor) data module error"
ErrorNumber44=149
ErrorString44="Mode_x data module error"
ErrorNumber45=150
ErrorString45="burn data module error"
ErrorNumber46=151
ErrorString46="tube_cur data module error"
ErrorNumber47=152
ErrorString47="Tubehead data module error"
ErrorNumber48=153
ErrorString48="Target data module error"
ErrorNumber49=154
ErrorString49="read_def_tube_cur data module error"
ErrorNumber50=155
ErrorString50="write_def_tube_cur data module error"
ErrorNumber51=156
ErrorString51="Filament data module error"
ErrorNumber52=201
ErrorString52="Underflow of actual target (amp1) current"
ErrorNumber53=202
ErrorString53="Overflow of actual target (amp1) current"
ErrorNumber54=203
ErrorString54="Underflow of actual target (amp2) current"
ErrorNumber55=204
ErrorString55="Overflow of actual target (amp2) current"
ErrorNumber56=205
ErrorString56="Underflow of actual amp3 current"
ErrorNumber57=206
ErrorString57="Overflow of actual amp3 current"
ErrorNumber58=301
ErrorString58="Time limit for centering current overflow exeeded, x-ray switched off"
ErrorNumber59=302
ErrorString59="Time limit for shutter exceeded, xray switched off"
ErrorNumber60=401
ErrorString60="Overflow of target power"
ErrorNumber61=402
ErrorString61="Missing amplifier"
ErrorNumber62=403
ErrorString62="Scanning coil temperature overflow"
ErrorNumber63=501
ErrorString63="Overflow of nominal focus current"
ErrorNumber64=502
ErrorString64="Overflow of nominal centering current x"
ErrorNumber65=503
ErrorString65="Overflow of nominal centering current y"
ErrorNumber66=504
ErrorString66="Overflow of nominal centering current"
ErrorNumber67=505
ErrorString67="Overflow of nominal shutter current"
ErrorNumber68=506
ErrorString68="Overflow of actual focus current"
ErrorNumber69=507
ErrorString69="Negative focus current"
ErrorNumber70=611
ErrorString70="Error during autocentering (plane 1)"
ErrorNumber71=612
ErrorString71="Error during autocentering (plane 1)"
ErrorNumber72=613
ErrorString72="Error during autocentering (plane 1)"
ErrorNumber73=614
ErrorString73="Error during autocentering (plane 1)"
ErrorNumber74=615
ErrorString74="Error during autocentering (plane 1)"
ErrorNumber75=616
ErrorString75="Error during autocentering (plane 1)"
ErrorNumber76=617
ErrorString76="Max. number of runs for autocentering exceeded (plane 1)"
ErrorNumber77=618
ErrorString77="Centering current exceeded (plane 1)"
ErrorNumber78=621
ErrorString78="Error during autocentering (plane 2)"
ErrorNumber79=622
ErrorString79="Error during autocentering (plane 2)"
ErrorNumber80=623
ErrorString80="Error during autocentering (plane 2)"
ErrorNumber81=624
ErrorString81="Error during autocentering (plane 2)"
ErrorNumber82=625
ErrorString82="Error during autocentering (plane 2)"
ErrorNumber83=626
ErrorString83="Error during autocentering (plane 2)"
ErrorNumber84=627
ErrorString84="Max. number of runs for autocentering exceeded (plane 2)"
ErrorNumber85=628
ErrorString85="Centering current exceeded (plane 2)"
ErrorNumber86=631
ErrorString86="Error during autocentering (plane 3)"
ErrorNumber87=632
ErrorString87="Error during autocentering (plane 3)"
ErrorNumber88=633
ErrorString88="Error during autocentering (plane 3)"
ErrorNumber89=634
ErrorString89="Error during autocentering (plane 3)"
ErrorNumber90=635
ErrorString90="Error during autocentering (plane 3)"
ErrorNumber91=636
ErrorString91="Error during autocentering (plane 3)"
ErrorNumber92=637
ErrorString92="Max. number of runs for autocentering exceeded (plane 3)"
ErrorNumber93=638
ErrorString93="Centering current exceeded (plane 3)"
[VacuumErrors]
ErrorNumber1=101
ErrorString1="Data module error"
ErrorNumber2=201
ErrorString2="Underflow of actual vacuum value"
ErrorNumber3=202
ErrorString3="Overflow of actual vacuum value"
ErrorNumber4=301
ErrorString4="Timeout vacuum"
ErrorNumber5=301
ErrorString5="Timeout vacuum breakdown"
ErrorNumber6=401
ErrorString6="Bad vacuum during X-ray on"
[AxisErrors]
ErrorNumber0=1002
ErrorString0="general EL_Critical"
[ManiErrors]
Binary file not shown.