Files
XplorePlane/XP.Hardware.MotionControl/Implementations/SimulatedLinearAxis.cs
T

224 lines
6.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
}
}