ToolLifeTest.java 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. package com.mes.test;
  2. import com.mes.util.OpcUaUtils;
  3. import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
  4. import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
  5. import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
  6. import java.util.Scanner;
  7. /**
  8. * 刀具寿命采集测试类
  9. * 用于测试从西门子CNC系统采集刀具寿命数据
  10. *
  11. * 采集参数:
  12. * - $TC_MOP4[T,D] - 剩余工件数
  13. * - $TC_MOP3[T,D] - 预警阈值
  14. * - $TC_MOP13[T,D] - 目标寿命值
  15. */
  16. public class ToolLifeTest {
  17. private OpcUaUtils opcUaClient;
  18. public ToolLifeTest(String endpointUrl, String username, String password) {
  19. this.opcUaClient = new OpcUaUtils(endpointUrl, username, password);
  20. }
  21. /**
  22. * 连接到OPC UA服务器
  23. */
  24. public boolean connect() {
  25. System.out.println("正在连接到CNC设备...");
  26. boolean connected = opcUaClient.connect();
  27. if (connected) {
  28. System.out.println("✓ 连接成功");
  29. } else {
  30. System.out.println("✗ 连接失败");
  31. }
  32. return connected;
  33. }
  34. /**
  35. * 读取刀具寿命数据
  36. * @param dNumber D号(刀具号)
  37. * @param hNumber H号(刀沿号)
  38. */
  39. public void readToolLifeData(int dNumber, int hNumber) {
  40. System.out.println("\n========================================");
  41. System.out.println("刀具信息: D" + dNumber + " H" + hNumber);
  42. System.out.println("========================================");
  43. try {
  44. // 读取剩余工件数 $TC_MOP4[D,H]
  45. Integer remainingLife = readToolParameter("TC_MOP4", dNumber, hNumber);
  46. // 读取预警阈值 $TC_MOP3[D,H]
  47. Integer warningThreshold = readToolParameter("TC_MOP3", dNumber, hNumber);
  48. // 读取目标寿命值 $TC_MOP13[D,H]
  49. Integer targetLife = readToolParameter("TC_MOP13", dNumber, hNumber);
  50. // 显示结果
  51. System.out.println("\n采集结果:");
  52. System.out.println("----------------------------------------");
  53. System.out.println("剩余工件数 ($TC_MOP4): " + (remainingLife != null ? remainingLife : "读取失败"));
  54. System.out.println("预警阈值 ($TC_MOP3): " + (warningThreshold != null ? warningThreshold : "读取失败"));
  55. System.out.println("目标寿命 ($TC_MOP13): " + (targetLife != null ? targetLife : "读取失败"));
  56. // 计算寿命百分比和状态
  57. if (remainingLife != null && targetLife != null && targetLife > 0) {
  58. double lifePercentage = (remainingLife * 100.0) / targetLife;
  59. System.out.println("寿命百分比: " + String.format("%.2f%%", lifePercentage));
  60. // 判断状态
  61. String status;
  62. if (remainingLife == 0) {
  63. status = "❌ 寿命耗尽";
  64. } else if (warningThreshold != null && remainingLife <= warningThreshold) {
  65. status = "⚠️ 预警状态";
  66. } else {
  67. status = "✓ 正常";
  68. }
  69. System.out.println("刀具状态: " + status);
  70. }
  71. System.out.println("----------------------------------------\n");
  72. } catch (Exception e) {
  73. System.err.println("读取刀具数据时发生错误: " + e.getMessage());
  74. e.printStackTrace();
  75. }
  76. }
  77. /**
  78. * 读取刀具参数
  79. * @param paramName 参数名称 (TC_MOP3, TC_MOP4, TC_MOP13)
  80. * @param dNumber D号(刀具号)
  81. * @param hNumber H号(刀沿号)- 暂时未使用
  82. * @return 参数值
  83. */
  84. private Integer readToolParameter(String paramName, int dNumber, int hNumber) {
  85. // 根据测试结果,索引映射关系:
  86. // $TC_MOP4[T,D] -> /Tool/Supervision/data[u1,cT,4] (剩余工件数)
  87. // $TC_MOP3[T,D] -> /Tool/Supervision/data[u1,cT,3] (预警阈值)
  88. // $TC_MOP13[T,D] -> /Tool/Supervision/data[u1,cT,6] (目标寿命)
  89. int index;
  90. if (paramName.equals("TC_MOP4")) {
  91. index = 4; // 剩余工件数
  92. } else if (paramName.equals("TC_MOP3")) {
  93. index = 3; // 预警阈值
  94. } else if (paramName.equals("TC_MOP13")) {
  95. index = 6; // 目标寿命
  96. } else {
  97. System.out.println(" ⚠️ 未知参数: " + paramName);
  98. return null;
  99. }
  100. String path = "/Tool/Supervision/data[u1,c" + dNumber + "," + index + "]";
  101. try {
  102. System.out.println("读取路径: " + path);
  103. DataValue dataValue = opcUaClient.readValue(path);
  104. if (dataValue != null && dataValue.getValue() != null && dataValue.getValue().getValue() != null) {
  105. Object value = dataValue.getValue().getValue();
  106. if (value instanceof Number) {
  107. Integer result = ((Number) value).intValue();
  108. System.out.println(" ✓ 读取成功: " + result);
  109. return result;
  110. }
  111. }
  112. } catch (Exception e) {
  113. System.out.println(" ✗ 读取失败: " + e.getMessage());
  114. }
  115. return null;
  116. }
  117. /**
  118. * 测试读取Tool/Supervision/data的不同索引
  119. */
  120. public void testToolSupervisionData(int dNumber) {
  121. System.out.println("\n========================================");
  122. System.out.println("测试 /Tool/Supervision/data 不同索引 (D" + dNumber + ")");
  123. System.out.println("========================================\n");
  124. // 测试索引 1-20
  125. for (int i = 1; i <= 20; i++) {
  126. String path = "/Tool/Supervision/data[u1,c" + dNumber + "," + i + "]";
  127. try {
  128. DataValue dataValue = opcUaClient.readValue(path);
  129. if (dataValue != null && dataValue.getValue() != null && dataValue.getValue().getValue() != null) {
  130. Object value = dataValue.getValue().getValue();
  131. System.out.println("索引 " + i + ": " + value);
  132. }
  133. } catch (Exception e) {
  134. // 忽略错误
  135. }
  136. }
  137. }
  138. /**
  139. * 查找刀具寿命三个参数的索引映射
  140. * 目标:找到哪个索引对应 $TC_MOP4、$TC_MOP3、$TC_MOP13
  141. */
  142. public void findToolLifeIndexes(int dNumber) {
  143. System.out.println("\n========================================");
  144. System.out.println("查找刀具寿命参数索引映射 (D" + dNumber + ")");
  145. System.out.println("========================================");
  146. System.out.println("目标值:");
  147. System.out.println(" $TC_MOP4[" + dNumber + ",1] - 剩余工件数(期望:2300左右)");
  148. System.out.println(" $TC_MOP3[" + dNumber + ",1] - 预警阈值(期望:20左右)");
  149. System.out.println(" $TC_MOP13[" + dNumber + ",1] - 目标寿命(期望:2280左右)");
  150. System.out.println("\n开始扫描索引 1-30...\n");
  151. // 扫描索引 1-30
  152. for (int i = 1; i <= 30; i++) {
  153. String path = "/Tool/Supervision/data[u1,c" + dNumber + "," + i + "]";
  154. try {
  155. DataValue dataValue = opcUaClient.readValue(path);
  156. if (dataValue != null && dataValue.getValue() != null && dataValue.getValue().getValue() != null) {
  157. Object value = dataValue.getValue().getValue();
  158. // 转换为数字
  159. if (value instanceof Number) {
  160. int intValue = ((Number) value).intValue();
  161. // 判断可能对应的参数
  162. String guess = "";
  163. if (intValue > 1000 && intValue < 3000) {
  164. guess = " <- 可能是 $TC_MOP4 (剩余工件数) 或 $TC_MOP13 (目标寿命)";
  165. } else if (intValue > 10 && intValue < 100) {
  166. guess = " <- 可能是 $TC_MOP3 (预警阈值)";
  167. } else if (intValue == 0) {
  168. continue; // 跳过0值
  169. }
  170. System.out.println("索引 " + String.format("%2d", i) + ": " + String.format("%6d", intValue) + guess);
  171. }
  172. }
  173. } catch (Exception e) {
  174. // 忽略错误
  175. }
  176. }
  177. System.out.println("\n========================================");
  178. System.out.println("扫描完成!请根据上面的值判断索引映射关系");
  179. System.out.println("========================================");
  180. }
  181. /**
  182. * 浏览OPC UA节点树,查找刀具相关节点
  183. */
  184. public void browseToolNodes() {
  185. System.out.println("\n========================================");
  186. System.out.println("浏览OPC UA节点结构");
  187. System.out.println("========================================\n");
  188. String[] basePaths = {
  189. "/Tool",
  190. "/Nck",
  191. "/Channel",
  192. "/Tool/Supervision",
  193. "/Tool/Data"
  194. };
  195. for (String basePath : basePaths) {
  196. System.out.println("\n检查路径: " + basePath);
  197. try {
  198. // 尝试读取该路径
  199. DataValue dataValue = opcUaClient.readValue(basePath);
  200. if (dataValue != null) {
  201. System.out.println(" ✓ 路径存在");
  202. }
  203. } catch (Exception e) {
  204. System.out.println(" ✗ 路径不存在或无法访问: " + e.getMessage());
  205. }
  206. }
  207. }
  208. /**
  209. * 批量读取多个刀具的寿命数据
  210. */
  211. public void readMultipleTools(int[] dNumbers, int[] hNumbers) {
  212. if (dNumbers.length != hNumbers.length) {
  213. System.err.println("D号和H号数量不匹配");
  214. return;
  215. }
  216. for (int i = 0; i < dNumbers.length; i++) {
  217. readToolLifeData(dNumbers[i], hNumbers[i]);
  218. // 间隔一下,避免请求过快
  219. try {
  220. Thread.sleep(500);
  221. } catch (InterruptedException e) {
  222. e.printStackTrace();
  223. }
  224. }
  225. }
  226. /**
  227. * 测试自定义路径(支持指定命名空间)
  228. */
  229. public void testCustomPath(String path) {
  230. System.out.println("\n测试路径: " + path);
  231. // 尝试不同的命名空间索引 (ns=2, ns=3, ns=4)
  232. int[] namespaces = {2, 3, 4, 5};
  233. for (int ns : namespaces) {
  234. try {
  235. System.out.println("尝试命名空间 ns=" + ns);
  236. NodeId nodeId = new NodeId(ns, path);
  237. DataValue dataValue = opcUaClient.getClient().readValue(0.0, TimestampsToReturn.Both, nodeId).get();
  238. if (dataValue != null && dataValue.getValue() != null && dataValue.getValue().getValue() != null) {
  239. Object value = dataValue.getValue().getValue();
  240. System.out.println("✓ 读取成功 (ns=" + ns + ")");
  241. System.out.println(" 值: " + value);
  242. System.out.println(" 类型: " + value.getClass().getSimpleName());
  243. return;
  244. } else {
  245. System.out.println(" 状态: " + (dataValue != null ? dataValue.getStatusCode() : "null"));
  246. }
  247. } catch (Exception e) {
  248. System.out.println(" 失败: " + e.getMessage());
  249. }
  250. }
  251. System.out.println("✗ 所有命名空间都读取失败");
  252. }
  253. /**
  254. * 获取OPC UA客户端实例(用于测试)
  255. */
  256. public OpcUaUtils getClient() {
  257. return opcUaClient;
  258. }
  259. /**
  260. * 批量测试多个刀具的寿命数据(1-8号和20号刀)
  261. */
  262. public void testMultipleToolsLife() {
  263. System.out.println("\n========================================");
  264. System.out.println("批量测试刀具寿命(1-8号和20号刀)");
  265. System.out.println("========================================\n");
  266. // 测试刀具号:1, 2, 3, 4, 5, 6, 7, 8, 20
  267. int[] toolNumbers = {1, 2, 3, 4, 5, 6, 7, 8, 20};
  268. for (int toolNo : toolNumbers) {
  269. System.out.println("\n----------------------------------------");
  270. System.out.println("刀具 T" + toolNo + "D1 的寿命数据:");
  271. System.out.println("----------------------------------------");
  272. try {
  273. // 读取三个参数
  274. Integer mop4 = readToolLifeParameter(toolNo, 4); // 剩余工件数
  275. Integer mop3 = readToolLifeParameter(toolNo, 3); // 预警阈值
  276. Integer mop13 = readToolLifeParameter(toolNo, 6); // 目标寿命
  277. // 显示结果
  278. System.out.println("剩余工件数 ($TC_MOP4): " + (mop4 != null ? mop4 : "读取失败"));
  279. System.out.println("预警阈值 ($TC_MOP3): " + (mop3 != null ? mop3 : "读取失败"));
  280. System.out.println("目标寿命 ($TC_MOP13): " + (mop13 != null ? mop13 : "读取失败"));
  281. // 计算寿命百分比和状态
  282. if (mop4 != null && mop13 != null && mop13 > 0) {
  283. double lifePercentage = (mop4 * 100.0) / mop13;
  284. System.out.println("寿命百分比: " + String.format("%.2f%%", lifePercentage));
  285. // 判断状态
  286. String status;
  287. if (mop4 == 0) {
  288. status = "❌ 寿命耗尽";
  289. } else if (mop3 != null && mop4 <= mop3) {
  290. status = "⚠️ 预警状态";
  291. } else {
  292. status = "✓ 正常";
  293. }
  294. System.out.println("刀具状态: " + status);
  295. }
  296. // 间隔一下
  297. Thread.sleep(300);
  298. } catch (Exception e) {
  299. System.err.println("读取刀具 T" + toolNo + " 数据时发生错误: " + e.getMessage());
  300. }
  301. }
  302. System.out.println("\n========================================");
  303. System.out.println("批量测试完成!");
  304. System.out.println("========================================");
  305. }
  306. /**
  307. * 读取刀具寿命参数(简化版,不打印详细日志)
  308. */
  309. private Integer readToolLifeParameter(int toolNo, int index) {
  310. String path = "/Tool/Supervision/data[u1,c" + toolNo + "," + index + "]";
  311. try {
  312. DataValue dataValue = opcUaClient.readValue(path);
  313. if (dataValue != null && dataValue.getValue() != null && dataValue.getValue().getValue() != null) {
  314. Object value = dataValue.getValue().getValue();
  315. if (value instanceof Number) {
  316. return ((Number) value).intValue();
  317. }
  318. }
  319. } catch (Exception e) {
  320. // 忽略错误
  321. }
  322. return null;
  323. }
  324. /**
  325. * 断开连接
  326. */
  327. public void disconnect() {
  328. if (opcUaClient != null) {
  329. opcUaClient.disconnect();
  330. System.out.println("已断开连接");
  331. }
  332. }
  333. /**
  334. * 主测试方法
  335. */
  336. public static void main(String[] args) {
  337. Scanner scanner = new Scanner(System.in);
  338. // 配置连接参数
  339. System.out.println("=== CNC刀具寿命采集测试 ===\n");
  340. System.out.print("请输入CNC设备IP地址 (默认: 80.23.10.45): ");
  341. String ip = scanner.nextLine().trim();
  342. if (ip.isEmpty()) {
  343. ip = "80.23.10.45";
  344. }
  345. String endpointUrl = "opc.tcp://" + ip + ":4840";
  346. String username = "OPCUA";
  347. String password = "OPCUA123";
  348. ToolLifeTest test = new ToolLifeTest(endpointUrl, username, password);
  349. // 连接到设备
  350. if (!test.connect()) {
  351. System.err.println("无法连接到设备,程序退出");
  352. scanner.close();
  353. return;
  354. }
  355. // 交互式测试
  356. while (true) {
  357. System.out.println("\n请选择操作:");
  358. System.out.println("1. 读取单个刀具寿命");
  359. System.out.println("2. 批量读取刀具寿命");
  360. System.out.println("3. 浏览OPC UA节点结构");
  361. System.out.println("4. 测试已知路径");
  362. System.out.println("5. 测试Tool/Supervision/data索引");
  363. System.out.println("6. 测试刀具寿命三个参数(找索引映射)");
  364. System.out.println("7. 批量测试多个刀具(1-8和20号刀)");
  365. System.out.println("8. 退出");
  366. System.out.print("请输入选项 (1-8): ");
  367. String choice = scanner.nextLine().trim();
  368. switch (choice) {
  369. case "1":
  370. // 单个刀具测试
  371. System.out.print("请输入D号 (刀具号,例如: 1): ");
  372. int dNumber = Integer.parseInt(scanner.nextLine().trim());
  373. System.out.print("请输入H号 (刀沿号,例如: 1): ");
  374. int hNumber = Integer.parseInt(scanner.nextLine().trim());
  375. test.readToolLifeData(dNumber, hNumber);
  376. break;
  377. case "2":
  378. // 批量测试 - 根据图片中的刀具表
  379. System.out.println("\n批量读取刀具寿命 (D1-D3, 各H1刀沿)...");
  380. int[] dNumbers = {1, 2, 3};
  381. int[] hNumbers = {1, 1, 1};
  382. test.readMultipleTools(dNumbers, hNumbers);
  383. break;
  384. case "3":
  385. // 浏览节点
  386. test.browseToolNodes();
  387. break;
  388. case "4":
  389. // 测试自定义路径
  390. System.out.print("请输入完整的OPC UA路径 (例如: /Tool/Supervision/data[u1,c1,3]): ");
  391. String customPath = scanner.nextLine().trim();
  392. test.testCustomPath(customPath);
  393. break;
  394. case "5":
  395. // 测试Tool/Supervision/data索引
  396. System.out.print("请输入D号 (刀具号): ");
  397. int testD = Integer.parseInt(scanner.nextLine().trim());
  398. test.testToolSupervisionData(testD);
  399. break;
  400. case "6":
  401. // 测试三个参数的索引映射
  402. System.out.print("请输入D号 (刀具号,例如: 3): ");
  403. int toolD = Integer.parseInt(scanner.nextLine().trim());
  404. test.findToolLifeIndexes(toolD);
  405. break;
  406. case "7":
  407. // 批量测试1-8和20号刀
  408. test.testMultipleToolsLife();
  409. break;
  410. case "8":
  411. // 退出
  412. test.disconnect();
  413. scanner.close();
  414. System.out.println("程序已退出");
  415. return;
  416. default:
  417. System.out.println("无效选项,请重新输入");
  418. }
  419. }
  420. }
  421. }