224 lines
6.8 KiB
C#
224 lines
6.8 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 Linear Axis Implementation
|
||
/// 通过后台任务线性插值模拟轴移动,无需真实 PLC 硬件
|
||
/// Simulates axis movement via background task with linear interpolation, no real PLC required
|
||
/// </summary>
|
||
public class SimulatedLinearAxis : LinearAxisBase
|
||
{
|
||
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="min">最小位置(mm)| Minimum position (mm)</param>
|
||
/// <param name="max">最大位置(mm)| Maximum position (mm)</param>
|
||
/// <param name="origin">原点偏移(mm)| Origin offset (mm)</param>
|
||
/// <param name="defaultSpeed">默认速度(mm/s)| Default speed (mm/s)</param>
|
||
public SimulatedLinearAxis(AxisId axisId, double min, double max, double origin, double defaultSpeed = 5.0)
|
||
: base(axisId, min, max, origin)
|
||
{
|
||
_defaultSpeed = defaultSpeed;
|
||
}
|
||
|
||
/// <inheritdoc/>
|
||
public override MotionResult MoveToTarget(double target, double? speed = null)
|
||
{
|
||
if (!ValidateTarget(target))
|
||
return MotionResult.Fail($"[Simulated] 目标位置 {target} 超出范围 [{_min}, {_max}]");
|
||
|
||
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(target, 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 ? _max : _min;
|
||
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 move
|
||
/// </summary>
|
||
private async Task ExecuteMove(double target, double speed, CancellationToken token)
|
||
{
|
||
var startPosition = ActualPosition;
|
||
var distance = target - startPosition;
|
||
|
||
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)
|
||
{
|
||
ActualPosition = target;
|
||
UpdateLimitFlags();
|
||
Status = AxisStatus.Idle;
|
||
return;
|
||
}
|
||
|
||
var progress = elapsed / totalTime;
|
||
ActualPosition = startPosition + distance * progress;
|
||
UpdateLimitFlags();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 取消当前移动任务 | Cancel current move task
|
||
/// </summary>
|
||
private void CancelCurrentMove()
|
||
{
|
||
if (_moveCts != null)
|
||
{
|
||
_moveCts.Cancel();
|
||
_moveCts.Dispose();
|
||
_moveCts = null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新限位标志 | Update limit flags
|
||
/// </summary>
|
||
private void UpdateLimitFlags()
|
||
{
|
||
PositiveLimitHit = ActualPosition >= _max;
|
||
NegativeLimitHit = ActualPosition <= _min;
|
||
}
|
||
}
|
||
}
|