212 lines
6.5 KiB
C#
212 lines
6.5 KiB
C#
using System;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using XP.Hardware.MotionControl.Abstractions;
|
|
using XP.Hardware.MotionControl.Abstractions.Enums;
|
|
|
|
namespace XP.Hardware.MotionControl.Implementations
|
|
{
|
|
/// <summary>
|
|
/// 虚拟旋转轴实现 | Simulated Rotary Axis Implementation
|
|
/// 通过后台任务线性插值模拟旋转轴移动,无需真实 PLC 硬件
|
|
/// Simulates rotary axis movement via background task with linear interpolation, no real PLC required
|
|
/// </summary>
|
|
public class SimulatedRotaryAxis : RotaryAxisBase
|
|
{
|
|
private const int UpdateIntervalMs = 20;
|
|
|
|
private readonly double _defaultSpeed;
|
|
private readonly object _lock = new object();
|
|
|
|
private CancellationTokenSource _moveCts;
|
|
private Task _moveTask;
|
|
private double _jogSpeedPercent = 50.0;
|
|
|
|
/// <summary>
|
|
/// 构造函数 | Constructor
|
|
/// </summary>
|
|
/// <param name="axisId">轴标识 | Axis identifier</param>
|
|
/// <param name="minAngle">最小角度(度)| Minimum angle (degrees)</param>
|
|
/// <param name="maxAngle">最大角度(度)| Maximum angle (degrees)</param>
|
|
/// <param name="enabled">是否启用 | Is enabled</param>
|
|
/// <param name="defaultSpeed">默认角速度(度/秒)| Default angular speed (degrees/s)</param>
|
|
public SimulatedRotaryAxis(RotaryAxisId axisId, double minAngle, double maxAngle, bool enabled, double defaultSpeed = 30.0)
|
|
: base(axisId, minAngle, maxAngle, enabled)
|
|
{
|
|
_defaultSpeed = defaultSpeed;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override MotionResult MoveToTarget(double targetAngle, double? speed = null)
|
|
{
|
|
if (!ValidateTarget(targetAngle))
|
|
return MotionResult.Fail($"[Simulated] 目标角度 {targetAngle} 超出范围 [{_minAngle}, {_maxAngle}]");
|
|
|
|
lock (_lock)
|
|
{
|
|
if (Status == AxisStatus.Moving)
|
|
{
|
|
CancelCurrentMove();
|
|
}
|
|
|
|
var moveSpeed = speed ?? _defaultSpeed;
|
|
Status = AxisStatus.Moving;
|
|
|
|
_moveCts = new CancellationTokenSource();
|
|
var token = _moveCts.Token;
|
|
|
|
_moveTask = Task.Run(() => ExecuteMove(targetAngle, moveSpeed, token), token);
|
|
}
|
|
|
|
return MotionResult.Ok();
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override MotionResult Stop()
|
|
{
|
|
lock (_lock)
|
|
{
|
|
CancelCurrentMove();
|
|
Status = AxisStatus.Idle;
|
|
}
|
|
|
|
return MotionResult.Ok();
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override MotionResult Home()
|
|
{
|
|
lock (_lock)
|
|
{
|
|
if (Status == AxisStatus.Moving)
|
|
{
|
|
CancelCurrentMove();
|
|
}
|
|
|
|
Status = AxisStatus.Homing;
|
|
|
|
_moveCts = new CancellationTokenSource();
|
|
var token = _moveCts.Token;
|
|
|
|
_moveTask = Task.Run(async () =>
|
|
{
|
|
await ExecuteMove(0, _defaultSpeed, token);
|
|
if (!token.IsCancellationRequested)
|
|
{
|
|
Status = AxisStatus.Idle;
|
|
}
|
|
}, token);
|
|
}
|
|
|
|
return MotionResult.Ok();
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override MotionResult JogStart(bool positive)
|
|
{
|
|
lock (_lock)
|
|
{
|
|
if (Status == AxisStatus.Moving || Status == AxisStatus.Homing)
|
|
{
|
|
CancelCurrentMove();
|
|
}
|
|
|
|
var target = positive ? _maxAngle : _minAngle;
|
|
var jogSpeed = _defaultSpeed * (_jogSpeedPercent / 100.0);
|
|
|
|
Status = AxisStatus.Moving;
|
|
|
|
_moveCts = new CancellationTokenSource();
|
|
var token = _moveCts.Token;
|
|
|
|
_moveTask = Task.Run(() => ExecuteMove(target, jogSpeed, token), token);
|
|
}
|
|
|
|
return MotionResult.Ok();
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override MotionResult JogStop()
|
|
{
|
|
lock (_lock)
|
|
{
|
|
CancelCurrentMove();
|
|
Status = AxisStatus.Idle;
|
|
}
|
|
|
|
return MotionResult.Ok();
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override MotionResult SetJogSpeed(double speedPercent)
|
|
{
|
|
if (speedPercent < 0 || speedPercent > 100)
|
|
return MotionResult.Fail($"[Simulated] Jog 速度百分比 {speedPercent} 超出范围 [0, 100]");
|
|
|
|
_jogSpeedPercent = speedPercent;
|
|
return MotionResult.Ok();
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override void UpdateStatus()
|
|
{
|
|
// 虚拟轴无需从 PLC 轮询状态 | No PLC polling needed for simulated axis
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行线性插值旋转移动 | Execute linear interpolation rotary move
|
|
/// </summary>
|
|
private async Task ExecuteMove(double targetAngle, double speed, CancellationToken token)
|
|
{
|
|
var startAngle = ActualAngle;
|
|
var distance = targetAngle - startAngle;
|
|
|
|
if (Math.Abs(distance) < 0.001)
|
|
{
|
|
Status = AxisStatus.Idle;
|
|
return;
|
|
}
|
|
|
|
var totalTime = Math.Abs(distance) / speed; // seconds
|
|
var elapsed = 0.0;
|
|
|
|
while (!token.IsCancellationRequested)
|
|
{
|
|
try
|
|
{
|
|
await Task.Delay(UpdateIntervalMs, token);
|
|
}
|
|
catch (TaskCanceledException)
|
|
{
|
|
return;
|
|
}
|
|
|
|
elapsed += UpdateIntervalMs / 1000.0;
|
|
|
|
if (elapsed >= totalTime)
|
|
{
|
|
ActualAngle = targetAngle;
|
|
Status = AxisStatus.Idle;
|
|
return;
|
|
}
|
|
|
|
var progress = elapsed / totalTime;
|
|
ActualAngle = startAngle + distance * progress;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 取消当前移动任务 | Cancel current move task
|
|
/// </summary>
|
|
private void CancelCurrentMove()
|
|
{
|
|
if (_moveCts != null)
|
|
{
|
|
_moveCts.Cancel();
|
|
_moveCts.Dispose();
|
|
_moveCts = null;
|
|
}
|
|
}
|
|
}
|
|
}
|