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; } } }