SimulatedMotionCard.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Threading;
  5. using TeamAAS.Global.Devices;
  6. using TeamAAS.Motion.Models;
  7. namespace TeamAAS.Motion.Motions
  8. {
  9. /// <summary>
  10. /// 仿真运动卡:无硬件环境下的全功能实现(联调/演示/单元测试用)。
  11. /// </summary>
  12. public class SimulatedMotionCard : IMotionCard
  13. {
  14. private readonly MotionDeviceConfig _cfg;
  15. private readonly int _axisCount;
  16. private readonly SimulatedAxis[] _axes;
  17. private readonly bool[] _simulatedDi = new bool[100];
  18. private readonly bool[] _simulatedDo = new bool[100];
  19. private Timer _updateTimer;
  20. private bool _isConnected;
  21. private readonly object _lock = new object();
  22. private const double UpdateIntervalMs = 10;
  23. private static readonly string[] DefaultAxisNames = { "X", "Y", "Z", "R" };
  24. public string Name => _cfg.Name;
  25. public bool IsConnected => _isConnected;
  26. public int AxisCount => _axisCount;
  27. public event EventHandler<MotionConnectionStateChangedEventArgs> ConnectionStateChanged;
  28. public event EventHandler<MotionErrorEventArgs> ErrorOccurred;
  29. public SimulatedMotionCard(MotionDeviceConfig cfg)
  30. {
  31. _cfg = cfg ?? new MotionDeviceConfig();
  32. _axisCount = Math.Max(1, _cfg.AxisCount);
  33. _axes = new SimulatedAxis[_axisCount];
  34. for (int i = 0; i < _axisCount; i++)
  35. {
  36. var axis = new SimulatedAxis(i);
  37. axis.Name = i < DefaultAxisNames.Length ? DefaultAxisNames[i] : $"Axis-{i}";
  38. _axes[i] = axis;
  39. }
  40. for (int i = 0; i < _simulatedDi.Length; i++)
  41. _simulatedDi[i] = i % 2 == 0;
  42. }
  43. public bool Open()
  44. {
  45. lock (_lock)
  46. {
  47. if (_isConnected) return true;
  48. _isConnected = true;
  49. _updateTimer = new Timer(UpdateAxes, null, Timeout.Infinite, Timeout.Infinite);
  50. }
  51. _updateTimer.Change(0, (int)UpdateIntervalMs);
  52. ConnectionStateChanged?.Invoke(this, new MotionConnectionStateChangedEventArgs(true));
  53. return true;
  54. }
  55. public void Close()
  56. {
  57. Timer timerToDispose = null;
  58. lock (_lock)
  59. {
  60. if (!_isConnected) return;
  61. _isConnected = false;
  62. timerToDispose = _updateTimer;
  63. _updateTimer = null;
  64. }
  65. timerToDispose?.Dispose();
  66. ConnectionStateChanged?.Invoke(this, new MotionConnectionStateChangedEventArgs(false));
  67. }
  68. public bool SetEnable(int axis, bool enable)
  69. {
  70. if (!_isConnected) return false;
  71. if (axis < 0)
  72. {
  73. foreach (var a in _axes) { if (enable) a.Enable(); else a.Disable(); }
  74. return true;
  75. }
  76. if (!IsValidAxis(axis)) return false;
  77. if (enable) _axes[axis].Enable(); else _axes[axis].Disable();
  78. return true;
  79. }
  80. public bool Home(int axis, int mode = 0)
  81. {
  82. if (!_isConnected || !IsValidAxis(axis)) return false;
  83. _axes[axis].Home((HomeMode)mode);
  84. return true;
  85. }
  86. public bool IsHomed(int axis)
  87. {
  88. if (!IsValidAxis(axis)) return false;
  89. return _axes[axis].IsHomed;
  90. }
  91. public bool MoveAbsolute(int axis, double position, double velocity)
  92. {
  93. if (!_isConnected || !IsValidAxis(axis)) return false;
  94. _axes[axis].Velocity = velocity;
  95. _axes[axis].MoveTo(position);
  96. return true;
  97. }
  98. public bool MoveRelative(int axis, double distance, double velocity)
  99. {
  100. if (!_isConnected || !IsValidAxis(axis)) return false;
  101. _axes[axis].Velocity = velocity;
  102. _axes[axis].MoveBy(distance);
  103. return true;
  104. }
  105. public bool JogStart(int axis, int direction, double velocity)
  106. {
  107. if (!_isConnected || !IsValidAxis(axis)) return false;
  108. var dir = direction >= 0 ? 1 : -1;
  109. _axes[axis].Jog(dir * Math.Abs(velocity));
  110. return true;
  111. }
  112. public bool JogStop(int axis)
  113. {
  114. if (!IsValidAxis(axis)) return false;
  115. _axes[axis].Stop();
  116. return true;
  117. }
  118. public bool Stop(int axis = -1)
  119. {
  120. if (!_isConnected) return false;
  121. if (axis < 0)
  122. {
  123. foreach (var a in _axes) a.Stop();
  124. return true;
  125. }
  126. if (!IsValidAxis(axis)) return false;
  127. _axes[axis].Stop();
  128. return true;
  129. }
  130. public AxisStatus GetAxisStatus(int axis)
  131. {
  132. if (!IsValidAxis(axis)) return null;
  133. var a = _axes[axis];
  134. return new AxisStatus
  135. {
  136. Index = axis,
  137. Name = a.Name,
  138. Position = a.ActualPosition,
  139. Velocity = a.ActualVelocity,
  140. IsMoving = a.IsMoving,
  141. IsHomed = a.IsHomed,
  142. IsEnabled = a.IsEnabled
  143. };
  144. }
  145. public List<AxisStatus> GetAllAxisStatus()
  146. {
  147. var list = new List<AxisStatus>(_axisCount);
  148. for (int i = 0; i < _axisCount; i++)
  149. list.Add(GetAxisStatus(i));
  150. return list;
  151. }
  152. public IAxis GetAxis(int axisNo)
  153. {
  154. if (!IsValidAxis(axisNo))
  155. throw new ArgumentOutOfRangeException(nameof(axisNo), "轴号超出范围");
  156. return _axes[axisNo];
  157. }
  158. public IList<IAxis> GetAxes() => _axes.Cast<IAxis>().ToList();
  159. public bool MoveLinear(double[] targetPositions, double velocity)
  160. {
  161. if (!_isConnected || targetPositions == null) return false;
  162. if (targetPositions.Length > _axisCount) return false;
  163. for (int i = 0; i < targetPositions.Length; i++)
  164. {
  165. _axes[i].Velocity = velocity;
  166. _axes[i].MoveTo(targetPositions[i]);
  167. }
  168. return true;
  169. }
  170. public bool MoveArc(double[] centerPoint, double[] targetPositions, double velocity)
  171. {
  172. if (!_isConnected || targetPositions == null) return false;
  173. if (targetPositions.Length > _axisCount) return false;
  174. for (int i = 0; i < targetPositions.Length; i++)
  175. {
  176. _axes[i].Velocity = velocity;
  177. _axes[i].MoveTo(targetPositions[i]);
  178. }
  179. return true;
  180. }
  181. public bool EmergencyStop()
  182. {
  183. if (!_isConnected) return false;
  184. foreach (var axis in _axes) axis.Stop();
  185. return true;
  186. }
  187. public bool ReadDI(int portIndex)
  188. {
  189. if (portIndex < 0 || portIndex >= _simulatedDi.Length) return false;
  190. return _simulatedDi[portIndex];
  191. }
  192. public bool WriteDO(int portIndex, bool value)
  193. {
  194. if (portIndex < 0 || portIndex >= _simulatedDo.Length) return false;
  195. _simulatedDo[portIndex] = value;
  196. return true;
  197. }
  198. private bool IsValidAxis(int axis) => axis >= 0 && axis < _axisCount;
  199. private void UpdateAxes(object state)
  200. {
  201. lock (_lock)
  202. {
  203. if (!_isConnected) return;
  204. double deltaTime = UpdateIntervalMs / 1000.0;
  205. foreach (var axis in _axes)
  206. axis.Update(deltaTime);
  207. }
  208. }
  209. }
  210. public class SimulatedAxis : IAxis
  211. {
  212. private readonly object _lock = new object();
  213. private AxisState _state = AxisState.Off;
  214. private bool _isEnabled;
  215. private bool _isMoving;
  216. private bool _isHomed;
  217. private double _commandPos;
  218. private double _actualPos;
  219. private double _actualVel;
  220. private double _targetPos;
  221. private double _velocity = 100.0;
  222. private double _accel = 500.0;
  223. private double _decel = 500.0;
  224. private double _jerk = 1000.0;
  225. private double _jogVelocity;
  226. public int AxisNo { get; }
  227. public string Name { get; set; }
  228. public AxisState State => _state;
  229. public bool IsEnabled => _isEnabled;
  230. public bool IsMoving => _isMoving;
  231. public bool IsHomed => _isHomed;
  232. public double CommandPosition => _commandPos;
  233. public double ActualPosition => _actualPos;
  234. public double ActualVelocity => _actualVel;
  235. public double Velocity { get => _velocity; set => _velocity = value; }
  236. public double Acceleration { get => _accel; set => _accel = value; }
  237. public double Deceleration { get => _decel; set => _decel = value; }
  238. public double Jerk { get => _jerk; set => _jerk = value; }
  239. public event EventHandler<AxisMotionEventArgs> MotionStarted;
  240. public event EventHandler<AxisMotionEventArgs> MotionCompleted;
  241. public event EventHandler<AxisStateChangedEventArgs> StateChanged;
  242. public event EventHandler<AxisErrorEventArgs> ErrorOccurred;
  243. public SimulatedAxis(int axisNo)
  244. {
  245. AxisNo = axisNo;
  246. Name = $"Axis-{axisNo}";
  247. }
  248. internal void Update(double deltaTime)
  249. {
  250. lock (_lock)
  251. {
  252. if (!_isEnabled || _state == AxisState.Error || _state == AxisState.Off)
  253. return;
  254. if (_state == AxisState.ContinuousMotion && Math.Abs(_jogVelocity) > 0.0001)
  255. {
  256. double delta = _jogVelocity * deltaTime;
  257. _commandPos += delta;
  258. _actualPos += delta;
  259. _actualVel = _jogVelocity;
  260. return;
  261. }
  262. if (_state == AxisState.DiscreteMotion)
  263. {
  264. double remaining = _targetPos - _actualPos;
  265. double distance = Math.Abs(remaining);
  266. if (distance < 0.0001)
  267. {
  268. _actualPos = _targetPos;
  269. _commandPos = _targetPos;
  270. _actualVel = 0;
  271. SetState(AxisState.StandStill);
  272. _isMoving = false;
  273. OnMotionCompleted(new AxisMotionEventArgs(AxisNo, _targetPos, _actualPos));
  274. return;
  275. }
  276. double speed = Math.Sign(remaining) * _velocity;
  277. double step = speed * deltaTime;
  278. if (Math.Abs(step) > Math.Abs(remaining))
  279. step = remaining;
  280. _actualPos += step;
  281. _commandPos = _actualPos;
  282. _actualVel = step / deltaTime;
  283. }
  284. }
  285. }
  286. private void StartMoveTo(double target)
  287. {
  288. lock (_lock)
  289. {
  290. if (!_isEnabled) { OnError("轴未使能,无法运动"); return; }
  291. if (_state == AxisState.Error) { OnError("轴处于错误状态"); return; }
  292. _targetPos = target;
  293. SetState(AxisState.DiscreteMotion);
  294. _isMoving = true;
  295. _jogVelocity = 0;
  296. OnMotionStarted(new AxisMotionEventArgs(AxisNo, target, _actualPos));
  297. }
  298. }
  299. public void Enable()
  300. {
  301. lock (_lock)
  302. {
  303. if (_isEnabled) return;
  304. _isEnabled = true;
  305. if (_state == AxisState.Off) SetState(AxisState.StandStill);
  306. }
  307. }
  308. public void Disable()
  309. {
  310. lock (_lock)
  311. {
  312. if (!_isEnabled) return;
  313. _isMoving = false;
  314. _jogVelocity = 0;
  315. SetState(AxisState.Off);
  316. _isEnabled = false;
  317. }
  318. }
  319. public void MoveTo(double position) => StartMoveTo(position);
  320. public void MoveBy(double distance) => StartMoveTo(_actualPos + distance);
  321. public void Jog(double velocity)
  322. {
  323. lock (_lock)
  324. {
  325. if (!_isEnabled) { OnError("轴未使能,无法点动"); return; }
  326. if (_state == AxisState.Error) { OnError("轴处于错误状态"); return; }
  327. _jogVelocity = velocity;
  328. SetState(AxisState.ContinuousMotion);
  329. _isMoving = true;
  330. _actualVel = velocity;
  331. OnMotionStarted(new AxisMotionEventArgs(AxisNo, double.NaN, _actualPos));
  332. }
  333. }
  334. public void Stop()
  335. {
  336. lock (_lock)
  337. {
  338. if (!_isMoving) return;
  339. _jogVelocity = 0;
  340. _actualVel = 0;
  341. SetState(AxisState.StandStill);
  342. _isMoving = false;
  343. OnMotionCompleted(new AxisMotionEventArgs(AxisNo, _targetPos, _actualPos));
  344. }
  345. }
  346. public void Home(HomeMode mode = HomeMode.Default)
  347. {
  348. lock (_lock)
  349. {
  350. if (!_isEnabled) { OnError("轴未使能,无法回零"); return; }
  351. _isMoving = false;
  352. _jogVelocity = 0;
  353. _actualVel = 0;
  354. _actualPos = 0;
  355. _commandPos = 0;
  356. _targetPos = 0;
  357. _isHomed = true;
  358. SetState(AxisState.StandStill);
  359. OnMotionCompleted(new AxisMotionEventArgs(AxisNo, 0, 0));
  360. }
  361. }
  362. public void SetPosition(double position)
  363. {
  364. lock (_lock)
  365. {
  366. _actualPos = position;
  367. _commandPos = position;
  368. }
  369. }
  370. public void SetMotionParams(MotionParams parameters)
  371. {
  372. lock (_lock)
  373. {
  374. if (parameters == null) return;
  375. _velocity = parameters.Velocity;
  376. _accel = parameters.Acceleration;
  377. _decel = parameters.Deceleration;
  378. _jerk = parameters.Jerk;
  379. }
  380. }
  381. private void SetState(AxisState newState)
  382. {
  383. if (_state == newState) return;
  384. var old = _state;
  385. _state = newState;
  386. OnStateChanged(new AxisStateChangedEventArgs(AxisNo, old, newState));
  387. }
  388. protected virtual void OnMotionStarted(AxisMotionEventArgs e) => MotionStarted?.Invoke(this, e);
  389. protected virtual void OnMotionCompleted(AxisMotionEventArgs e) => MotionCompleted?.Invoke(this, e);
  390. protected virtual void OnStateChanged(AxisStateChangedEventArgs e) => StateChanged?.Invoke(this, e);
  391. protected virtual void OnError(string message) => ErrorOccurred?.Invoke(this, new AxisErrorEventArgs(AxisNo, message));
  392. }
  393. public class SimulatedIOCard : IIOCard
  394. {
  395. private readonly MotionDeviceConfig _cfg;
  396. private readonly bool[] _inputs;
  397. private readonly bool[] _outputs;
  398. public string Name => _cfg.Name;
  399. public int InputCount => _inputs.Length;
  400. public int OutputCount => _outputs.Length;
  401. public bool IsConnected { get; private set; }
  402. public SimulatedIOCard(MotionDeviceConfig cfg)
  403. {
  404. _cfg = cfg ?? new MotionDeviceConfig();
  405. _inputs = new bool[Math.Max(1, _cfg.InputCount)];
  406. _outputs = new bool[Math.Max(1, _cfg.OutputCount)];
  407. }
  408. public bool Open() { IsConnected = true; return true; }
  409. public void Close() { IsConnected = false; }
  410. public bool[] ReadAllInputs()
  411. {
  412. lock (_inputs) { return (bool[])_inputs.Clone(); }
  413. }
  414. public bool[] ReadAllOutputs()
  415. {
  416. lock (_outputs) { return (bool[])_outputs.Clone(); }
  417. }
  418. public bool WriteOutput(int index, bool value)
  419. {
  420. if (index < 0 || index >= _outputs.Length) return false;
  421. lock (_outputs) { _outputs[index] = value; }
  422. return true;
  423. }
  424. public bool WriteOutputs(bool[] values)
  425. {
  426. if (values == null) return false;
  427. lock (_outputs)
  428. {
  429. int n = Math.Min(values.Length, _outputs.Length);
  430. for (int i = 0; i < n; i++) _outputs[i] = values[i];
  431. }
  432. return true;
  433. }
  434. }
  435. }