补充射线源和PLC类库所需配置和信号地址定义文件。
29 KiB
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时,左右键功能互换。
信号下发流程:
// 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 | 正向 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:点动) |
| 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:点动) |
PLC 信号地址(旋转轴 Jog):
| 轴 | 信号名 | 地址 | 说明 |
|---|---|---|---|
| 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(正向/反向)
信号下发流程:
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:
// 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%):
var speedPercent = Math.Abs(output) * 100; // 0% ~ 100%
速度计算流程:
-
MotionControlService.SetJogSpeed 计算实际速度:
public MotionResult SetJogSpeed(AxisId axisId, double speedPercent) { var axis = _motionSystem.GetLinearAxis(axisId); var actualSpeed = _config.DefaultVelocity * speedPercent / 100.0; return axis.SetJogSpeed(actualSpeed); } -
PlcLinearAxis.SetJogSpeed 将实际速度写入 PLC:
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 = 100mm/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
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
// 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):
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"按钮切换联动状态:
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:
public MotionResult SetSZDZLinkageToPlc()
{
return _motionControlService.SetSourceDetectorZLinkage(SZDZLock);
}
PLC 信号:
| 信号名 | 地址 | 类型 | 说明 |
|---|---|---|---|
MC_SourceDetZ_Linkage_Enable |
101 | bool | 联动使能(true=启用, false=禁用) |
3.2 联动移动 | Linkage Move
当 SZDZLock=true 时,移动 SourceZ 或 DetectorZ 会同时移动两个轴,保持位移量相同:
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 信号下发:
-
SourceZ 移动:
MC_SourceZ_Speed= DefaultVelocity(地址 14)MC_SourceZ_Target= targetValue(地址 10)
-
DetectorZ 移动:
MC_DetZ_Speed= DefaultVelocity(地址 26)MC_DetZ_Target= currentDetectorZ + (targetValue - currentSourceZ)(地址 22)
3.3 联动 Jog | Linkage Jog
当 SZDZLock=true 时,单轴摇杆的左右键会同时控制两个轴:
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
点击"使能开关"按钮切换虚拟摇杆使能状态:
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
点击"交换按键"按钮切换摇杆左右键功能映射:
private void ExecuteToggleSwapMouseButtons()
{
SwapMouseButtons = !SwapMouseButtons;
}
此功能为纯 UI 层逻辑,不涉及 PLC 信号写入。当 SwapMouseButtons=true 时,摇杆控件内部交换左右键的功能映射。
6. 保存/恢复轴位置 | Save/Restore Axis Positions
6.1 保存当前位置 | Save Current Positions
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
恢复时会同时向所有轴发送移动命令:
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 |
校准自动计算值 |
public void ConfirmSafetyHeight()
{
_logger.Info("探测器安全高度限定值已保存:{Value}", SafetyHeight);
}
public void ConfirmCalibrationValue()
{
_logger.Info("校准自动计算值已保存:{Value}", CalibrationValue);
}
注:当前实现仅记录日志,未写入 PLC 信号。后续可能扩展为写入 PLC 安全参数区域。
8. PLC 断连安全处理 | PLC Disconnection Safety
当 PLC 连接断开时,ViewModel 自动执行以下安全操作:
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 事件订阅实体摇杆的激活状态:
_eventAggregator.GetEvent<JoystickActiveEvent>().Subscribe(OnJoystickActiveChanged, ThreadOption.UIThread);
private void OnJoystickActiveChanged(bool isActive)
{
IsJoystickActive = isActive;
}
PLC 信号(读取):
| 信号名 | 说明 |
|---|---|
MC_Joystick_Active |
实体摇杆输入激活状态(由 PLC 轮询读取) |
10. 旋转轴可见性控制 | Rotary Axis Visibility Control
根据配置动态控制旋转轴输入框的可见性:
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() 进入写入队列:
public interface ISignalDataService
{
bool EnqueueWrite(string signalName, object value);
bool EnqueueWriteBatch(Dictionary<string, object> writes);
T GetValueByName<T>(string signalName);
}
队列处理流程:
- 轴实现层(
PlcLinearAxis/PlcRotaryAxis)调用EnqueueWrite(signalName, value) - 信号数据服务将写入请求加入队列
- PLC 服务在轮询周期(默认 100ms)中批量写入
- 写入成功后从队列移除
优势:
- 批量写入减少 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 |
读取 |