package com.mes.test; import com.mes.util.OpcUaUtils; import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue; import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId; import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn; import java.util.Scanner; /** * 刀具寿命采集测试类 * 用于测试从西门子CNC系统采集刀具寿命数据 * * 采集参数: * - $TC_MOP4[T,D] - 剩余工件数 * - $TC_MOP3[T,D] - 预警阈值 * - $TC_MOP13[T,D] - 目标寿命值 */ public class ToolLifeTest { private OpcUaUtils opcUaClient; public ToolLifeTest(String endpointUrl, String username, String password) { this.opcUaClient = new OpcUaUtils(endpointUrl, username, password); } /** * 连接到OPC UA服务器 */ public boolean connect() { System.out.println("正在连接到CNC设备..."); boolean connected = opcUaClient.connect(); if (connected) { System.out.println("✓ 连接成功"); } else { System.out.println("✗ 连接失败"); } return connected; } /** * 读取刀具寿命数据 * @param dNumber D号(刀具号) * @param hNumber H号(刀沿号) */ public void readToolLifeData(int dNumber, int hNumber) { System.out.println("\n========================================"); System.out.println("刀具信息: D" + dNumber + " H" + hNumber); System.out.println("========================================"); try { // 读取剩余工件数 $TC_MOP4[D,H] Integer remainingLife = readToolParameter("TC_MOP4", dNumber, hNumber); // 读取预警阈值 $TC_MOP3[D,H] Integer warningThreshold = readToolParameter("TC_MOP3", dNumber, hNumber); // 读取目标寿命值 $TC_MOP13[D,H] Integer targetLife = readToolParameter("TC_MOP13", dNumber, hNumber); // 显示结果 System.out.println("\n采集结果:"); System.out.println("----------------------------------------"); System.out.println("剩余工件数 ($TC_MOP4): " + (remainingLife != null ? remainingLife : "读取失败")); System.out.println("预警阈值 ($TC_MOP3): " + (warningThreshold != null ? warningThreshold : "读取失败")); System.out.println("目标寿命 ($TC_MOP13): " + (targetLife != null ? targetLife : "读取失败")); // 计算寿命百分比和状态 if (remainingLife != null && targetLife != null && targetLife > 0) { double lifePercentage = (remainingLife * 100.0) / targetLife; System.out.println("寿命百分比: " + String.format("%.2f%%", lifePercentage)); // 判断状态 String status; if (remainingLife == 0) { status = "❌ 寿命耗尽"; } else if (warningThreshold != null && remainingLife <= warningThreshold) { status = "⚠️ 预警状态"; } else { status = "✓ 正常"; } System.out.println("刀具状态: " + status); } System.out.println("----------------------------------------\n"); } catch (Exception e) { System.err.println("读取刀具数据时发生错误: " + e.getMessage()); e.printStackTrace(); } } /** * 读取刀具参数 * @param paramName 参数名称 (TC_MOP3, TC_MOP4, TC_MOP13) * @param dNumber D号(刀具号) * @param hNumber H号(刀沿号)- 暂时未使用 * @return 参数值 */ private Integer readToolParameter(String paramName, int dNumber, int hNumber) { // 根据测试结果,索引映射关系: // $TC_MOP4[T,D] -> /Tool/Supervision/data[u1,cT,4] (剩余工件数) // $TC_MOP3[T,D] -> /Tool/Supervision/data[u1,cT,3] (预警阈值) // $TC_MOP13[T,D] -> /Tool/Supervision/data[u1,cT,6] (目标寿命) int index; if (paramName.equals("TC_MOP4")) { index = 4; // 剩余工件数 } else if (paramName.equals("TC_MOP3")) { index = 3; // 预警阈值 } else if (paramName.equals("TC_MOP13")) { index = 6; // 目标寿命 } else { System.out.println(" ⚠️ 未知参数: " + paramName); return null; } String path = "/Tool/Supervision/data[u1,c" + dNumber + "," + index + "]"; try { System.out.println("读取路径: " + path); DataValue dataValue = opcUaClient.readValue(path); if (dataValue != null && dataValue.getValue() != null && dataValue.getValue().getValue() != null) { Object value = dataValue.getValue().getValue(); if (value instanceof Number) { Integer result = ((Number) value).intValue(); System.out.println(" ✓ 读取成功: " + result); return result; } } } catch (Exception e) { System.out.println(" ✗ 读取失败: " + e.getMessage()); } return null; } /** * 测试读取Tool/Supervision/data的不同索引 */ public void testToolSupervisionData(int dNumber) { System.out.println("\n========================================"); System.out.println("测试 /Tool/Supervision/data 不同索引 (D" + dNumber + ")"); System.out.println("========================================\n"); // 测试索引 1-20 for (int i = 1; i <= 20; i++) { String path = "/Tool/Supervision/data[u1,c" + dNumber + "," + i + "]"; try { DataValue dataValue = opcUaClient.readValue(path); if (dataValue != null && dataValue.getValue() != null && dataValue.getValue().getValue() != null) { Object value = dataValue.getValue().getValue(); System.out.println("索引 " + i + ": " + value); } } catch (Exception e) { // 忽略错误 } } } /** * 查找刀具寿命三个参数的索引映射 * 目标:找到哪个索引对应 $TC_MOP4、$TC_MOP3、$TC_MOP13 */ public void findToolLifeIndexes(int dNumber) { System.out.println("\n========================================"); System.out.println("查找刀具寿命参数索引映射 (D" + dNumber + ")"); System.out.println("========================================"); System.out.println("目标值:"); System.out.println(" $TC_MOP4[" + dNumber + ",1] - 剩余工件数(期望:2300左右)"); System.out.println(" $TC_MOP3[" + dNumber + ",1] - 预警阈值(期望:20左右)"); System.out.println(" $TC_MOP13[" + dNumber + ",1] - 目标寿命(期望:2280左右)"); System.out.println("\n开始扫描索引 1-30...\n"); // 扫描索引 1-30 for (int i = 1; i <= 30; i++) { String path = "/Tool/Supervision/data[u1,c" + dNumber + "," + i + "]"; try { DataValue dataValue = opcUaClient.readValue(path); if (dataValue != null && dataValue.getValue() != null && dataValue.getValue().getValue() != null) { Object value = dataValue.getValue().getValue(); // 转换为数字 if (value instanceof Number) { int intValue = ((Number) value).intValue(); // 判断可能对应的参数 String guess = ""; if (intValue > 1000 && intValue < 3000) { guess = " <- 可能是 $TC_MOP4 (剩余工件数) 或 $TC_MOP13 (目标寿命)"; } else if (intValue > 10 && intValue < 100) { guess = " <- 可能是 $TC_MOP3 (预警阈值)"; } else if (intValue == 0) { continue; // 跳过0值 } System.out.println("索引 " + String.format("%2d", i) + ": " + String.format("%6d", intValue) + guess); } } } catch (Exception e) { // 忽略错误 } } System.out.println("\n========================================"); System.out.println("扫描完成!请根据上面的值判断索引映射关系"); System.out.println("========================================"); } /** * 浏览OPC UA节点树,查找刀具相关节点 */ public void browseToolNodes() { System.out.println("\n========================================"); System.out.println("浏览OPC UA节点结构"); System.out.println("========================================\n"); String[] basePaths = { "/Tool", "/Nck", "/Channel", "/Tool/Supervision", "/Tool/Data" }; for (String basePath : basePaths) { System.out.println("\n检查路径: " + basePath); try { // 尝试读取该路径 DataValue dataValue = opcUaClient.readValue(basePath); if (dataValue != null) { System.out.println(" ✓ 路径存在"); } } catch (Exception e) { System.out.println(" ✗ 路径不存在或无法访问: " + e.getMessage()); } } } /** * 批量读取多个刀具的寿命数据 */ public void readMultipleTools(int[] dNumbers, int[] hNumbers) { if (dNumbers.length != hNumbers.length) { System.err.println("D号和H号数量不匹配"); return; } for (int i = 0; i < dNumbers.length; i++) { readToolLifeData(dNumbers[i], hNumbers[i]); // 间隔一下,避免请求过快 try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * 测试自定义路径(支持指定命名空间) */ public void testCustomPath(String path) { System.out.println("\n测试路径: " + path); // 尝试不同的命名空间索引 (ns=2, ns=3, ns=4) int[] namespaces = {2, 3, 4, 5}; for (int ns : namespaces) { try { System.out.println("尝试命名空间 ns=" + ns); NodeId nodeId = new NodeId(ns, path); DataValue dataValue = opcUaClient.getClient().readValue(0.0, TimestampsToReturn.Both, nodeId).get(); if (dataValue != null && dataValue.getValue() != null && dataValue.getValue().getValue() != null) { Object value = dataValue.getValue().getValue(); System.out.println("✓ 读取成功 (ns=" + ns + ")"); System.out.println(" 值: " + value); System.out.println(" 类型: " + value.getClass().getSimpleName()); return; } else { System.out.println(" 状态: " + (dataValue != null ? dataValue.getStatusCode() : "null")); } } catch (Exception e) { System.out.println(" 失败: " + e.getMessage()); } } System.out.println("✗ 所有命名空间都读取失败"); } /** * 获取OPC UA客户端实例(用于测试) */ public OpcUaUtils getClient() { return opcUaClient; } /** * 批量测试多个刀具的寿命数据(1-8号和20号刀) */ public void testMultipleToolsLife() { System.out.println("\n========================================"); System.out.println("批量测试刀具寿命(1-8号和20号刀)"); System.out.println("========================================\n"); // 测试刀具号:1, 2, 3, 4, 5, 6, 7, 8, 20 int[] toolNumbers = {1, 2, 3, 4, 5, 6, 7, 8, 20}; for (int toolNo : toolNumbers) { System.out.println("\n----------------------------------------"); System.out.println("刀具 T" + toolNo + "D1 的寿命数据:"); System.out.println("----------------------------------------"); try { // 读取三个参数 Integer mop4 = readToolLifeParameter(toolNo, 4); // 剩余工件数 Integer mop3 = readToolLifeParameter(toolNo, 3); // 预警阈值 Integer mop13 = readToolLifeParameter(toolNo, 6); // 目标寿命 // 显示结果 System.out.println("剩余工件数 ($TC_MOP4): " + (mop4 != null ? mop4 : "读取失败")); System.out.println("预警阈值 ($TC_MOP3): " + (mop3 != null ? mop3 : "读取失败")); System.out.println("目标寿命 ($TC_MOP13): " + (mop13 != null ? mop13 : "读取失败")); // 计算寿命百分比和状态 if (mop4 != null && mop13 != null && mop13 > 0) { double lifePercentage = (mop4 * 100.0) / mop13; System.out.println("寿命百分比: " + String.format("%.2f%%", lifePercentage)); // 判断状态 String status; if (mop4 == 0) { status = "❌ 寿命耗尽"; } else if (mop3 != null && mop4 <= mop3) { status = "⚠️ 预警状态"; } else { status = "✓ 正常"; } System.out.println("刀具状态: " + status); } // 间隔一下 Thread.sleep(300); } catch (Exception e) { System.err.println("读取刀具 T" + toolNo + " 数据时发生错误: " + e.getMessage()); } } System.out.println("\n========================================"); System.out.println("批量测试完成!"); System.out.println("========================================"); } /** * 读取刀具寿命参数(简化版,不打印详细日志) */ private Integer readToolLifeParameter(int toolNo, int index) { String path = "/Tool/Supervision/data[u1,c" + toolNo + "," + index + "]"; try { DataValue dataValue = opcUaClient.readValue(path); if (dataValue != null && dataValue.getValue() != null && dataValue.getValue().getValue() != null) { Object value = dataValue.getValue().getValue(); if (value instanceof Number) { return ((Number) value).intValue(); } } } catch (Exception e) { // 忽略错误 } return null; } /** * 断开连接 */ public void disconnect() { if (opcUaClient != null) { opcUaClient.disconnect(); System.out.println("已断开连接"); } } /** * 主测试方法 */ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); // 配置连接参数 System.out.println("=== CNC刀具寿命采集测试 ===\n"); System.out.print("请输入CNC设备IP地址 (默认: 80.23.10.45): "); String ip = scanner.nextLine().trim(); if (ip.isEmpty()) { ip = "80.23.10.45"; } String endpointUrl = "opc.tcp://" + ip + ":4840"; String username = "OPCUA"; String password = "OPCUA123"; ToolLifeTest test = new ToolLifeTest(endpointUrl, username, password); // 连接到设备 if (!test.connect()) { System.err.println("无法连接到设备,程序退出"); scanner.close(); return; } // 交互式测试 while (true) { System.out.println("\n请选择操作:"); System.out.println("1. 读取单个刀具寿命"); System.out.println("2. 批量读取刀具寿命"); System.out.println("3. 浏览OPC UA节点结构"); System.out.println("4. 测试已知路径"); System.out.println("5. 测试Tool/Supervision/data索引"); System.out.println("6. 测试刀具寿命三个参数(找索引映射)"); System.out.println("7. 批量测试多个刀具(1-8和20号刀)"); System.out.println("8. 退出"); System.out.print("请输入选项 (1-8): "); String choice = scanner.nextLine().trim(); switch (choice) { case "1": // 单个刀具测试 System.out.print("请输入D号 (刀具号,例如: 1): "); int dNumber = Integer.parseInt(scanner.nextLine().trim()); System.out.print("请输入H号 (刀沿号,例如: 1): "); int hNumber = Integer.parseInt(scanner.nextLine().trim()); test.readToolLifeData(dNumber, hNumber); break; case "2": // 批量测试 - 根据图片中的刀具表 System.out.println("\n批量读取刀具寿命 (D1-D3, 各H1刀沿)..."); int[] dNumbers = {1, 2, 3}; int[] hNumbers = {1, 1, 1}; test.readMultipleTools(dNumbers, hNumbers); break; case "3": // 浏览节点 test.browseToolNodes(); break; case "4": // 测试自定义路径 System.out.print("请输入完整的OPC UA路径 (例如: /Tool/Supervision/data[u1,c1,3]): "); String customPath = scanner.nextLine().trim(); test.testCustomPath(customPath); break; case "5": // 测试Tool/Supervision/data索引 System.out.print("请输入D号 (刀具号): "); int testD = Integer.parseInt(scanner.nextLine().trim()); test.testToolSupervisionData(testD); break; case "6": // 测试三个参数的索引映射 System.out.print("请输入D号 (刀具号,例如: 3): "); int toolD = Integer.parseInt(scanner.nextLine().trim()); test.findToolLifeIndexes(toolD); break; case "7": // 批量测试1-8和20号刀 test.testMultipleToolsLife(); break; case "8": // 退出 test.disconnect(); scanner.close(); System.out.println("程序已退出"); return; default: System.out.println("无效选项,请重新输入"); } } } }