fix:opcda协议批量读
This commit is contained in:
@@ -24,5 +24,8 @@ public class WResponse {
|
||||
*/
|
||||
private WValue wValue;
|
||||
|
||||
/**
|
||||
* 异常信息
|
||||
*/
|
||||
private String exceptionMessage;
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@ package org.nl.iot.core.driver.protocol.opcda;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jinterop.dcom.common.JIException;
|
||||
import org.jinterop.dcom.core.JIVariant;
|
||||
import org.nl.common.exception.CommonException;
|
||||
import org.nl.iot.core.driver.bo.AttributeBO;
|
||||
import org.nl.iot.core.driver.bo.DeviceBO;
|
||||
import org.nl.iot.core.driver.bo.MetadataEventDTO;
|
||||
import org.nl.iot.core.driver.bo.SiteBO;
|
||||
@@ -22,11 +22,10 @@ import org.nl.iot.core.driver.protocol.opcda.org.openscada.opc.lib.common.NotCon
|
||||
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;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -83,13 +82,11 @@ public class OpcDaProtocolDriverImpl implements DriverCustomService {
|
||||
|
||||
@Override
|
||||
public List<RValue> batchRead(DeviceBO device, List<SiteBO> point) {
|
||||
// todo
|
||||
return List.of();
|
||||
return batchReadValue(getConnector(device), device, point);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<WResponse> batchWrite(DeviceBO device, List<WValue> wValue) {
|
||||
// todo
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@@ -137,6 +134,29 @@ public class OpcDaProtocolDriverImpl implements DriverCustomService {
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private List<RValue> batchReadValue(Server server, DeviceBO deviceBO, List<SiteBO> points) {
|
||||
List<RValue> res = new ArrayList<>();
|
||||
Map<String, Item> itemsMap = getItems(server, deviceBO, points);
|
||||
for (Item item : itemsMap.values()) {
|
||||
RValue rValue = new RValue();
|
||||
rValue.setDeviceBO(deviceBO);
|
||||
SiteBO site = findSite(points, deviceBO, item.getId());
|
||||
rValue.setSiteBO(ObjectUtil.isNotEmpty(site) ? site : null);
|
||||
try {
|
||||
// 组装数据
|
||||
String value = readItem(item);
|
||||
rValue.setValue(value);
|
||||
rValue.setExceptionMessage("");
|
||||
} catch (JIException e) {
|
||||
log.error("读取失败: {} -> {}", item.getId(), e.getMessage());
|
||||
rValue.setExceptionMessage("读取失败!");
|
||||
}
|
||||
res.add(rValue);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 OPC DA 服务器中的 Item 对象
|
||||
* <p>
|
||||
@@ -155,6 +175,31 @@ public class OpcDaProtocolDriverImpl implements DriverCustomService {
|
||||
return group.addItem(groupName + "." + siteBO.getDeviceCode() + "." + siteBO.getAlias());
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public Map<String, Item> getItems(Server server, DeviceBO deviceBO, List<SiteBO> points) {
|
||||
Group group;
|
||||
String groupName = deviceBO.getCode();
|
||||
try {
|
||||
group = server.findGroup(groupName);
|
||||
} catch (UnknownGroupException e) {
|
||||
group = server.addGroup(groupName);
|
||||
}
|
||||
String[] arr = points.stream()
|
||||
.map(site -> groupName + "." + site.getDeviceCode() + "." + site.getAlias())
|
||||
.toArray(String[]::new);
|
||||
return group.addItems(arr);
|
||||
}
|
||||
|
||||
public SiteBO findSite(List<SiteBO> points, DeviceBO deviceBO, String key) {
|
||||
for (SiteBO point : points) {
|
||||
String itemKey = deviceBO.getCode() + "." + point.getDeviceCode() + "." + point.getAlias();
|
||||
if (itemKey.equals(key)) {
|
||||
return point;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取 OPC DA 位号值
|
||||
* <p>
|
||||
@@ -168,34 +213,6 @@ public class OpcDaProtocolDriverImpl implements DriverCustomService {
|
||||
*/
|
||||
public String readItem(Item item) throws JIException {
|
||||
JIVariant jiVariant = item.read(false).getValue();
|
||||
// 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));
|
||||
}
|
||||
|
||||
|
||||
@@ -158,72 +158,100 @@ public class JIVariantToStringUtil {
|
||||
return convertToString(itemValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取JIVariant的实际值,适配不同数据类型
|
||||
* @param jiVariant JIVariant对象
|
||||
* @return 转换后的实际值,空值类型返回null
|
||||
* @throws CommonException 当DCOM解析异常时抛出
|
||||
*/
|
||||
public static Object getValue(JIVariant jiVariant) {
|
||||
if (jiVariant == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
int type = jiVariant.getType();
|
||||
|
||||
// 处理空值类型
|
||||
if (type == JIVariant.VT_EMPTY || type == JIVariant.VT_NULL) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Object value = jiVariant.getObject();
|
||||
|
||||
// 处理无符号整数类型
|
||||
if (value instanceof IJIUnsigned) {
|
||||
return ((IJIUnsigned) value).getValue().intValue();
|
||||
}
|
||||
|
||||
// 处理布尔类型 - 转换为整数表示
|
||||
if (value instanceof Boolean) {
|
||||
return jiVariant.getObjectAsBoolean() ? 1 : 0;
|
||||
}
|
||||
|
||||
// 处理字符串类型
|
||||
if (value instanceof JIString) {
|
||||
return ((JIString) value).getString();
|
||||
}
|
||||
|
||||
// 处理数组类型
|
||||
if (value instanceof JIArray) {
|
||||
return handleArrayValue((JIArray) value, type);
|
||||
}
|
||||
|
||||
// 处理基本数据类型
|
||||
if (isBasicType(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
// 其他类型直接返回原始对象
|
||||
return value;
|
||||
|
||||
} catch (JIException e) {
|
||||
throw new CommonException(e.getMessage());
|
||||
throw new CommonException("JIVariant解析异常: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理数组类型的值
|
||||
*/
|
||||
private static Object handleArrayValue(JIArray jiArray, int type) throws JIException {
|
||||
Class<?> arrayClass = jiArray.getArrayClass();
|
||||
Object arrayInstance = jiArray.getArrayInstance();
|
||||
|
||||
// 处理无符号字节数组
|
||||
if (JIUnsignedByte.class.isAssignableFrom(arrayClass)) {
|
||||
JIUnsignedByte[] byteArray = (JIUnsignedByte[]) arrayInstance;
|
||||
int[] result = new int[byteArray.length];
|
||||
for (int i = 0; i < byteArray.length; i++) {
|
||||
result[i] = byteArray[i].getValue().byteValue() & 0xFF; // 确保无符号
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// 处理无符号短整型数组
|
||||
if (JIUnsignedShort.class.isAssignableFrom(arrayClass)) {
|
||||
JIUnsignedShort[] shortArray = (JIUnsignedShort[]) arrayInstance;
|
||||
int[] result = new int[shortArray.length];
|
||||
for (int i = 0; i < shortArray.length; i++) {
|
||||
result[i] = shortArray[i].getValue().intValue() & 0xFFFF; // 确保无符号
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// 其他数组类型直接返回数组实例
|
||||
return arrayInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为基本数据类型
|
||||
*/
|
||||
private static boolean isBasicType(Object value) {
|
||||
return value instanceof Integer ||
|
||||
value instanceof Short ||
|
||||
value instanceof Float ||
|
||||
value instanceof Double ||
|
||||
value instanceof Long ||
|
||||
value instanceof Byte;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1648,4 +1648,171 @@ public class ApiTest {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void opcDaBatchReadTest() {
|
||||
// 构建OPC DA连接对象
|
||||
JSONObject properties = new JSONObject();
|
||||
properties.put("clsId", "7BC0CC8E-482C-47CA-ABDC-0FE7F9C6E729"); // Kepware OPC Server CLSID
|
||||
properties.put("username", "administrator");
|
||||
properties.put("password", "P@ssw0rd.");
|
||||
|
||||
IotConnect connect = IotConnect.builder()
|
||||
.id(4)
|
||||
.code("B_CBJ01")
|
||||
.host("192.168.81.251")
|
||||
.port(135) // OPC DA默认端口
|
||||
.properties(JSONObject.toJSONString(properties))
|
||||
.protocol("opc-da")
|
||||
.enabled(true)
|
||||
.description("测试OPC DA连接")
|
||||
.build();
|
||||
|
||||
// 构建多个配置对象进行批量读取
|
||||
IotConfig config1 = IotConfig.builder()
|
||||
.id(1)
|
||||
.connectId(4)
|
||||
.alias("action1")
|
||||
.aliasName("温度传感器")
|
||||
.registerAddress("Channel1.Device1.Temperature") // OPC DA标签地址格式
|
||||
.dataType("WORD")
|
||||
.readonly(true)
|
||||
.enabled(true)
|
||||
.description("测试OPC DA温度读取")
|
||||
.build();
|
||||
|
||||
IotConfig config2 = IotConfig.builder()
|
||||
.id(2)
|
||||
.connectId(4)
|
||||
.alias("mode")
|
||||
.aliasName("压力传感器")
|
||||
.registerAddress("Channel1.Device1.Pressure")
|
||||
.dataType("WORD")
|
||||
.readonly(true)
|
||||
.enabled(true)
|
||||
.description("测试OPC DA压力读取")
|
||||
.build();
|
||||
|
||||
IotConfig config3 = IotConfig.builder()
|
||||
.id(3)
|
||||
.connectId(4)
|
||||
.alias("material1")
|
||||
.aliasName("湿度传感器")
|
||||
.registerAddress("Channel1.Device1.Humidity")
|
||||
.dataType("STRING")
|
||||
.readonly(true)
|
||||
.enabled(true)
|
||||
.description("测试OPC DA湿度读取")
|
||||
.build();
|
||||
|
||||
// IotConfig config4 = IotConfig.builder()
|
||||
// .id(4)
|
||||
// .connectId(4)
|
||||
// .alias("status")
|
||||
// .aliasName("设备状态")
|
||||
// .registerAddress("Channel1.Device1.Status")
|
||||
// .dataType("BOOLEAN")
|
||||
// .readonly(true)
|
||||
// .enabled(true)
|
||||
// .description("测试OPC DA状态读取")
|
||||
// .build();
|
||||
|
||||
// 执行批量读取操作
|
||||
try {
|
||||
System.out.println("========== 开始测试OPC DA批量读取 ==========");
|
||||
System.out.println("连接信息: " + connect.getHost() + ":" + connect.getPort());
|
||||
System.out.println("CLSID: " + properties.getString("clsId"));
|
||||
System.out.println("用户名: " + properties.getString("username"));
|
||||
|
||||
// 转换为DeviceBO
|
||||
DeviceBO deviceBO = DeviceBO.builder()
|
||||
.id(String.valueOf(connect.getId()))
|
||||
.code(connect.getCode())
|
||||
.properties(connect.getProperties())
|
||||
.host(connect.getHost())
|
||||
.port(connect.getPort())
|
||||
.protocol(connect.getProtocol())
|
||||
.build();
|
||||
|
||||
// 转换为SiteBO列表
|
||||
java.util.List<SiteBO> siteBOList = java.util.Arrays.asList(
|
||||
SiteBO.builder()
|
||||
.deviceCode("B_CBJ01.B_CBJ01")
|
||||
.alias(config1.getAlias())
|
||||
.aliasName(config1.getAliasName())
|
||||
.registerAddress(config1.getRegisterAddress())
|
||||
.dataType(config1.getDataType())
|
||||
.readonly(config1.getReadonly())
|
||||
.build(),
|
||||
SiteBO.builder()
|
||||
.deviceCode("B_CBJ01.B_CBJ01")
|
||||
.alias(config2.getAlias())
|
||||
.aliasName(config2.getAliasName())
|
||||
.registerAddress(config2.getRegisterAddress())
|
||||
.dataType(config2.getDataType())
|
||||
.readonly(config2.getReadonly())
|
||||
.build(),
|
||||
SiteBO.builder()
|
||||
.deviceCode("B_CBJ01.B_CBJ01")
|
||||
.alias(config3.getAlias())
|
||||
.aliasName(config3.getAliasName())
|
||||
.registerAddress(config3.getRegisterAddress())
|
||||
.dataType(config3.getDataType())
|
||||
.readonly(config3.getReadonly())
|
||||
.build()
|
||||
// SiteBO.builder()
|
||||
// .deviceCode(connect.getCode())
|
||||
// .alias(config4.getAlias())
|
||||
// .aliasName(config4.getAliasName())
|
||||
// .registerAddress(config4.getRegisterAddress())
|
||||
// .dataType(config4.getDataType())
|
||||
// .readonly(config4.getReadonly())
|
||||
// .build()
|
||||
);
|
||||
|
||||
System.out.println("批量读取标签数量: " + siteBOList.size());
|
||||
for (SiteBO site : siteBOList) {
|
||||
System.out.println(" - " + site.getAliasName() + " (" + site.getAlias() + "): " +
|
||||
site.getRegisterAddress() + " [" + site.getDataType() + "]");
|
||||
}
|
||||
|
||||
// 调用驱动批量读取数据
|
||||
List<RValue> result = opcDaProtocolDriver.batchRead(deviceBO, siteBOList);
|
||||
|
||||
System.out.println("批量读取完成!");
|
||||
System.out.println("读取结果:");
|
||||
|
||||
// 输出每个标签的读取结果
|
||||
for (RValue rValue : result) {
|
||||
SiteBO site = rValue.getSiteBO();
|
||||
String value = rValue.getValue();
|
||||
String exceptionMsg = rValue.getExceptionMessage();
|
||||
|
||||
if (site != null) {
|
||||
System.out.println(" - " + site.getAliasName() + " (" + site.getAlias() + "): " +
|
||||
(value != null ? value : "null") +
|
||||
(exceptionMsg != null && !exceptionMsg.isEmpty() ? " [" + exceptionMsg + "]" : ""));
|
||||
} else {
|
||||
System.out.println(" - 未知标签: " +
|
||||
(value != null ? value : "null") +
|
||||
(exceptionMsg != null && !exceptionMsg.isEmpty() ? " [" + exceptionMsg + "]" : ""));
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("========== OPC DA批量读取测试完成 ==========");
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("OPC DA批量读取测试失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
|
||||
// 提供故障排除建议
|
||||
System.err.println("\n========== 故障排除建议 ==========");
|
||||
System.err.println("1. 检查OPC DA服务器是否正在运行");
|
||||
System.err.println("2. 验证CLSID是否正确: " + properties.getString("clsId"));
|
||||
System.err.println("3. 确认标签地址格式是否正确");
|
||||
System.err.println("4. 检查DCOM配置和防火墙设置");
|
||||
System.err.println("5. 验证用户权限和密码");
|
||||
System.err.println("6. 建议先使用OPC客户端工具(如Matrikon OPC Explorer)测试连接");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user