SimulatedMotionCard.cs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848
  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, IMasterFollowHost, IMotionSimulation, IMotionSafetyHost, IMotionIoHost
  13. {
  14. private readonly MotionDeviceConfig _cfg;
  15. private int _axisCount;
  16. private IAxis[] _axes = Array.Empty<IAxis>();
  17. private Dictionary<int, IAxis> _axisMap = new Dictionary<int, IAxis>();
  18. private Dictionary<int, SimulatedAxis> _hwAxes = new Dictionary<int, SimulatedAxis>();
  19. private Dictionary<int, MotionAxisConfig> _axisCfgs = new Dictionary<int, MotionAxisConfig>();
  20. private readonly MasterFollowSession _follow = new MasterFollowSession();
  21. private readonly MotionSafetyGuard _guard;
  22. private readonly bool[] _localDi = new bool[64];
  23. private readonly bool[] _localDo = new bool[64];
  24. private readonly bool[] _ecatDi = new bool[256];
  25. private readonly bool[] _ecatDo = new bool[256];
  26. private Timer _updateTimer;
  27. private bool _isConnected;
  28. private readonly object _lock = new object();
  29. private const double UpdateIntervalMs = 10;
  30. public string Name => _cfg.Name;
  31. public bool IsConnected => _isConnected;
  32. public int AxisCount => _axisCount;
  33. public bool IsEstopActive => _guard.EstopActive;
  34. public bool IsStopActive => _guard.StopActive;
  35. internal MotionDeviceConfig DeviceConfig => _cfg;
  36. public event EventHandler<MotionConnectionStateChangedEventArgs> ConnectionStateChanged;
  37. public event EventHandler<MotionErrorEventArgs> ErrorOccurred;
  38. public event EventHandler SafetyChanged;
  39. public SimulatedMotionCard(MotionDeviceConfig cfg)
  40. {
  41. _cfg = cfg ?? new MotionDeviceConfig();
  42. RebuildAxes();
  43. _guard = new MotionSafetyGuard(
  44. () => _cfg,
  45. ReadDI,
  46. OnSafetyEstopRise,
  47. OnSafetyEstopHold,
  48. OnSafetyStopRise);
  49. _guard.Changed += (_, __) =>
  50. {
  51. try { SafetyChanged?.Invoke(this, EventArgs.Empty); }
  52. catch { }
  53. };
  54. }
  55. public bool AllowEnable(out string reason) => _guard.AllowEnable(out reason);
  56. public bool AllowMotion(out string reason) => _guard.AllowMotion(out reason);
  57. private bool RejectEnable()
  58. {
  59. if (_guard.AllowEnable(out var reason)) return false;
  60. RaiseError(reason);
  61. return true;
  62. }
  63. private bool RejectMotion()
  64. {
  65. if (_guard.AllowMotion(out var reason)) return false;
  66. RaiseError(reason);
  67. return true;
  68. }
  69. private void RaiseError(string message)
  70. {
  71. ErrorOccurred?.Invoke(this, new MotionErrorEventArgs(message));
  72. }
  73. private void OnSafetyEstopRise()
  74. {
  75. if (!_isConnected) return;
  76. EmergencyStop();
  77. }
  78. private void OnSafetyEstopHold()
  79. {
  80. if (!_isConnected) return;
  81. SimulatedAxis[] axes;
  82. lock (_lock)
  83. {
  84. if (!_isConnected) return;
  85. axes = _hwAxes.Values.ToArray();
  86. }
  87. foreach (var a in axes) a.Disable();
  88. }
  89. private void OnSafetyStopRise()
  90. {
  91. if (!_isConnected) return;
  92. Stop(-1);
  93. }
  94. public bool CanSimulateDi => true;
  95. public int GetDiCount(MotionIoBank bank)
  96. => bank == MotionIoBank.EtherCat
  97. ? ClampCount(_cfg.InputCount, _ecatDi.Length, 16)
  98. : ClampCount(_cfg.LocalInputCount, _localDi.Length, 16);
  99. public int GetDoCount(MotionIoBank bank)
  100. => bank == MotionIoBank.EtherCat
  101. ? ClampCount(_cfg.OutputCount, _ecatDo.Length, 16)
  102. : ClampCount(_cfg.LocalOutputCount, _localDo.Length, 16);
  103. private static int ClampCount(int configured, int max, int fallback)
  104. {
  105. if (configured <= 0) return Math.Min(fallback, max);
  106. return Math.Min(configured, max);
  107. }
  108. private static bool[] BankDi(MotionIoBank bank, bool[] local, bool[] ecat)
  109. => bank == MotionIoBank.EtherCat ? ecat : local;
  110. public bool ReadDI(int portIndex) => ReadDI(MotionIoBank.Local, portIndex);
  111. public bool ReadDI(MotionIoBank bank, int index)
  112. {
  113. var bits = BankDi(bank, _localDi, _ecatDi);
  114. if (index < 0 || index >= bits.Length) return false;
  115. return bits[index];
  116. }
  117. public bool ReadDO(MotionIoBank bank, int index)
  118. {
  119. var bits = BankDi(bank, _localDo, _ecatDo);
  120. if (index < 0 || index >= bits.Length) return false;
  121. return bits[index];
  122. }
  123. public bool WriteDO(int portIndex, bool value) => WriteDO(MotionIoBank.Local, portIndex, value);
  124. public bool WriteDO(MotionIoBank bank, int index, bool value)
  125. {
  126. var bits = BankDi(bank, _localDo, _ecatDo);
  127. if (index < 0 || index >= bits.Length) return false;
  128. bits[index] = value;
  129. return true;
  130. }
  131. public bool WriteDI(MotionIoBank bank, int index, bool value)
  132. {
  133. var bits = BankDi(bank, _localDi, _ecatDi);
  134. if (index < 0 || index >= bits.Length) return false;
  135. bits[index] = value;
  136. return true;
  137. }
  138. public void ReloadFromConfig()
  139. {
  140. lock (_lock)
  141. {
  142. _follow.Clear();
  143. RebuildAxes();
  144. }
  145. }
  146. public bool Open()
  147. {
  148. lock (_lock)
  149. {
  150. if (_isConnected)
  151. {
  152. EnsureTimerUnlocked();
  153. return true;
  154. }
  155. _isConnected = true;
  156. EnsureTimerUnlocked();
  157. }
  158. _guard.Start();
  159. ConnectionStateChanged?.Invoke(this, new MotionConnectionStateChangedEventArgs(true));
  160. return true;
  161. }
  162. public void Simulate(double deltaSeconds)
  163. {
  164. if (deltaSeconds <= 0) return;
  165. SimulatedAxis[] axes;
  166. lock (_lock)
  167. {
  168. if (!_isConnected) return;
  169. EnsureTimerUnlocked();
  170. axes = _hwAxes.Values.ToArray();
  171. }
  172. foreach (var axis in axes)
  173. axis.Update(deltaSeconds);
  174. }
  175. public void FinishDiscreteMoves()
  176. {
  177. SimulatedAxis[] axes;
  178. lock (_lock)
  179. {
  180. if (!_isConnected) return;
  181. axes = _hwAxes.Values.ToArray();
  182. }
  183. double dt = 0;
  184. foreach (var axis in axes)
  185. dt = Math.Max(dt, axis.RemainingDiscreteSeconds());
  186. if (dt > 0)
  187. Simulate(dt + 1e-6);
  188. else
  189. {
  190. foreach (var axis in axes)
  191. axis.SnapDiscreteIfIdleVelocity();
  192. }
  193. }
  194. private void EnsureTimerUnlocked()
  195. {
  196. if (!_isConnected) return;
  197. if (_updateTimer != null) return;
  198. _updateTimer = new Timer(UpdateAxes, null, 0, (int)UpdateIntervalMs);
  199. }
  200. public void Close()
  201. {
  202. _guard.Stop();
  203. Timer timerToDispose = null;
  204. lock (_lock)
  205. {
  206. if (!_isConnected) return;
  207. _isConnected = false;
  208. timerToDispose = _updateTimer;
  209. _updateTimer = null;
  210. _follow.Clear();
  211. }
  212. timerToDispose?.Dispose();
  213. ConnectionStateChanged?.Invoke(this, new MotionConnectionStateChangedEventArgs(false));
  214. }
  215. void IMasterFollowHost.BeginSoloMasterFollow(int masterAxisNo, IEnumerable<IAxis> slaves)
  216. => _follow.Begin(masterAxisNo, slaves);
  217. void IMasterFollowHost.ReleaseMasterFollow() => _follow.Release();
  218. IDisposable IMasterFollowHost.SuspendFollowRelease() => _follow.Suspend();
  219. internal MotionAxisConfig GetAxisConfig(int axisNo)
  220. {
  221. if (_axisCfgs.TryGetValue(axisNo, out var cfg) && cfg != null)
  222. return cfg;
  223. return new MotionAxisConfig { AxisNo = axisNo };
  224. }
  225. private void ReleaseFollowIfIndependent(int axis)
  226. {
  227. if (_follow.Suspended || _follow.IsReleasing) return;
  228. if (axis >= 0 && TryCombined(axis, out _)) return;
  229. _follow.Release();
  230. }
  231. public bool SetEnable(int axis, bool enable)
  232. {
  233. if (enable && RejectEnable()) return false;
  234. if (!_isConnected) return false;
  235. if (axis < 0)
  236. {
  237. foreach (var a in _hwAxes.Values) { if (enable) a.Enable(); else a.Disable(); }
  238. return true;
  239. }
  240. if (TryCombined(axis, out var combined))
  241. {
  242. if (enable) combined.Enable(); else combined.Disable();
  243. return true;
  244. }
  245. if (!_hwAxes.TryGetValue(axis, out var hw)) return false;
  246. if (enable) hw.Enable(); else hw.Disable();
  247. return true;
  248. }
  249. public bool Home(int axis, int mode = 0)
  250. {
  251. if (RejectMotion()) return false;
  252. if (!_isConnected || !IsValidAxis(axis)) return false;
  253. if (TryCombined(axis, out var combined))
  254. {
  255. combined.Home((HomeMode)mode);
  256. return true;
  257. }
  258. ReleaseFollowIfIndependent(axis);
  259. if (!_hwAxes.TryGetValue(axis, out var hw)) return false;
  260. hw.Home((HomeMode)mode);
  261. return true;
  262. }
  263. public bool IsHomed(int axis)
  264. {
  265. if (!IsValidAxis(axis)) return false;
  266. return _axisMap[axis].IsHomed;
  267. }
  268. public bool MoveAbsolute(int axis, double position, double velocity)
  269. {
  270. if (RejectMotion()) return false;
  271. if (!_isConnected || !IsValidAxis(axis)) return false;
  272. if (TryCombined(axis, out var combined))
  273. {
  274. combined.Velocity = velocity;
  275. combined.MoveTo(position);
  276. return true;
  277. }
  278. ReleaseFollowIfIndependent(axis);
  279. var a = _axisMap[axis];
  280. var cfg = GetAxisConfig(axis);
  281. a.Velocity = Math.Abs(cfg.ClampVelocity(velocity));
  282. a.MoveTo(cfg.ClampPosition(position));
  283. return true;
  284. }
  285. public bool MoveRelative(int axis, double distance, double velocity)
  286. {
  287. if (RejectMotion()) return false;
  288. if (!_isConnected || !IsValidAxis(axis)) return false;
  289. if (TryCombined(axis, out var combined))
  290. {
  291. combined.Velocity = velocity;
  292. combined.MoveBy(distance);
  293. return true;
  294. }
  295. ReleaseFollowIfIndependent(axis);
  296. var a = _axisMap[axis];
  297. var cfg = GetAxisConfig(axis);
  298. a.Velocity = Math.Abs(cfg.ClampVelocity(velocity));
  299. double target = cfg.ClampPosition(a.CommandPosition + distance);
  300. a.MoveTo(target);
  301. return true;
  302. }
  303. public bool JogStart(int axis, int direction, double velocity)
  304. {
  305. if (RejectMotion()) return false;
  306. if (!_isConnected || !IsValidAxis(axis)) return false;
  307. if (TryCombined(axis, out var combined))
  308. {
  309. combined.Jog((direction >= 0 ? 1 : -1) * Math.Abs(velocity));
  310. return true;
  311. }
  312. ReleaseFollowIfIndependent(axis);
  313. var dir = direction >= 0 ? 1 : -1;
  314. var axisObj = _axisMap[axis];
  315. double vel = GetAxisConfig(axis).ClampJog(dir * Math.Abs(velocity), axisObj.CommandPosition);
  316. axisObj.Jog(vel);
  317. return true;
  318. }
  319. public bool JogStop(int axis)
  320. {
  321. if (!IsValidAxis(axis)) return false;
  322. return Stop(axis);
  323. }
  324. public bool Stop(int axis = -1)
  325. {
  326. if (!_isConnected) return false;
  327. if (axis < 0)
  328. {
  329. _follow.Release();
  330. foreach (var a in _hwAxes.Values) a.Stop();
  331. return true;
  332. }
  333. if (!IsValidAxis(axis)) return false;
  334. if (TryCombined(axis, out var combined))
  335. {
  336. combined.Stop();
  337. return true;
  338. }
  339. ReleaseFollowIfIndependent(axis);
  340. _axisMap[axis].Stop();
  341. return true;
  342. }
  343. public AxisStatus GetAxisStatus(int axis)
  344. {
  345. if (!IsValidAxis(axis)) return null;
  346. var a = _axisMap[axis];
  347. return new AxisStatus
  348. {
  349. Index = axis,
  350. Name = a.Name,
  351. Position = a.ActualPosition,
  352. Velocity = a.ActualVelocity,
  353. IsMoving = a.IsMoving,
  354. IsHomed = a.IsHomed,
  355. IsEnabled = a.IsEnabled
  356. };
  357. }
  358. public List<AxisStatus> GetAllAxisStatus()
  359. {
  360. var list = new List<AxisStatus>(_axes.Length);
  361. foreach (var a in _axes)
  362. list.Add(GetAxisStatus(a.AxisNo));
  363. return list;
  364. }
  365. public IAxis GetAxis(int axisNo)
  366. {
  367. if (!IsValidAxis(axisNo))
  368. throw new ArgumentOutOfRangeException(nameof(axisNo), "轴号超出范围");
  369. return _axisMap[axisNo];
  370. }
  371. public IList<IAxis> GetAxes() => _axes.ToList();
  372. public bool MoveLinear(double[] targetPositions, double velocity)
  373. {
  374. if (RejectMotion()) return false;
  375. if (!_isConnected || targetPositions == null) return false;
  376. _follow.Release();
  377. var hw = _hwAxes.Values.OrderBy(a => a.AxisNo).ToList();
  378. if (targetPositions.Length > hw.Count) return false;
  379. for (int i = 0; i < targetPositions.Length; i++)
  380. {
  381. var cfg = GetAxisConfig(hw[i].AxisNo);
  382. hw[i].Velocity = Math.Abs(cfg.ClampVelocity(velocity));
  383. hw[i].MoveTo(cfg.ClampPosition(targetPositions[i]));
  384. }
  385. return true;
  386. }
  387. public bool MoveArc(double[] centerPoint, double[] targetPositions, double velocity)
  388. => MoveLinear(targetPositions, velocity);
  389. public bool EmergencyStop()
  390. {
  391. if (!_isConnected) return false;
  392. _follow.Clear();
  393. foreach (var axis in _hwAxes.Values) axis.Stop();
  394. return true;
  395. }
  396. private bool IsValidAxis(int axis) => _axisMap.ContainsKey(axis);
  397. private bool TryCombined(int axis, out CombinedMotionAxis combined)
  398. {
  399. combined = null;
  400. if (_axisMap.TryGetValue(axis, out var a) && a is CombinedMotionAxis c)
  401. {
  402. combined = c;
  403. return true;
  404. }
  405. return false;
  406. }
  407. private void RebuildAxes()
  408. {
  409. _follow.Clear();
  410. var oldHw = _hwAxes;
  411. var cfgs = MotionAxisCatalog.GetEffective(_cfg);
  412. _hwAxes = new Dictionary<int, SimulatedAxis>();
  413. _axisCfgs = new Dictionary<int, MotionAxisConfig>();
  414. _axisMap = new Dictionary<int, IAxis>();
  415. var view = new List<IAxis>(cfgs.Count);
  416. foreach (var cfg in cfgs)
  417. {
  418. var axis = new SimulatedAxis(this, cfg.AxisNo)
  419. {
  420. Name = string.IsNullOrWhiteSpace(cfg.Name) ? $"Axis-{cfg.AxisNo}" : cfg.Name,
  421. Velocity = cfg.Velocity,
  422. Acceleration = cfg.Acceleration,
  423. Deceleration = cfg.Deceleration,
  424. Jerk = cfg.Jerk
  425. };
  426. if (oldHw != null && oldHw.TryGetValue(cfg.AxisNo, out var prev))
  427. {
  428. axis.SetPosition(prev.ActualPosition);
  429. }
  430. _hwAxes[cfg.AxisNo] = axis;
  431. _axisMap[cfg.AxisNo] = axis;
  432. _axisCfgs[cfg.AxisNo] = cfg;
  433. axis.BindSafety(cfg);
  434. }
  435. foreach (var cfg in cfgs)
  436. {
  437. if (cfg.IsGrouped && _hwAxes.TryGetValue(cfg.AxisNo, out var master))
  438. {
  439. var combined = new CombinedMotionAxis(this, cfg, master, no =>
  440. _hwAxes.TryGetValue(no, out var a) ? a : null);
  441. _axisMap[cfg.AxisNo] = combined;
  442. view.Add(combined);
  443. }
  444. else if (_axisMap.TryGetValue(cfg.AxisNo, out var hw))
  445. {
  446. view.Add(hw);
  447. }
  448. }
  449. _axes = view.ToArray();
  450. _axisCount = _axes.Length;
  451. }
  452. private void UpdateAxes(object state)
  453. {
  454. Simulate(UpdateIntervalMs / 1000.0);
  455. }
  456. }
  457. public class SimulatedAxis : IAxis
  458. {
  459. private readonly SimulatedMotionCard _owner;
  460. private readonly object _lock = new object();
  461. private AxisState _state = AxisState.Off;
  462. private bool _isEnabled;
  463. private bool _isMoving;
  464. private bool _isHomed;
  465. private double _commandPos;
  466. private double _actualPos;
  467. private double _actualVel;
  468. private double _targetPos;
  469. private double _velocity = 100.0;
  470. private double _accel = 500.0;
  471. private double _decel = 500.0;
  472. private double _jerk = 1000.0;
  473. private double _jogVelocity;
  474. private MotionAxisConfig _safety = new MotionAxisConfig();
  475. public int AxisNo { get; }
  476. public string Name { get; set; }
  477. public AxisState State => _state;
  478. public bool IsEnabled => _isEnabled;
  479. public bool IsMoving => _isMoving;
  480. public bool IsHomed => _isHomed;
  481. public double CommandPosition => _commandPos;
  482. public double ActualPosition => _actualPos;
  483. public double ActualVelocity => _actualVel;
  484. public double Velocity { get => _velocity; set => _velocity = value; }
  485. public double Acceleration { get => _accel; set => _accel = value; }
  486. public double Deceleration { get => _decel; set => _decel = value; }
  487. public double Jerk { get => _jerk; set => _jerk = value; }
  488. public event EventHandler<AxisMotionEventArgs> MotionStarted;
  489. public event EventHandler<AxisMotionEventArgs> MotionCompleted;
  490. public event EventHandler<AxisStateChangedEventArgs> StateChanged;
  491. public event EventHandler<AxisErrorEventArgs> ErrorOccurred;
  492. public SimulatedAxis(SimulatedMotionCard owner, int axisNo)
  493. {
  494. _owner = owner;
  495. AxisNo = axisNo;
  496. Name = $"Axis-{axisNo}";
  497. }
  498. internal void BindSafety(MotionAxisConfig cfg)
  499. {
  500. _safety = cfg ?? new MotionAxisConfig { AxisNo = AxisNo };
  501. }
  502. internal double RemainingDiscreteSeconds()
  503. {
  504. lock (_lock)
  505. {
  506. if (!_isMoving || _state != AxisState.DiscreteMotion) return 0;
  507. if (_velocity < 1e-9) return 0;
  508. return Math.Abs(_targetPos - _actualPos) / _velocity;
  509. }
  510. }
  511. internal void SnapDiscreteIfIdleVelocity()
  512. {
  513. lock (_lock)
  514. {
  515. if (!_isMoving || _state != AxisState.DiscreteMotion) return;
  516. _actualPos = _targetPos;
  517. _commandPos = _targetPos;
  518. _actualVel = 0;
  519. _isMoving = false;
  520. SetState(AxisState.StandStill);
  521. OnMotionCompleted(new AxisMotionEventArgs(AxisNo, _targetPos, _actualPos));
  522. }
  523. }
  524. internal void Update(double deltaTime)
  525. {
  526. lock (_lock)
  527. {
  528. if (!_isEnabled || _state == AxisState.Error || _state == AxisState.Off)
  529. return;
  530. if (_state == AxisState.ContinuousMotion && Math.Abs(_jogVelocity) > 0.0001)
  531. {
  532. double delta = _jogVelocity * deltaTime;
  533. double next = _safety.ClampPosition(_actualPos + delta);
  534. if (Math.Abs(next - _actualPos) < 1e-9)
  535. {
  536. _jogVelocity = 0;
  537. _actualVel = 0;
  538. SetState(AxisState.StandStill);
  539. _isMoving = false;
  540. OnMotionCompleted(new AxisMotionEventArgs(AxisNo, _actualPos, _actualPos));
  541. return;
  542. }
  543. _commandPos = next;
  544. _actualPos = next;
  545. _actualVel = _jogVelocity;
  546. return;
  547. }
  548. if (_state == AxisState.DiscreteMotion)
  549. {
  550. double remaining = _targetPos - _actualPos;
  551. double distance = Math.Abs(remaining);
  552. if (distance < 0.0001)
  553. {
  554. _actualPos = _targetPos;
  555. _commandPos = _targetPos;
  556. _actualVel = 0;
  557. SetState(AxisState.StandStill);
  558. _isMoving = false;
  559. OnMotionCompleted(new AxisMotionEventArgs(AxisNo, _targetPos, _actualPos));
  560. return;
  561. }
  562. double speed = Math.Sign(remaining) * Math.Max(_velocity, 1e-6);
  563. double step = speed * deltaTime;
  564. if (Math.Abs(step) > Math.Abs(remaining) || _velocity < 1e-9)
  565. step = remaining;
  566. _actualPos += step;
  567. _commandPos = _actualPos;
  568. _actualVel = deltaTime > 1e-12 ? step / deltaTime : 0;
  569. }
  570. }
  571. }
  572. private void StartMoveTo(double target)
  573. {
  574. if (_owner != null && !_owner.AllowMotion(out var reason))
  575. {
  576. OnError(reason);
  577. return;
  578. }
  579. lock (_lock)
  580. {
  581. if (!_isEnabled) { OnError("轴未使能,无法运动"); return; }
  582. if (_state == AxisState.Error) { OnError("轴处于错误状态"); return; }
  583. _velocity = Math.Abs(_safety.ClampVelocity(_velocity));
  584. _targetPos = _safety.ClampPosition(target);
  585. _jogVelocity = 0;
  586. if (Math.Abs(_targetPos - _actualPos) < 0.0001 || _velocity < 1e-9)
  587. {
  588. _actualPos = _targetPos;
  589. _commandPos = _targetPos;
  590. _actualVel = 0;
  591. _isMoving = false;
  592. SetState(AxisState.StandStill);
  593. OnMotionCompleted(new AxisMotionEventArgs(AxisNo, _targetPos, _actualPos));
  594. return;
  595. }
  596. SetState(AxisState.DiscreteMotion);
  597. _isMoving = true;
  598. OnMotionStarted(new AxisMotionEventArgs(AxisNo, _targetPos, _actualPos));
  599. }
  600. }
  601. public void Enable()
  602. {
  603. if (_owner != null && !_owner.AllowEnable(out var reason))
  604. {
  605. OnError(reason);
  606. return;
  607. }
  608. lock (_lock)
  609. {
  610. if (_isEnabled) return;
  611. _isEnabled = true;
  612. if (_state == AxisState.Off) SetState(AxisState.StandStill);
  613. }
  614. }
  615. public void Disable()
  616. {
  617. lock (_lock)
  618. {
  619. if (!_isEnabled) return;
  620. _isMoving = false;
  621. _jogVelocity = 0;
  622. SetState(AxisState.Off);
  623. _isEnabled = false;
  624. }
  625. }
  626. public void MoveTo(double position) => StartMoveTo(position);
  627. public void MoveBy(double distance) => StartMoveTo(_actualPos + distance);
  628. public void Jog(double velocity)
  629. {
  630. if (_owner != null && !_owner.AllowMotion(out var reason))
  631. {
  632. OnError(reason);
  633. return;
  634. }
  635. lock (_lock)
  636. {
  637. if (!_isEnabled) { OnError("轴未使能,无法点动"); return; }
  638. if (_state == AxisState.Error) { OnError("轴处于错误状态"); return; }
  639. _jogVelocity = _safety.ClampJog(velocity, _actualPos);
  640. if (Math.Abs(_jogVelocity) < 1e-9)
  641. {
  642. _isMoving = false;
  643. _actualVel = 0;
  644. SetState(AxisState.StandStill);
  645. return;
  646. }
  647. SetState(AxisState.ContinuousMotion);
  648. _isMoving = true;
  649. _actualVel = _jogVelocity;
  650. OnMotionStarted(new AxisMotionEventArgs(AxisNo, double.NaN, _actualPos));
  651. }
  652. }
  653. public void Stop()
  654. {
  655. lock (_lock)
  656. {
  657. if (!_isMoving) return;
  658. _jogVelocity = 0;
  659. _actualVel = 0;
  660. SetState(AxisState.StandStill);
  661. _isMoving = false;
  662. OnMotionCompleted(new AxisMotionEventArgs(AxisNo, _targetPos, _actualPos));
  663. }
  664. }
  665. public void Home(HomeMode mode = HomeMode.Default)
  666. {
  667. if (_owner != null && !_owner.AllowMotion(out var reason))
  668. {
  669. OnError(reason);
  670. return;
  671. }
  672. lock (_lock)
  673. {
  674. if (!_isEnabled) { OnError("轴未使能,无法回零"); return; }
  675. _isMoving = false;
  676. _jogVelocity = 0;
  677. _actualVel = 0;
  678. _actualPos = 0;
  679. _commandPos = 0;
  680. _targetPos = 0;
  681. _isHomed = true;
  682. SetState(AxisState.StandStill);
  683. OnMotionCompleted(new AxisMotionEventArgs(AxisNo, 0, 0));
  684. }
  685. }
  686. public void SetPosition(double position)
  687. {
  688. lock (_lock)
  689. {
  690. _actualPos = position;
  691. _commandPos = position;
  692. }
  693. }
  694. public void SetMotionParams(MotionParams parameters)
  695. {
  696. lock (_lock)
  697. {
  698. if (parameters == null) return;
  699. _velocity = parameters.Velocity;
  700. _accel = parameters.Acceleration;
  701. _decel = parameters.Deceleration;
  702. _jerk = parameters.Jerk;
  703. }
  704. }
  705. private void SetState(AxisState newState)
  706. {
  707. if (_state == newState) return;
  708. var old = _state;
  709. _state = newState;
  710. OnStateChanged(new AxisStateChangedEventArgs(AxisNo, old, newState));
  711. }
  712. protected virtual void OnMotionStarted(AxisMotionEventArgs e) => MotionStarted?.Invoke(this, e);
  713. protected virtual void OnMotionCompleted(AxisMotionEventArgs e) => MotionCompleted?.Invoke(this, e);
  714. protected virtual void OnStateChanged(AxisStateChangedEventArgs e) => StateChanged?.Invoke(this, e);
  715. protected virtual void OnError(string message) => ErrorOccurred?.Invoke(this, new AxisErrorEventArgs(AxisNo, message));
  716. }
  717. public class SimulatedIOCard : IIOCard
  718. {
  719. private readonly MotionDeviceConfig _cfg;
  720. private readonly bool[] _inputs;
  721. private readonly bool[] _outputs;
  722. public string Name => _cfg.Name;
  723. public int InputCount => _inputs.Length;
  724. public int OutputCount => _outputs.Length;
  725. public bool IsConnected { get; private set; }
  726. public SimulatedIOCard(MotionDeviceConfig cfg)
  727. {
  728. _cfg = cfg ?? new MotionDeviceConfig();
  729. _inputs = new bool[Math.Max(1, _cfg.InputCount)];
  730. _outputs = new bool[Math.Max(1, _cfg.OutputCount)];
  731. }
  732. public bool Open() { IsConnected = true; return true; }
  733. public void Close() { IsConnected = false; }
  734. public bool[] ReadAllInputs()
  735. {
  736. lock (_inputs) { return (bool[])_inputs.Clone(); }
  737. }
  738. public bool[] ReadAllOutputs()
  739. {
  740. lock (_outputs) { return (bool[])_outputs.Clone(); }
  741. }
  742. public bool WriteOutput(int index, bool value)
  743. {
  744. if (index < 0 || index >= _outputs.Length) return false;
  745. lock (_outputs) { _outputs[index] = value; }
  746. return true;
  747. }
  748. public bool WriteOutputs(bool[] values)
  749. {
  750. if (values == null) return false;
  751. lock (_outputs)
  752. {
  753. int n = Math.Min(values.Length, _outputs.Length);
  754. for (int i = 0; i < n; i++) _outputs[i] = values[i];
  755. }
  756. return true;
  757. }
  758. }
  759. }