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 Rotary Axis Implementation /// 通过后台任务线性插值模拟旋转轴移动,无需真实 PLC 硬件 /// Simulates rotary axis movement via background task with linear interpolation, no real PLC required /// 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; /// /// 构造函数 | Constructor /// /// 轴标识 | Axis identifier /// 最小角度(度)| Minimum angle (degrees) /// 最大角度(度)| Maximum angle (degrees) /// 是否启用 | Is enabled /// 默认角速度(度/秒)| Default angular speed (degrees/s) public SimulatedRotaryAxis(RotaryAxisId axisId, double minAngle, double maxAngle, bool enabled, double defaultSpeed = 30.0) : base(axisId, minAngle, maxAngle, enabled) { _defaultSpeed = defaultSpeed; } /// 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(); } /// 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 ? _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(); } /// 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 rotary move /// 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; } } /// /// 取消当前移动任务 | Cancel current move task /// private void CancelCurrentMove() { if (_moveCts != null) { _moveCts.Cancel(); _moveCts.Dispose(); _moveCts = null; } } } }