XYZU_Robot.cs 82 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190
  1. using MahApps.Metro.Controls;
  2. using Opc.Ua;
  3. using Prism;
  4. using Prism.Ioc;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Collections.ObjectModel;
  8. using System.ComponentModel;
  9. using System.Diagnostics;
  10. using System.Linq;
  11. using System.Runtime.CompilerServices;
  12. using System.Text;
  13. using System.Threading;
  14. using System.Threading.Tasks;
  15. using TeamAAS_VP.Core.PLCs;
  16. using TeamAAS_VP.Enums;
  17. using TeamAAS_VP.Interfaces;
  18. using TeamAAS_VP.Models;
  19. using TouchSocket.Core;
  20. using TouchSocket.Sockets;
  21. using static OpenCvSharp.ML.DTrees;
  22. using static System.Windows.Forms.AxHost;
  23. namespace TeamAAS_VP.Core.Robots
  24. {
  25. /// <summary>
  26. /// Generic multi-axis robot that composes axis objects and uses PLC nodes configured in RobotInfo.PlcRobotParameter
  27. /// to perform coordinated moves (Go/Move/CalibMotion) for 3 or 4 axes.
  28. /// This implementation maps X/Y/Z/(U) axes by name and writes their position nodes then triggers ExecuteMove node.
  29. /// </summary>
  30. public class XYZU_Robot : IRobot, INotifyPropertyChanged
  31. {
  32. public event Action<Guid, object, ConnectedEventArgs> ConnectedEvent;
  33. public event Action<Guid, object, ClosedEventArgs> DisconnectedEvent;
  34. public event Action<Guid, object, ReceivedDataEventArgs> ReceivedEvent;
  35. public event Action<Guid, object, string> SendEvent;
  36. public XYZU_Robot(RobotInfo robot, OpcUaClientPLC pLC)
  37. {
  38. RobotInfo = robot;
  39. Name = robot.RobotName;
  40. Id = robot.Id;
  41. RobotNo = robot.RobotNo;
  42. RobotIp = robot.IP;
  43. RobotPort = robot.Port;
  44. ConnectType = robot.ConnectType;
  45. Terminator = robot.Terminator;
  46. DataEncoding = robot.DataEncoding;
  47. Brand = RobotBrand.XYZ_Platform;
  48. SafeZ= robot.SafeZ;
  49. Plc = pLC;
  50. Plc.ConnectChangedEvent += Plc_ConnectChangedEvent;
  51. // build axis list from known RobotInfo.PlcRobotParameter nodes (best-effort)
  52. Axes = new List<Axis>();
  53. // X
  54. var ax = new Axis();
  55. ax.Name = "X";
  56. ax.Index = 1;
  57. ax.Command = robot.PlcRobotParameter.AxixList[0].Command;
  58. ax.Parameter = robot.PlcRobotParameter.AxixList[0].Parameter;
  59. ax.State = robot.PlcRobotParameter.AxixList[0].State;
  60. Axes.Add(ax);
  61. // Y
  62. var ay = new Axis();
  63. ay.Name = "Y";
  64. ay.Index = 2;
  65. ay.Command = robot.PlcRobotParameter.AxixList[1].Command;
  66. ay.Parameter = robot.PlcRobotParameter.AxixList[1].Parameter;
  67. ay.State = robot.PlcRobotParameter.AxixList[1].State;
  68. Axes.Add(ay);
  69. // Z
  70. var az = new Axis();
  71. az.Name = "Z";
  72. az.Index = 3;
  73. az.Command = robot.PlcRobotParameter.AxixList[2].Command;
  74. az.Parameter = robot.PlcRobotParameter.AxixList[2].Parameter;
  75. az.State = robot.PlcRobotParameter.AxixList[2].State;
  76. Axes.Add(az);
  77. // U optional
  78. if (robot.PlcRobotParameter.AxixList.Count >= 4)
  79. {
  80. var au = new Axis();
  81. au.Name = "U";
  82. au.Index = 4;
  83. au.Command = robot.PlcRobotParameter.AxixList[3].Command;
  84. au.Parameter = robot.PlcRobotParameter.AxixList[3].Parameter;
  85. au.State = robot.PlcRobotParameter.AxixList[3].State;
  86. Axes.Add(au);
  87. Brand = RobotBrand.XYZU_Platform;
  88. }
  89. //附加轴集合
  90. if(robot.PlcRobotParameter.AdditionalAxisList!=null && robot.PlcRobotParameter.AdditionalAxisList.Count>0)
  91. {
  92. AdditionalAxisList = new ObservableCollection<AxisExtended>();
  93. foreach(var addAxis in robot.PlcRobotParameter.AdditionalAxisList)
  94. {
  95. AxisExtended axisExt = new AxisExtended()
  96. {
  97. Name = addAxis.Name,
  98. Index = addAxis.Index,
  99. Command = addAxis.Command,
  100. Parameter = addAxis.Parameter,
  101. State = addAxis.State
  102. };
  103. AdditionalAxisList.Add(axisExt);
  104. }
  105. }
  106. //相机1的移动轴
  107. if (robot.PlcRobotParameter.Camera1AxisList != null && robot.PlcRobotParameter.Camera1AxisList.Count > 0)
  108. {
  109. Camera1AxisList = new List<Axis>();
  110. foreach (var cam1Axis in robot.PlcRobotParameter.Camera1AxisList)
  111. {
  112. Axis axisExt = new Axis()
  113. {
  114. Name = cam1Axis.Name,
  115. Index = cam1Axis.Index,
  116. Command = cam1Axis.Command,
  117. Parameter = cam1Axis.Parameter,
  118. State = cam1Axis.State
  119. };
  120. Camera1AxisList.Add(axisExt);
  121. }
  122. }
  123. //相机2的移动轴
  124. if (robot.PlcRobotParameter.Camera2AxisList != null && robot.PlcRobotParameter.Camera2AxisList.Count > 0)
  125. {
  126. Camera2AxisList = new List<Axis>();
  127. foreach (var cam2Axis in robot.PlcRobotParameter.Camera2AxisList)
  128. {
  129. Axis axisExt = new Axis()
  130. {
  131. Name = cam2Axis.Name,
  132. Index = cam2Axis.Index,
  133. Command = cam2Axis.Command,
  134. Parameter = cam2Axis.Parameter,
  135. State = cam2Axis.State
  136. };
  137. Camera2AxisList.Add(axisExt);
  138. }
  139. }
  140. //相机3的移动轴
  141. if (robot.PlcRobotParameter.Camera3AxisList != null && robot.PlcRobotParameter.Camera3AxisList.Count > 0)
  142. {
  143. Camera3AxisList = new List<Axis>();
  144. foreach (var cam3Axis in robot.PlcRobotParameter.Camera3AxisList)
  145. {
  146. Axis axisExt = new Axis()
  147. {
  148. Name = cam3Axis.Name,
  149. Index = cam3Axis.Index,
  150. Command = cam3Axis.Command,
  151. Parameter = cam3Axis.Parameter,
  152. State = cam3Axis.State
  153. };
  154. Camera3AxisList.Add(axisExt);
  155. }
  156. }
  157. //相机4的移动轴
  158. if (robot.PlcRobotParameter.Camera4AxisList != null && robot.PlcRobotParameter.Camera4AxisList.Count > 0)
  159. {
  160. Camera4AxisList = new List<Axis>();
  161. foreach (var cam4Axis in robot.PlcRobotParameter.Camera4AxisList)
  162. {
  163. Axis axisExt = new Axis()
  164. {
  165. Name = cam4Axis.Name,
  166. Index = cam4Axis.Index,
  167. Command = cam4Axis.Command,
  168. Parameter = cam4Axis.Parameter,
  169. State = cam4Axis.State
  170. };
  171. Camera4AxisList.Add(axisExt);
  172. }
  173. }
  174. if (Plc.IsConnected)
  175. {
  176. try
  177. {
  178. //所有轴的位置节点
  179. var nodeIds = Axes.Where(a => !string.IsNullOrWhiteSpace(a.State.ActPositionNode)).Select(a => a.State.ActPositionNode).ToArray();
  180. List<string> positionNodes = new List<string>(nodeIds);
  181. pLC.SubscribeNodes("AxisPosition", positionNodes, (res) =>
  182. {
  183. if (res.key != "AxisPosition") return;
  184. for (int i = 0; i < Axes.Count; i++)
  185. {
  186. var axis = Axes[i];
  187. if (axis.State.ActPositionNode.Contains(res.nodeId))
  188. {
  189. float v = Convert.ToSingle(res.value);
  190. switch (axis.Name)
  191. {
  192. case "X": CurrentPosition.X = v; break;
  193. case "Y": CurrentPosition.Y = v; break;
  194. case "Z": CurrentPosition.Z = v; break;
  195. case "U": CurrentPosition.U = v; break;
  196. }
  197. }
  198. }
  199. });
  200. //所有附加轴的位置节点
  201. if (AdditionalAxisList != null && AdditionalAxisList.Count > 0)
  202. {
  203. var addNodeIds = AdditionalAxisList
  204. .Where(a => !string.IsNullOrWhiteSpace(a.State.ActPositionNode))
  205. .Select(a => a.State.ActPositionNode)
  206. .ToArray();
  207. List<string> addPositionNodes = new List<string>(addNodeIds);
  208. pLC.SubscribeNodes("AdditionalAxisPosition", addPositionNodes, (res) =>
  209. {
  210. if (res.key != "AdditionalAxisPosition") return;
  211. for (int i = 0; i < AdditionalAxisList.Count; i++)
  212. {
  213. var axis = AdditionalAxisList[i];
  214. if (axis.State.ActPositionNode.Contains(res.nodeId))
  215. {
  216. axis.CurrentPosition = Convert.ToSingle(res.value);
  217. }
  218. }
  219. });
  220. }
  221. //所有相机1轴的位置节点
  222. if (Camera1AxisList != null && Camera1AxisList.Count > 0)
  223. {
  224. var cam1NodeIds = Camera1AxisList
  225. .Where(a => !string.IsNullOrWhiteSpace(a.State.ActPositionNode))
  226. .Select(a => a.State.ActPositionNode)
  227. .ToArray();
  228. List<string> cam1PositionNodes = new List<string>(cam1NodeIds);
  229. pLC.SubscribeNodes("Camera1AxisPosition", cam1PositionNodes, (res) =>
  230. {
  231. if (res.key != "Camera1AxisPosition") return;
  232. for (int i = 0; i < Camera1AxisList.Count; i++)
  233. {
  234. var axis = Camera1AxisList[i];
  235. if (axis.State.ActPositionNode.Contains(res.nodeId))
  236. {
  237. float v = Convert.ToSingle(res.value);
  238. switch (axis.Name)
  239. {
  240. case "X": Camera1CurrentPosition.X = v; break;
  241. case "Y": Camera1CurrentPosition.Y = v; break;
  242. }
  243. }
  244. }
  245. });
  246. }
  247. //所有相机2轴的位置节点
  248. if (Camera2AxisList != null && Camera2AxisList.Count > 0)
  249. {
  250. var cam2NodeIds = Camera2AxisList
  251. .Where(a => !string.IsNullOrWhiteSpace(a.State.ActPositionNode))
  252. .Select(a => a.State.ActPositionNode)
  253. .ToArray();
  254. List<string> cam2PositionNodes = new List<string>(cam2NodeIds);
  255. pLC.SubscribeNodes("Camera2AxisPosition", cam2PositionNodes, (res) =>
  256. {
  257. if (res.key != "Camera2AxisPosition") return;
  258. for (int i = 0; i < Camera2AxisList.Count; i++)
  259. {
  260. var axis = Camera2AxisList[i];
  261. if (axis.State.ActPositionNode.Contains(res.nodeId))
  262. {
  263. float v = Convert.ToSingle(res.value);
  264. switch (axis.Name)
  265. {
  266. case "X": Camera2CurrentPosition.X = v; break;
  267. case "Y": Camera2CurrentPosition.Y = v; break;
  268. }
  269. }
  270. }
  271. });
  272. }
  273. //所有相机3轴的位置节点
  274. if (Camera3AxisList != null && Camera3AxisList.Count > 0)
  275. {
  276. var cam3NodeIds = Camera3AxisList
  277. .Where(a => !string.IsNullOrWhiteSpace(a.State.ActPositionNode))
  278. .Select(a => a.State.ActPositionNode)
  279. .ToArray();
  280. List<string> cam3PositionNodes = new List<string>(cam3NodeIds);
  281. pLC.SubscribeNodes("Camera3AxisPosition", cam3PositionNodes, (res) =>
  282. {
  283. if (res.key != "Camera3AxisPosition") return;
  284. for (int i = 0; i < Camera3AxisList.Count; i++)
  285. {
  286. var axis = Camera3AxisList[i];
  287. if (axis.State.ActPositionNode.Contains(res.nodeId))
  288. {
  289. float v = Convert.ToSingle(res.value);
  290. switch (axis.Name)
  291. {
  292. case "X": Camera3CurrentPosition.X = v; break;
  293. case "Y": Camera3CurrentPosition.Y = v; break;
  294. }
  295. }
  296. }
  297. });
  298. }
  299. //所有相机4轴的位置节点
  300. if (Camera4AxisList != null && Camera4AxisList.Count > 0)
  301. {
  302. var cam4NodeIds = Camera4AxisList
  303. .Where(a => !string.IsNullOrWhiteSpace(a.State.ActPositionNode))
  304. .Select(a => a.State.ActPositionNode)
  305. .ToArray();
  306. List<string> cam4PositionNodes = new List<string>(cam4NodeIds);
  307. pLC.SubscribeNodes("Camera4AxisPosition", cam4PositionNodes, (res) =>
  308. {
  309. if (res.key != "Camera4AxisPosition") return;
  310. for (int i = 0; i < Camera4AxisList.Count; i++)
  311. {
  312. var axis = Camera4AxisList[i];
  313. if (axis.State.ActPositionNode.Contains(res.nodeId))
  314. {
  315. float v = Convert.ToSingle(res.value);
  316. switch (axis.Name)
  317. {
  318. case "X": Camera4CurrentPosition.X = v; break;
  319. case "Y": Camera4CurrentPosition.Y = v; break;
  320. }
  321. }
  322. }
  323. });
  324. }
  325. }
  326. catch (Exception)
  327. {
  328. }
  329. }
  330. }
  331. private void Plc_ConnectChangedEvent(object arg1, bool arg2)
  332. {
  333. CanExecute = arg2;
  334. if (arg2)
  335. ConnectedEvent?.Invoke(Id, this, null);
  336. else
  337. DisconnectedEvent?.Invoke(Id, this, null);
  338. }
  339. #region 属性
  340. public TcpClient TcpClient { get; private set; }
  341. public TcpService TcpService { get; private set; }
  342. public Guid Id { get; set; }
  343. public string Name { get; set; }
  344. /// <summary>
  345. /// 机器人编号
  346. /// </summary>
  347. public int RobotNo { get; set; }
  348. public int RobotPort { get; private set; }
  349. public string RobotIp { get; private set; }
  350. public TCPConnectType ConnectType { get; private set; }
  351. public Terminator Terminator { get; private set; }
  352. public DataEncoding DataEncoding { get; private set; }
  353. public bool IsConnected
  354. {
  355. get
  356. {
  357. if (Plc != null)
  358. {
  359. return Plc.IsConnected;
  360. }
  361. else
  362. {
  363. return false;
  364. }
  365. }
  366. }
  367. public int Timeout { get; set; } = 5000;
  368. private bool _CanExecute = true;
  369. public bool CanExecute
  370. {
  371. get { return _CanExecute; }
  372. set { SetProperty(ref _CanExecute, value); }
  373. }
  374. public int SelectedTool { get; private set; } = 0;
  375. public RobotBrand Brand { get; private set; }
  376. public OpcUaClientPLC Plc { get; private set; }
  377. public RobotInfo RobotInfo { get; private set; }
  378. public List<Axis> Axes { get; private set; }
  379. private ObservableCollection<AxisExtended> _AdditionalAxisList;
  380. /// <summary>
  381. /// 附加轴
  382. /// </summary>
  383. public ObservableCollection<AxisExtended> AdditionalAxisList
  384. {
  385. get { return _AdditionalAxisList; }
  386. set { SetProperty(ref _AdditionalAxisList, value); }
  387. }
  388. /// <summary>
  389. /// 相机1的移动轴
  390. /// </summary>
  391. public List<Axis> Camera1AxisList { get; private set; }
  392. /// <summary>
  393. /// 相机2的移动轴集合
  394. /// </summary>
  395. public List<Axis> Camera2AxisList { get; private set; }
  396. /// <summary>
  397. /// 相机3的移动轴集合
  398. /// </summary>
  399. public List<Axis> Camera3AxisList { get; private set; }
  400. /// <summary>
  401. /// 相机4的移动轴集合
  402. /// </summary>
  403. public List<Axis> Camera4AxisList { get; private set; }
  404. /// <summary>
  405. /// 进入调试模式
  406. /// </summary>
  407. /// <returns></returns>
  408. public bool EnterDebugMode { get; set; }
  409. private RPoint _CurrentPosition = new RPoint();
  410. /// <summary>
  411. /// 当前位置
  412. /// </summary>
  413. public RPoint CurrentPosition
  414. {
  415. get { return _CurrentPosition; }
  416. set { SetProperty(ref _CurrentPosition, value); }
  417. }
  418. private RPoint _Camera1CurrentPosition= new RPoint();
  419. /// <summary>
  420. /// 当前相机1的移动轴位置
  421. /// </summary>
  422. public RPoint Camera1CurrentPosition
  423. {
  424. get { return _Camera1CurrentPosition; }
  425. set { SetProperty(ref _Camera1CurrentPosition, value); }
  426. }
  427. private RPoint _Camera2CurrentPosition = new RPoint();
  428. /// <summary>
  429. /// 当前相机2的移动轴位置
  430. /// </summary>
  431. public RPoint Camera2CurrentPosition
  432. {
  433. get { return _Camera2CurrentPosition; }
  434. set { SetProperty(ref _Camera2CurrentPosition, value); }
  435. }
  436. private RPoint _Camera3CurrentPosition = new RPoint();
  437. /// <summary>
  438. /// 当前相机3的移动轴位置
  439. /// </summary>
  440. public RPoint Camera3CurrentPosition
  441. {
  442. get { return _Camera3CurrentPosition; }
  443. set { SetProperty(ref _Camera3CurrentPosition, value); }
  444. }
  445. private RPoint _Camera4CurrentPosition = new RPoint();
  446. /// <summary>
  447. /// 当前相机4的移动轴位置
  448. /// </summary>
  449. public RPoint Camera4CurrentPosition
  450. {
  451. get { return _Camera4CurrentPosition; }
  452. set { SetProperty(ref _Camera4CurrentPosition, value); }
  453. }
  454. private double _SafeZ;
  455. /// <summary>
  456. /// Z轴的安全高度
  457. /// </summary>
  458. public double SafeZ
  459. {
  460. get { return _SafeZ; }
  461. set { SetProperty(ref _SafeZ, value); }
  462. }
  463. #endregion
  464. #region 连接
  465. public void Connect()
  466. {
  467. if (Plc.IsConnected)
  468. {
  469. StopMove();
  470. ConnectedEvent?.Invoke(Id, this, null);
  471. }
  472. }
  473. public Task ConnectAsync()
  474. {
  475. if (Plc.IsConnected)
  476. {
  477. StopMove();
  478. ConnectedEvent?.Invoke(Id, this, null);
  479. }
  480. return Task.CompletedTask;
  481. }
  482. public void Disconnect()
  483. {
  484. }
  485. public void Dispose()
  486. {
  487. }
  488. #endregion
  489. #region 控制
  490. public bool Reset() => true;
  491. public Task<bool> ResetAsync() { return Task.Run(() => Reset()); }
  492. public bool Motor(bool state)
  493. {
  494. //遍历所有轴,设置电机状态
  495. //获取所有轴的Poer节点,组成一个集合,一次性写入
  496. Dictionary<string, object> nodesToWrite = new Dictionary<string, object>();
  497. foreach (var axis in Axes)
  498. {
  499. if (!string.IsNullOrWhiteSpace(axis.Command.PowerNode))
  500. {
  501. nodesToWrite[axis.Command.PowerNode] = state;
  502. }
  503. }
  504. if (Plc == null || !Plc.IsConnected) return false;
  505. return Plc.WriteNodes(nodesToWrite);
  506. }
  507. public async Task<bool> MotorAsync(bool state)
  508. {
  509. //遍历所有轴,设置电机状态
  510. //获取所有轴的Poer节点,组成一个集合,一次性写入
  511. Dictionary<string, object> nodesToWrite = new Dictionary<string, object>();
  512. foreach (var axis in Axes)
  513. {
  514. if (!string.IsNullOrWhiteSpace(axis.Command.PowerNode))
  515. {
  516. nodesToWrite[axis.Command.PowerNode] = state;
  517. }
  518. }
  519. if (Plc == null || !Plc.IsConnected) return false;
  520. return await Plc.WriteNodesAsync(nodesToWrite);
  521. }
  522. public bool Power(bool state) => true;
  523. public Task<bool> PowerAsync(bool state) => Task.FromResult(true);
  524. public bool Speed(int value)
  525. {
  526. return true;
  527. }
  528. public Task<bool> SpeedAsync(int value) => Task.FromResult(true);
  529. public bool Speedfactor(int value) => true;
  530. public Task<bool> SpeedfactorAsync(int value) => Task.FromResult(true);
  531. public bool Speeds(double value)
  532. {
  533. //遍历所有轴,设置电机状态
  534. //获取所有轴的Poer节点,组成一个集合,一次性写入
  535. Dictionary<string, object> nodesToWrite = new Dictionary<string, object>();
  536. foreach (var axis in Axes)
  537. {
  538. if (!string.IsNullOrWhiteSpace(axis.Parameter.ManuVelocityNode))
  539. {
  540. nodesToWrite[axis.Command.PowerNode] = (float)value;
  541. }
  542. }
  543. if (Plc == null || !Plc.IsConnected) return false;
  544. return Plc.WriteNodes(nodesToWrite);
  545. }
  546. public async Task<bool> SpeedsAsync(double value)
  547. {
  548. //遍历所有轴,设置电机状态
  549. //获取所有轴的Poer节点,组成一个集合,一次性写入
  550. Dictionary<string, object> nodesToWrite = new Dictionary<string, object>();
  551. foreach (var axis in Axes)
  552. {
  553. if (!string.IsNullOrWhiteSpace(axis.Parameter.ManuVelocityNode))
  554. {
  555. nodesToWrite[axis.Command.PowerNode] = (float)value;
  556. }
  557. }
  558. if (Plc == null || !Plc.IsConnected) return false;
  559. return await Plc.WriteNodesAsync(nodesToWrite);
  560. }
  561. public bool Accel(int value) => true;
  562. public Task<bool> AccelAsync(int value) => Task.FromResult(true);
  563. public bool Accels(double value) => true;
  564. public Task<bool> AccelsAsync(double value) => Task.FromResult(true);
  565. public RPoint GetRobotPos()
  566. {
  567. if (Plc == null || !Plc.IsConnected) return null;
  568. try
  569. {
  570. var nodeIds = Axes.Where(a => !string.IsNullOrWhiteSpace(a.State.ActPositionNode)).Select(a => a.State.ActPositionNode).ToArray();
  571. var data = Plc.ReadNodes(nodeIds);
  572. if (data == null || data.Count == 0) return null;
  573. //获取所有的值
  574. var values = data.Values.ToArray();
  575. RPoint point = new RPoint();
  576. for (int i = 0; i < Axes.Count && i < values.Length; i++)
  577. {
  578. var val = values[i];
  579. float v = 0;
  580. if (val != null)
  581. {
  582. try { v = Convert.ToSingle(val); } catch { }
  583. }
  584. switch (Axes[i].Name)
  585. {
  586. case "X": point.X = v; break;
  587. case "Y": point.Y = v; break;
  588. case "Z": point.Z = v; break;
  589. case "U": point.U = v; break;
  590. default: break;
  591. }
  592. }
  593. return point;
  594. }
  595. catch (Exception ex)
  596. {
  597. LogHelper.WriteLogError("获取机器人位置出错", ex);
  598. throw;
  599. }
  600. }
  601. public Task<RPoint> GetRobotPosAsync()
  602. {
  603. return Task.Run(() => GetRobotPos());
  604. }
  605. public bool Go(RPoint position)
  606. {
  607. return WaitMoveFinished(position);
  608. }
  609. public Task<bool> GoAsync(RPoint position)
  610. {
  611. return Task.Run(() => Go(position));
  612. }
  613. public bool Move(RPoint position) => Go(position);
  614. public Task<bool> MoveAsync(RPoint position) => GoAsync(position);
  615. public bool Jump(RPoint position, double? LimZ)
  616. {
  617. return CalibMotion(position,LimZ);
  618. }
  619. public Task<bool> JumpAsync(RPoint position, double? LimZ) => CalibMotionAsync(position,LimZ);
  620. public bool Jog(string axis, double distance)
  621. {
  622. //获取当前机器人坐标
  623. var currentPos = GetRobotPos();
  624. if (currentPos == null) return false;
  625. switch (axis.ToUpper())
  626. {
  627. case "X":
  628. currentPos.X += (float)distance;
  629. break;
  630. case "Y":
  631. currentPos.Y += (float)distance;
  632. break;
  633. case "Z":
  634. currentPos.Z += (float)distance;
  635. break;
  636. case "U":
  637. currentPos.U += (float)distance;
  638. break;
  639. default:
  640. return false;
  641. }
  642. return Go(currentPos);
  643. }
  644. public Task<bool> JogAsync(string axis, double distance)
  645. {
  646. return Task.Run(() => Jog(axis, distance));
  647. }
  648. public bool Joint(int joint, double distance)
  649. {
  650. return false;
  651. }
  652. public Task<bool> JointAsync(int joint, double distance) => Task.FromResult(false);
  653. public bool SFree()
  654. {
  655. return Motor(false);
  656. }
  657. public Task<bool> SFreeAsync() => Task.Run(() => SFree());
  658. public bool SLock() => Motor(true);
  659. public Task<bool> SLockAsync() => Task.Run(() => SLock());
  660. public bool CalibMotion(RPoint position, double? LimZ)
  661. {
  662. //先Z轴到安全高度
  663. var currentPos = GetRobotPos();
  664. if (currentPos == null) return false;
  665. if (LimZ.HasValue)
  666. {
  667. currentPos.Z = (float)LimZ.Value;
  668. if (!Go(currentPos)) return false;
  669. }
  670. else
  671. {
  672. currentPos.Z = (float)SafeZ;
  673. if (!Go(currentPos)) return false;
  674. }
  675. //再XYU轴到位
  676. currentPos.X = position.X;
  677. currentPos.Y = position.Y;
  678. currentPos.U = position.U;
  679. if (!Go(currentPos)) return false;
  680. //最后Z轴到目标高度
  681. currentPos.Z = position.Z;
  682. var isSucceed = Go(currentPos);
  683. Thread.Sleep(150);
  684. return isSucceed;
  685. }
  686. public Task<bool> CalibMotionAsync(RPoint position, double? LimZ) => Task.Run(() => CalibMotion(position, LimZ));
  687. public bool CalibOutIO(bool state)
  688. {
  689. if (Plc == null || !Plc.IsConnected) return false;
  690. string openNodeId = "ns=4;s=DB_DriveControl|VacCtrl[{0}].Cmd.ManuToWork";
  691. string closeNodeId = "ns=4;s=DB_DriveControl|VacCtrl[{0}].Cmd.ManuToHome";
  692. try
  693. {
  694. if (state)
  695. {
  696. string node = string.Format(openNodeId, _PickInfo.Inhal);
  697. if (!Plc.WriteNode<bool>(node,true))
  698. {
  699. return false;
  700. }
  701. Thread.Sleep(_WaitSuction);
  702. return true;
  703. }
  704. else
  705. {
  706. string node = string.Format(closeNodeId, _PickInfo.Blow);
  707. if (!Plc.WriteNode<bool>(node, true))
  708. {
  709. return false;
  710. }
  711. Thread.Sleep(_WaitBlow);
  712. return true;
  713. }
  714. }
  715. catch (Exception ex)
  716. {
  717. LogHelper.WriteLogError("执行XYZU平台,真空操作时出错!", ex);
  718. return false;
  719. }
  720. }
  721. public Task<bool> CalibOutIOAsync(bool state)
  722. {
  723. return Task.Run(() => CalibOutIO(state));
  724. }
  725. public bool CalibParame(PickInfo Pick, int Speed, int Accel, bool Power, int WaitSuction, int WaitBlow)
  726. {
  727. return false;
  728. }
  729. private PickInfo _PickInfo = new PickInfo();
  730. private int _WaitSuction=100, _WaitBlow=100;
  731. public async Task<bool> CalibParameAsync(PickInfo Pick, int Speed, int Accel, bool Power, int WaitSuction, int WaitBlow)
  732. {
  733. if (Plc == null || !Plc.IsConnected) return false;
  734. try
  735. {
  736. _PickInfo = Pick;
  737. _WaitSuction = WaitSuction;
  738. _WaitBlow=WaitBlow;
  739. Dictionary<string, object> keyValues = new Dictionary<string, object>();
  740. foreach (var axis in Axes)
  741. {
  742. if (string.IsNullOrWhiteSpace(axis.Parameter.ManuVelocityNode)) continue;
  743. string nodeid = axis.Parameter.ManuVelocityNode;
  744. float value = Speed;
  745. keyValues.Add(nodeid, value);
  746. }
  747. // 2. 写入速度给所有轴
  748. if (!Plc.WriteNodes(keyValues))
  749. {
  750. return false;
  751. }
  752. return true;
  753. }
  754. catch (Exception ex)
  755. {
  756. LogHelper.WriteLogError("为XYZU平台设置速度时出错!", ex);
  757. return false;
  758. }
  759. }
  760. /// <summary>
  761. /// 阻塞式等待运动完成
  762. /// </summary>
  763. /// <param name="position"></param>
  764. /// <returns></returns>
  765. private bool WaitMoveFinished(RPoint position)
  766. {
  767. if (Plc == null || !Plc.IsConnected) return false;
  768. try
  769. {
  770. CanExecute = false;
  771. Motor(true);
  772. Dictionary<string, object> keyValues = new Dictionary<string, object>();
  773. // 1. 写入目标位置到各轴的 ManuPositionNode
  774. keyValues = new Dictionary<string, object>();
  775. foreach (var axis in Axes)
  776. {
  777. if (string.IsNullOrWhiteSpace(axis.Parameter.ManuPositionNode)) continue;
  778. string nodeid = axis.Parameter.ManuPositionNode;
  779. float value = 0;
  780. switch (axis.Name)
  781. {
  782. case "X": value = position.X; break;
  783. case "Y": value = position.Y; break;
  784. case "Z": value = position.Z; break;
  785. case "U": value = position.U; break;
  786. default: value = 0; break;
  787. }
  788. keyValues.Add(nodeid, value);
  789. }
  790. // 2. 写入位置给所有轴
  791. if (!Plc.WriteNodes(keyValues))
  792. {
  793. return false;
  794. }
  795. // 3. 写入开始移动命令给所有轴
  796. if (!StartMove())
  797. {
  798. return false;
  799. }
  800. // 准备停滞检测:收集所有实际位置节点
  801. var actNodes = Axes.Where(a => !string.IsNullOrWhiteSpace(a.State.ActPositionNode))
  802. .Select(a => a.State.ActPositionNode)
  803. .Distinct()
  804. .ToArray();
  805. // lastPos 存储上一次读取到的位置,lastChange 存储上次发生“实质性”变化的时间
  806. Dictionary<string, float> lastPos = new Dictionary<string, float>();
  807. Dictionary<string, DateTime> lastChange = new Dictionary<string, DateTime>();
  808. foreach (var node in actNodes)
  809. {
  810. lastPos[node] = float.NaN;
  811. lastChange[node] = DateTime.UtcNow;
  812. }
  813. const float movementThreshold = 0.01f; // 判断位置变化的阈值,避免噪声
  814. Stopwatch sw = new Stopwatch();
  815. sw.Start();
  816. while (true)
  817. {
  818. // 先读取各轴的实际位置,用于停滞检测
  819. Dictionary<string, object> posRead = new Dictionary<string, object>();
  820. try
  821. {
  822. if (actNodes.Length > 0)
  823. {
  824. posRead = Plc.ReadNodes(actNodes);
  825. }
  826. }
  827. catch
  828. {
  829. // 如果读取失败,继续让 CheckMoveFinished 来处理状态或超时
  830. }
  831. var now = DateTime.UtcNow;
  832. // 更新每个节点的变化时间:只要任意一个轴在最近 Timeout 时间内有变化,就视为系统仍在运动
  833. foreach (var node in actNodes)
  834. {
  835. float actual = 0;
  836. if (posRead != null && posRead.ContainsKey(node))
  837. {
  838. var raw = posRead[node];
  839. if (raw != null)
  840. {
  841. try { actual = Convert.ToSingle(raw); } catch { /* 保持 actual = 0 */ }
  842. }
  843. }
  844. if (float.IsNaN(lastPos[node]))
  845. {
  846. lastPos[node] = actual;
  847. lastChange[node] = now;
  848. }
  849. else
  850. {
  851. if (Math.Abs(actual - lastPos[node]) > movementThreshold)
  852. {
  853. // 有实质性变化,更新记录时间和值
  854. lastPos[node] = actual;
  855. lastChange[node] = now;
  856. }
  857. // 否则保持 lastChange 不变(表示该轴最近一次变化的时间)
  858. }
  859. }
  860. // 判断是否至少有一个轴在最近 Timeout 时间内发生过变化(即认为还在运动)
  861. bool anyAxisMovedRecently = actNodes.Any(node => (now - lastChange[node]).TotalMilliseconds <= 500);
  862. // 如果没有任何轴在最近 Timeout 时间内发生变化,则认为出现停滞异常
  863. if (!anyAxisMovedRecently && actNodes.Length > 0)
  864. {
  865. StopMove();
  866. LogHelper.WriteLogInfo($"轴停滞超时(整体判定),目标位置:X={position.X},Y={position.Y},Z={position.Z},U={position.U},超时阈值(ms)={Timeout}");
  867. return false;
  868. }
  869. // 检查是否整体到位或有错误(保留原有逻辑)
  870. var (isFinished, isError) = CheckMoveFinished(position);
  871. if (isFinished)
  872. {
  873. StopMove();
  874. return true;
  875. }
  876. if (isError)
  877. {
  878. LogHelper.WriteLogInfo($"机器人执行移动时发生错误,位置:X={position.X},Y={position.Y},Z={position.Z},U={position.U}");
  879. return false;
  880. }
  881. // 检查整体超时(防止一直有抖动导致 anyAxisMovedRecently 一直为 true)
  882. if (sw.ElapsedMilliseconds > 60000) // 保守超时,一分钟,可根据需求调整
  883. {
  884. LogHelper.WriteLogInfo($"机器人执行移动超时,位置:X={position.X},Y={position.Y},Z={position.Z},U={position.U}");
  885. StopMove();
  886. return false;
  887. }
  888. Thread.Sleep(10);
  889. }
  890. }
  891. catch (Exception ex)
  892. {
  893. LogHelper.WriteLogError("执行XYZU平台,阻塞式等待运动完成时出错!", ex);
  894. CanExecute = true;
  895. return false;
  896. }
  897. finally
  898. {
  899. CanExecute = true;
  900. }
  901. }
  902. /// <summary>
  903. /// 开始运动
  904. /// </summary>
  905. /// <returns></returns>
  906. private bool StartMove()
  907. {
  908. Dictionary<string, object> keyValues = new Dictionary<string, object>();
  909. // 1. 复位所有轴的开始移动命令
  910. foreach (var axis in Axes)
  911. {
  912. if (!string.IsNullOrWhiteSpace(axis.Command.ManuPositionNode))
  913. {
  914. keyValues.Add(axis.Command.ManuPositionNode, true);
  915. }
  916. }
  917. return Plc.WriteNodes(keyValues);
  918. }
  919. /// <summary>
  920. /// 停止运动
  921. /// </summary>
  922. /// <returns></returns>
  923. private bool StopMove()
  924. {
  925. Dictionary<string, object> keyValues = new Dictionary<string, object>();
  926. // 1. 复位所有轴的开始移动命令
  927. foreach (var axis in Axes)
  928. {
  929. if (!string.IsNullOrWhiteSpace(axis.Command.ManuPositionNode))
  930. {
  931. keyValues.Add(axis.Command.ManuPositionNode, false);
  932. }
  933. //if (!string.IsNullOrWhiteSpace(axis.Command.StopNode))
  934. //{
  935. // keyValues.Add(axis.Command.StopNode, true);
  936. //}
  937. }
  938. bool result = Plc.WriteNodes(keyValues);
  939. // 异步等待100ms后复位停止命令
  940. //Task.Run(async () =>
  941. //{
  942. // await Task.Delay(100);
  943. //Thread.Sleep(50);
  944. //Dictionary<string, object> resetValues = new Dictionary<string, object>();
  945. //foreach (var axis in Axes)
  946. //{
  947. // if (!string.IsNullOrWhiteSpace(axis.Command.StopNode))
  948. // {
  949. // resetValues.Add(axis.Command.StopNode, false);
  950. // }
  951. //}
  952. //Plc.WriteNodes(resetValues);
  953. //});
  954. return result;
  955. }
  956. /// <summary>
  957. /// 检查运动是否结束,返回是否停止、是否有错误
  958. /// </summary>
  959. /// <param name="position"></param>
  960. /// <returns></returns>
  961. private (bool isFinished, bool isError) CheckMoveFinished(RPoint position)
  962. {
  963. //检查各轴是否到位和目标位置一致
  964. //批量获取所有轴的定位状态和位置、是否错误
  965. var nodeIds = new List<string>();
  966. foreach (var axis in Axes)
  967. {
  968. if (!string.IsNullOrWhiteSpace(axis.State.PosOKNode))
  969. {
  970. nodeIds.Add(axis.State.PosOKNode);
  971. }
  972. if (!string.IsNullOrWhiteSpace(axis.State.ActPositionNode))
  973. {
  974. nodeIds.Add(axis.State.ActPositionNode);
  975. }
  976. if (!string.IsNullOrWhiteSpace(axis.State.ErrorNode))
  977. {
  978. nodeIds.Add(axis.State.ErrorNode);
  979. }
  980. if (!string.IsNullOrWhiteSpace(axis.State.PausedNode))
  981. {
  982. nodeIds.Add(axis.State.PausedNode);
  983. }
  984. }
  985. var res = Plc.ReadNodes(nodeIds.ToArray());
  986. bool allOk = true;
  987. bool isAnyError = false;
  988. foreach (var axis in Axes)
  989. {
  990. bool posOk = false;
  991. float actualPos = 0;
  992. bool isError = false;
  993. //获取此轴的定位状态、实际位置、错误状态
  994. if (res.ContainsKey(axis.State.PosOKNode))
  995. {
  996. posOk = (bool)res[axis.State.PosOKNode];
  997. }
  998. if (res.ContainsKey(axis.State.ActPositionNode))
  999. {
  1000. try { actualPos = Convert.ToSingle(res[axis.State.ActPositionNode]); } catch { }
  1001. }
  1002. if (res.ContainsKey(axis.State.ErrorNode))
  1003. {
  1004. isError = (bool)res[axis.State.ErrorNode];
  1005. }
  1006. if (res.ContainsKey(axis.State.PausedNode))
  1007. {
  1008. isError = (bool)res[axis.State.PausedNode];
  1009. }
  1010. //if (isError)
  1011. //{
  1012. // isAnyError = true;
  1013. // break;
  1014. //}
  1015. //检查是否到位和位置一致
  1016. float targetPos = 0;
  1017. switch (axis.Name)
  1018. {
  1019. case "X": targetPos = position.X; break;
  1020. case "Y": targetPos = position.Y; break;
  1021. case "Z": targetPos = position.Z; break;
  1022. case "U": targetPos = position.U; break;
  1023. default: targetPos = 0; break;
  1024. }
  1025. //如果定位完成且位置一致,则此轴完成,如果有错误则失败
  1026. if (!(posOk && Math.Abs(actualPos - targetPos) < 0.02))
  1027. {
  1028. //if (isError)
  1029. //{
  1030. // isAnyError = true;
  1031. // break;
  1032. //}
  1033. allOk = false;
  1034. break;
  1035. }
  1036. }
  1037. return (allOk, isAnyError);
  1038. }
  1039. #region 附加轴操作
  1040. /// <summary>
  1041. /// 检查单个附加轴是否结束,返回是否停止、是否有错误
  1042. /// </summary>
  1043. /// <param name="axis"></param>
  1044. /// <param name="targetPos"></param>
  1045. /// <returns></returns>
  1046. private (bool isFinished, bool isError) CheckAdditionalAxisMoveFinished(AxisExtended axis, double targetPos)
  1047. {
  1048. //读取实际位置和到位状态
  1049. bool posOk = false;
  1050. float actualPos = 0;
  1051. bool isError = false;
  1052. var res = Plc.ReadNodes(new string[] { axis.State.PosOKNode, axis.State.ActPositionNode, axis.State.ErrorNode, axis.State.PausedNode });
  1053. if (res.ContainsKey(axis.State.PosOKNode))
  1054. {
  1055. posOk = (bool)res[axis.State.PosOKNode];
  1056. }
  1057. if (res.ContainsKey(axis.State.ActPositionNode))
  1058. {
  1059. try { actualPos = Convert.ToSingle(res[axis.State.ActPositionNode]); } catch { }
  1060. }
  1061. if (res.ContainsKey(axis.State.ErrorNode))
  1062. {
  1063. isError = (bool)res[axis.State.ErrorNode];
  1064. }
  1065. if (res.ContainsKey(axis.State.PausedNode))
  1066. {
  1067. isError = (bool)res[axis.State.PausedNode];
  1068. }
  1069. //检查是否到位和位置一致
  1070. if (isError)
  1071. {
  1072. return (true, true);
  1073. }
  1074. if (posOk && Math.Abs(actualPos - targetPos) < 0.02)
  1075. {
  1076. return (true, false);
  1077. }
  1078. return (false, false);
  1079. }
  1080. /// <summary>
  1081. /// 附加轴使能操作
  1082. /// </summary>
  1083. /// <param name="axisName"></param>
  1084. /// <param name="state"></param>
  1085. /// <returns></returns>
  1086. public bool AdditionalAxisMotor(string axisName, bool state)
  1087. {
  1088. var axis = AdditionalAxisList.FirstOrDefault(a => a.Name.Equals(axisName, StringComparison.OrdinalIgnoreCase));
  1089. if (axis == null) return false;
  1090. if (Plc == null || !Plc.IsConnected) return false;
  1091. try
  1092. {
  1093. if (!string.IsNullOrWhiteSpace(axis.Command.PowerNode))
  1094. {
  1095. return Plc.WriteNode(axis.Command.PowerNode, state);
  1096. }
  1097. return false;
  1098. }
  1099. catch (Exception ex)
  1100. {
  1101. LogHelper.WriteLogError("执行附加轴使能操作时出错!", ex);
  1102. return false;
  1103. }
  1104. }
  1105. /// <summary>
  1106. /// 附加轴使能操作,异步方法
  1107. /// </summary>
  1108. /// <param name="axisName"></param>
  1109. /// <param name="state"></param>
  1110. /// <returns></returns>
  1111. public Task<bool> AdditionalAxisMotorAsync(string axisName, bool state)
  1112. {
  1113. return Task.Run(() => AdditionalAxisMotor(axisName, state));
  1114. }
  1115. /// <summary>
  1116. /// 附加轴,阻塞式等待运动完成
  1117. /// </summary>
  1118. /// <param name="axisName"></param>
  1119. /// <param name="targetPos"></param>
  1120. /// <returns></returns>
  1121. public bool AdditionalAxisMove(string axisName, double targetPos)
  1122. {
  1123. var axis = AdditionalAxisList.FirstOrDefault(a => a.Name.Equals(axisName, StringComparison.OrdinalIgnoreCase));
  1124. if (axis == null) return false;
  1125. if (Plc == null || !Plc.IsConnected) return false;
  1126. try
  1127. {
  1128. CanExecute = false;
  1129. AdditionalAxisMotor(axis.Name, true);
  1130. //写入目标位置
  1131. if (!string.IsNullOrWhiteSpace(axis.Parameter.ManuPositionNode))
  1132. {
  1133. Plc.WriteNode(axis.Parameter.ManuPositionNode, (float)targetPos);
  1134. }
  1135. //开始移动
  1136. if (!string.IsNullOrWhiteSpace(axis.Command.ManuPositionNode))
  1137. {
  1138. Plc.WriteNode(axis.Command.ManuPositionNode, true);
  1139. }
  1140. //准备停滞检测:收集所有实际位置节点
  1141. float lastPos = float.NaN;
  1142. DateTime lastChange = DateTime.UtcNow;
  1143. const float movementThreshold = 0.01f; // 判断位置变化的阈值,避免噪声
  1144. Stopwatch sw = new Stopwatch();
  1145. sw.Start();
  1146. while (true)
  1147. {
  1148. var now = DateTime.UtcNow;
  1149. // 先读取轴的实际位置,用于停滞检测
  1150. var actual = Plc.ReadNode<float>(axis.State.ActPositionNode);
  1151. if (float.IsNaN(lastPos))
  1152. {
  1153. lastPos = actual;
  1154. lastChange = now;
  1155. }
  1156. else
  1157. {
  1158. if (Math.Abs(actual - lastPos) > movementThreshold)
  1159. {
  1160. // 有实质性变化,更新记录时间和值
  1161. lastPos = actual;
  1162. lastChange = now;
  1163. }
  1164. // 否则保持 lastChange 不变(表示该轴最近一次变化的时间)
  1165. }
  1166. // 判断是否至少有一个轴在最近 Timeout 时间内发生过变化(即认为还在运动)
  1167. bool anyAxisMovedRecently = (now - lastChange).TotalMilliseconds <= 500;
  1168. // 如果没有任何轴在最近 Timeout 时间内发生变化,则认为出现停滞异常
  1169. if (!anyAxisMovedRecently)
  1170. {
  1171. //停止移动
  1172. if (!string.IsNullOrWhiteSpace(axis.Command.ManuPositionNode))
  1173. {
  1174. Plc.WriteNode(axis.Command.ManuPositionNode, false);
  1175. }
  1176. return false;
  1177. }
  1178. // 检查是否整体到位或有错误(保留原有逻辑)
  1179. var (isFinished, isError) = CheckAdditionalAxisMoveFinished(axis, targetPos);
  1180. if (isFinished)
  1181. {
  1182. //停止移动
  1183. if (!string.IsNullOrWhiteSpace(axis.Command.ManuPositionNode))
  1184. {
  1185. Plc.WriteNode(axis.Command.ManuPositionNode, false);
  1186. }
  1187. if (isError)
  1188. {
  1189. return false;
  1190. }
  1191. return true;
  1192. }
  1193. // 检查整体超时(防止一直有抖动导致 anyAxisMovedRecently 一直为 true)
  1194. if (sw.ElapsedMilliseconds > 60000) // 保守超时,一分钟,可根据需求调整
  1195. {
  1196. //停止移动
  1197. if (!string.IsNullOrWhiteSpace(axis.Command.ManuPositionNode))
  1198. {
  1199. Plc.WriteNode(axis.Command.ManuPositionNode, false);
  1200. }
  1201. return false;
  1202. }
  1203. Thread.Sleep(10);
  1204. }
  1205. }
  1206. catch (Exception ex)
  1207. {
  1208. LogHelper.WriteLogError("执行附加轴移动时出错!", ex);
  1209. return false;
  1210. }
  1211. finally
  1212. {
  1213. CanExecute = true;
  1214. }
  1215. }
  1216. /// <summary>
  1217. /// 附加轴,阻塞式等待运动完成,异步方法
  1218. /// </summary>
  1219. /// <param name="axisName"></param>
  1220. /// <param name="targetPos"></param>
  1221. /// <returns></returns>
  1222. public Task<bool> AdditionalAxisMoveAsync(string axisName, double targetPos)
  1223. {
  1224. return Task.Run(() => AdditionalAxisMove(axisName, targetPos));
  1225. }
  1226. /// <summary>
  1227. /// 附加轴Jog+点动
  1228. /// </summary>
  1229. /// <param name="axisName"></param>
  1230. /// <param name="distance"></param>
  1231. /// <returns></returns>
  1232. public bool AdditionalAxisJog(string axisName, double distance)
  1233. {
  1234. var axis = AdditionalAxisList.FirstOrDefault(a => a.Name.Equals(axisName, StringComparison.OrdinalIgnoreCase));
  1235. if (axis == null) return false;
  1236. if (Plc == null || !Plc.IsConnected) return false;
  1237. try
  1238. {
  1239. CanExecute = false;
  1240. // 先读取轴的实际位置,用于停滞检测
  1241. var actual = Plc.ReadNode<float>(axis.State.ActPositionNode);
  1242. //计算目标位置
  1243. double targetPos = actual + distance;
  1244. AdditionalAxisMotor(axis.Name, true);
  1245. return AdditionalAxisMove(axis.Name, targetPos);
  1246. }
  1247. catch (Exception ex)
  1248. {
  1249. LogHelper.WriteLogError("执行附加轴点动时出错!", ex);
  1250. return false;
  1251. }
  1252. finally
  1253. {
  1254. CanExecute = true;
  1255. }
  1256. }
  1257. /// <summary>
  1258. /// 附加轴Jog-点动
  1259. /// </summary>
  1260. /// <param name="axisName"></param>
  1261. /// <param name="distance"></param>
  1262. /// <returns></returns>
  1263. public bool AdditionalAxisJogNeg(string axisName, double distance)
  1264. {
  1265. return AdditionalAxisJog(axisName, -distance);
  1266. }
  1267. /// <summary>
  1268. /// 附加轴异步 Jog+点动
  1269. /// </summary>
  1270. /// <param name="axisName"></param>
  1271. /// <param name="distance"></param>
  1272. /// <returns></returns>
  1273. public Task<bool> AdditionalAxisJogAsync(string axisName, double distance)
  1274. {
  1275. return Task.Run(() => AdditionalAxisJog(axisName, distance));
  1276. }
  1277. /// <summary>
  1278. /// 附加轴异步 Jog-点动
  1279. /// </summary>
  1280. /// <param name="axisName"></param>
  1281. /// <param name="distance"></param>
  1282. /// <returns></returns>
  1283. public Task<bool> AdditionalAxisJogNegAsync(string axisName, double distance)
  1284. {
  1285. return Task.Run(() => AdditionalAxisJogNeg(axisName, distance));
  1286. }
  1287. /// <summary>
  1288. /// 获取附加轴的当前坐标
  1289. /// </summary>
  1290. /// <param name="axisName"></param>
  1291. /// <returns></returns>
  1292. public float GetAdditionalAxisCurrentPos(string axisName)
  1293. {
  1294. var axis = AdditionalAxisList.FirstOrDefault(x => x.Name == axisName);
  1295. if (axis==null)
  1296. {
  1297. return 0;
  1298. }
  1299. return Plc.ReadNode<float>(axis.State.ActPositionNode);
  1300. }
  1301. /// <summary>
  1302. /// 获取附加轴的当前坐标
  1303. /// </summary>
  1304. /// <param name="axisName"></param>
  1305. /// <returns></returns>
  1306. public Task<float> GetAdditionalAxisCurrentPosAsync(string axisName)
  1307. {
  1308. return Task.Run(()=> GetAdditionalAxisCurrentPos(axisName));
  1309. }
  1310. #endregion
  1311. #region 相机操作
  1312. /// <summary>
  1313. /// 获取相机1的位置
  1314. /// </summary>
  1315. /// <returns></returns>
  1316. public RPoint GetCamera1Pos()
  1317. {
  1318. if (Plc == null || !Plc.IsConnected) return null;
  1319. try
  1320. {
  1321. var nodeIds = Camera1AxisList.Where(a => !string.IsNullOrWhiteSpace(a.State.ActPositionNode)).Select(a => a.State.ActPositionNode).ToArray();
  1322. var data = Plc.ReadNodes(nodeIds);
  1323. if (data == null || data.Count == 0) return null;
  1324. //获取所有的值
  1325. var values = data.Values.ToArray();
  1326. RPoint point = new RPoint();
  1327. for (int i = 0; i < Camera1AxisList.Count && i < values.Length; i++)
  1328. {
  1329. var val = values[i];
  1330. float v = 0;
  1331. if (val != null)
  1332. {
  1333. try { v = Convert.ToSingle(val); } catch { }
  1334. }
  1335. switch (Camera1AxisList[i].Name)
  1336. {
  1337. case "X": point.X = v; break;
  1338. case "Y": point.Y = v; break;
  1339. //case "Z": point.Z = v; break;
  1340. //case "U": point.U = v; break;
  1341. default: break;
  1342. }
  1343. }
  1344. return point;
  1345. }
  1346. catch (Exception ex)
  1347. {
  1348. LogHelper.WriteLogError("获取相机1的位置出错", ex);
  1349. throw;
  1350. }
  1351. }
  1352. /// <summary>
  1353. /// 获取相机1的位置,异步方法
  1354. /// </summary>
  1355. /// <returns></returns>
  1356. public Task<RPoint> GetCamera1PosAsync()
  1357. {
  1358. return Task.Run(() => GetCamera1Pos());
  1359. }
  1360. /// <summary>
  1361. /// 获取相机2的位置
  1362. /// </summary>
  1363. public RPoint GetCamera2Pos()
  1364. {
  1365. if (Plc == null || !Plc.IsConnected) return null;
  1366. try
  1367. {
  1368. var nodeIds = Camera2AxisList.Where(a => !string.IsNullOrWhiteSpace(a.State.ActPositionNode)).Select(a => a.State.ActPositionNode).ToArray();
  1369. var data = Plc.ReadNodes(nodeIds);
  1370. if (data == null || data.Count == 0) return null;
  1371. //获取所有的值
  1372. var values = data.Values.ToArray();
  1373. RPoint point = new RPoint();
  1374. for (int i = 0; i < Camera2AxisList.Count && i < values.Length; i++)
  1375. {
  1376. var val = values[i];
  1377. float v = 0;
  1378. if (val != null)
  1379. {
  1380. try { v = Convert.ToSingle(val); } catch { }
  1381. }
  1382. switch (Camera2AxisList[i].Name)
  1383. {
  1384. case "X": point.X = v; break;
  1385. case "Y": point.Y = v; break;
  1386. //case "Z": point.Z = v; break;
  1387. //case "U": point.U = v; break;
  1388. default: break;
  1389. }
  1390. }
  1391. return point;
  1392. }
  1393. catch (Exception ex)
  1394. {
  1395. LogHelper.WriteLogError("获取相机2的位置出错", ex);
  1396. throw;
  1397. }
  1398. }
  1399. /// <summary>
  1400. /// 获取相机2的位置,异步方法
  1401. /// </summary>
  1402. /// <returns></returns>
  1403. public Task<RPoint> GetCamera2PosAsync()
  1404. {
  1405. return Task.Run(() => GetCamera2Pos());
  1406. }
  1407. /// <summary>
  1408. /// 获取相机3的位置
  1409. /// </summary>
  1410. /// <returns></returns>
  1411. public RPoint GetCamera3Pos()
  1412. {
  1413. if (Plc == null || !Plc.IsConnected) return null;
  1414. try
  1415. {
  1416. var nodeIds = Camera3AxisList.Where(a => !string.IsNullOrWhiteSpace(a.State.ActPositionNode)).Select(a => a.State.ActPositionNode).ToArray();
  1417. var data = Plc.ReadNodes(nodeIds);
  1418. if (data == null || data.Count == 0) return null;
  1419. //获取所有的值
  1420. var values = data.Values.ToArray();
  1421. RPoint point = new RPoint();
  1422. for (int i = 0; i < Camera3AxisList.Count && i < values.Length; i++)
  1423. {
  1424. var val = values[i];
  1425. float v = 0;
  1426. if (val != null)
  1427. {
  1428. try { v = Convert.ToSingle(val); } catch { }
  1429. }
  1430. switch (Camera3AxisList[i].Name)
  1431. {
  1432. case "X": point.X = v; break;
  1433. case "Y": point.Y = v; break;
  1434. //case "Z": point.Z = v; break;
  1435. //case "U": point.U = v; break;
  1436. default: break;
  1437. }
  1438. }
  1439. return point;
  1440. }
  1441. catch (Exception ex)
  1442. {
  1443. LogHelper.WriteLogError("获取相机3的位置出错", ex);
  1444. throw;
  1445. }
  1446. }
  1447. /// <summary>
  1448. /// 获取相机3的位置,异步方法
  1449. /// </summary>
  1450. /// <returns></returns>
  1451. public Task<RPoint> GetCamera3PosAsync()
  1452. {
  1453. return Task.Run(() => GetCamera3Pos());
  1454. }
  1455. /// <summary>
  1456. /// 获取相机4的位置
  1457. /// </summary>
  1458. public RPoint GetCamera4Pos()
  1459. {
  1460. if (Plc == null || !Plc.IsConnected) return null;
  1461. try
  1462. {
  1463. var nodeIds = Camera4AxisList.Where(a => !string.IsNullOrWhiteSpace(a.State.ActPositionNode)).Select(a => a.State.ActPositionNode).ToArray();
  1464. var data = Plc.ReadNodes(nodeIds);
  1465. if (data == null || data.Count == 0) return null;
  1466. //获取所有的值
  1467. var values = data.Values.ToArray();
  1468. RPoint point = new RPoint();
  1469. for (int i = 0; i < Camera4AxisList.Count && i < values.Length; i++)
  1470. {
  1471. var val = values[i];
  1472. float v = 0;
  1473. if (val != null)
  1474. {
  1475. try { v = Convert.ToSingle(val); } catch { }
  1476. }
  1477. switch (Camera4AxisList[i].Name)
  1478. {
  1479. case "X": point.X = v; break;
  1480. case "Y": point.Y = v; break;
  1481. //case "Z": point.Z = v; break;
  1482. //case "U": point.U = v; break;
  1483. default: break;
  1484. }
  1485. }
  1486. return point;
  1487. }
  1488. catch (Exception ex)
  1489. {
  1490. LogHelper.WriteLogError("获取相机4的位置出错", ex);
  1491. throw;
  1492. }
  1493. }
  1494. /// <summary>
  1495. /// 获取相机4的位置,异步方法
  1496. /// </summary>
  1497. /// <returns></returns>
  1498. public Task<RPoint> GetCamera4PosAsync()
  1499. {
  1500. return Task.Run(() => GetCamera4Pos());
  1501. }
  1502. /// <summary>
  1503. /// 获取指定相机的位置
  1504. /// </summary>
  1505. /// <param name="cameraIndex"></param>
  1506. /// <returns></returns>
  1507. public RPoint GetCameraPos(int cameraIndex)
  1508. {
  1509. switch (cameraIndex)
  1510. {
  1511. case 1: return GetCamera1Pos();
  1512. case 2: return GetCamera2Pos();
  1513. case 3: return GetCamera3Pos();
  1514. case 4: return GetCamera4Pos();
  1515. default: return null;
  1516. }
  1517. }
  1518. /// <summary>
  1519. /// 获取指定相机的位置,异步方法
  1520. /// </summary>
  1521. /// <param name="cameraIndex"></param>
  1522. /// <returns></returns>
  1523. public Task<RPoint> GetCameraPosAsync(int cameraIndex)
  1524. {
  1525. return Task.Run(() => GetCameraPos(cameraIndex));
  1526. }
  1527. /// <summary>
  1528. /// 指定相机的使能操作
  1529. /// </summary>
  1530. /// <param name="cameraIndex">相机编号</param>
  1531. /// <param name="state">使能状态</param>
  1532. /// <returns></returns>
  1533. public bool CameraMotor(int cameraIndex, bool state)
  1534. {
  1535. if (Plc == null || !Plc.IsConnected) return false;
  1536. try
  1537. {
  1538. List<Axis> cameraAxisList = null;
  1539. switch (cameraIndex)
  1540. {
  1541. case 1: cameraAxisList = Camera1AxisList; break;
  1542. case 2: cameraAxisList = Camera2AxisList; break;
  1543. case 3: cameraAxisList = Camera3AxisList; break;
  1544. case 4: cameraAxisList = Camera4AxisList; break;
  1545. default: return false;
  1546. }
  1547. if(cameraAxisList==null || cameraAxisList.Count==0)
  1548. {
  1549. return false;
  1550. }
  1551. Dictionary<string, object> keyValues = new Dictionary<string, object>();
  1552. foreach (var axis in cameraAxisList)
  1553. {
  1554. if (!string.IsNullOrWhiteSpace(axis.Command.PowerNode))
  1555. {
  1556. keyValues.Add(axis.Command.PowerNode, state);
  1557. }
  1558. }
  1559. return Plc.WriteNodes(keyValues);
  1560. }
  1561. catch (Exception ex)
  1562. {
  1563. LogHelper.WriteLogError("执行指定相机使能操作时出错!", ex);
  1564. return false;
  1565. }
  1566. }
  1567. /// <summary>
  1568. /// 指定相机的使能操作,异步方法
  1569. /// </summary>
  1570. /// <param name="cameraIndex"></param>
  1571. /// <param name="state"></param>
  1572. /// <returns></returns>
  1573. public Task<bool> CameraMotorAsync(int cameraIndex, bool state)
  1574. {
  1575. return Task.Run(() => CameraMotor(cameraIndex, state));
  1576. }
  1577. /// <summary>
  1578. /// 指定相机开始运动
  1579. /// </summary>
  1580. /// <param name="cameraIndex"></param>
  1581. /// <returns></returns>
  1582. private bool StartCameraMove(int cameraIndex)
  1583. {
  1584. Dictionary<string, object> keyValues = new Dictionary<string, object>();
  1585. List<Axis> cameraAxisList = null;
  1586. switch (cameraIndex)
  1587. {
  1588. case 1: cameraAxisList = Camera1AxisList; break;
  1589. case 2: cameraAxisList = Camera2AxisList; break;
  1590. case 3: cameraAxisList = Camera3AxisList; break;
  1591. case 4: cameraAxisList = Camera4AxisList; break;
  1592. default: return false;
  1593. }
  1594. if (cameraAxisList == null || cameraAxisList.Count == 0)
  1595. {
  1596. return false;
  1597. }
  1598. // 1. 复位所有轴的开始移动命令
  1599. foreach (var axis in cameraAxisList)
  1600. {
  1601. if (!string.IsNullOrWhiteSpace(axis.Command.ManuPositionNode))
  1602. {
  1603. keyValues.Add(axis.Command.ManuPositionNode, true);
  1604. }
  1605. }
  1606. return Plc.WriteNodes(keyValues);
  1607. }
  1608. /// <summary>
  1609. /// 指定相机停止运动
  1610. /// </summary>
  1611. /// <param name="cameraIndex"></param>
  1612. /// <returns></returns>
  1613. private bool StopCameraMove(int cameraIndex)
  1614. {
  1615. Dictionary<string, object> keyValues = new Dictionary<string, object>();
  1616. List<Axis> cameraAxisList = null;
  1617. switch (cameraIndex)
  1618. {
  1619. case 1: cameraAxisList = Camera1AxisList; break;
  1620. case 2: cameraAxisList = Camera2AxisList; break;
  1621. case 3: cameraAxisList = Camera3AxisList; break;
  1622. case 4: cameraAxisList = Camera4AxisList; break;
  1623. default: return false;
  1624. }
  1625. if (cameraAxisList == null || cameraAxisList.Count == 0)
  1626. {
  1627. return false;
  1628. }
  1629. // 1. 复位所有轴的开始移动命令
  1630. foreach (var axis in cameraAxisList)
  1631. {
  1632. if (!string.IsNullOrWhiteSpace(axis.Command.ManuPositionNode))
  1633. {
  1634. keyValues.Add(axis.Command.ManuPositionNode, false);
  1635. }
  1636. //if (!string.IsNullOrWhiteSpace(axis.Command.StopNode))
  1637. //{
  1638. // keyValues.Add(axis.Command.StopNode, true);
  1639. //}
  1640. }
  1641. bool result = Plc.WriteNodes(keyValues);
  1642. // 异步等待100ms后复位停止命令
  1643. //Task.Run(async () =>
  1644. //{
  1645. // await Task.Delay(100);
  1646. //Thread.Sleep(50);
  1647. //Dictionary<string, object> resetValues = new Dictionary<string, object>();
  1648. //foreach (var axis in cameraAxisList)
  1649. //{
  1650. // if (!string.IsNullOrWhiteSpace(axis.Command.StopNode))
  1651. // {
  1652. // resetValues.Add(axis.Command.StopNode, false);
  1653. // }
  1654. //}
  1655. //Plc.WriteNodes(resetValues);
  1656. //});
  1657. return result;
  1658. }
  1659. /// <summary>
  1660. /// 检查指定相机的运动是否结束,返回是否停止、是否有错误
  1661. /// </summary>
  1662. /// <param name="cameraIndex"></param>
  1663. /// <param name="position"></param>
  1664. /// <returns></returns>
  1665. private (bool isFinished, bool isError) CheckCameraMoveFinished(int cameraIndex, RPoint position)
  1666. {
  1667. List<Axis> cameraAxisList = null;
  1668. switch (cameraIndex)
  1669. {
  1670. case 1: cameraAxisList = Camera1AxisList; break;
  1671. case 2: cameraAxisList = Camera2AxisList; break;
  1672. case 3: cameraAxisList = Camera3AxisList; break;
  1673. case 4: cameraAxisList = Camera4AxisList; break;
  1674. default: return (false, false);
  1675. }
  1676. if (cameraAxisList == null || cameraAxisList.Count == 0)
  1677. {
  1678. return (false, false);
  1679. }
  1680. //检查各轴是否到位和目标位置一致
  1681. //批量获取所有轴的定位状态和位置、是否错误
  1682. var nodeIds = new List<string>();
  1683. foreach (var axis in cameraAxisList)
  1684. {
  1685. if (!string.IsNullOrWhiteSpace(axis.State.PosOKNode))
  1686. {
  1687. nodeIds.Add(axis.State.PosOKNode);
  1688. }
  1689. if (!string.IsNullOrWhiteSpace(axis.State.ActPositionNode))
  1690. {
  1691. nodeIds.Add(axis.State.ActPositionNode);
  1692. }
  1693. if (!string.IsNullOrWhiteSpace(axis.State.ErrorNode))
  1694. {
  1695. nodeIds.Add(axis.State.ErrorNode);
  1696. }
  1697. if (!string.IsNullOrWhiteSpace(axis.State.PausedNode))
  1698. {
  1699. nodeIds.Add(axis.State.PausedNode);
  1700. }
  1701. }
  1702. var res = Plc.ReadNodes(nodeIds.ToArray());
  1703. bool allOk = true;
  1704. bool isAnyError = false;
  1705. foreach (var axis in cameraAxisList)
  1706. {
  1707. bool posOk = false;
  1708. float actualPos = 0;
  1709. bool isError = false;
  1710. bool isPaused = false;
  1711. //获取此轴的定位状态、实际位置、错误状态
  1712. if (res.ContainsKey(axis.State.PosOKNode))
  1713. {
  1714. posOk = (bool)res[axis.State.PosOKNode];
  1715. }
  1716. if (res.ContainsKey(axis.State.ActPositionNode))
  1717. {
  1718. try { actualPos = Convert.ToSingle(res[axis.State.ActPositionNode]); } catch { }
  1719. }
  1720. if (res.ContainsKey(axis.State.ErrorNode))
  1721. {
  1722. isError = (bool)res[axis.State.ErrorNode];
  1723. }
  1724. if (res.ContainsKey(axis.State.PausedNode))
  1725. {
  1726. isPaused = (bool)res[axis.State.PausedNode];
  1727. }
  1728. // 更新状态
  1729. if (isError)
  1730. {
  1731. isAnyError = true;
  1732. break;
  1733. }
  1734. if (isPaused)
  1735. {
  1736. isAnyError = true;
  1737. break;
  1738. }
  1739. //检查是否到位和位置一致
  1740. float targetPos = 0;
  1741. switch (axis.Name)
  1742. {
  1743. case "X": targetPos = position.X; break;
  1744. case "Y": targetPos = position.Y; break;
  1745. case "Z": targetPos = position.Z; break;
  1746. case "U": targetPos = position.U; break;
  1747. default: targetPos = 0; break;
  1748. }
  1749. //如果定位完成且位置一致,则此轴完成,如果有错误则失败
  1750. if (!(posOk && Math.Abs(actualPos - targetPos) < 0.02))
  1751. {
  1752. allOk = false;
  1753. break;
  1754. }
  1755. }
  1756. return (allOk, isAnyError);
  1757. }
  1758. /// <summary>
  1759. /// 阻塞式等待指定相机位置运动完成
  1760. /// </summary>
  1761. /// <param name="cameraIndex"></param>
  1762. /// <param name="position"></param>
  1763. /// <returns></returns>
  1764. public bool WaitCameraMoveFinished(int cameraIndex, RPoint position)
  1765. {
  1766. if (Plc == null || !Plc.IsConnected) return false;
  1767. try
  1768. {
  1769. CanExecute = false;
  1770. List<Axis> cameraAxisList = null;
  1771. switch (cameraIndex)
  1772. {
  1773. case 1: cameraAxisList = Camera1AxisList; break;
  1774. case 2: cameraAxisList = Camera2AxisList; break;
  1775. case 3: cameraAxisList = Camera3AxisList; break;
  1776. case 4: cameraAxisList = Camera4AxisList; break;
  1777. default: return false;
  1778. }
  1779. if (cameraAxisList == null || cameraAxisList.Count == 0)
  1780. {
  1781. return false;
  1782. }
  1783. CameraMotor(cameraIndex,true);
  1784. Dictionary<string, object> keyValues = new Dictionary<string, object>();
  1785. // 1. 写入目标位置到各轴的 ManuPositionNode
  1786. keyValues = new Dictionary<string, object>();
  1787. foreach (var axis in cameraAxisList)
  1788. {
  1789. if (string.IsNullOrWhiteSpace(axis.Parameter.ManuPositionNode)) continue;
  1790. string nodeid = axis.Parameter.ManuPositionNode;
  1791. float value = 0;
  1792. switch (axis.Name)
  1793. {
  1794. case "X": value = position.X; break;
  1795. case "Y": value = position.Y; break;
  1796. case "Z": value = position.Z; break;
  1797. case "U": value = position.U; break;
  1798. default: value = 0; break;
  1799. }
  1800. keyValues.Add(nodeid, value);
  1801. }
  1802. // 2. 写入位置给所有轴
  1803. if (!Plc.WriteNodes(keyValues))
  1804. {
  1805. return false;
  1806. }
  1807. // 3. 写入开始移动命令给所有轴
  1808. if (!StartCameraMove(cameraIndex))
  1809. {
  1810. return false;
  1811. }
  1812. // 准备停滞检测:收集所有实际位置节点
  1813. var actNodes = cameraAxisList.Where(a => !string.IsNullOrWhiteSpace(a.State.ActPositionNode))
  1814. .Select(a => a.State.ActPositionNode)
  1815. .Distinct()
  1816. .ToArray();
  1817. // lastPos 存储上一次读取到的位置,lastChange 存储上次发生“实质性”变化的时间
  1818. Dictionary<string, float> lastPos = new Dictionary<string, float>();
  1819. Dictionary<string, DateTime> lastChange = new Dictionary<string, DateTime>();
  1820. foreach (var node in actNodes)
  1821. {
  1822. lastPos[node] = float.NaN;
  1823. lastChange[node] = DateTime.UtcNow;
  1824. }
  1825. const float movementThreshold = 0.01f; // 判断位置变化的阈值,避免噪声
  1826. Stopwatch sw = new Stopwatch();
  1827. sw.Start();
  1828. while (true)
  1829. {
  1830. // 先读取各轴的实际位置,用于停滞检测
  1831. Dictionary<string, object> posRead = new Dictionary<string, object>();
  1832. try
  1833. {
  1834. if (actNodes.Length > 0)
  1835. {
  1836. posRead = Plc.ReadNodes(actNodes);
  1837. }
  1838. }
  1839. catch
  1840. {
  1841. // 如果读取失败,继续让 CheckMoveFinished 来处理状态或超时
  1842. }
  1843. var now = DateTime.UtcNow;
  1844. // 更新每个节点的变化时间:只要任意一个轴在最近 Timeout 时间内有变化,就视为系统仍在运动
  1845. foreach (var node in actNodes)
  1846. {
  1847. float actual = 0;
  1848. if (posRead != null && posRead.ContainsKey(node))
  1849. {
  1850. var raw = posRead[node];
  1851. if (raw != null)
  1852. {
  1853. try { actual = Convert.ToSingle(raw); } catch { /* 保持 actual = 0 */ }
  1854. }
  1855. }
  1856. if (float.IsNaN(lastPos[node]))
  1857. {
  1858. lastPos[node] = actual;
  1859. lastChange[node] = now;
  1860. }
  1861. else
  1862. {
  1863. if (Math.Abs(actual - lastPos[node]) > movementThreshold)
  1864. {
  1865. // 有实质性变化,更新记录时间和值
  1866. lastPos[node] = actual;
  1867. lastChange[node] = now;
  1868. }
  1869. // 否则保持 lastChange 不变(表示该轴最近一次变化的时间)
  1870. }
  1871. }
  1872. // 判断是否至少有一个轴在最近 Timeout 时间内发生过变化(即认为还在运动)
  1873. bool anyAxisMovedRecently = actNodes.Any(node => (now - lastChange[node]).TotalMilliseconds <= 500);
  1874. // 如果没有任何轴在最近 Timeout 时间内发生变化,则认为出现停滞异常
  1875. if (!anyAxisMovedRecently && actNodes.Length > 0)
  1876. {
  1877. StopCameraMove(cameraIndex);
  1878. LogHelper.WriteLogInfo($"轴停滞超时(整体判定),目标位置:X={position.X},Y={position.Y},Z={position.Z},U={position.U},超时阈值(ms)={Timeout}");
  1879. return false;
  1880. }
  1881. // 检查是否整体到位或有错误(保留原有逻辑)
  1882. var (isFinished, isError) = CheckCameraMoveFinished(cameraIndex, position);
  1883. if (isFinished)
  1884. {
  1885. StopCameraMove(cameraIndex);
  1886. return true;
  1887. }
  1888. if (isError)
  1889. {
  1890. StopCameraMove(cameraIndex);
  1891. LogHelper.WriteLogInfo($"相机{cameraIndex}执行移动时发生错误,位置:X={position.X},Y={position.Y},Z={position.Z},U={position.U}");
  1892. return false;
  1893. }
  1894. // 检查整体超时(防止一直有抖动导致 anyAxisMovedRecently 一直为 true)
  1895. if (sw.ElapsedMilliseconds > 60000) // 保守超时,一分钟,可根据需求调整
  1896. {
  1897. LogHelper.WriteLogInfo($"相机{cameraIndex}执行移动超时,位置:X={position.X},Y={position.Y},Z={position.Z},U={position.U}");
  1898. StopCameraMove(cameraIndex);
  1899. return false;
  1900. }
  1901. Thread.Sleep(10);
  1902. }
  1903. }
  1904. catch (Exception ex)
  1905. {
  1906. LogHelper.WriteLogError($"执行相机{cameraIndex},阻塞式等待运动完成时出错!", ex);
  1907. CanExecute = true;
  1908. return false;
  1909. }
  1910. finally
  1911. {
  1912. CanExecute = true;
  1913. }
  1914. }
  1915. /// <summary>
  1916. /// 阻塞式等待指定相机位置运动完成,异步方法
  1917. /// </summary>
  1918. /// <param name="cameraIndex"></param>
  1919. /// <param name="position"></param>
  1920. /// <returns></returns>
  1921. public Task<bool> WaitCameraMoveFinishedAsync(int cameraIndex, RPoint position)
  1922. {
  1923. return Task.Run(() => WaitCameraMoveFinished(cameraIndex, position));
  1924. }
  1925. /// <summary>
  1926. /// 指定相机的轴点动操作
  1927. /// </summary>
  1928. /// <param name="cameraIndex"></param>
  1929. /// <param name="axisName"></param>
  1930. /// <param name="distance"></param>
  1931. /// <returns></returns>
  1932. public bool CameraJog(int cameraIndex, string axisName, double distance)
  1933. {
  1934. List<Axis> cameraAxisList = null;
  1935. switch (cameraIndex)
  1936. {
  1937. case 1: cameraAxisList = Camera1AxisList; break;
  1938. case 2: cameraAxisList = Camera2AxisList; break;
  1939. case 3: cameraAxisList = Camera3AxisList; break;
  1940. case 4: cameraAxisList = Camera4AxisList; break;
  1941. default: return false;
  1942. }
  1943. if (cameraAxisList == null || cameraAxisList.Count == 0)
  1944. {
  1945. return false;
  1946. }
  1947. var axis = cameraAxisList.FirstOrDefault(a => a.Name.Equals(axisName, StringComparison.OrdinalIgnoreCase));
  1948. if (axis == null) return false;
  1949. if (Plc == null || !Plc.IsConnected) return false;
  1950. try
  1951. {
  1952. CanExecute = false;
  1953. // 获取当前指定相机轴的位置
  1954. var currentPos = GetCameraPos(cameraIndex);
  1955. CameraMotor(cameraIndex, true);
  1956. //根据轴名称计算目标位置
  1957. switch (axisName)
  1958. {
  1959. case "X":
  1960. currentPos.X += (float)distance;
  1961. break;
  1962. case "Y":
  1963. currentPos.Y += (float)distance;
  1964. break;
  1965. //case "Z":
  1966. // currentPos.Z += (float)distance;
  1967. // break;
  1968. //case "U":
  1969. // currentPos.U += (float)distance;
  1970. // break;
  1971. default:
  1972. break;
  1973. }
  1974. //阻塞式等待运动完成
  1975. return WaitCameraMoveFinished(cameraIndex, currentPos);
  1976. }
  1977. catch (Exception ex)
  1978. {
  1979. LogHelper.WriteLogError("执行指定相机轴点动时出错!", ex);
  1980. return false;
  1981. }
  1982. finally
  1983. {
  1984. CanExecute = true;
  1985. }
  1986. }
  1987. /// <summary>
  1988. /// 指定相机的轴点动操作,异步方法
  1989. /// </summary>
  1990. /// <param name="cameraIndex"></param>
  1991. /// <param name="axisName"></param>
  1992. /// <param name="distance"></param>
  1993. /// <returns></returns>
  1994. public Task<bool> CameraJogAsync(int cameraIndex, string axisName, double distance)
  1995. {
  1996. return Task.Run(() => CameraJog(cameraIndex, axisName, distance));
  1997. }
  1998. #endregion
  1999. #endregion
  2000. #region 收发数据
  2001. public string SendAndReceive(string send) => string.Empty;
  2002. public Task<string> SendAndReceiveAsync(string send) => Task.FromResult(string.Empty);
  2003. public string SendAndReceive(ITcpSessionClient client, string send) => string.Empty;
  2004. public Task<string> SendAndReceiveAsync(ITcpSessionClient client, string send) => Task.FromResult(string.Empty);
  2005. public void Send(string send) { }
  2006. public Task SendAsync(string send) => Task.CompletedTask;
  2007. public void Send(ITcpSessionClient client, string send) { }
  2008. public Task SendAsync(ITcpSessionClient client, string send) => Task.CompletedTask;
  2009. public Encoding GetEncoding() => Encoding.Default;
  2010. #endregion
  2011. #region 属性通知
  2012. public event PropertyChangedEventHandler PropertyChanged;
  2013. protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
  2014. {
  2015. if (EqualityComparer<T>.Default.Equals(storage, value)) return false;
  2016. storage = value;
  2017. OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
  2018. return true;
  2019. }
  2020. protected void OnPropertyChanged(PropertyChangedEventArgs args) => PropertyChanged?.Invoke(this, args);
  2021. #endregion
  2022. #region IRobot SetTool/SelectTool stubs
  2023. public bool SetTool(int index, double x, double y) => true;
  2024. public Task<bool> SetToolAsync(int index, double x, double y) => Task.FromResult(true);
  2025. public bool SelectTool(int index) => true;
  2026. public Task<bool> SelectToolAsync(int index) => Task.FromResult(true);
  2027. #endregion
  2028. }
  2029. }