fix:opcda协议读写
This commit is contained in:
@@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user