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
{
///
/// 虚拟直线轴实现 | Simulated Linear Axis Implementation
/// 通过后台任务线性插值模拟轴移动,无需真实 PLC 硬件
/// Simulates axis movement via background task with linear interpolation, no real PLC required
///
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;
///
/// 构造函数 | Constructor
///
/// 轴标识 | Axis identifier
/// 最小位置(mm)| Minimum position (mm)
/// 最大位置(mm)| Maximum position (mm)
/// 原点偏移(mm)| Origin offset (mm)
/// 默认速度(mm/s)| Default speed (mm/s)
public SimulatedLinearAxis(AxisId axisId, double min, double max, double origin, double defaultSpeed = 5.0)
: base(axisId, min, max, origin)
{
_defaultSpeed = defaultSpeed;
}
///
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();
}
///
public override MotionResult Stop()
{
lock (_lock)
{
CancelCurrentMove();
Status = AxisStatus.Idle;
}
return MotionResult.Ok();
}
///
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();
}
///
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();
}
///
public override MotionResult JogStop()
{
lock (_lock)
{
CancelCurrentMove();
Status = AxisStatus.Idle;
}
return MotionResult.Ok();
}
///
public override MotionResult SetJogSpeed(double speedPercent)
{
if (speedPercent < 0 || speedPercent > 100)
return MotionResult.Fail($"[Simulated] Jog 速度百分比 {speedPercent} 超出范围 [0, 100]");
_jogSpeedPercent = speedPercent;
return MotionResult.Ok();
}
///
public override void UpdateStatus()
{
// 虚拟轴无需从 PLC 轮询状态 | No PLC polling needed for simulated axis
}
///
/// 执行线性插值移动 | Execute linear interpolation move
///
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();
}
}
///
/// 取消当前移动任务 | Cancel current move task
///
private void CancelCurrentMove()
{
if (_moveCts != null)
{
_moveCts.Cancel();
_moveCts.Dispose();
_moveCts = null;
}
}
///
/// 更新限位标志 | Update limit flags
///
private void UpdateLimitFlags()
{
PositiveLimitHit = ActualPosition >= _max;
NegativeLimitHit = ActualPosition <= _min;
}
}
}