XYD_ScrewDriver.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Net;
  4. using System.Net.Sockets;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. using System.Web.UI.WebControls;
  8. using TeamAAS_VP.Enums;
  9. using TeamAAS_VP.Interfaces;
  10. namespace TeamAAS_VP.Core.ScrewDriver
  11. {
  12. /// <summary>
  13. /// XYD 系列电动螺丝刀数据读取与解析器。
  14. /// 通过 UDP 向设备发送 "GET_TSPC" 探测命令并接收包含 TSPB/TSPA 数据包的响应,
  15. /// 解析设备返回的扭矩、角度、速度、时间等曲线和结果信息。
  16. /// </summary>
  17. public class XYD_ScrewDriver : IScrewDriver
  18. {
  19. private Socket _socket;
  20. private IPEndPoint _endPoint;
  21. // 协议最小数据长度(TSPB 固定头与描述长度)
  22. private const int MinDataLength = 508;
  23. /// <summary>
  24. /// 发送超时(ms)
  25. /// </summary>
  26. public int SendTimeout { get; set; } = 1000;
  27. /// <summary>
  28. /// 接收超时(ms)
  29. /// </summary>
  30. public int ReceiveTimeout { get; set; } = 2000;
  31. #region 属性
  32. public Guid Id { get; private set; }
  33. private bool _isConnected;
  34. /// <summary>
  35. /// 当前连接状态(是否已与设备建立连接并成功接收过有效数据)。
  36. /// </summary>
  37. public bool IsConnected => _isConnected;
  38. /// <summary>
  39. /// 螺丝刀设备 IP 地址。
  40. /// </summary>
  41. public string IPAdress { get; private set; }
  42. /// <summary>
  43. /// 设备端口号。
  44. /// </summary>
  45. public int Port { get; private set; }
  46. /// <summary>
  47. /// 螺丝刀品牌标识(XYD 固定)。
  48. /// </summary>
  49. public ElectricScrewdriverBrand Brand => ElectricScrewdriverBrand.XYD;
  50. /// <summary>
  51. /// 螺丝编号(设备返回的整型编号)。
  52. /// </summary>
  53. public int ScrewNum { get; private set; }
  54. /// <summary>
  55. /// 曲线采样频率(单位:Hz 或协议定义的采样单位)。
  56. /// </summary>
  57. public int Frequency { get; private set; }
  58. /// <summary>
  59. /// 结果时间戳(设备返回的年月日时分秒)。
  60. /// </summary>
  61. public DateTime ResultDateTime { get; private set; }
  62. /// <summary>
  63. /// 锁止角(单位:度)。
  64. /// </summary>
  65. public double LockAngel { get; private set; }
  66. public double StrokeAngle1 { get; private set; }
  67. public double StrokeAngle2 { get; private set; }
  68. public double StrokeAngle3 { get; private set; }
  69. public double StrokeAngle4 { get; private set; }
  70. public double StrokeAngle5 { get; private set; }
  71. public double StrokeTorque1 { get; private set; }
  72. public double StrokeTorque2 { get; private set; }
  73. public double StrokeTorque3 { get; private set; }
  74. public double StrokeTorque4 { get; private set; }
  75. public double StrokeTorque5 { get; private set; }
  76. /// <summary>
  77. /// 峰值扭矩(单位:kgf,协议返回值需乘以 0.001)。
  78. /// </summary>
  79. public double MaxTorque { get; private set; }
  80. public double OverlockAngle { get; private set; }
  81. /// <summary>
  82. /// 过锁扭矩(单位:kgf)。
  83. /// </summary>
  84. public double OverlockTorque { get; private set; }
  85. public double SlopeCheck { get; private set; }
  86. public double ClampingAngle { get; private set; }
  87. public double ClampingAngleCheck { get; private set; }
  88. public double FitAngle { get; private set; }
  89. public double FitAngleCheck { get; private set; }
  90. /// <summary>
  91. /// 夹紧扭矩(单位:fgf)。
  92. /// </summary>
  93. public double ClampingTorque { get; private set; }
  94. public double ClampingTorqueCheck { get; private set; }
  95. /// <summary>
  96. /// 过程最大扭矩(单位:kgf)。
  97. /// </summary>
  98. public double ProcessMaxTorque { get; private set; }
  99. public double ProcessMaxTorqueCheck { get; private set; }
  100. public double FitPointCoord { get; private set; }
  101. public double FitAngle1 { get; private set; }
  102. /// <summary>
  103. /// 贴合点扭矩(单位:kgf)。
  104. /// </summary>
  105. public double FitTorque { get; private set; }
  106. public double SectionTooth { get; private set; }
  107. public double SectionStroke1 { get; private set; }
  108. public double SectionStroke2 { get; private set; }
  109. public double SectionStroke3 { get; private set; }
  110. public double SectionStroke4 { get; private set; }
  111. public double SectionStroke5 { get; private set; }
  112. public double SectionLockAngle { get; private set; }
  113. public double SectionLockFinish1 { get; private set; }
  114. public double SectionOverLock { get; private set; }
  115. public double SectionFinalEnd { get; private set; }
  116. /// <summary>
  117. /// 解析出的错误信息或结果说明(根据设备返回码映射)。
  118. /// </summary>
  119. public string ErrorInfo { get; private set; }
  120. /// <summary>
  121. /// 本次结果是否判定为成功(设备返回结果码为 4 表示成功)。
  122. /// </summary>
  123. public bool Success { get; private set; }
  124. public int ProgramNumber { get; private set; }
  125. /// <summary>
  126. /// 总圈数(单位:圈,协议返回为度需除以 360)。
  127. /// </summary>
  128. public double Truns { get; private set; }
  129. /// <summary>
  130. /// 结果扭矩值(单位:N·m)。
  131. /// </summary>
  132. public double TorqueValue { get; private set; }
  133. /// <summary>
  134. /// 锁付时长(单位:秒)。
  135. /// </summary>
  136. public int FinalTime { get; private set; }
  137. /// <summary>
  138. /// 曲线数据集合(解析后的时间、角度、扭矩、斜率等)。
  139. /// </summary>
  140. public List<WaveData> WaveDatas { get; } = new List<WaveData>();
  141. //设定值
  142. /// <summary>
  143. /// 目标扭矩上限
  144. /// </summary>
  145. public float TargetTorqueUpperLimit { get; private set; }
  146. /// <summary>
  147. /// 目标扭矩下限
  148. /// </summary>
  149. public float TargetTorqueLowerLimit { get; private set; }
  150. /// <summary>
  151. /// 目标圈数上限
  152. /// </summary>
  153. public float TargetTurnsUpperLimit { get; private set; }
  154. /// <summary>
  155. /// 目标圈数下限
  156. /// </summary>
  157. public float TargetTurnsLowerLimit { get; private set; }
  158. /// <summary>
  159. /// 目标锁付速度
  160. /// </summary>
  161. public float TargetLockSpeed { get; private set; }
  162. #endregion
  163. #region 事件
  164. /// <summary>
  165. /// 连接状态变化事件,参数:sender, connected。
  166. /// 当连接状态改变时触发(包括连接失败/成功与获取数据后状态更新)。
  167. /// </summary>
  168. public event Action<object, bool> ConnectStateChangedEvent;
  169. #endregion
  170. /// <summary>
  171. /// 构造函数,初始化目标设备的 IP 与端口。
  172. /// </summary>
  173. /// <param name="ip">设备 IP 地址</param>
  174. /// <param name="port">设备 UDP 端口</param>
  175. public XYD_ScrewDriver(string ip, int port)
  176. {
  177. IPAdress = ip;
  178. Port = port;
  179. Id=Guid.NewGuid();
  180. }
  181. /// <summary>
  182. /// 确保内部 Socket 已创建并配置好超时与目标 EndPoint。
  183. /// 如果已存在则直接返回。
  184. /// </summary>
  185. private void EnsureSocket()
  186. {
  187. if (_socket == null)
  188. {
  189. _socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
  190. // 发送/接收超时,避免阻塞 UI 线程过久
  191. _socket.SendTimeout = SendTimeout;
  192. _socket.ReceiveTimeout = ReceiveTimeout;
  193. _endPoint = new IPEndPoint(System.Net.IPAddress.Parse(IPAdress), Port);
  194. }
  195. }
  196. /// <summary>
  197. /// 同步连接到设备并进行一次探测(发送 GET_TSPC 并解析响应)。
  198. /// 返回是否成功连接并解析到有效数据。
  199. /// </summary>
  200. public bool Connect()
  201. {
  202. try
  203. {
  204. EnsureSocket();
  205. _socket.Connect(_endPoint);
  206. // 发送探测命令并等待响应
  207. if (!SendGetTspc())
  208. {
  209. UpdateConnectState(false);
  210. return false;
  211. }
  212. byte[] data = ReceiveOnce();
  213. if (data==null || data.Length==0)
  214. {
  215. return false;
  216. }
  217. DataAnalysis(data);
  218. //if (data == null || !DataAnalysis(data))
  219. //{
  220. // UpdateConnectState(true);
  221. // return true;
  222. //}
  223. UpdateConnectState(true);
  224. return true;
  225. }
  226. catch (Exception)
  227. {
  228. UpdateConnectState(false);
  229. return false;
  230. }
  231. }
  232. /// <summary>
  233. /// 异步版本的 Connect。
  234. /// </summary>
  235. public Task<bool> ConnectAsync()
  236. {
  237. return Task.Run(() => Connect());
  238. }
  239. /// <summary>
  240. /// 向设备请求数据并解析,最多尝试若干次(容错网络丢包)。
  241. /// </summary>
  242. public bool GetData()
  243. {
  244. if (_socket == null)
  245. {
  246. EnsureSocket();
  247. }
  248. // 允许多次重试以应对 UDP 丢包/超时
  249. for (int i = 0; i < 10; i++)
  250. {
  251. try
  252. {
  253. if (!SendGetTspc())
  254. continue;
  255. byte[] data = ReceiveOnce();
  256. if (data == null)
  257. continue;
  258. if (!DataAnalysis(data))
  259. continue;
  260. UpdateConnectState(true);
  261. return true;
  262. }
  263. catch (Exception)
  264. {
  265. UpdateConnectState(false);
  266. }
  267. }
  268. UpdateConnectState(false);
  269. return false;
  270. }
  271. /// <summary>
  272. /// 异步版本的 GetData。
  273. /// </summary>
  274. public Task<bool> GetDataAsync()
  275. {
  276. return Task.Run(() => GetData());
  277. }
  278. /// <summary>
  279. /// 发送协议探测命令 "GET_TSPC" 到设备。
  280. /// 返回是否发送成功。
  281. /// </summary>
  282. private bool SendGetTspc()
  283. {
  284. try
  285. {
  286. var cmd = "GET_TSPC";
  287. var bytes = Encoding.ASCII.GetBytes(cmd);
  288. _socket.SendTo(bytes, _endPoint);
  289. return true;
  290. }
  291. catch
  292. {
  293. // 发送失败通常来自网络/Socket 状态,调用方会做重试或断开处理
  294. return false;
  295. }
  296. }
  297. /// <summary>
  298. /// 从 Socket 接收一次数据(单次 ReceiveFrom),并返回实际接收到的字节数组。
  299. /// 超时或异常返回 null。
  300. /// </summary>
  301. private byte[] ReceiveOnce()
  302. {
  303. try
  304. {
  305. EndPoint remote = new IPEndPoint(System.Net.IPAddress.Any, 0);
  306. byte[] buf = new byte[_socket.ReceiveBufferSize];
  307. int len = _socket.ReceiveFrom(buf, ref remote);
  308. if (len <= 0) return null;
  309. var data = new byte[len];
  310. Array.Copy(buf, 0, data, 0, len);
  311. return data;
  312. }
  313. catch
  314. {
  315. // 接收异常(例如超时),返回 null 以触发上层重试逻辑
  316. return null;
  317. }
  318. }
  319. /// <summary>
  320. /// 更新连接状态并触发事件。
  321. /// </summary>
  322. /// <param name="connected">新的连接状态</param>
  323. private void UpdateConnectState(bool connected)
  324. {
  325. _isConnected = connected;
  326. ConnectStateChangedEvent?.Invoke(this, connected);
  327. }
  328. /// <summary>
  329. /// 释放资源实现(受保护,可由派生类覆盖)。
  330. /// </summary>
  331. /// <param name="disposing">是否为显式释放</param>
  332. protected virtual void Dispose(bool disposing)
  333. {
  334. if (disposing)
  335. {
  336. if (_socket != null)
  337. {
  338. try
  339. {
  340. _socket.Shutdown(SocketShutdown.Both);
  341. }
  342. catch { }
  343. _socket.Close();
  344. _socket.Dispose();
  345. _socket = null;
  346. }
  347. }
  348. }
  349. /// <summary>
  350. /// 释放所有托管资源并抑制终结化。
  351. /// </summary>
  352. public void Dispose()
  353. {
  354. Dispose(true);
  355. GC.SuppressFinalize(this);
  356. }
  357. /// <summary>
  358. /// 解析设备返回的字节数据(TSPB + 可选的 TSPA 曲线数据)。
  359. /// 对协议字段按固定偏移解析并进行单位转换。
  360. /// 返回解析是否成功(数据完整且符合长度要求)。
  361. /// </summary>
  362. /// <param name="data">原始字节数据</param>
  363. /// <returns>是否解析成功</returns>
  364. private bool DataAnalysis(byte[] data)
  365. {
  366. if (data == null || data.Length < MinDataLength)
  367. return false;
  368. try
  369. {
  370. #region 注释(协议字段说明)
  371. // 协议字节说明(基于设备文档):
  372. // 1 - 4 “TSPB”
  373. // 5 - 8 螺丝编号 (4 bytes, DCBA order)
  374. // 9 - 10 曲线采样频率 (2 bytes)
  375. // 11 - 22 结果日期 - 年 月 日 时 分 秒 (每项 2 bytes)
  376. // 23 - 24 程序编号
  377. // 25 - 26 总圈数 (单位:度)
  378. // 27 锁止角度 (2 bytes)
  379. // 29 结果扭矩 (2 bytes)
  380. // 31 结果时间 (2 bytes)
  381. // 33 锁附结果 (2 bytes)
  382. // 35 ... 94 各阶段角度/扭矩等(按文档固定偏移)
  383. // 205 TSPA 数据起始坐标(协议中以 1-base 或 0-base 可能不同)
  384. // 207 TSPA 数据总数(样本点数)
  385. // 随后按扭矩/速度/角度/斜率分别存放(每项为 2 bytes,sectionBytes = length * 2)
  386. #endregion
  387. // 基本字段解析
  388. ScrewNum = ToInt32N(data, 4);
  389. Frequency = ToInt16(data, 8);
  390. int year = ToInt16(data, 10);
  391. int month = ToInt16(data, 12);
  392. int day = ToInt16(data, 14);
  393. int hour = ToInt16(data, 16);
  394. int minute = ToInt16(data, 18);
  395. int second = ToInt16(data, 20);
  396. //ResultDateTime = new DateTime(year, month, day, hour, minute, second);
  397. ProgramNumber = ToInt16(data, 22);
  398. // 设备返回的总圈数字段以度为单位,转换为圈
  399. Truns = ToInt16(data, 24) / 360.0;
  400. LockAngel = ToInt16(data, 26);
  401. // 扭矩值以 mN·m(或协议指定单位)返回,乘以 0.001 转换为 N·m
  402. TorqueValue = ToInt16(data, 28) * 0.001;
  403. FinalTime = ToInt16(data, 30);
  404. int finalResult = ToInt16(data, 32);
  405. switch (finalResult)
  406. {
  407. case 1: ErrorInfo = "圈数超限"; break;
  408. case 2: ErrorInfo = "区段行程NG"; break;
  409. case 3: ErrorInfo = "超时"; break;
  410. case 4: ErrorInfo = string.Empty; break;
  411. case 5: ErrorInfo = "中断"; break;
  412. case 6: ErrorInfo = "扭矩超限"; break;
  413. case 7: ErrorInfo = "反转"; break;
  414. case 8: ErrorInfo = "锁止角不合格"; break;
  415. case 9: ErrorInfo = "过锁异常"; break;
  416. case 10: ErrorInfo = "斜率异常"; break;
  417. case 11: ErrorInfo = "夹角异常"; break;
  418. case 12: ErrorInfo = "贴合角异常"; break;
  419. case 13: ErrorInfo = "夹紧力异常"; break;
  420. case 14: ErrorInfo = "过程力异常"; break;
  421. default: ErrorInfo = finalResult.ToString(); break;
  422. }
  423. Success = finalResult == 4;
  424. // 阶段行程角度:协议中以度为单位,代码将其转换为圈(度/360)
  425. StrokeAngle1 = ToInt16(data, 34) / 360.0;
  426. StrokeAngle2 = ToInt16(data, 36) / 360.0;
  427. StrokeAngle3 = ToInt16(data, 38) / 360.0;
  428. StrokeAngle4 = ToInt16(data, 40) / 360.0;
  429. StrokeAngle5 = ToInt16(data, 42) / 360.0;
  430. // 阶段扭矩转换(单位缩放)
  431. StrokeTorque1 = ToInt16(data, 44) * 0.001;
  432. StrokeTorque2 = ToInt16(data, 46) * 0.001;
  433. StrokeTorque3 = ToInt16(data, 48) * 0.001;
  434. StrokeTorque4 = ToInt16(data, 50) * 0.001;
  435. StrokeTorque5 = ToInt16(data, 52) * 0.001;
  436. MaxTorque = ToInt16(data, 54) * 0.001;
  437. OverlockAngle = ToInt16(data, 56);
  438. OverlockTorque = ToInt16(data, 58) * 0.001;
  439. SlopeCheck = ToInt16(data, 60);
  440. ClampingAngle = ToInt16(data, 62);
  441. ClampingAngleCheck = ToInt16(data, 64);
  442. FitAngle = ToInt16(data, 66);
  443. FitAngleCheck = ToInt16(data, 68);
  444. ClampingTorque = ToInt16(data, 70) * 0.001;
  445. ClampingTorqueCheck = ToInt16(data, 72);
  446. ProcessMaxTorque = ToInt16(data, 74) * 0.001;
  447. ProcessMaxTorqueCheck = ToInt16(data, 76);
  448. FitPointCoord = ToInt16(data, 78);
  449. FitAngle1 = ToInt16(data, 80);
  450. FitTorque = ToInt16(data, 82) * 0.001;
  451. SectionTooth = ToInt16(data, 84);
  452. SectionStroke1 = ToInt16(data, 86);
  453. SectionStroke2 = ToInt16(data, 88);
  454. SectionStroke3 = ToInt16(data, 90);
  455. SectionStroke4 = ToInt16(data, 92);
  456. SectionStroke5 = ToInt16(data, 94);
  457. SectionLockAngle = ToInt16(data, 96);
  458. SectionLockFinish1 = ToInt16(data, 98);
  459. SectionOverLock = ToInt16(data, 100);
  460. SectionFinalEnd = ToInt16(data, 102);
  461. TargetTorqueUpperLimit = ToInt16(data, 386) * 0.001f;//结果扭矩上限
  462. TargetTorqueLowerLimit = ToInt16(data, 388) * 0.001f;//结果扭矩下限
  463. TargetTurnsUpperLimit = ToInt16(data, 246);//最终旋转角度上限
  464. TargetTurnsLowerLimit= ToInt16(data, 248);//最终旋转角度下限
  465. TargetLockSpeed= ToInt16(data, 394);//停止速度
  466. // 计算 TSPA 曲线数据段长度(协议: 在偏移 504/506 存有开始/结束索引或总数)
  467. int length = ToInt16(data, 506) - ToInt16(data, 504);
  468. if (length <= 0)
  469. {
  470. // 无曲线数据,清空集合并返回成功
  471. WaveDatas.Clear();
  472. return true;
  473. }
  474. int sectionBytes = length * 2;
  475. int expectedTotal = 508 + sectionBytes * 4;
  476. // 如果整体长度不足,则认为数据不完整,返回失败以触发重试
  477. if (data.Length < expectedTotal)
  478. return false;
  479. var torques = new byte[sectionBytes];
  480. var speeds = new byte[sectionBytes];
  481. var angles = new byte[sectionBytes];
  482. var slopes = new byte[sectionBytes];
  483. // 按顺序从数据中拷贝各段(扭矩、速度、角度、斜率)
  484. Array.Copy(data, 508, torques, 0, sectionBytes);
  485. Array.Copy(data, 508 + sectionBytes, speeds, 0, sectionBytes);
  486. Array.Copy(data, 508 + sectionBytes * 2, angles, 0, sectionBytes);
  487. Array.Copy(data, 508 + sectionBytes * 3, slopes, 0, sectionBytes);
  488. WaveDatas.Clear();
  489. // 每 2 字节为一个样本,使用 ToInt16 解析(注意字节序)
  490. for (int i = 0; i < sectionBytes; i += 2)
  491. {
  492. double angleVal = ToInt16(angles, i) / 360.0;
  493. double torqueVal = ToInt16(torques, i) * 0.001;
  494. double slopeVal = ToInt16(slopes, i);
  495. // 时间:按采样频率换算(单位:秒),注意 i 为字节索引,样本索引为 i/2
  496. double time = (Frequency * i) / 1000.0;
  497. WaveDatas.Add(new WaveData
  498. {
  499. LockAngle = angleVal,
  500. Torque = torqueVal,
  501. Slopes = slopeVal,
  502. Turns = angleVal,
  503. Time = time
  504. });
  505. }
  506. return true;
  507. }
  508. catch
  509. {
  510. // 任意解析或转换异常均视为解析失败
  511. return false;
  512. }
  513. }
  514. /// <summary>
  515. /// 将指定数组中从 startIndex 开始的两个字节按设备协议的字节序解析为有符号 16 位值。
  516. /// 协议采用“DCBA 风格(低字节在前,高字节在后)”,因此这里以小端方式合成。
  517. /// </summary>
  518. /// <param name="value">字节数组</param>
  519. /// <param name="startIndex">起始索引</param>
  520. /// <returns>解析出的 short 值,出错返回 0</returns>
  521. private short ToInt16(byte[] value, int startIndex)
  522. {
  523. if (value == null)
  524. {
  525. return 0;
  526. }
  527. byte[] data = new byte[2];
  528. data[0] = value[startIndex + 1];
  529. data[1] = value[startIndex];
  530. return BitConverter.ToInt16(data, 0);
  531. }
  532. /// <summary>
  533. /// 将指定数组中从 startIndex 开始的 4 个字节按设备协议解析为 32 位整型(DCBA 顺序)。
  534. /// 注意字节顺序为设备特定格式,这里按原实现保持位移组合。
  535. /// </summary>
  536. /// <param name="value">字节数组</param>
  537. /// <param name="startIndex">起始索引</param>
  538. /// <returns>解析出的 int 值,出错返回 0</returns>
  539. private int ToInt32N(byte[] value, int startIndex)
  540. {
  541. if (false)
  542. {
  543. return BitConverter.ToInt32(value, startIndex);
  544. }
  545. else
  546. {
  547. byte[] data = new byte[4];
  548. data[0] = value[startIndex + 3];
  549. data[1] = value[startIndex + 2];
  550. data[2] = value[startIndex + 1];
  551. data[3] = value[startIndex];
  552. return BitConverter.ToInt32(data, 0);
  553. }
  554. }
  555. /// <summary>
  556. /// 曲线数据结构,包含速度、扭矩、角度、圈数、斜率与时间。
  557. /// </summary>
  558. public struct WaveData
  559. {
  560. /// <summary>
  561. /// 速度
  562. /// </summary>
  563. public double Speed { get; set; }
  564. /// <summary>
  565. /// 扭矩
  566. /// </summary>
  567. public double Torque { get; set; }
  568. /// <summary>
  569. /// 角度
  570. /// </summary>
  571. public double LockAngle { get; set; }
  572. /// <summary>
  573. /// 圈数
  574. /// </summary>
  575. public double Turns { get; set; }
  576. /// <summary>
  577. /// 斜率
  578. /// </summary>
  579. public double Slopes { get; set; }
  580. /// <summary>
  581. /// 时间(秒)
  582. /// </summary>
  583. public double Time { get; set; }
  584. }
  585. }
  586. }