diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/OpcDaProtocolDriverImpl.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/OpcDaProtocolDriverImpl.java index 944f2bc..cff1637 100644 --- a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/OpcDaProtocolDriverImpl.java +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/OpcDaProtocolDriverImpl.java @@ -20,6 +20,7 @@ import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.common.Alread import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.common.ConnectionInformation; import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.common.NotConnectedException; import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.da.*; +import org.nl.iot.core.driver.protocol.opcda.util.JIVariantToStringUtil; import org.nl.iot.core.driver.service.DriverCustomService; import org.nl.iot.modular.iot.entity.IotConfig; import org.nl.iot.modular.iot.entity.IotConnect; @@ -167,30 +168,35 @@ public class OpcDaProtocolDriverImpl implements DriverCustomService { */ public String readItem(Item item) throws JIException { JIVariant jiVariant = item.read(false).getValue(); - switch (jiVariant.getType()) { - case JIVariant.VT_I2: - short shortValue = jiVariant.getObjectAsShort(); - return String.valueOf(shortValue); - case JIVariant.VT_I4: - int intValue = jiVariant.getObjectAsInt(); - return String.valueOf(intValue); - case JIVariant.VT_I8: - long longValue = jiVariant.getObjectAsLong(); - return String.valueOf(longValue); - case JIVariant.VT_R4: - float floatValue = jiVariant.getObjectAsFloat(); - return String.valueOf(floatValue); - case JIVariant.VT_R8: - double doubleValue = jiVariant.getObjectAsDouble(); - return String.valueOf(doubleValue); - case JIVariant.VT_BOOL: - boolean boolValue = jiVariant.getObjectAsBoolean(); - return String.valueOf(boolValue); - case JIVariant.VT_BSTR: - return jiVariant.getObjectAsString2(); - default: - return jiVariant.getObject().toString(); - } +// return JIVariantToStringUtil.convertFromOpenSCADA(jiVariant); +// switch (jiVariant.getType()) { +// case JIVariant.VT_I2: +// short shortValue = jiVariant.getObjectAsShort(); +// return String.valueOf(shortValue); +// case JIVariant.VT_UI2: +// short shortValue2 = jiVariant.getObjectAsShort(); +// return String.valueOf(shortValue2 & 0xFFFF); +// case JIVariant.VT_I4: +// int intValue = jiVariant.getObjectAsInt(); +// return String.valueOf(intValue); +// case JIVariant.VT_I8: +// long longValue = jiVariant.getObjectAsLong(); +// return String.valueOf(longValue); +// case JIVariant.VT_R4: +// float floatValue = jiVariant.getObjectAsFloat(); +// return String.valueOf(floatValue); +// case JIVariant.VT_R8: +// double doubleValue = jiVariant.getObjectAsDouble(); +// return String.valueOf(doubleValue); +// case JIVariant.VT_BOOL: +// boolean boolValue = jiVariant.getObjectAsBoolean(); +// return String.valueOf(boolValue); +// case JIVariant.VT_BSTR: +// return jiVariant.getObjectAsString2(); +// default: +// return jiVariant.getObject().toString(); +// } + return String.valueOf(JIVariantToStringUtil.getValue(jiVariant)); } /** diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/util/JIVariantToStringUtil.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/util/JIVariantToStringUtil.java new file mode 100644 index 0000000..5a03bc8 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/util/JIVariantToStringUtil.java @@ -0,0 +1,229 @@ +package org.nl.iot.core.driver.protocol.opcda.util; + +import org.jinterop.dcom.common.JIException; +import org.jinterop.dcom.core.*; +import org.nl.common.exception.CommonException; + +import java.math.BigDecimal; +import java.util.Arrays; + +/** + * OpenSCADA中JIVariant转String的工具类 + * 全覆盖JIVariant所有VT_类型,适配DCOM数据解析场景 + */ +public class JIVariantToStringUtil { + + /** + * 将JIVariant转换为String类型(全覆盖所有VT_类型) + * @param jiVariant OpenSCADA读取的JIVariant数据 + * @return 转换后的字符串,未知类型返回"未知类型[类型码]",异常返回"解析异常:xxx" + */ + public static String convertToString(JIVariant jiVariant) { + // 空值校验 + if (jiVariant == null) { + return ""; + } + + try { + int type = jiVariant.getType(); + + // 处理数组类型(VT_ARRAY是位标志,需要单独检查) + if ((type & JIVariant.VT_ARRAY) != 0) { + JIArray array = jiVariant.getObjectAsArray(); + Object[] arrayElements = (Object[]) array.getArrayInstance(); + // 遍历数组元素,递归转换为字符串 + String[] strElements = Arrays.stream(arrayElements) + .map(element -> { + if (element instanceof JIVariant) { + return convertToString((JIVariant) element); + } else { + return element == null ? "" : element.toString(); + } + }) + .toArray(String[]::new); + return Arrays.toString(strElements); // 格式:[元素1, 元素2, ...] + } + + switch (type) { + // 空值/未初始化类型 + case JIVariant.VT_EMPTY: + return ""; // 未初始化空值返回空字符串 + case JIVariant.VT_NULL: + return "NULL"; // 显式NULL返回"NULL" + + // 整数类型 + case JIVariant.VT_I1: // 1字节有符号整数(byte) + return String.valueOf(((Number) jiVariant.getObject()).byteValue()); + case JIVariant.VT_I2: // 2字节有符号整数(short) + return String.valueOf(jiVariant.getObjectAsShort()); + case JIVariant.VT_I4: // 4字节有符号整数(int) + return String.valueOf(jiVariant.getObjectAsInt()); + case JIVariant.VT_I8: // 8字节有符号整数(long) + return String.valueOf(jiVariant.getObjectAsLong()); + case JIVariant.VT_UI1: // 1字节无符号整数(Byte) + return String.valueOf(((Number) jiVariant.getObject()).intValue() & 0xFF); + case JIVariant.VT_UI2: // 2字节无符号整数(Short) + return String.valueOf(jiVariant.getObjectAsShort() & 0xFFFF); + case JIVariant.VT_UI4: // 4字节无符号整数(Integer) + return String.valueOf(jiVariant.getObjectAsInt() & 0xFFFFFFFFL); + case JIVariant.VT_INT: // 平台相关有符号整数(int) + return String.valueOf(jiVariant.getObjectAsInt()); + case JIVariant.VT_UINT: // 平台相关无符号整数(long) + return String.valueOf(jiVariant.getObjectAsInt() & 0xFFFFFFFFL); + + // 浮点类型 + case JIVariant.VT_R4: // 4字节浮点(float) + return String.valueOf(jiVariant.getObjectAsFloat()); + case JIVariant.VT_R8: // 8字节浮点(double) + return String.valueOf(jiVariant.getObjectAsDouble()); + + // 布尔类型 + case JIVariant.VT_BOOL: // 布尔值 + // COM的BOOL是short类型(0/非0),转换为标准boolean字符串 + return String.valueOf(jiVariant.getObjectAsBoolean()); + + // 货币/高精度数值类型 +// case JIVariant.VT_CY: // 货币类型 +// JICurrency currency = jiVariant.getObjectAsCurrency(); +// return currency.getValue().toPlainString(); // 避免科学计数法 +// case JIVariant.VT_DECIMAL: // 高精度十进制 +// BigDecimal decimal = jiVariant.getObjectAsDecimal(); +// return decimal.toPlainString(); + + // 日期类型 +// case JIVariant.VT_DATE: // 日期时间 +// JIDate date = jiVariant.getObjectAsDate(); +// return date.getValue().toString(); // 转Java Date字符串,可自定义格式 + + // 字符串类型 + case JIVariant.VT_BSTR: // COM宽字符字符串 + return jiVariant.getObjectAsString2(); // 使用标准的getString方法 + + // 错误类型 + case JIVariant.VT_ERROR: // COM错误码 + return String.valueOf(jiVariant.getObjectAsInt()); // 错误码通常是int类型 + + // COM接口类型(无法直接转字符串,返回标识) + case JIVariant.VT_UNKNOWN: // IUnknown接口 + return "[COM_IUnknown]"; + case JIVariant.VT_DISPATCH: // IDispatch接口 + return "[COM_IDispatch]"; + + // 变体类型(递归解析) + case JIVariant.VT_VARIANT: + JIVariant variantValue = (JIVariant) jiVariant.getObject(); + return convertToString(variantValue); + + // 按引用传递类型(先获取引用的实际值,再递归转换) +// case JIVariant.VT_BYREF_VT_UI1: +// case JIVariant.VT_BYREF_VT_I2: +// case JIVariant.VT_BYREF_VT_I4: +// case JIVariant.VT_BYREF_VT_R4: +// case JIVariant.VT_BYREF_VT_R8: +// case JIVariant.VT_BYREF_VT_BOOL: +// case JIVariant.VT_BYREF_VT_ERROR: +// case JIVariant.VT_BYREF_VT_CY: +// case JIVariant.VT_BYREF_VT_DATE: +// case JIVariant.VT_BYREF_VT_BSTR: +// case JIVariant.VT_BYREF_VT_UNKNOWN: +// case JIVariant.VT_BYREF_VT_DISPATCH: +// case JIVariant.VT_BYREF_VT_ARRAY: +// case JIVariant.VT_BYREF_VT_VARIANT: +// case JIVariant.VT_BYREF_VT_DECIMAL: +// case JIVariant.VT_BYREF_VT_I1: +// case JIVariant.VT_BYREF_VT_UI2: +// case JIVariant.VT_BYREF_VT_UI4: +// case JIVariant.VT_BYREF_VT_I8: +// case JIVariant.VT_BYREF_VT_INT: +// case JIVariant.VT_BYREF_VT_UINT: +// JIVariant refVariant = jiVariant.getReferent(); // 获取引用的实际值 +// return convertToString(refVariant); + + // 未知类型 + default: + return "未知类型[" + type + "]"; + } + } catch (Exception e) { + // 捕获DCOM解析异常,返回异常信息(可根据业务调整,比如返回空字符串) + return "解析异常:" + e.getMessage(); + } + } + + /** + * 简化调用:适配OpenSCADA的item.read(false).getValue()场景 + * @param itemValue OpenSCADA读取的JIVariant对象(item.read(false).getValue()) + * @return 转换后的字符串 + */ + public static String convertFromOpenSCADA(JIVariant itemValue) { + return convertToString(itemValue); + } + + public static Object getValue(JIVariant jiVariant) { + try { + Object e = jiVariant.getObject(); + if (e instanceof IJIUnsigned) { + return Integer.valueOf(((IJIUnsigned) e).getValue().intValue()); + } else if (e instanceof Boolean) { + return jiVariant.getObjectAsBoolean() ? Integer.valueOf(1) : Integer.valueOf(0); + } else if (e instanceof JIString) { + return ((JIString) e).getString(); + } else if (!(e instanceof JIArray)) { + if (e instanceof Integer) { + return jiVariant.getObject(); + } else if (e instanceof Short) { + return jiVariant.getObject(); + } else if (e instanceof Float) { + return jiVariant.getObject(); + } else if (e instanceof Double) { + return jiVariant.getObject(); + } else { + if (jiVariant.getType() == 0) { + System.err.println("因类型为emtpy 返回 null"); + return null; + } else if (jiVariant.getType() == 1) { + System.err.println("因类型为null 返回 null"); + return null; + } else { + return jiVariant.getObject(); + } + } + } else { + Class clazz = ((JIArray) e).getArrayClass(); + int[] r; + int i; + if (JIUnsignedByte.class.isAssignableFrom(clazz)) { + JIUnsignedByte[] arg7 = (JIUnsignedByte[]) ((JIUnsignedByte[]) ((JIArray) e).getArrayInstance()); + r = new int[arg7.length]; + + for (i = 0; i < arg7.length; ++i) { + r[i] = arg7[i].getValue().byteValue(); + } + + return r; + } else if (!JIUnsignedShort.class.isAssignableFrom(clazz)) { + if (jiVariant.getType() == 0) { + System.err.println("因类型为emtpy 返回 null"); + return null; + } else if (jiVariant.getType() == 1) { + System.err.println("因类型为null 返回 null"); + return null; + } else { + return ((JIArray) e).getArrayInstance(); + } + } else { + JIUnsignedShort[] array = (JIUnsignedShort[]) ((JIUnsignedShort[]) ((JIArray) e) + .getArrayInstance()); + r = new int[array.length]; + + for (i = 0; i < array.length; ++i) { + r[i] = array[i].getValue().intValue(); + } + + return r; + } + } + } catch (JIException e) { + throw new CommonException(e.getMessage()); + } + } +} diff --git a/nl-web-app/src/test/java/org/nl/ApiTest.java b/nl-web-app/src/test/java/org/nl/ApiTest.java index eeb1799..f0982a0 100644 --- a/nl-web-app/src/test/java/org/nl/ApiTest.java +++ b/nl-web-app/src/test/java/org/nl/ApiTest.java @@ -1481,7 +1481,13 @@ public class ApiTest { public void opcDaReadTest() { // 构建OPC DA连接对象 JSONObject properties = new JSONObject(); - properties.put("clsId", "{F8582CF2-88FB-11D0-B850-00C0F0104305}"); // 示例CLSID,需要根据实际OPC服务器修改 + + // 常见的OPC DA服务器CLSID示例(需要根据实际服务器修改): + // Kepware: "7BC0CC8E-482C-47CA-ABDC-0FE7F9C6E729" + // Matrikon: "F8582CF2-88FB-11D0-B850-00C0F0104305" + // RSLinx: "A06B0CF6-4E1F-11D3-8F4D-00104B33C6E8" + // 注意:CLSID格式不要包含花括号 + properties.put("clsId", "7BC0CC8E-482C-47CA-ABDC-0FE7F9C6E729"); // Kepware OPC Server CLSID properties.put("username", "administrator"); properties.put("password", "P@ssw0rd."); @@ -1496,17 +1502,17 @@ public class ApiTest { .description("测试OPC DA连接") .build(); - // 构建配置对象 - 写入操作,设置为可写 + // 构建配置对象 IotConfig config = IotConfig.builder() .id(2) .connectId(3) .alias("action1") - .aliasName("标签2") - .registerAddress("ns=4;s=|var|HCFA-PLC.Application.B_CBJ01.action1") // OPC DA标签地址 + .aliasName("标签1") + .registerAddress("Channel1.Device1.Tag1") // OPC DA标签地址,格式:通道.设备.标签 .dataType("WORD") - .readonly(false) // 设置为可写 + .readonly(true) .enabled(true) - .description("测试OPC DA写入") + .description("测试OPC DA读取") .build(); // 执行读取操作 @@ -1529,7 +1535,7 @@ public class ApiTest { // 转换为SiteBO SiteBO siteBO = SiteBO.builder() - .deviceCode("B_CBJ01") + .deviceCode("B_CBJ01.B_CBJ01") .alias(config.getAlias()) .aliasName(config.getAliasName()) .registerAddress(config.getRegisterAddress()) @@ -1554,7 +1560,13 @@ public class ApiTest { public void opcDaWriteTest() { // 构建OPC DA连接对象 JSONObject properties = new JSONObject(); - properties.put("clsId", "{F8582CF2-88FB-11D0-B850-00C0F0104305}"); // 示例CLSID,需要根据实际OPC服务器修改 + + // 常见的OPC DA服务器CLSID示例(需要根据实际服务器修改): + // Kepware: "7BC0CC8E-482C-47CA-ABDC-0FE7F9C6E729" + // Matrikon: "F8582CF2-88FB-11D0-B850-00C0F0104305" + // RSLinx: "A06B0CF6-4E1F-11D3-8F4D-00104B33C6E8" + // 注意:CLSID格式不要包含花括号 + properties.put("clsId", "7BC0CC8E-482C-47CA-ABDC-0FE7F9C6E729"); // Kepware OPC Server CLSID properties.put("username", "administrator"); properties.put("password", "P@ssw0rd."); @@ -1575,7 +1587,7 @@ public class ApiTest { .connectId(3) .alias("tag2") .aliasName("标签2") - .registerAddress("B_CBJ01.B_CBJ01") // OPC DA标签地址 + .registerAddress("Channel1.Device1.Tag2") // OPC DA标签地址,格式:通道.设备.标签 .dataType("INT") .readonly(false) // 设置为可写 .enabled(true)