using System; using System.Collections.Generic; using System.Linq; using System.Threading; using TeamAAS.Global.Devices; using TeamAAS.Motion.Models; namespace TeamAAS.Motion.Motions { /// /// 仿真运动卡:无硬件环境下的全功能实现(联调/演示/单元测试用)。 /// public class SimulatedMotionCard : IMotionCard { private readonly MotionDeviceConfig _cfg; private readonly int _axisCount; private readonly SimulatedAxis[] _axes; private readonly bool[] _simulatedDi = new bool[100]; private readonly bool[] _simulatedDo = new bool[100]; private Timer _updateTimer; private bool _isConnected; private readonly object _lock = new object(); private const double UpdateIntervalMs = 10; private static readonly string[] DefaultAxisNames = { "X", "Y", "Z", "R" }; public string Name => _cfg.Name; public bool IsConnected => _isConnected; public int AxisCount => _axisCount; public event EventHandler ConnectionStateChanged; public event EventHandler ErrorOccurred; public SimulatedMotionCard(MotionDeviceConfig cfg) { _cfg = cfg ?? new MotionDeviceConfig(); _axisCount = Math.Max(1, _cfg.AxisCount); _axes = new SimulatedAxis[_axisCount]; for (int i = 0; i < _axisCount; i++) { var axis = new SimulatedAxis(i); axis.Name = i < DefaultAxisNames.Length ? DefaultAxisNames[i] : $"Axis-{i}"; _axes[i] = axis; } for (int i = 0; i < _simulatedDi.Length; i++) _simulatedDi[i] = i % 2 == 0; } public bool Open() { lock (_lock) { if (_isConnected) return true; _isConnected = true; _updateTimer = new Timer(UpdateAxes, null, Timeout.Infinite, Timeout.Infinite); } _updateTimer.Change(0, (int)UpdateIntervalMs); ConnectionStateChanged?.Invoke(this, new MotionConnectionStateChangedEventArgs(true)); return true; } public void Close() { Timer timerToDispose = null; lock (_lock) { if (!_isConnected) return; _isConnected = false; timerToDispose = _updateTimer; _updateTimer = null; } timerToDispose?.Dispose(); ConnectionStateChanged?.Invoke(this, new MotionConnectionStateChangedEventArgs(false)); } public bool SetEnable(int axis, bool enable) { if (!_isConnected) return false; if (axis < 0) { foreach (var a in _axes) { if (enable) a.Enable(); else a.Disable(); } return true; } if (!IsValidAxis(axis)) return false; if (enable) _axes[axis].Enable(); else _axes[axis].Disable(); return true; } public bool Home(int axis, int mode = 0) { if (!_isConnected || !IsValidAxis(axis)) return false; _axes[axis].Home((HomeMode)mode); return true; } public bool IsHomed(int axis) { if (!IsValidAxis(axis)) return false; return _axes[axis].IsHomed; } public bool MoveAbsolute(int axis, double position, double velocity) { if (!_isConnected || !IsValidAxis(axis)) return false; _axes[axis].Velocity = velocity; _axes[axis].MoveTo(position); return true; } public bool MoveRelative(int axis, double distance, double velocity) { if (!_isConnected || !IsValidAxis(axis)) return false; _axes[axis].Velocity = velocity; _axes[axis].MoveBy(distance); return true; } public bool JogStart(int axis, int direction, double velocity) { if (!_isConnected || !IsValidAxis(axis)) return false; var dir = direction >= 0 ? 1 : -1; _axes[axis].Jog(dir * Math.Abs(velocity)); return true; } public bool JogStop(int axis) { if (!IsValidAxis(axis)) return false; _axes[axis].Stop(); return true; } public bool Stop(int axis = -1) { if (!_isConnected) return false; if (axis < 0) { foreach (var a in _axes) a.Stop(); return true; } if (!IsValidAxis(axis)) return false; _axes[axis].Stop(); return true; } public AxisStatus GetAxisStatus(int axis) { if (!IsValidAxis(axis)) return null; var a = _axes[axis]; return new AxisStatus { Index = axis, Name = a.Name, Position = a.ActualPosition, Velocity = a.ActualVelocity, IsMoving = a.IsMoving, IsHomed = a.IsHomed, IsEnabled = a.IsEnabled }; } public List GetAllAxisStatus() { var list = new List(_axisCount); for (int i = 0; i < _axisCount; i++) list.Add(GetAxisStatus(i)); return list; } public IAxis GetAxis(int axisNo) { if (!IsValidAxis(axisNo)) throw new ArgumentOutOfRangeException(nameof(axisNo), "轴号超出范围"); return _axes[axisNo]; } public IList GetAxes() => _axes.Cast().ToList(); public bool MoveLinear(double[] targetPositions, double velocity) { if (!_isConnected || targetPositions == null) return false; if (targetPositions.Length > _axisCount) return false; for (int i = 0; i < targetPositions.Length; i++) { _axes[i].Velocity = velocity; _axes[i].MoveTo(targetPositions[i]); } return true; } public bool MoveArc(double[] centerPoint, double[] targetPositions, double velocity) { if (!_isConnected || targetPositions == null) return false; if (targetPositions.Length > _axisCount) return false; for (int i = 0; i < targetPositions.Length; i++) { _axes[i].Velocity = velocity; _axes[i].MoveTo(targetPositions[i]); } return true; } public bool EmergencyStop() { if (!_isConnected) return false; foreach (var axis in _axes) axis.Stop(); return true; } public bool ReadDI(int portIndex) { if (portIndex < 0 || portIndex >= _simulatedDi.Length) return false; return _simulatedDi[portIndex]; } public bool WriteDO(int portIndex, bool value) { if (portIndex < 0 || portIndex >= _simulatedDo.Length) return false; _simulatedDo[portIndex] = value; return true; } private bool IsValidAxis(int axis) => axis >= 0 && axis < _axisCount; private void UpdateAxes(object state) { lock (_lock) { if (!_isConnected) return; double deltaTime = UpdateIntervalMs / 1000.0; foreach (var axis in _axes) axis.Update(deltaTime); } } } public class SimulatedAxis : IAxis { private readonly object _lock = new object(); private AxisState _state = AxisState.Off; private bool _isEnabled; private bool _isMoving; private bool _isHomed; private double _commandPos; private double _actualPos; private double _actualVel; private double _targetPos; private double _velocity = 100.0; private double _accel = 500.0; private double _decel = 500.0; private double _jerk = 1000.0; private double _jogVelocity; public int AxisNo { get; } public string Name { get; set; } public AxisState State => _state; public bool IsEnabled => _isEnabled; public bool IsMoving => _isMoving; public bool IsHomed => _isHomed; public double CommandPosition => _commandPos; public double ActualPosition => _actualPos; public double ActualVelocity => _actualVel; public double Velocity { get => _velocity; set => _velocity = value; } public double Acceleration { get => _accel; set => _accel = value; } public double Deceleration { get => _decel; set => _decel = value; } public double Jerk { get => _jerk; set => _jerk = value; } public event EventHandler MotionStarted; public event EventHandler MotionCompleted; public event EventHandler StateChanged; public event EventHandler ErrorOccurred; public SimulatedAxis(int axisNo) { AxisNo = axisNo; Name = $"Axis-{axisNo}"; } internal void Update(double deltaTime) { lock (_lock) { if (!_isEnabled || _state == AxisState.Error || _state == AxisState.Off) return; if (_state == AxisState.ContinuousMotion && Math.Abs(_jogVelocity) > 0.0001) { double delta = _jogVelocity * deltaTime; _commandPos += delta; _actualPos += delta; _actualVel = _jogVelocity; return; } if (_state == AxisState.DiscreteMotion) { double remaining = _targetPos - _actualPos; double distance = Math.Abs(remaining); if (distance < 0.0001) { _actualPos = _targetPos; _commandPos = _targetPos; _actualVel = 0; SetState(AxisState.StandStill); _isMoving = false; OnMotionCompleted(new AxisMotionEventArgs(AxisNo, _targetPos, _actualPos)); return; } double speed = Math.Sign(remaining) * _velocity; double step = speed * deltaTime; if (Math.Abs(step) > Math.Abs(remaining)) step = remaining; _actualPos += step; _commandPos = _actualPos; _actualVel = step / deltaTime; } } } private void StartMoveTo(double target) { lock (_lock) { if (!_isEnabled) { OnError("轴未使能,无法运动"); return; } if (_state == AxisState.Error) { OnError("轴处于错误状态"); return; } _targetPos = target; SetState(AxisState.DiscreteMotion); _isMoving = true; _jogVelocity = 0; OnMotionStarted(new AxisMotionEventArgs(AxisNo, target, _actualPos)); } } public void Enable() { lock (_lock) { if (_isEnabled) return; _isEnabled = true; if (_state == AxisState.Off) SetState(AxisState.StandStill); } } public void Disable() { lock (_lock) { if (!_isEnabled) return; _isMoving = false; _jogVelocity = 0; SetState(AxisState.Off); _isEnabled = false; } } public void MoveTo(double position) => StartMoveTo(position); public void MoveBy(double distance) => StartMoveTo(_actualPos + distance); public void Jog(double velocity) { lock (_lock) { if (!_isEnabled) { OnError("轴未使能,无法点动"); return; } if (_state == AxisState.Error) { OnError("轴处于错误状态"); return; } _jogVelocity = velocity; SetState(AxisState.ContinuousMotion); _isMoving = true; _actualVel = velocity; OnMotionStarted(new AxisMotionEventArgs(AxisNo, double.NaN, _actualPos)); } } public void Stop() { lock (_lock) { if (!_isMoving) return; _jogVelocity = 0; _actualVel = 0; SetState(AxisState.StandStill); _isMoving = false; OnMotionCompleted(new AxisMotionEventArgs(AxisNo, _targetPos, _actualPos)); } } public void Home(HomeMode mode = HomeMode.Default) { lock (_lock) { if (!_isEnabled) { OnError("轴未使能,无法回零"); return; } _isMoving = false; _jogVelocity = 0; _actualVel = 0; _actualPos = 0; _commandPos = 0; _targetPos = 0; _isHomed = true; SetState(AxisState.StandStill); OnMotionCompleted(new AxisMotionEventArgs(AxisNo, 0, 0)); } } public void SetPosition(double position) { lock (_lock) { _actualPos = position; _commandPos = position; } } public void SetMotionParams(MotionParams parameters) { lock (_lock) { if (parameters == null) return; _velocity = parameters.Velocity; _accel = parameters.Acceleration; _decel = parameters.Deceleration; _jerk = parameters.Jerk; } } private void SetState(AxisState newState) { if (_state == newState) return; var old = _state; _state = newState; OnStateChanged(new AxisStateChangedEventArgs(AxisNo, old, newState)); } protected virtual void OnMotionStarted(AxisMotionEventArgs e) => MotionStarted?.Invoke(this, e); protected virtual void OnMotionCompleted(AxisMotionEventArgs e) => MotionCompleted?.Invoke(this, e); protected virtual void OnStateChanged(AxisStateChangedEventArgs e) => StateChanged?.Invoke(this, e); protected virtual void OnError(string message) => ErrorOccurred?.Invoke(this, new AxisErrorEventArgs(AxisNo, message)); } public class SimulatedIOCard : IIOCard { private readonly MotionDeviceConfig _cfg; private readonly bool[] _inputs; private readonly bool[] _outputs; public string Name => _cfg.Name; public int InputCount => _inputs.Length; public int OutputCount => _outputs.Length; public bool IsConnected { get; private set; } public SimulatedIOCard(MotionDeviceConfig cfg) { _cfg = cfg ?? new MotionDeviceConfig(); _inputs = new bool[Math.Max(1, _cfg.InputCount)]; _outputs = new bool[Math.Max(1, _cfg.OutputCount)]; } public bool Open() { IsConnected = true; return true; } public void Close() { IsConnected = false; } public bool[] ReadAllInputs() { lock (_inputs) { return (bool[])_inputs.Clone(); } } public bool[] ReadAllOutputs() { lock (_outputs) { return (bool[])_outputs.Clone(); } } public bool WriteOutput(int index, bool value) { if (index < 0 || index >= _outputs.Length) return false; lock (_outputs) { _outputs[index] = value; } return true; } public bool WriteOutputs(bool[] values) { if (values == null) return false; lock (_outputs) { int n = Math.Min(values.Length, _outputs.Length); for (int i = 0; i < n; i++) _outputs[i] = values[i]; } return true; } } }