res = new ArrayList<>();
+ PlcWriteRequest.Builder writeRequestBuilder = doBuildWriteRequest(modbusMaster, wValues);
+ PlcWriteRequest writeRequest = writeRequestBuilder.build();
+ // 执行写入(异步+超时控制)
+ CompletableFuture extends PlcWriteResponse> writeFuture = writeRequest.execute();
+ PlcWriteResponse coilResponse = writeFuture.get(10, TimeUnit.SECONDS);
+ for (WValue wValue : wValues) {
+ try {
+ PlcResponseCode responseCode = coilResponse.getResponseCode(wValue.getPoint().getAlias());
+ res.add(new WResponse(responseCode == PlcResponseCode.OK,
+ wValue, responseCode == PlcResponseCode.OK ? "" : String.format(
+ "写入Modbus失败,设备编码:%s,地址:%s,响应码:%s", wValue.getPoint().getAlias(),
+ wValue.getPoint().getRegisterAddress(), responseCode
+ )));
+ } catch (Exception e) {
+ res.add(new WResponse(false, wValue, String.format("写入Modbus失败,设备编码:%s,地址:%s,响应码:%s"
+ , wValue.getPoint().getAlias(), wValue.getPoint().getRegisterAddress(), e.getMessage()
+ )));
+ }
+ }
+ return res;
}
/**
* 获取 Modbus Master 连接器
- *
- * 该方法用于根据设备ID和驱动配置获取或创建 Modbus Master 连接器。
- * 如果连接器已存在, 则直接返回;否则, 根据配置创建新的连接器并初始化。
- * 初始化失败时, 会移除连接器并抛出异常。
- *
- * @param connectId 连接ID(一个设备对应多个连接)
- * @param driverConfig 驱动配置, 包含连接 Modbus 设备所需的主机地址和端口号
- * @return ModbusMaster 返回与设备关联的 Modbus Master 连接器
- * @throws CommonException 如果连接器初始化失败, 抛出此异常
*/
- private ModbusMaster getConnector(IotConnect connect) {
- log.debug("Modbus Tcp Connection Info: {}", connect);
- ModbusMaster modbusMaster = connectMap.get(connect.getId().toString());
- if (Objects.isNull(modbusMaster)) {
- IpParameters params = new IpParameters();
- params.setHost(connect.getHost());
- params.setPort(connect.getPort());
- modbusMaster = modbusFactory.createTcpMaster(params, true);
- try {
- modbusMaster.init();
- connectMap.put(connect.getId().toString(), modbusMaster);
- } catch (ModbusInitException e) {
- connectMap.entrySet().removeIf(next -> next.getKey().equals(connect.getId().toString()));
- log.error("Connect modbus master error: {}", e.getMessage(), e);
- throw new CommonException(e.getMessage());
+ private PlcConnection getConnector(DeviceBO deviceBO) {
+ log.debug("Modbus Tcp Connection Info: {}", deviceBO);
+ PlcConnection modbusMaster = connectMap.get(deviceBO.getId());
+ try {
+ if (Objects.isNull(modbusMaster)) {
+ modbusMaster = new DefaultPlcDriverManager().getConnection(ModBusTcpUtils.buildModBusPlcUrl(deviceBO));
}
+ } catch (Exception e) {
+ throw new RuntimeException(e);
}
return modbusMaster;
}
- /**
- * 读取 Modbus 设备点位值
- *
- * 根据点位配置中的功能码(functionCode)和偏移量(offset), 从 Modbus 设备中读取相应类型的值。
- * 支持的功能码包括:
- * - 1: 读取线圈状态(Coil Status)
- * - 2: 读取输入状态(Input Status)
- * - 3: 读取保持寄存器(Holding Register)
- * - 4: 读取输入寄存器(Input Register)
- *
- * @param modbusMaster ModbusMaster 连接器, 用于与设备通信
- * @param pointConfig 点位配置, 包含从站ID(slaveId), 功能码(functionCode), 偏移量(offset)等信息
- * @param type 点位值类型, 用于确定寄存器中数据的解析方式
- * @return String 返回读取到的点位值, 以字符串形式表示。如果功能码不支持, 则返回 "0"。
- */
- private String readValue(ModbusMaster modbusMaster, IotConnect connect, IotConfig config) {
- JSONObject pointConfig = JSONObject.parseObject(connect.getProperties());
- String type = config.getDataType();
- int slaveId = pointConfig.getIntValue("slaveId");
- int offset = Integer.parseInt(config.getRegisterAddress());
- int functionCode = ModBusTcpUtils.getFunctionCode(offset);
-
- // 计算实际的寄存器地址(Modbus协议地址从0开始)
- int actualAddress = ModBusTcpUtils.getActualAddress(offset, functionCode);
+ @SneakyThrows
+ public String readValue(PlcConnection modbusMaster, SiteBO point) {
+ // 1. 解析配置
+ PlcReadRequest.Builder readBuilder = doBuildReadRequest(modbusMaster, Collections.singletonList(point));
- switch (functionCode) {
- case 1:
- BaseLocator coilLocator = BaseLocator.coilStatus(slaveId, actualAddress);
- Boolean coilValue = ModBusTcpUtils.getMasterValue(modbusMaster, coilLocator);
- return String.valueOf(coilValue);
- case 2:
- BaseLocator inputLocator = BaseLocator.inputStatus(slaveId, actualAddress);
- Boolean inputStatusValue = ModBusTcpUtils.getMasterValue(modbusMaster, inputLocator);
- return String.valueOf(inputStatusValue);
- case 3:
- BaseLocator holdingLocator = BaseLocator.holdingRegister(slaveId, actualAddress, ModBusTcpUtils.getValueType(type));
- Number holdingValue = ModBusTcpUtils.getMasterValue(modbusMaster, holdingLocator);
- return String.valueOf(holdingValue);
- case 4:
- BaseLocator inputRegister = BaseLocator.inputRegister(slaveId, actualAddress, ModBusTcpUtils.getValueType(type));
- Number inputRegisterValue = ModBusTcpUtils.getMasterValue(modbusMaster, inputRegister);
- return String.valueOf(inputRegisterValue);
- default:
- return "0";
+ // 3. 执行请求
+ PlcReadRequest readRequest = readBuilder.build();
+ CompletableFuture extends PlcReadResponse> readFuture = readRequest.execute();
+ PlcReadResponse readResponse = readFuture.get(10, TimeUnit.SECONDS);
+
+ // 4. 校验响应码
+ PlcResponseCode responseCode = readResponse.getResponseCode(point.getAlias());
+ if (responseCode != PlcResponseCode.OK) {
+ log.warn("读取Modbus失败,设备编码:{},地址:{},响应码:{}", point.getAlias(), point.getRegisterAddress(), responseCode);
}
+
+ // 5. 取值并转换
+ PlcValue plcValue = readResponse.getPlcValue(point.getAlias());
+ if (plcValue == null) {
+ log.warn("读取到空值,设备编码:{},地址:{}", point.getAlias(), point.getRegisterAddress());
+ }
+ // 根据类型转换
+ return ModbusPlcValueConvertUtil.convertPlcValueToString(plcValue, point.getDataType());
+ }
+
+ @SneakyThrows
+ public List batchReadValue(PlcConnection modbusMaster, DeviceBO deviceBO, List points) {
+ // 1. 解析配置
+ PlcReadRequest.Builder readBuilder = doBuildReadRequest(modbusMaster, points);
+ // 3. 执行请求
+ PlcReadRequest readRequest = readBuilder.build();
+ CompletableFuture extends PlcReadResponse> readFuture = readRequest.execute();
+ PlcReadResponse readResponse = readFuture.get(10, TimeUnit.SECONDS);
+ List list = new ArrayList<>();
+ // 4.组装数据
+ for (SiteBO point : points) {
+ try {
+ PlcResponseCode responseCode = readResponse.getResponseCode(point.getAlias());
+ // 4. 校验响应码
+ if (responseCode != PlcResponseCode.OK) {
+ list.add(new RValue(deviceBO, point, null, String.format(
+ "读取Modbus失败,设备编码:%s,地址:%s,响应码:%s", point.getAlias(), point.getRegisterAddress(), responseCode
+ )));
+ continue;
+ }
+ // 5. 取值并转换
+ PlcValue plcValue = readResponse.getPlcValue(point.getAlias());
+ list.add(new RValue(deviceBO, point, ModbusPlcValueConvertUtil.convertPlcValueToString(plcValue, point.getDataType()), ""));
+ } catch (Exception e) {
+ list.add(new RValue(deviceBO, point, null, String.format(
+ "读取Modbus失败,设备编码:%s,地址:%s,响应码:%s", point.getAlias(), point.getRegisterAddress(), e.getMessage()
+ )));
+ }
+ }
+ return list;
+ }
+
+ public static PlcReadRequest.Builder doBuildReadRequest(PlcConnection modbusMaster, List points) {
+ PlcReadRequest.Builder readBuilder = modbusMaster.readRequestBuilder();
+ for (SiteBO point : points) {
+ int offset = Integer.parseInt(point.getRegisterAddress());
+ int functionCode = ModBusTcpUtils.getFunctionCode(offset);
+ int actualAddress = ModBusTcpUtils.getActualAddress(offset, functionCode);
+
+ // 构建读取请求
+ String tagName = point.getAlias();
+ // 校验地址格式
+ if (!ModbusPlcValueConvertUtil.containerType(point.getDataType())) {
+ log.warn("Modbus数据类型错误:设备编码:{}", tagName);
+ continue;
+ }
+ String modbusAddress = ModBusTcpUtils.getModBus4JAddress(offset, actualAddress, point.getDataType());
+ if (!modbusAddress.contains(":")) {
+ log.warn("Modbus地址格式错误:{},设备编码:{}" ,modbusAddress, tagName);
+ continue;
+ }
+ readBuilder.addTagAddress(tagName, modbusAddress);
+ }
+ return readBuilder;
+ }
+
+ public static PlcWriteRequest.Builder doBuildWriteRequest(PlcConnection modbusMaster, List wValues) {
+ PlcWriteRequest.Builder writeRequestBuilder = modbusMaster.writeRequestBuilder();
+ // 1. 解析配置
+ for (WValue wValue : wValues) {
+ SiteBO point = wValue.getPoint();
+ String type = point.getDataType();
+ int offset = Integer.parseInt(point.getRegisterAddress());
+ // 功能码
+ int functionCode = ModBusTcpUtils.getFunctionCode(offset);
+ // 寄存器实际地址
+ int actualAddress = ModBusTcpUtils.getActualAddress(offset, functionCode);
+
+ // 2. 构建读取请求
+ String tagName = point.getAlias();
+ // 校验地址格式(可选,用于调试)
+ String modbusAddress = ModBusTcpUtils.getModBus4JAddress(offset, actualAddress, type);
+ if (!modbusAddress.contains(":")) {
+ throw new IllegalArgumentException("Modbus地址格式错误:" + modbusAddress + ",设备编码:" + tagName);
+ }
+ // modbusMaster.connect();
+ // 2. 设置要写入的值
+ writeRequestBuilder.addTagAddress(tagName, modbusAddress, JavaToModBusPlcValueConvertUtil.convert(wValue.getValue(), type));
+ }
+ return writeRequestBuilder;
}
/**
* 向 Modbus 设备写入点位值
- *
- * 根据点位配置中的功能码(functionCode)和偏移量(offset), 将指定值写入 Modbus 设备的相应点位。
- * 支持的功能码包括:
- * - 1: 写入线圈状态(Coil Status)
- * - 3: 写入保持寄存器(Holding Register)
- *
- * 对于功能码 1, 写入布尔值到线圈状态, 并返回写入结果。
- * 对于功能码 3, 写入数值到保持寄存器, 并返回写入成功状态。
- * 其他功能码暂不支持, 返回 false。
- *
- * @param modbusMaster ModbusMaster 连接器, 用于与设备通信
- * @param pointConfig 点位配置, 包含从站ID(slaveId), 功能码(functionCode), 偏移量(offset)等信息
- * @param wValue 待写入的值, 包含值类型和具体数值
- * @return boolean 返回写入结果, true 表示写入成功, false 表示写入失败或不支持的功能码
*/
- private boolean writeValue(ModbusMaster modbusMaster, IotConnect connect, IotConfig config, WValue wValue) {
- JSONObject pointConfig = JSONObject.parseObject(connect.getProperties());
- String type = config.getDataType();
- int slaveId = pointConfig.getIntValue("slaveId");
- int offset = Integer.parseInt(config.getRegisterAddress());
- int functionCode = ModBusTcpUtils.getFunctionCode(offset);
- wValue.setType(type);
-
- // 计算实际的寄存器地址(Modbus协议地址从0开始)
- int actualAddress = ModBusTcpUtils.getActualAddress(offset, functionCode);
- switch (functionCode) {
- case 1:
- WriteCoilResponse coilResponse = ModBusTcpUtils.setMasterValue(modbusMaster, slaveId, actualAddress, wValue);
- return !coilResponse.isException();
- case 3:
- BaseLocator locator = BaseLocator.holdingRegister(slaveId, actualAddress, ModBusTcpUtils.getValueType(type));
- ModBusTcpUtils.setMasterValue(modbusMaster, locator, wValue);
- return true;
- default:
- return false;
- }
+ @SneakyThrows
+ private boolean writeValue(PlcConnection modbusMaster, DeviceBO deviceBO, WValue wValue) {
+ // 1. 解析配置
+ PlcWriteRequest.Builder writeRequestBuilder = doBuildWriteRequest(modbusMaster, Collections.singletonList(wValue));
+ PlcWriteRequest writeRequest = writeRequestBuilder.build();
+ // 3. 执行写入(异步+超时控制)
+ CompletableFuture extends PlcWriteResponse> writeFuture = writeRequest.execute();
+ PlcWriteResponse coilResponse = writeFuture.get(10, TimeUnit.SECONDS);
+ PlcResponseCode responseCode = coilResponse.getResponseCode(wValue.getPoint().getAlias());
+ return responseCode == PlcResponseCode.OK;
}
}
diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/modbustcp/util/JavaToModBusPlcValueConvertUtil.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/modbustcp/util/JavaToModBusPlcValueConvertUtil.java
new file mode 100644
index 0000000..f7639b1
--- /dev/null
+++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/modbustcp/util/JavaToModBusPlcValueConvertUtil.java
@@ -0,0 +1,277 @@
+package org.nl.iot.core.driver.protocol.modbustcp.util;
+
+import java.math.BigInteger;
+import java.time.Duration;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+
+/**
+ * 根据modbus类型string 将String转成对应的Java类型
+ * eg: (value(String), type(String)) -> ("TRUE", "BOOL") 需要返回 true
+ * eg: (value(String), type(String)) -> ("12", "INT") 需要返回 12
+ * @author: lyd
+ * @date: 2026/3/12
+ */
+public class JavaToModBusPlcValueConvertUtil {
+
+ /**
+ * 核心转换方法:根据Modbus类型字符串,将String值转为对应的Java类型
+ * @param value 字符串值
+ * @param type Modbus类型字符串(如"BOOL"、"INT"、"REAL")
+ * @return 转换后的Java对象
+ * @throws IllegalArgumentException 类型不支持或参数异常
+ */
+ public static Object convert(String value, String type) {
+ if (value == null) {
+ throw new IllegalArgumentException("Value cannot be null");
+ }
+ if (type == null || type.trim().isEmpty()) {
+ throw new IllegalArgumentException("Type cannot be null or empty");
+ }
+
+ String normalizedType = type.trim().toUpperCase();
+
+ return switch (normalizedType) {
+ case "BOOL" -> convertToBool(value);
+ case "BYTE" -> convertToByte(value);
+ case "WORD" -> convertToWord(value);
+ case "DWORD" -> convertToDWord(value);
+ case "LWORD" -> convertToLWord(value);
+ case "USINT" -> convertToUSInt(value);
+ case "UINT" -> convertToUInt(value);
+ case "UDINT" -> convertToUDInt(value);
+ case "ULINT" -> convertToULInt(value);
+ case "SINT" -> convertToSInt(value);
+ case "INT" -> convertToInt(value);
+ case "DINT" -> convertToDInt(value);
+ case "LINT" -> convertToLInt(value);
+ case "REAL" -> convertToReal(value);
+ case "LREAL" -> convertToLReal(value);
+ case "CHAR" -> convertToChar(value);
+ case "WCHAR" -> convertToWChar(value);
+ case "STRING", "WSTRING" -> value;
+ case "TIME", "LTIME" -> convertToTime(value);
+ case "DATE", "LDATE" -> convertToDate(value);
+ case "TIME_OF_DAY", "LTIME_OF_DAY" -> convertToTimeOfDay(value);
+ case "DATE_AND_TIME", "LDATE_AND_TIME" -> convertToDateTime(value);
+ default -> throw new IllegalArgumentException("Unsupported Modbus type: " + type);
+ };
+ }
+
+ // BOOL类型转换
+ private static Boolean convertToBool(String value) {
+ String normalized = value.trim().toUpperCase();
+ return switch (normalized) {
+ case "TRUE", "1", "YES", "ON" -> true;
+ case "FALSE", "0", "NO", "OFF" -> false;
+ default -> throw new IllegalArgumentException("Invalid BOOL value: " + value);
+ };
+ }
+
+ // BYTE类型转换(8位有符号)
+ private static Byte convertToByte(String value) {
+ try {
+ return Byte.parseByte(value.trim());
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Invalid BYTE value: " + value, e);
+ }
+ }
+
+ // WORD类型转换(16位无符号,用Short存储)
+ private static Short convertToWord(String value) {
+ try {
+ int intValue = Integer.parseInt(value.trim());
+ if (intValue < 0 || intValue > 65535) {
+ throw new IllegalArgumentException("WORD value out of range [0, 65535]: " + value);
+ }
+ return (short) intValue;
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Invalid WORD value: " + value, e);
+ }
+ }
+
+ // DWORD类型转换(32位无符号,用Integer存储)
+ private static Integer convertToDWord(String value) {
+ try {
+ long longValue = Long.parseLong(value.trim());
+ if (longValue < 0 || longValue > 4294967295L) {
+ throw new IllegalArgumentException("DWORD value out of range [0, 4294967295]: " + value);
+ }
+ return (int) longValue;
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Invalid DWORD value: " + value, e);
+ }
+ }
+
+ // LWORD类型转换(64位无符号,用Long存储)
+ private static Long convertToLWord(String value) {
+ try {
+ BigInteger bigInt = new BigInteger(value.trim());
+ if (bigInt.compareTo(BigInteger.ZERO) < 0 || bigInt.compareTo(new BigInteger("18446744073709551615")) > 0) {
+ throw new IllegalArgumentException("LWORD value out of range");
+ }
+ return bigInt.longValue();
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Invalid LWORD value: " + value, e);
+ }
+ }
+
+ // USINT类型转换(8位无符号)
+ private static Byte convertToUSInt(String value) {
+ try {
+ int intValue = Integer.parseInt(value.trim());
+ if (intValue < 0 || intValue > 255) {
+ throw new IllegalArgumentException("USINT value out of range [0, 255]: " + value);
+ }
+ return (byte) intValue;
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Invalid USINT value: " + value, e);
+ }
+ }
+
+ // UINT类型转换(16位无符号)
+ private static Integer convertToUInt(String value) {
+ try {
+ int intValue = Integer.parseInt(value.trim());
+ if (intValue < 0 || intValue > 65535) {
+ throw new IllegalArgumentException("UINT value out of range [0, 65535]: " + value);
+ }
+ return intValue;
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Invalid UINT value: " + value, e);
+ }
+ }
+
+ // UDINT类型转换(32位无符号)
+ private static Long convertToUDInt(String value) {
+ try {
+ long longValue = Long.parseLong(value.trim());
+ if (longValue < 0 || longValue > 4294967295L) {
+ throw new IllegalArgumentException("UDINT value out of range [0, 4294967295]: " + value);
+ }
+ return longValue;
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Invalid UDINT value: " + value, e);
+ }
+ }
+
+ // ULINT类型转换(64位无符号)
+ private static BigInteger convertToULInt(String value) {
+ try {
+ BigInteger bigInt = new BigInteger(value.trim());
+ if (bigInt.compareTo(BigInteger.ZERO) < 0) {
+ throw new IllegalArgumentException("ULINT value cannot be negative: " + value);
+ }
+ return bigInt;
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Invalid ULINT value: " + value, e);
+ }
+ }
+
+ // SINT类型转换(8位有符号)
+ private static Byte convertToSInt(String value) {
+ try {
+ return Byte.parseByte(value.trim());
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Invalid SINT value: " + value, e);
+ }
+ }
+
+ // INT类型转换(16位有符号)
+ private static Short convertToInt(String value) {
+ try {
+ return Short.parseShort(value.trim());
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Invalid INT value: " + value, e);
+ }
+ }
+
+ // DINT类型转换(32位有符号)
+ private static Integer convertToDInt(String value) {
+ try {
+ return Integer.parseInt(value.trim());
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Invalid DINT value: " + value, e);
+ }
+ }
+
+ // LINT类型转换(64位有符号)
+ private static Long convertToLInt(String value) {
+ try {
+ return Long.parseLong(value.trim());
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Invalid LINT value: " + value, e);
+ }
+ }
+
+ // REAL类型转换(32位浮点)
+ private static Float convertToReal(String value) {
+ try {
+ return Float.parseFloat(value.trim());
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Invalid REAL value: " + value, e);
+ }
+ }
+
+ // LREAL类型转换(64位浮点)
+ private static Double convertToLReal(String value) {
+ try {
+ return Double.parseDouble(value.trim());
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Invalid LREAL value: " + value, e);
+ }
+ }
+
+ // CHAR类型转换
+ private static Character convertToChar(String value) {
+ if (value.length() != 1) {
+ throw new IllegalArgumentException("CHAR value must be a single character: " + value);
+ }
+ return value.charAt(0);
+ }
+
+ // WCHAR类型转换(宽字符)
+ private static Character convertToWChar(String value) {
+ if (value.length() != 1) {
+ throw new IllegalArgumentException("WCHAR value must be a single character: " + value);
+ }
+ return value.charAt(0);
+ }
+
+ // TIME类型转换(Duration)
+ private static Duration convertToTime(String value) {
+ try {
+ return Duration.parse(value.trim());
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Invalid TIME value (expected ISO-8601 duration format): " + value, e);
+ }
+ }
+
+ // DATE类型转换
+ private static LocalDate convertToDate(String value) {
+ try {
+ return LocalDate.parse(value.trim());
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Invalid DATE value (expected ISO-8601 date format): " + value, e);
+ }
+ }
+
+ // TIME_OF_DAY类型转换
+ private static LocalTime convertToTimeOfDay(String value) {
+ try {
+ return LocalTime.parse(value.trim());
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Invalid TIME_OF_DAY value (expected ISO-8601 time format): " + value, e);
+ }
+ }
+
+ // DATE_AND_TIME类型转换
+ private static LocalDateTime convertToDateTime(String value) {
+ try {
+ return LocalDateTime.parse(value.trim());
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Invalid DATE_AND_TIME value (expected ISO-8601 datetime format): " + value, e);
+ }
+ }
+}
diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/modbustcp/util/ModBusTcpUtils.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/modbustcp/util/ModBusTcpUtils.java
index 154759b..ee37daa 100644
--- a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/modbustcp/util/ModBusTcpUtils.java
+++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/modbustcp/util/ModBusTcpUtils.java
@@ -1,8 +1,13 @@
package org.nl.iot.core.driver.protocol.modbustcp.util;
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
+import org.apache.plc4x.java.DefaultPlcDriverManager;
+import org.apache.plc4x.java.api.PlcConnection;
import org.apache.poi.ss.formula.functions.T;
import org.nl.common.exception.CommonException;
+import org.nl.iot.core.driver.bo.DeviceBO;
import org.nl.iot.core.driver.entity.WValue;
import org.nl.iot.core.driver.protocol.modbustcp.com.serotonin.modbus4j.ModbusMaster;
import org.nl.iot.core.driver.protocol.modbustcp.com.serotonin.modbus4j.code.DataType;
@@ -20,6 +25,9 @@ import org.nl.iot.core.driver.protocol.modbustcp.com.serotonin.modbus4j.msg.Writ
@Slf4j
public class ModBusTcpUtils {
+ private static final String MODBUS_CONN_PRX = "modbus-tcp://";
+ private static final int DEFAULT_SLAVE_ID = 1;
+
/**
* 获取 Modbus 数据类型
*
@@ -83,29 +91,28 @@ public class ModBusTcpUtils {
};
}
-
/**
* 根据Modbus地址偏移量和功能码计算实际的寄存器地址
* Modbus协议中,寄存器地址从0开始,需要减去相应的偏移量
*
* @param offset 地址偏移量(1-9999/10001-19999/30001-39999/40001-49999)
* @param functionCode 功能码(1-4)
- * @return 实际的寄存器地址(从0开始)
+ * @return 实际的寄存器地址(从1开始)
*/
public static int getActualAddress(int offset, int functionCode) {
switch (functionCode) {
case 1:
// 线圈状态:1-9999 -> 0-9998
- return offset - 1;
+ return offset;
case 2:
// 离散输入:10001-19999 -> 0-9998
- return offset - 10001;
+ return offset - 10000;
case 3:
// 保持寄存器:40001-49999 -> 0-9998
- return offset - 40001;
+ return offset - 40000;
case 4:
// 输入寄存器:30001-39999 -> 0-9998
- return offset - 30001;
+ return offset - 30000;
default:
return 0;
}
@@ -141,7 +148,35 @@ public class ModBusTcpUtils {
return functionCode;
}
+ /**
+ * 根据Modbus地址偏移量获取对应的功能码
+ * 注意:不同厂商对地址范围的定义可能不同,这里采用常见的约定:
+ * - 00001-09999: 功能码01 (线圈 Coil)
+ * - 10001-19999: 功能码02 (离散输入 Discrete Input)
+ * - 30001-39999: 功能码04 (输入寄存器 Input Register)
+ * - 40001-49999: 功能码03 (保持寄存器 Holding Register)
+ *
+ * @param offset 地址偏移量(1-9999/10001-19999/30001-39999/40001-49999)
+ * @return 对应的功能码(1-4)
+ * @throws CommonException 当偏移量不在合法范围时抛出异常
+ */
+ public static String getModBus4JAddress(int offset, int actualAddress, String type) {
+ String functionCode;
+ if (offset >= 1 && offset <= 9999) {
+ functionCode = "coil"; // 线圈
+ } else if (offset >= 10001 && offset <= 19999) {
+ functionCode = "discrete-input"; // 离散输入
+ } else if (offset >= 30001 && offset <= 39999) {
+ functionCode = "input-register"; // 输入寄存器
+ } else if (offset >= 40001 && offset <= 49999) {
+ functionCode = "holding-register"; // 保持寄存器
+ } else {
+ throw new CommonException("无效的偏移量:" + offset);
+ }
+
+ return functionCode + ":" + actualAddress + ":" + type;
+ }
/**
* 从 ModbusMaster 连接器中读取指定点位的数据
@@ -211,4 +246,17 @@ public class ModBusTcpUtils {
throw new CommonException(e.getMessage());
}
}
+
+
+ public static String buildModBusPlcUrl(DeviceBO deviceBO) {
+ String host = deviceBO.getHost();
+ if (host == null || host.trim().isEmpty()) {
+ throw new IllegalArgumentException("设备IP/域名(host)不能为空");
+ }
+ JSONObject pointConfig = JSONObject.parseObject(deviceBO.getProperties());
+ int slaveId = pointConfig.getIntValue("slaveId");
+ int finalSlaveId = ObjectUtil.isEmpty(slaveId) ? DEFAULT_SLAVE_ID : slaveId;
+ return MODBUS_CONN_PRX + host.trim() + ":" + deviceBO.getPort()
+ + "?default-unit-identifier=" + finalSlaveId;
+ }
}
diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/modbustcp/util/ModbusPlcValueConvertUtil.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/modbustcp/util/ModbusPlcValueConvertUtil.java
new file mode 100644
index 0000000..3733e24
--- /dev/null
+++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/modbustcp/util/ModbusPlcValueConvertUtil.java
@@ -0,0 +1,318 @@
+package org.nl.iot.core.driver.protocol.modbustcp.util;
+import org.apache.plc4x.java.api.value.PlcValue;
+import org.apache.plc4x.java.api.types.PlcValueType;
+import org.apache.plc4x.java.api.exceptions.PlcIncompatibleDatatypeException;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.Duration;
+import java.util.Objects;
+
+/**
+ * 修正版:Modbus PlcValue 转换工具类
+ * 适配PLC4X标准API,移除不存在的具体值类型导入,基于PlcValueType枚举和PlcValue接口实现
+ */
+public class ModbusPlcValueConvertUtil {
+
+ // 对齐PlcValueType枚举的Modbus类型映射(覆盖PLC4X定义的所有基础类型)
+ public enum ModbusType {
+ BOOL(PlcValueType.BOOL),
+ BYTE(PlcValueType.BYTE),
+ WORD(PlcValueType.WORD),
+ DWORD(PlcValueType.DWORD),
+ LWORD(PlcValueType.LWORD),
+ USINT(PlcValueType.USINT),
+ UINT(PlcValueType.UINT),
+ UDINT(PlcValueType.UDINT),
+ ULINT(PlcValueType.ULINT),
+ SINT(PlcValueType.SINT),
+ INT(PlcValueType.INT),
+ DINT(PlcValueType.DINT),
+ LINT(PlcValueType.LINT),
+ REAL(PlcValueType.REAL),
+ LREAL(PlcValueType.LREAL),
+ CHAR(PlcValueType.CHAR),
+ WCHAR(PlcValueType.WCHAR),
+ STRING(PlcValueType.STRING),
+ WSTRING(PlcValueType.WSTRING),
+ TIME(PlcValueType.TIME),
+ LTIME(PlcValueType.LTIME),
+ DATE(PlcValueType.DATE),
+ LDATE(PlcValueType.LDATE),
+ TIME_OF_DAY(PlcValueType.TIME_OF_DAY),
+ LTIME_OF_DAY(PlcValueType.LTIME_OF_DAY),
+ DATE_AND_TIME(PlcValueType.DATE_AND_TIME),
+ LDATE_AND_TIME(PlcValueType.LDATE_AND_TIME);
+
+ private final PlcValueType plcValueType;
+
+ ModbusType(PlcValueType plcValueType) {
+ this.plcValueType = plcValueType;
+ }
+
+ public PlcValueType getPlcValueType() {
+ return plcValueType;
+ }
+
+ // 根据字符串解析ModbusType(兼容大小写)
+ public static ModbusType fromString(String typeStr) {
+ if (typeStr == null || typeStr.trim().isEmpty()) {
+ throw new IllegalArgumentException("Modbus type string cannot be null or empty");
+ }
+ try {
+ return ModbusType.valueOf(typeStr.trim().toUpperCase());
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException("Unsupported Modbus type: " + typeStr, e);
+ }
+ }
+ }
+
+ /**
+ * 判断枚举是否存在modbus类型
+ * @param modbusTypeStr
+ * @return
+ */
+ public static boolean containerType(String modbusTypeStr) {
+ if (modbusTypeStr == null || modbusTypeStr.trim().isEmpty()) {
+ return false;
+ }
+ try {
+ ModbusType.valueOf(modbusTypeStr.trim().toUpperCase());
+ return true;
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ }
+
+ /**
+ * 核心转换方法:根据Modbus类型字符串,将PlcValue转为对应Java类型后统一转String
+ * @param plcValue PLC4X读取的PlcValue对象
+ * @param modbusTypeStr Modbus类型字符串(如"BOOL"、"INT"、"REAL")
+ * @return 转换后的String结果
+ * @throws IllegalArgumentException 类型不支持或参数异常
+ * @throws PlcIncompatibleDatatypeException 类型不匹配
+ */
+ public static String convertPlcValueToString(PlcValue plcValue, String modbusTypeStr) {
+ // 空值校验
+ if (plcValue == null) {
+ throw new IllegalArgumentException("PlcValue cannot be null");
+ }
+
+ // 解析Modbus类型
+ ModbusType modbusType = ModbusType.fromString(modbusTypeStr);
+ PlcValueType targetType = modbusType.getPlcValueType();
+
+ // 根据PlcValueType执行转换
+ return switch (targetType) {
+ case BOOL -> convertBool(plcValue);
+ case BYTE -> convertByte(plcValue);
+ case WORD -> convertWord(plcValue);
+ case DWORD -> convertDWord(plcValue);
+ case LWORD -> convertLWord(plcValue);
+ case USINT -> convertUSInt(plcValue);
+ case UINT -> convertUInt(plcValue);
+ case UDINT -> convertUDInt(plcValue);
+ case ULINT -> convertULInt(plcValue);
+ case SINT -> convertSInt(plcValue);
+ case INT -> convertInt(plcValue);
+ case DINT -> convertDInt(plcValue);
+ case LINT -> convertLInt(plcValue);
+ case REAL -> convertReal(plcValue);
+ case LREAL -> convertLReal(plcValue);
+// case CHAR -> convertChar(plcValue);
+ case WCHAR -> convertWChar(plcValue);
+ case STRING, WSTRING -> convertString(plcValue);
+ case TIME, LTIME -> convertTime(plcValue);
+ case DATE, LDATE -> convertDate(plcValue);
+ case TIME_OF_DAY, LTIME_OF_DAY -> convertTimeOfDay(plcValue);
+ case DATE_AND_TIME, DATE_AND_LTIME, LDATE_AND_TIME -> convertDateTime(plcValue);
+ default -> throw new PlcIncompatibleDatatypeException("Unsupported PlcValueType: " + targetType);
+ };
+ }
+
+ // 布尔类型转换(BOOL)
+ private static String convertBool(PlcValue plcValue) {
+ if (!plcValue.isBoolean()) {
+ throw new PlcIncompatibleDatatypeException("PlcValue is not a BOOL type");
+ }
+ return Objects.toString(plcValue.getBoolean(), "false");
+ }
+
+ // 字节类型(BYTE)
+ private static String convertByte(PlcValue plcValue) {
+ if (!plcValue.isByte()) {
+ throw new PlcIncompatibleDatatypeException("PlcValue is not a BYTE type");
+ }
+ return Objects.toString(plcValue.getByte(), "0");
+ }
+
+ // 16位无符号字(WORD)
+ private static String convertWord(PlcValue plcValue) {
+ if (!plcValue.isShort()) {
+ throw new PlcIncompatibleDatatypeException("PlcValue is not a WORD type");
+ }
+ return Objects.toString(plcValue.getShort(), "0");
+ }
+
+ // 32位无符号双字(DWORD)
+ private static String convertDWord(PlcValue plcValue) {
+ if (!plcValue.isInteger()) {
+ throw new PlcIncompatibleDatatypeException("PlcValue is not a DWORD type");
+ }
+ return Objects.toString(plcValue.getInteger(), "0");
+ }
+
+ // 64位无符号四字(LWORD)
+ private static String convertLWord(PlcValue plcValue) {
+ if (!plcValue.isLong()) {
+ throw new PlcIncompatibleDatatypeException("PlcValue is not a LWORD type");
+ }
+ return Objects.toString(plcValue.getLong(), "0");
+ }
+
+ // 8位无符号短整型(USINT)
+ private static String convertUSInt(PlcValue plcValue) {
+ if (!plcValue.isByte()) {
+ throw new PlcIncompatibleDatatypeException("PlcValue is not a USINT type");
+ }
+ // USINT是无符号字节,转换为正整数
+ return Objects.toString(Byte.toUnsignedInt(plcValue.getByte()), "0");
+ }
+
+ // 16位无符号整型(UINT)
+ private static String convertUInt(PlcValue plcValue) {
+ if (!plcValue.isShort()) {
+ throw new PlcIncompatibleDatatypeException("PlcValue is not a UINT type");
+ }
+ // UINT是无符号16位,转换为正整数
+ return Objects.toString(Short.toUnsignedInt(plcValue.getShort()), "0");
+ }
+
+ // 32位无符号整型(UDINT)
+ private static String convertUDInt(PlcValue plcValue) {
+ if (!plcValue.isInteger()) {
+ throw new PlcIncompatibleDatatypeException("PlcValue is not a UDINT type");
+ }
+ // UDINT是无符号32位,转换为正长整数
+ return Objects.toString(Integer.toUnsignedLong(plcValue.getInteger()), "0");
+ }
+
+ // 64位无符号整型(ULINT)
+ private static String convertULInt(PlcValue plcValue) {
+ if (!plcValue.isBigInteger()) {
+ throw new PlcIncompatibleDatatypeException("PlcValue is not a ULINT type");
+ }
+ return Objects.toString(plcValue.getBigInteger(), "0");
+ }
+
+ // 8位有符号短整型(SINT)
+ private static String convertSInt(PlcValue plcValue) {
+ if (!plcValue.isByte()) {
+ throw new PlcIncompatibleDatatypeException("PlcValue is not a SINT type");
+ }
+ return Objects.toString(plcValue.getByte(), "0");
+ }
+
+ // 16位有符号整型(INT)
+ private static String convertInt(PlcValue plcValue) {
+ if (!plcValue.isShort()) {
+ throw new PlcIncompatibleDatatypeException("PlcValue is not a INT type");
+ }
+ return Objects.toString(plcValue.getShort(), "0");
+ }
+
+ // 32位有符号整型(DINT)
+ private static String convertDInt(PlcValue plcValue) {
+ if (!plcValue.isInteger()) {
+ throw new PlcIncompatibleDatatypeException("PlcValue is not a DINT type");
+ }
+ return Objects.toString(plcValue.getInteger(), "0");
+ }
+
+ // 64位有符号整型(LINT)
+ private static String convertLInt(PlcValue plcValue) {
+ if (!plcValue.isLong()) {
+ throw new PlcIncompatibleDatatypeException("PlcValue is not a LINT type");
+ }
+ return Objects.toString(plcValue.getLong(), "0");
+ }
+
+ // 32位浮点型(REAL/FLOAT)
+ private static String convertReal(PlcValue plcValue) {
+ if (!plcValue.isFloat()) {
+ throw new PlcIncompatibleDatatypeException("PlcValue is not a REAL type");
+ }
+ return Objects.toString(plcValue.getFloat(), "0.0");
+ }
+
+ // 64位浮点型(LREAL/DOUBLE)
+ private static String convertLReal(PlcValue plcValue) {
+ if (!plcValue.isDouble()) {
+ throw new PlcIncompatibleDatatypeException("PlcValue is not a LREAL type");
+ }
+ return Objects.toString(plcValue.getDouble(), "0.0");
+ }
+
+ // 字符类型(CHAR)
+// private static String convertChar(PlcValue plcValue) {
+// if (!plcValue.isCharacter()) {
+// throw new PlcIncompatibleDatatypeException("PlcValue is not a CHAR type");
+// }
+// return Objects.toString(plcValue.getCharacter(), "");
+// }
+
+ // 宽字符类型(WCHAR)
+ private static String convertWChar(PlcValue plcValue) {
+ if (!plcValue.isShort()) { // WCHAR在PLC4X中以Short存储
+ throw new PlcIncompatibleDatatypeException("PlcValue is not a WCHAR type");
+ }
+ return Objects.toString((char) plcValue.getShort(), "");
+ }
+
+ // 字符串类型(STRING/WSTRING)
+ private static String convertString(PlcValue plcValue) {
+ if (!plcValue.isString()) {
+ throw new PlcIncompatibleDatatypeException("PlcValue is not a STRING type");
+ }
+ return Objects.toString(plcValue.getString(), "");
+ }
+
+ // 时间类型(TIME/LTIME,对应Duration)
+ private static String convertTime(PlcValue plcValue) {
+ if (!plcValue.isDuration()) {
+ throw new PlcIncompatibleDatatypeException("PlcValue is not a TIME type");
+ }
+ Duration duration = plcValue.getDuration();
+ return Objects.toString(duration, "PT0S");
+ }
+
+ // 日期类型(DATE/LDATE)
+ private static String convertDate(PlcValue plcValue) {
+ if (!plcValue.isDate()) {
+ throw new PlcIncompatibleDatatypeException("PlcValue is not a DATE type");
+ }
+ LocalDate date = plcValue.getDate();
+ return Objects.toString(date, "");
+ }
+
+ // 时间戳类型(TIME_OF_DAY/LTIME_OF_DAY)
+ private static String convertTimeOfDay(PlcValue plcValue) {
+ if (!plcValue.isTime()) {
+ throw new PlcIncompatibleDatatypeException("PlcValue is not a TIME_OF_DAY type");
+ }
+ LocalTime time = plcValue.getTime();
+ return Objects.toString(time, "");
+ }
+
+ // 日期时间类型(DATE_AND_TIME等)
+ private static String convertDateTime(PlcValue plcValue) {
+ if (!plcValue.isDateTime()) {
+ throw new PlcIncompatibleDatatypeException("PlcValue is not a DATE_AND_TIME type");
+ }
+ LocalDateTime dateTime = plcValue.getDateTime();
+ return Objects.toString(dateTime, "");
+ }
+}
diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/service/DriverCustomService.java b/nl-iot/src/main/java/org/nl/iot/core/driver/service/DriverCustomService.java
index 1166ff8..94802ff 100644
--- a/nl-iot/src/main/java/org/nl/iot/core/driver/service/DriverCustomService.java
+++ b/nl-iot/src/main/java/org/nl/iot/core/driver/service/DriverCustomService.java
@@ -1,12 +1,16 @@
package org.nl.iot.core.driver.service;
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;
import org.nl.iot.core.driver.entity.RValue;
+import org.nl.iot.core.driver.entity.WResponse;
import org.nl.iot.core.driver.entity.WValue;
import org.nl.iot.modular.iot.entity.IotConfig;
import org.nl.iot.modular.iot.entity.IotConnect;
+import java.util.List;
import java.util.Map;
/**
@@ -29,39 +33,36 @@ public interface DriverCustomService {
*/
void schedule();
+ /**
+ * 更新内存
+ * @param metadataEvent
+ */
void event(MetadataEventDTO metadataEvent);
- // todo驱动事件,暂时不需要
-
/**
* 执行读操作
- *
- * 该接口用于从指定设备中读取位号的数据。由于设备类型和通信协议的差异, 读取操作可能无法直接执行, 请根据实际情况灵活处理。
- *
- * 注意: 读取操作可能会抛出异常, 调用方需做好异常处理。
- *
- * @param driverConfig 驱动属性配置, 包含驱动相关的配置信息
- * @param pointConfig 位号属性配置, 包含位号相关的配置信息
- * @param device 设备对象, 包含设备的基本信息和属性
- * @param point 位号对象, 包含位号的基本信息和属性
- * @return 返回读取到的数据, 封装在 {@link RValue} 对象中
*/
- RValue read(IotConnect device, IotConfig point);
+ RValue read(DeviceBO device, SiteBO point);
+
+ /**
+ * 批量读取
+ * @param device
+ * @param point
+ * @return
+ */
+ List batchRead(DeviceBO device, List point);
/**
* 执行写操作
- *
- * 该接口用于向指定设备中的位号写入数据。由于设备类型和通信协议的差异, 写入操作可能无法直接执行, 请根据实际情况灵活处理。
- *
- * 注意: 写入操作可能会抛出异常, 调用方需做好异常处理。
- *
- * @param driverConfig 驱动属性配置, 包含驱动相关的配置信息
- * @param pointConfig 位号属性配置, 包含位号相关的配置信息
- * @param device 设备对象, 包含设备的基本信息和属性
- * @param point 位号对象, 包含位号的基本信息和属性
- * @param wValue 待写入的数据, 封装在 {@link WValue} 对象中
- * @return 返回写入操作是否成功, 若成功则返回 {@code true}, 否则返回 {@code false} 或抛出异常
*/
- Boolean write(IotConnect device, IotConfig point, WValue wValue);
+ Boolean write(DeviceBO device, WValue wValue);
+
+ /**
+ * 批量写
+ * @param device
+ * @param wValue
+ * @return
+ */
+ List batchWrite(DeviceBO device, List wValue);
}
diff --git a/nl-iot/src/main/java/org/nl/iot/core/package-info.java b/nl-iot/src/main/java/org/nl/iot/core/package-info.java
index 8e5b81d..77df19d 100644
--- a/nl-iot/src/main/java/org/nl/iot/core/package-info.java
+++ b/nl-iot/src/main/java/org/nl/iot/core/package-info.java
@@ -1,5 +1,38 @@
/**
* 核心包(通信协议工具、配置等)
+ * BOOL (boolean)
+ *
+ * SINT (int 8)
+ *
+ * USINT (uint 8)
+ *
+ * BYTE (uint 8)
+ *
+ * INT (int 16)
+ *
+ * UINT (uint 16)
+ *
+ * WORD (uint 16)
+ *
+ * DINT (int 32)
+ *
+ * UDINT (uint 32)
+ *
+ * DWORD (uint 32)
+ *
+ * LINT (int 64)
+ *
+ * ULINT (uint 64)
+ *
+ * LWORD (uint 64)
+ *
+ * REAL (float)
+ *
+ * LREAL (double)
+ *
+ * CHAR (char)
+ *
+ * WCHAR (2 byte char)
* @author: lyd
* @date: 2026/2/28
*/
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 8c8a876..dd9a3e0 100644
--- a/nl-web-app/src/test/java/org/nl/ApiTest.java
+++ b/nl-web-app/src/test/java/org/nl/ApiTest.java
@@ -1,21 +1,21 @@
package org.nl;
+import com.alibaba.fastjson.JSONObject;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.nl.iot.core.driver.bo.AttributeBO;
+import org.nl.iot.core.driver.bo.DeviceBO;
+import org.nl.iot.core.driver.bo.SiteBO;
import org.nl.iot.core.driver.entity.RValue;
+import org.nl.iot.core.driver.entity.WResponse;
import org.nl.iot.core.driver.entity.WValue;
import org.nl.iot.core.driver.protocol.modbustcp.ModBusProtocolDriverImpl;
-import org.nl.iot.core.driver.protocol.opcda.OpcDaProtocolDriverImpl;
-import org.nl.iot.core.driver.protocol.opcua.OpcUaProtocolDriverImpl;
-import org.nl.iot.core.driver.protocol.plcs7.PlcS7ProtocolDriverImpl;
import org.nl.iot.modular.iot.entity.IotConfig;
import org.nl.iot.modular.iot.entity.IotConnect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
-import java.util.HashMap;
+import java.util.List;
import java.util.Map;
/**
@@ -32,23 +32,15 @@ public class ApiTest {
@Test
public void modbusTest() {
- // 构建驱动配置(连接配置)
- Map driverConfig = new HashMap<>();
- driverConfig.put("host", AttributeBO.builder().value("192.168.81.251").build());
- driverConfig.put("port", AttributeBO.builder().value("502").build());
-
- // 构建点位配置
- Map pointConfig = new HashMap<>();
- pointConfig.put("slaveId", AttributeBO.builder().value("1").build());
- pointConfig.put("offset", AttributeBO.builder().value("40001").build()); // 功能码3:保持寄存器
- pointConfig.put("data_type", AttributeBO.builder().value("int16").build());
-
// 构建连接对象
+ JSONObject o = new JSONObject();
+ o.put("slaveId", "1");
IotConnect connect = IotConnect.builder()
.id(1)
.code("MODBUS_TCP_001")
.host("192.168.81.251")
.port(502)
+ .properties(JSONObject.toJSONString(o))
.protocol("modbus-tcp")
.enabled(true)
.description("测试Modbus TCP连接")
@@ -61,614 +53,414 @@ public class ApiTest {
.alias("temperature")
.aliasName("温度传感器")
.registerAddress("40001")
- .dataType("int16")
+ .dataType("INT")
.readonly(true)
.enabled(true)
.description("测试温度读取")
.build();
+ // 执行读取操作
try {
- // 调用read方法进行测试
- RValue result = modBusProtocolDriver.read(driverConfig, pointConfig, connect, config);
+ System.out.println("========== 开始测试Modbus TCP读取 ==========");
+ System.out.println("连接信息: " + connect.getHost() + ":" + connect.getPort());
+ System.out.println("寄存器地址: " + config.getRegisterAddress());
+ System.out.println("数据类型: " + config.getDataType());
- // 输出测试结果
- System.out.println("=== Modbus读取测试结果 ===");
- System.out.println("连接信息: " + result.getConnect());
- System.out.println("配置信息: " + result.getConfig());
- System.out.println("读取值: " + result.getValue());
- System.out.println("测试完成!");
+ // 转换为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
+ SiteBO siteBO = SiteBO.builder()
+ .deviceCode(connect.getCode())
+ .alias(config.getAlias())
+ .aliasName(config.getAliasName())
+ .registerAddress(config.getRegisterAddress())
+ .dataType(config.getDataType())
+ .readonly(config.getReadonly())
+ .build();
+
+ // 调用驱动读取数据
+ RValue result = modBusProtocolDriver.read(deviceBO, siteBO);
+
+ System.out.println("读取成功!");
+ System.out.println("读取结果: " + result.getValue());
+ System.out.println("========== 测试完成 ==========");
} catch (Exception e) {
System.err.println("测试失败: " + e.getMessage());
-// e.printStackTrace();
+ e.printStackTrace();
}
}
@Test
public void modbusTestWrite() {
- // 构建驱动配置(连接配置)
- Map driverConfig = new HashMap<>();
- driverConfig.put("host", AttributeBO.builder().value("192.168.81.251").build());
- driverConfig.put("port", AttributeBO.builder().value("502").build());
-
- // 构建点位配置 - 写入保持寄存器(功能码3)
- Map pointConfig = new HashMap<>();
- pointConfig.put("slaveId", AttributeBO.builder().value("1").build());
- pointConfig.put("offset", AttributeBO.builder().value("40001").build()); // 功能码3:保持寄存器,地址0
- pointConfig.put("data_type", AttributeBO.builder().value("int16").build());
-
// 构建连接对象
+ JSONObject o = new JSONObject();
+ o.put("slaveId", "1");
IotConnect connect = IotConnect.builder()
.id(1)
.code("MODBUS_TCP_001")
.host("192.168.81.251")
.port(502)
+ .properties(JSONObject.toJSONString(o))
.protocol("modbus-tcp")
.enabled(true)
- .description("测试Modbus TCP写入连接")
+ .description("测试Modbus TCP连接")
.build();
- // 构建配置对象
+ // 构建配置对象 - 写入操作,设置为可写
IotConfig config = IotConfig.builder()
+ .id(2)
+ .connectId(1)
+ .alias("output_value")
+ .aliasName("输出值")
+ .registerAddress("40001")
+ .dataType("LREAL")
+ .readonly(false) // 设置为可写
+ .enabled(true)
+ .description("测试写入操作")
+ .build();
+
+ // 执行写入操作
+ try {
+ System.out.println("========== 开始测试Modbus TCP写入 ==========");
+ System.out.println("连接信息: " + connect.getHost() + ":" + connect.getPort());
+ System.out.println("寄存器地址: " + config.getRegisterAddress());
+ System.out.println("数据类型: " + config.getDataType());
+
+ // 转换为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
+ SiteBO siteBO = SiteBO.builder()
+ .deviceCode(connect.getCode())
+ .alias(config.getAlias())
+ .aliasName(config.getAliasName())
+ .registerAddress(config.getRegisterAddress())
+ .dataType(config.getDataType())
+ .readonly(config.getReadonly())
+ .build();
+
+ // 构建写入值对象
+ String writeValue = "25.5"; // 要写入的值
+ WValue wValue = WValue.builder()
+ .point(siteBO)
+ .value(writeValue)
+ .type(config.getDataType())
+ .build();
+
+ System.out.println("写入值: " + writeValue);
+
+ // 调用驱动写入数据
+ Boolean result = modBusProtocolDriver.write(deviceBO, wValue);
+
+ if (result != null && result) {
+ System.out.println("写入成功!");
+ System.out.println("写入结果: " + result);
+ } else {
+ System.out.println("写入失败!");
+ }
+ System.out.println("========== 测试完成 ==========");
+
+ } catch (Exception e) {
+ System.err.println("测试失败: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+
+ @Test
+ public void modbusBatchReadTest() {
+ // 构建连接对象
+ JSONObject o = new JSONObject();
+ o.put("slaveId", "1");
+ IotConnect connect = IotConnect.builder()
+ .id(1)
+ .code("MODBUS_TCP_001")
+ .host("192.168.81.251")
+ .port(502)
+ .properties(JSONObject.toJSONString(o))
+ .protocol("modbus-tcp")
+ .enabled(true)
+ .description("测试Modbus TCP连接")
+ .build();
+
+ // 构建多个配置对象进行批量读取
+ IotConfig config1 = IotConfig.builder()
.id(1)
.connectId(1)
.alias("temperature")
.aliasName("温度传感器")
.registerAddress("40001")
- .dataType("int16")
- .readonly(false)
- .enabled(true)
- .description("测试温度写入")
- .build();
-
- try {
- // 先读取当前值
- System.out.println("=== Modbus写入测试开始 ===");
- RValue beforeValue = modBusProtocolDriver.read(driverConfig, pointConfig, connect, config);
- System.out.println("写入前的值: " + beforeValue.getValue());
-
- // 构建写入值对象 - 写入新的温度值(例如:100)
- org.nl.iot.core.driver.entity.WValue wValue = new org.nl.iot.core.driver.entity.WValue();
- wValue.setValue("100");
- wValue.setType("int16");
-
- // 调用write方法进行写入测试
- Boolean writeResult = modBusProtocolDriver.write(driverConfig, pointConfig, connect, config, wValue);
- System.out.println("写入结果: " + (writeResult ? "成功" : "失败"));
-
- // 等待一小段时间后再次读取,验证写入是否成功
- Thread.sleep(500);
- RValue afterValue = modBusProtocolDriver.read(driverConfig, pointConfig, connect, config);
- System.out.println("写入后的值: " + afterValue.getValue());
-
- // 验证写入是否成功
- if (writeResult && "100".equals(afterValue.getValue())) {
- System.out.println("✓ 写入验证成功!值已更新为: " + afterValue.getValue());
- } else {
- System.out.println("✗ 写入验证失败!期望值: 100, 实际值: " + afterValue.getValue());
- }
-
- System.out.println("=== 测试完成 ===");
-
- } catch (Exception e) {
- System.err.println("测试失败: " + e.getMessage());
-// e.printStackTrace();
- }
- }
-
- @Autowired
- private PlcS7ProtocolDriverImpl plcS7ProtocolDriver;
-
- @Test
- public void plcS7TestRead() {
- // 初始化驱动
- plcS7ProtocolDriver.initial();
-
- // 构建驱动配置(连接配置)
- Map driverConfig = new HashMap<>();
- driverConfig.put("host", AttributeBO.builder().value("192.168.10.33").build());
- driverConfig.put("port", AttributeBO.builder().value("102").build());
-
- // 构建点位配置
- Map pointConfig = new HashMap<>();
- pointConfig.put("dbNum", AttributeBO.builder().value("1").build()); // 数据块编号
- pointConfig.put("byteOffset", AttributeBO.builder().value("0").build()); // 字节偏移量
- pointConfig.put("bitOffset", AttributeBO.builder().value("0").build()); // 位偏移量
- pointConfig.put("blockSize", AttributeBO.builder().value("1").build()); // 数据块大小
- pointConfig.put("data_type", AttributeBO.builder().value("int").build()); // 数据类型
-
- // 构建连接对象
- IotConnect connect = IotConnect.builder()
- .id(1)
- .code("PLC_S7_001")
- .host("192.168.10.33")
- .port(102)
- .protocol("plc-s7")
- .enabled(true)
- .description("测试PLC S7连接")
- .build();
-
- // 构建配置对象
- IotConfig config = IotConfig.builder()
- .id(1)
- .connectId(1)
- .alias("pressure")
- .aliasName("压力传感器")
- .registerAddress("DB1.DBW0")
- .dataType("int")
+ .dataType("INT")
.readonly(true)
.enabled(true)
- .description("测试PLC S7数据读取")
+ .description("测试温度读取")
.build();
- try {
- System.out.println("=== PLC S7读取测试开始 ===");
- System.out.println("连接地址: " + driverConfig.get("host").getValue() + ":" + driverConfig.get("port").getValue());
- System.out.println("数据块: DB" + pointConfig.get("dbNum").getValue() +
- ", 偏移: " + pointConfig.get("byteOffset").getValue() +
- ", 类型: " + pointConfig.get("data_type").getValue());
-
- // 调用read方法进行测试
- RValue result = plcS7ProtocolDriver.read(driverConfig, pointConfig, connect, config);
-
- // 检查结果是否为null
- if (result != null) {
- System.out.println("\n✓ 读取成功!");
- System.out.println("连接信息: " + result.getConnect());
- System.out.println("配置信息: " + result.getConfig());
- System.out.println("读取值: " + result.getValue());
- } else {
- System.err.println("\n✗ 读取失败:返回结果为null");
- System.err.println("可能原因:");
- System.err.println("1. PLC设备未连接或IP地址不正确");
- System.err.println("2. 端口号配置错误(S7-300/400默认102)");
- System.err.println("3. 数据块(DB)不存在或无访问权限");
- System.err.println("4. 数据类型或偏移量配置错误");
- }
-
- System.out.println("\n=== 测试完成 ===");
-
- } catch (Exception e) {
- System.err.println("\n✗ 测试异常: " + e.getMessage());
-// e.printStackTrace();
- }
- }
-
-
- @Test
- public void plcS7TestWrite() {
- // 初始化驱动
- plcS7ProtocolDriver.initial();
-
- // 构建驱动配置(连接配置)
- Map driverConfig = new HashMap<>();
- driverConfig.put("host", AttributeBO.builder().value("192.168.10.33").build());
- driverConfig.put("port", AttributeBO.builder().value("102").build());
-
- // 构建点位配置
- Map pointConfig = new HashMap<>();
- pointConfig.put("dbNum", AttributeBO.builder().value("1").build()); // 数据块编号
- pointConfig.put("byteOffset", AttributeBO.builder().value("0").build()); // 字节偏移量
- pointConfig.put("bitOffset", AttributeBO.builder().value("0").build()); // 位偏移量
- pointConfig.put("blockSize", AttributeBO.builder().value("1").build()); // 数据块大小
- pointConfig.put("data_type", AttributeBO.builder().value("int").build()); // 数据类型
-
- // 构建连接对象
- IotConnect connect = IotConnect.builder()
- .id(1)
- .code("PLC_S7_001")
- .host("192.168.10.33")
- .port(102)
- .protocol("plc-s7")
- .enabled(true)
- .description("测试PLC S7写入连接")
- .build();
-
- // 构建配置对象
- IotConfig config = IotConfig.builder()
- .id(1)
+ IotConfig config2 = IotConfig.builder()
+ .id(2)
.connectId(1)
- .alias("pressure")
- .aliasName("压力传感器")
- .registerAddress("DB1.DBW0")
- .dataType("int")
- .readonly(false)
+ .alias("humidity")
+ .aliasName("湿度传感器")
+ .registerAddress("40002")
+ .dataType("INT")
+ .readonly(true)
.enabled(true)
- .description("测试PLC S7数据写入")
+ .description("测试湿度读取")
.build();
+// IotConfig config3 = IotConfig.builder()
+// .id(3)
+// .connectId(1)
+// .alias("press")
+// .aliasName("压力传感器")
+// .registerAddress("40003")
+// .dataType("LREAL")
+// .readonly(true)
+// .enabled(true)
+// .description("测试状态读取")
+// .build();
+ IotConfig config4 = IotConfig.builder()
+ .id(4)
+ .connectId(1)
+ .alias("move")
+ .aliasName("有无货")
+ .registerAddress("40008")
+ .dataType("SSSS")
+ .readonly(true)
+ .enabled(true)
+ .description("测试状态读取")
+ .build();
+
+ // 执行批量读取操作
try {
- System.out.println("=== PLC S7写入测试开始 ===");
+ System.out.println("========== 开始测试Modbus TCP批量读取 ==========");
+ System.out.println("连接信息: " + connect.getHost() + ":" + connect.getPort());
- // 先读取当前值
- RValue beforeValue = plcS7ProtocolDriver.read(driverConfig, pointConfig, connect, config);
- if (beforeValue != null) {
- System.out.println("写入前的值: " + beforeValue.getValue());
- } else {
- System.out.println("写入前读取失败,继续执行写入测试...");
- }
-
- // 构建写入值对象 - 写入新的压力值(例如:200)
- WValue wValue = new WValue();
- wValue.setValue("200");
- wValue.setType("int");
-
- // 调用write方法进行写入测试
- Boolean writeResult = plcS7ProtocolDriver.write(driverConfig, pointConfig, connect, config, wValue);
- System.out.println("写入结果: " + (writeResult ? "成功" : "失败"));
-
- // 等待一小段时间后再次读取,验证写入是否成功
- Thread.sleep(500);
- RValue afterValue = plcS7ProtocolDriver.read(driverConfig, pointConfig, connect, config);
+ // 转换为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();
- if (afterValue != null) {
- System.out.println("写入后的值: " + afterValue.getValue());
-
- // 验证写入是否成功
- if (writeResult && "200".equals(afterValue.getValue())) {
- System.out.println("✓ 写入验证成功!值已更新为: " + afterValue.getValue());
- } else {
- System.out.println("✗ 写入验证失败!期望值: 200, 实际值: " + afterValue.getValue());
- }
- } else {
- System.err.println("写入后读取失败,无法验证写入结果");
+ // 转换为SiteBO列表
+ java.util.List siteBOList = java.util.Arrays.asList(
+ SiteBO.builder()
+ .deviceCode(connect.getCode())
+ .alias(config1.getAlias())
+ .aliasName(config1.getAliasName())
+ .registerAddress(config1.getRegisterAddress())
+ .dataType(config1.getDataType())
+ .readonly(config1.getReadonly())
+ .build(),
+ SiteBO.builder()
+ .deviceCode(connect.getCode())
+ .alias(config2.getAlias())
+ .aliasName(config2.getAliasName())
+ .registerAddress(config2.getRegisterAddress())
+ .dataType(config2.getDataType())
+ .readonly(config2.getReadonly())
+ .build()
+// SiteBO.builder()
+// .deviceCode(connect.getCode())
+// .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() + "]");
}
-
- System.out.println("\n=== 测试完成 ===");
-
+
+ // 调用驱动批量读取数据
+ List result = modBusProtocolDriver.batchRead(deviceBO, siteBOList);
+
+ System.out.println("批量读取成功!");
+ System.out.println("读取结果:");
+
+ // 输出每个点位的读取结果
+ for (RValue rValue : result) {
+ SiteBO site = rValue.getSiteBO();
+ String value = rValue.getValue();
+ System.out.println(" - " + site.getAliasName() + " (" + site.getAlias() + "): " + value + (value == null ? "[" + rValue.getExceptionMessage() + "]" : ""));
+ }
+
+ System.out.println("========== 批量读取测试完成 ==========");
+
} catch (Exception e) {
- System.err.println("\n✗ 测试异常: " + e.getMessage());
-// e.printStackTrace();
+ System.err.println("批量读取测试失败: " + e.getMessage());
+ e.printStackTrace();
}
}
- @Autowired
- private OpcUaProtocolDriverImpl opcUaProtocolDriver;
@Test
- public void opcUaTestRead() {
- // 初始化驱动
-// opcUaProtocolDriver.initial();
-
- // 构建驱动配置(连接配置)
- Map driverConfig = new HashMap<>();
- driverConfig.put("host", AttributeBO.builder().value("Lyd-ThinkBook").build());
- driverConfig.put("port", AttributeBO.builder().value("53530").build());
- driverConfig.put("path", AttributeBO.builder().value("/OPCUA/SimulationServer").build());
-
- // 构建点位配置
- // 根据模拟器配置:ns=3;s=Temperature
- Map pointConfig = new HashMap<>();
- pointConfig.put("namespace", AttributeBO.builder().value("3").build()); // 命名空间3
- pointConfig.put("tag", AttributeBO.builder().value("Temperature").build()); // 节点标识:Temperature
-
+ public void modbusBatchWriteTest() {
// 构建连接对象
+ JSONObject o = new JSONObject();
+ o.put("slaveId", "1");
IotConnect connect = IotConnect.builder()
.id(1)
- .code("OPC_UA_001")
- .host("Lyd-ThinkBook")
- .port(53530)
- .protocol("opc-ua")
+ .code("MODBUS_TCP_001")
+ .host("192.168.81.251")
+ .port(502)
+ .properties(JSONObject.toJSONString(o))
+ .protocol("modbus-tcp")
.enabled(true)
- .description("测试OPC UA连接")
+ .description("测试Modbus TCP连接")
.build();
- // 构建配置对象
- IotConfig config = IotConfig.builder()
+ // 构建多个配置对象进行批量写入
+ IotConfig config1 = IotConfig.builder()
.id(1)
.connectId(1)
.alias("temperature")
- .aliasName("温度传感器")
- .registerAddress("ns=3;s=Temperature") // 与模拟器配置一致
- .dataType("float") // 模拟器中是Float类型
- .readonly(true)
+ .aliasName("温度")
+ .registerAddress("40001")
+ .dataType("INT")
+ .readonly(false) // 设置为可写
.enabled(true)
- .description("测试OPC UA数据读取")
+ .description("测试批量写入1")
.build();
- try {
- System.out.println("=== OPC UA读取测试开始 ===");
- System.out.println("连接地址: opc.tcp://" + driverConfig.get("host").getValue() +
- ":" + driverConfig.get("port").getValue() +
- driverConfig.get("path").getValue());
- System.out.println("节点信息: 命名空间=" + pointConfig.get("namespace").getValue() +
- ", 标签=" + pointConfig.get("tag").getValue());
- System.out.println("完整NodeId: " + config.getRegisterAddress());
-
- // 调用read方法进行测试
- RValue result = opcUaProtocolDriver.read(driverConfig, pointConfig, connect, config);
-
- // 检查结果是否为null
- if (result != null) {
- System.out.println("\n✓ 读取成功!");
- System.out.println("连接信息: " + result.getConnect());
- System.out.println("配置信息: " + result.getConfig());
- System.out.println("读取值: " + result.getValue());
- } else {
- System.err.println("\n✗ 读取失败:返回结果为null");
- System.err.println("可能原因:");
- System.err.println("1. OPC UA服务器未启动或IP地址不正确");
- System.err.println("2. 端口号配置错误(默认4840)");
- System.err.println("3. 节点不存在或命名空间索引错误");
- System.err.println("4. 服务器需要身份验证(当前使用匿名访问)");
- }
-
- System.out.println("\n=== 测试完成 ===");
-
- } catch (Exception e) {
- System.err.println("\n✗ 测试异常: " + e.getMessage());
- e.printStackTrace();
- }
- }
-
-
- @Test
- public void opcUaTestWrite() {
- // 初始化驱动
- opcUaProtocolDriver.initial();
-
- // 构建驱动配置(连接配置)
- Map driverConfig = new HashMap<>();
- driverConfig.put("host", AttributeBO.builder().value("Lyd-ThinkBook").build());
- driverConfig.put("port", AttributeBO.builder().value("53530").build());
- driverConfig.put("path", AttributeBO.builder().value("/OPCUA/SimulationServer").build());
-
- // 构建点位配置
- // 根据模拟器配置:ns=3;s=Temperature
- Map pointConfig = new HashMap<>();
- pointConfig.put("namespace", AttributeBO.builder().value("3").build()); // 命名空间3
- pointConfig.put("tag", AttributeBO.builder().value("Temperature").build()); // 节点标识:Temperature
-
- // 构建连接对象
- IotConnect connect = IotConnect.builder()
- .id(1)
- .code("OPC_UA_001")
- .host("Lyd-ThinkBook")
- .port(53530)
- .protocol("opc-ua")
- .enabled(true)
- .description("测试OPC UA连接")
- .build();
-
- // 构建配置对象
- IotConfig config = IotConfig.builder()
- .id(1)
+ IotConfig config2 = IotConfig.builder()
+ .id(2)
.connectId(1)
- .alias("temperature")
- .aliasName("温度传感器")
- .registerAddress("ns=3;s=Temperature") // 与模拟器配置一致
- .dataType("float") // 模拟器中是Float类型
- .readonly(true)
+ .alias("humidity")
+ .aliasName("湿度")
+ .registerAddress("40002")
+ .dataType("INT")
+ .readonly(false) // 设置为可写
.enabled(true)
- .description("测试OPC UA数据读取")
+ .description("测试批量写入2")
.build();
- try {
- System.out.println("=== OPC UA写入测试开始 ===");
-
- // 先读取当前值
- RValue beforeValue = opcUaProtocolDriver.read(driverConfig, pointConfig, connect, config);
- if (beforeValue != null) {
- System.out.println("写入前的值: " + beforeValue.getValue());
- } else {
- System.out.println("写入前读取失败,继续执行写入测试...");
- }
-
- // 构建写入值对象 - 写入新的温度值(例如:25.5)
- WValue wValue = new WValue();
- wValue.setValue("25.5");
- wValue.setType("float");
-
- // 调用write方法进行写入测试
- Boolean writeResult = opcUaProtocolDriver.write(driverConfig, pointConfig, connect, config, wValue);
- System.out.println("写入结果: " + (writeResult ? "成功" : "失败"));
-
- // 等待一小段时间后再次读取,验证写入是否成功
- Thread.sleep(500);
- RValue afterValue = opcUaProtocolDriver.read(driverConfig, pointConfig, connect, config);
-
- if (afterValue != null) {
- System.out.println("写入后的值: " + afterValue.getValue());
-
- // 验证写入是否成功
- if (writeResult && "25.5".equals(afterValue.getValue())) {
- System.out.println("✓ 写入验证成功!值已更新为: " + afterValue.getValue());
- } else {
- System.out.println("✗ 写入验证失败!期望值: 25.5, 实际值: " + afterValue.getValue());
- }
- } else {
- System.err.println("写入后读取失败,无法验证写入结果");
- }
-
- System.out.println("\n=== 测试完成 ===");
-
- } catch (Exception e) {
- System.err.println("\n✗ 测试异常: " + e.getMessage());
- e.printStackTrace();
- }
- }
-
- @Autowired
- private OpcDaProtocolDriverImpl opcDaProtocolDriver;
-
- @Test
- public void opcDaTestRead() {
- // 初始化驱动
- opcDaProtocolDriver.initial();
-
- // 构建驱动配置(连接配置)
- // 重要提示:
- // 1. 本地连接使用 "127.0.0.1" 或 "localhost"
- // 2. username 格式:
- // - 本地用户:直接使用用户名,如 "Administrator" 或 "YourUsername"
- // - 域用户:使用 "DOMAIN\\username" 格式
- // - 如果 OPC 服务器配置为允许匿名访问,可以尝试空字符串
- // 3. password 必须是 Windows 用户的实际密码
- // 4. 确保 Windows DCOM 配置正确(参考 Matrikon OPC 文档)
-
- Map driverConfig = new HashMap<>();
- driverConfig.put("host", AttributeBO.builder().value("127.0.0.1").build());
- driverConfig.put("clsId", AttributeBO.builder().value("F8582CF2-88FB-11D0-B850-00C0F0104305").build());
-
- // 方案1:使用当前登录用户(推荐本地测试)
- // 获取当前 Windows 用户名:在 CMD 中运行 "echo %USERNAME%"
- String currentUser = System.getProperty("user.name"); // 获取当前 Java 进程的用户名
-// driverConfig.put("username", AttributeBO.builder().value(currentUser).build());
-// driverConfig.put("password", AttributeBO.builder().value("6614").build()); // 替换为你的 Windows 密码
-
- // 方案2:如果模拟器配置了特定用户,使用该用户
- driverConfig.put("username", AttributeBO.builder().value("Administrator").build());
- driverConfig.put("password", AttributeBO.builder().value("12356").build());
-
- // 方案3:尝试匿名访问(某些 OPC 服务器支持)
- // driverConfig.put("username", AttributeBO.builder().value("").build());
- // driverConfig.put("password", AttributeBO.builder().value("").build());
-
- // 构建点位配置
- // 根据你的模拟器截图,Item ID 是 "Random.Int1"
- Map pointConfig = new HashMap<>();
- pointConfig.put("group", AttributeBO.builder().value("Group0").build()); // 组名
- pointConfig.put("tag", AttributeBO.builder().value("Random.Int1").build()); // 标签名,与模拟器中的 Item ID 一致
-
- // 构建连接对象
- IotConnect connect = IotConnect.builder()
- .id(1)
- .code("OPC_DA_001")
- .host("localhost")
- .protocol("opc-da")
- .enabled(true)
- .description("测试OPC DA连接")
- .build();
-
- // 构建配置对象
- IotConfig config = IotConfig.builder()
- .id(1)
+ IotConfig config3 = IotConfig.builder()
+ .id(3)
.connectId(1)
- .alias("sensor1")
- .aliasName("传感器1")
- .registerAddress("Random.Int1")
- .dataType("int")
- .readonly(true)
+ .alias("output_value3")
+ .aliasName("输出值3")
+ .registerAddress("40003")
+ .dataType("LREAL")
+ .readonly(false) // 设置为可写
.enabled(true)
- .description("测试OPC DA数据读取")
+ .description("测试批量写入3")
.build();
+ // 执行批量写入操作
try {
- System.out.println("=== OPC DA读取测试开始 ===");
- System.out.println("连接地址: " + driverConfig.get("host").getValue());
- System.out.println("CLSID: " + driverConfig.get("clsId").getValue());
- System.out.println("组名: " + pointConfig.get("group").getValue());
- System.out.println("标签: " + pointConfig.get("tag").getValue());
+ System.out.println("========== 开始测试Modbus TCP批量写入 ==========");
+ System.out.println("连接信息: " + connect.getHost() + ":" + connect.getPort());
- // 调用read方法进行测试
- RValue result = opcDaProtocolDriver.read(driverConfig, pointConfig, connect, config);
+ // 转换为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();
- // 检查结果是否为null
- if (result != null) {
- System.out.println("\n✓ 读取成功!");
- System.out.println("连接信息: " + result.getConnect());
- System.out.println("配置信息: " + result.getConfig());
- System.out.println("读取值: " + result.getValue());
- } else {
- System.err.println("\n✗ 读取失败:返回结果为null");
- System.err.println("可能原因:");
- System.err.println("1. OPC DA服务器未启动或主机地址不正确");
- System.err.println("2. CLSID配置错误或服务器未注册");
- System.err.println("3. 用户名或密码错误");
- System.err.println("4. 组名或标签名不存在");
- System.err.println("5. DCOM配置不正确");
+ // 转换为SiteBO并构建WValue列表
+ java.util.List wValueList = java.util.Arrays.asList(
+ WValue.builder()
+ .point(SiteBO.builder()
+ .deviceCode(connect.getCode())
+ .alias(config1.getAlias())
+ .aliasName(config1.getAliasName())
+ .registerAddress(config1.getRegisterAddress())
+ .dataType(config1.getDataType())
+ .readonly(config1.getReadonly())
+ .build())
+ .value("100") // 写入值1
+ .type(config1.getDataType())
+ .build(),
+ WValue.builder()
+ .point(SiteBO.builder()
+ .deviceCode(connect.getCode())
+ .alias(config2.getAlias())
+ .aliasName(config2.getAliasName())
+ .registerAddress(config2.getRegisterAddress())
+ .dataType(config2.getDataType())
+ .readonly(config2.getReadonly())
+ .build())
+ .value("200") // 写入值2
+ .type(config2.getDataType())
+ .build()
+// WValue.builder()
+// .point(SiteBO.builder()
+// .deviceCode(connect.getCode())
+// .alias(config3.getAlias())
+// .aliasName(config3.getAliasName())
+// .registerAddress(config3.getRegisterAddress())
+// .dataType(config3.getDataType())
+// .readonly(config3.getReadonly())
+// .build())
+// .value("25.5") // 写入值3
+// .type(config3.getDataType())
+// .build()
+ );
+
+ System.out.println("批量写入点位数量: " + wValueList.size());
+ for (WValue wValue : wValueList) {
+ SiteBO site = wValue.getPoint();
+ System.out.println(" - " + site.getAliasName() + " (" + site.getAlias() + "): " +
+ site.getRegisterAddress() + " [" + site.getDataType() + "] = " + wValue.getValue());
}
- System.out.println("\n=== 测试完成 ===");
+ // 调用驱动批量写入数据
+ List result = modBusProtocolDriver.batchWrite(deviceBO, wValueList);
+
+ System.out.println("批量写入完成!");
+ System.out.println("写入结果:");
+
+ // 输出每个点位的写入结果
+ for (WResponse wResponse : result) {
+ WValue wValue = wResponse.getWValue();
+ SiteBO site = wValue.getPoint();
+ Boolean success = wResponse.getIsOk();
+ System.out.println(" - " + site.getAliasName() + " (" + site.getAlias() + "): " +
+ "写入值=" + wValue.getValue() + " 结果=" + (success ? "成功" : "失败"));
+ }
+
+ System.out.println("========== 批量写入测试完成 ==========");
} catch (Exception e) {
- System.err.println("\n✗ 测试异常: " + e.getMessage());
+ System.err.println("批量写入测试失败: " + e.getMessage());
e.printStackTrace();
}
}
- @Test
- public void opcDaTestWrite() {
- // 初始化驱动
- opcDaProtocolDriver.initial();
-
- // 构建驱动配置(连接配置)
- Map driverConfig = new HashMap<>();
- driverConfig.put("host", AttributeBO.builder().value("localhost").build());
- driverConfig.put("clsId", AttributeBO.builder().value("F8582CF2-88FB-11D0-B850-00C0F0104305").build()); // OPC DA Server CLSID
- driverConfig.put("username", AttributeBO.builder().value("Administrator").build());
- driverConfig.put("password", AttributeBO.builder().value("password").build());
-
- // 构建点位配置
- Map pointConfig = new HashMap<>();
- pointConfig.put("group", AttributeBO.builder().value("Group1").build()); // 组名
- pointConfig.put("tag", AttributeBO.builder().value("Channel1.Device1.Tag1").build()); // 标签名
-
- // 构建连接对象
- IotConnect connect = IotConnect.builder()
- .id(1)
- .code("OPC_DA_001")
- .host("localhost")
- .protocol("opc-da")
- .enabled(true)
- .description("测试OPC DA写入连接")
- .build();
-
- // 构建配置对象
- IotConfig config = IotConfig.builder()
- .id(1)
- .connectId(1)
- .alias("sensor1")
- .aliasName("传感器1")
- .registerAddress("Channel1.Device1.Tag1")
- .dataType("int")
- .readonly(false)
- .enabled(true)
- .description("测试OPC DA数据写入")
- .build();
-
- try {
- System.out.println("=== OPC DA写入测试开始 ===");
-
- // 先读取当前值
- RValue beforeValue = opcDaProtocolDriver.read(driverConfig, pointConfig, connect, config);
- if (beforeValue != null) {
- System.out.println("写入前的值: " + beforeValue.getValue());
- } else {
- System.out.println("写入前读取失败,继续执行写入测试...");
- }
-
- // 构建写入值对象 - 写入新的值(例如:100)
- WValue wValue = new WValue();
- wValue.setValue("100");
- wValue.setType("int");
-
- // 调用write方法进行写入测试
- Boolean writeResult = opcDaProtocolDriver.write(driverConfig, pointConfig, connect, config, wValue);
- System.out.println("写入结果: " + (writeResult ? "成功" : "失败"));
-
- // 等待一小段时间后再次读取,验证写入是否成功
- Thread.sleep(500);
- RValue afterValue = opcDaProtocolDriver.read(driverConfig, pointConfig, connect, config);
-
- if (afterValue != null) {
- System.out.println("写入后的值: " + afterValue.getValue());
-
- // 验证写入是否成功
- if (writeResult && "100".equals(afterValue.getValue())) {
- System.out.println("✓ 写入验证成功!值已更新为: " + afterValue.getValue());
- } else {
- System.out.println("✗ 写入验证失败!期望值: 100, 实际值: " + afterValue.getValue());
- }
- } else {
- System.err.println("写入后读取失败,无法验证写入结果");
- }
-
- System.out.println("\n=== 测试完成 ===");
-
- } catch (Exception e) {
- System.err.println("\n✗ 测试异常: " + e.getMessage());
- e.printStackTrace();
- }
- }
}