From 7b4fd4ff07b3a56b2eebb87774697fb9e35ee45d Mon Sep 17 00:00:00 2001 From: liyongde <1419499670@qq.com> Date: Mon, 16 Mar 2026 09:58:00 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9As7=E8=AF=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plcs7/PlcS7ProtocolDriverImpl.java | 410 ++++++------------ .../protocol/plcs7/util/PlcS7Utils.java | 52 +++ nl-web-app/src/test/java/org/nl/ApiTest.java | 135 ++++++ 3 files changed, 312 insertions(+), 285 deletions(-) create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/util/PlcS7Utils.java diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/PlcS7ProtocolDriverImpl.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/PlcS7ProtocolDriverImpl.java index ed4f480..b0baff9 100644 --- a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/PlcS7ProtocolDriverImpl.java +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/PlcS7ProtocolDriverImpl.java @@ -1,32 +1,37 @@ package org.nl.iot.core.driver.protocol.plcs7; import jakarta.annotation.PostConstruct; -import jakarta.annotation.Resource; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import lombok.*; import lombok.extern.slf4j.Slf4j; +import org.apache.plc4x.java.DefaultPlcDriverManager; +import org.apache.plc4x.java.api.PlcConnection; +import org.apache.plc4x.java.api.messages.PlcReadRequest; +import org.apache.plc4x.java.api.messages.PlcReadResponse; +import org.apache.plc4x.java.api.types.PlcResponseCode; +import org.apache.plc4x.java.api.value.PlcValue; 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; 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.enums.AttributeTypeFlagEnum; import org.nl.iot.core.driver.enums.MetadataOperateTypeEnum; +import org.nl.iot.core.driver.protocol.modbustcp.util.ModbusPlcValueConvertUtil; import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.PlcS7PointVariable; import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Connector; import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.S7Serializer; -import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.factory.S7ConnectorFactory; -import org.nl.iot.core.driver.protocol.plcs7.com.github.s7.api.factory.S7SerializerFactory; +import org.nl.iot.core.driver.protocol.plcs7.util.PlcS7Utils; 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.util.Map; -import java.util.Objects; +import java.util.*; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantReadWriteLock; /** @@ -42,7 +47,7 @@ public class PlcS7ProtocolDriverImpl implements DriverCustomService { * Plc Connector Map * 仅供参考 */ - private Map connectMap; + private Map connectMap; @PostConstruct @Override @@ -67,299 +72,134 @@ public class PlcS7ProtocolDriverImpl implements DriverCustomService { } @Override - public RValue read(IotConnect connect, IotConfig config) { - /* - * PLC S7 数据读取逻辑 - * - * 提示: 此处逻辑仅供参考, 请务必结合实际应用场景进行修改。 - * 该方法用于从 PLC S7 设备中读取指定点位的数据。 - * 1. 获取设备的 S7 连接器。 - * 2. 加锁以确保线程安全。 - * 3. 使用 S7 序列化器读取点位数据。 - * 4. 将读取到的数据封装为 RValue 对象返回。 - * 5. 捕获并记录异常, 确保锁在 finally 块中释放。 - */ - log.debug("Plc S7 Read, connect: {}, config: {}", connect, config); - MyS7Connector myS7Connector = getS7Connector(connect); + public RValue read(DeviceBO device, SiteBO point) { + log.debug("Plc S7 Read, connect: {}, config: {}", device, point); + PlcConnection myS7Connector = getS7Connector(device); + String tagAddress = "%" + point.getRegisterAddress() + ":" + point.getDataType(); + log.info("构建读取请求 - 标签名: {}, 地址: {}", point.getAlias(), tagAddress); + PlcReadRequest readRequest = doBuildReadRequest(myS7Connector, Collections.singletonList(point)).build(); + CompletableFuture readFuture = readRequest.execute(); + PlcReadResponse readResponse; try { - myS7Connector.lock.writeLock().lock(); - S7Serializer serializer = S7SerializerFactory.buildSerializer(myS7Connector.getConnector()); - String type = config.getDataType(); - PlcS7PointVariable plcs7PointVariable = getPointVariable(config, type); - return new RValue(config, connect, String.valueOf(serializer.dispense(plcs7PointVariable))); + readResponse = readFuture.get(10, TimeUnit.SECONDS); + if (readResponse.getResponseCode(point.getAlias()) != PlcResponseCode.OK) { + return new RValue(device, point, null, String.format( + "读取 S7 失败,设备编码:%s,地址:%s,响应码:%s" + , point.getAlias() + , point.getRegisterAddress() + , readResponse.getResponseCode(point.getAlias()) + )); + } } catch (Exception e) { - log.error("Plc S7 Read Error: {}", e.getMessage()); - return null; - } finally { - myS7Connector.lock.writeLock().unlock(); + return new RValue(device, point, null, e.getMessage()); } + + log.info("读取响应 - 可用标签: {}", readResponse.getTagNames()); + + return new RValue(device, point, ModbusPlcValueConvertUtil.convertPlcValueToString(readResponse.getPlcValue(point.getAlias()) + , point.getDataType()), null); } @Override - public Boolean write(IotConnect device, IotConfig point, WValue wValue) { - log.debug("Plc S7 Write, device: {}, value: {}", device, wValue); - MyS7Connector myS7Connector = getS7Connector(device); - myS7Connector.lock.writeLock().lock(); - S7Serializer serializer = S7SerializerFactory.buildSerializer(myS7Connector.getConnector()); - PlcS7PointVariable plcs7PointVariable = getPointVariable(point, wValue.getType()); - - try { - store(serializer, plcs7PointVariable, wValue.getType(), wValue.getValue()); - return true; - } catch (Exception e) { - log.error("Plc S7 Write Error: {}", e.getMessage()); - return false; - } finally { - myS7Connector.lock.writeLock().unlock(); - } + public List batchRead(DeviceBO device, List points) { + return batchReadValue(getS7Connector(device), device, points); } - /** - * 向 PLC S7 写入数据 - *

- * 该方法用于将指定类型的数据写入到 PLC S7 的指定点位。 - * 1. 根据类型字符串获取对应的 {@link AttributeTypeFlagEnum} 枚举值。 - * 2. 如果类型不支持, 抛出 {@link UnSupportException} 异常。 - * 3. 根据类型将字符串值转换为相应的 Java 类型。 - * 4. 使用 {@link S7Serializer} 将数据写入到 PLC S7 的指定数据块和字节偏移量位置。 - *

- * 支持的数据类型包括: - * - INT: 整型 - * - LONG: 长整型 - * - FLOAT: 单精度浮点型 - * - DOUBLE: 双精度浮点型 - * - BOOLEAN: 布尔型 - * - STRING: 字符串 - * - * @param serializer S7 序列化器, 用于与 PLC S7 进行数据交互 - * @param plcS7PointVariable PLC S7 点位变量信息, 包含数据块编号, 字节偏移量等 - * @param type 数据类型字符串, 用于标识要写入的数据类型 - * @param value 要写入的字符串形式的数据值 - * @throws CommonException 如果数据类型不支持, 抛出此异常 - */ - private void store(S7Serializer serializer, PlcS7PointVariable plcS7PointVariable, String type, String value) { - AttributeTypeFlagEnum valueType = AttributeTypeFlagEnum.ofCode(type); - if (Objects.isNull(valueType)) { - throw new CommonException("Unsupported type of " + type); - } - AttributeBO attributeBOConfig = new AttributeBO(value); - switch (valueType) { - case INT: - int intValue = attributeBOConfig.getValueByClass(Integer.class); - serializer.store(intValue, plcS7PointVariable.getDbNum(), plcS7PointVariable.getByteOffset()); - break; - case LONG: - long longValue = attributeBOConfig.getValueByClass(Long.class); - serializer.store(longValue, plcS7PointVariable.getDbNum(), plcS7PointVariable.getByteOffset()); - break; - case FLOAT: - float floatValue = attributeBOConfig.getValueByClass(Float.class); - serializer.store(floatValue, plcS7PointVariable.getDbNum(), plcS7PointVariable.getByteOffset()); - break; - case DOUBLE: - double doubleValue = attributeBOConfig.getValueByClass(Double.class); - serializer.store(doubleValue, plcS7PointVariable.getDbNum(), plcS7PointVariable.getByteOffset()); - break; - case BOOLEAN: - boolean booleanValue = attributeBOConfig.getValueByClass(Boolean.class); - serializer.store(booleanValue, plcS7PointVariable.getDbNum(), plcS7PointVariable.getByteOffset()); - break; - case STRING: - serializer.store(value, plcS7PointVariable.getDbNum(), plcS7PointVariable.getByteOffset()); - break; - default: - break; - } + @Override + public Boolean write(DeviceBO device, WValue wValue) { + return null; } + + @Override + public List batchWrite(DeviceBO device, List wValue) { + return List.of(); + } + /** * 获取 PLC S7 连接器 - *

- * 该方法用于从缓存中获取指定设备的 S7 连接器。如果缓存中不存在该设备的连接器, - * 则会根据驱动配置信息创建一个新的连接器, 并将其缓存以供后续使用。 - *

- * 连接器创建过程中, 会从驱动配置中获取主机地址和端口号, 并初始化读写锁以确保线程安全。 - * 如果连接器创建失败, 将抛出 {@link CommonException} 异常。 - * - * @param connectId 设备ID, 用于标识唯一的设备连接器 - * @param driverConfig 驱动配置信息, 包含连接 PLC 所需的主机地址和端口号等参数 - * @return 返回与设备ID对应的 {@link MyS7Connector} 对象, 包含 S7 连接器和读写锁 - * @throws CommonException 如果连接器创建失败, 抛出此异常 */ - private MyS7Connector getS7Connector(IotConnect connect) { - MyS7Connector myS7Connector = connectMap.get(connect.getId().toString()); - if (Objects.isNull(myS7Connector)) { - myS7Connector = new MyS7Connector(); + private PlcConnection getS7Connector(DeviceBO deviceBO) { + String deviceId = deviceBO.getId(); + PlcConnection connection = connectMap.get(deviceId); - log.debug("Plc S7 Connection Info {}", connect); - try { - S7Connector s7Connector = S7ConnectorFactory.buildTCPConnector() - .withHost(connect.getHost()) - .withPort(connect.getPort()) - .build(); - myS7Connector.setLock(new ReentrantReadWriteLock()); - myS7Connector.setConnector(s7Connector); - } catch (Exception e) { - throw new CommonException("new s7connector fail" + e.getMessage()); - } - connectMap.put(connect.getId().toString(), myS7Connector); - } - return myS7Connector; - } - - /** - * 获取 PLC S7 点位变量信息 - *

- * 该方法用于从点位配置中提取 PLC S7 点位变量信息, 并封装为 {@link PlcS7PointVariable} 对象。 - * 点位配置中应包含以下关键属性: - * - dbNum: 数据块编号 - * - byteOffset: 字节偏移量 - * - bitOffset: 位偏移量 - * - blockSize: 数据块大小 - * - type: 点位数据类型 - *

- * 如果点位配置中缺少上述任一属性, 将抛出 {@link NullPointerException} 异常。 - * - * @param pointConfig 点位配置信息, 包含点位变量的相关属性 - * @param type 点位数据类型, 用于标识点位数据的类型 - * @return 返回封装好的 {@link PlcS7PointVariable} 对象, 包含点位变量的详细信息 - * @throws NullPointerException 如果点位配置中缺少必要的属性, 抛出此异常 - */ - private PlcS7PointVariable getPointVariable(IotConfig config, String type) { - log.debug("Plc S7 Point Attribute Config {}", config); - // DB 块地址解析 - return parseS7Address(config.getRegisterAddress(), type); - } - - // 通用解析方法:支持DB格式(DB1.DBX10.3)和S7-200格式(VB1、VD4、V1.3) - public static PlcS7PointVariable parseS7Address(String address, String type) { - if (address == null || address.trim().isEmpty()) { - throw new IllegalArgumentException("S7地址不能为空!"); - } - String cleanAddress = address.trim().toUpperCase(); - int dbNum = 0; - String area = ""; - int byteOffset = -1; - int bitOffset = -1; - int size = -1; - - // ========== 分支1:解析S7-1200/1500的DB格式(DB1.DBX10.3、DB2.DBD5) ========== - if (cleanAddress.startsWith("DB")) { - area = "DB"; - // 拆分DB编号和地址主体(DB1.DBX10.3 → DB1 和 DBX10.3) - String[] dbAndAddress = cleanAddress.split("\\.DB", 2); - if (dbAndAddress.length != 2) { - throw new IllegalArgumentException("DB地址格式错误:" + address); - } - // 解析DB编号 - String dbNumStr = dbAndAddress[0].replace("DB", ""); - try { - dbNum = Integer.parseInt(dbNumStr); - if (dbNum < 1) throw new IllegalArgumentException("DB编号必须≥1:" + dbNumStr); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("DB编号不是有效数字:" + dbNumStr, e); - } - // 解析类型和偏移(DBX10.3 → X10.3) - String addressBody = "DB" + dbAndAddress[1]; - char typeSuffix = addressBody.charAt(2); - String offsetStr = addressBody.substring(3); - // 按类型解析 - switch (typeSuffix) { - case 'X': // BOOL - String[] byteBit = offsetStr.split("\\."); - if (byteBit.length != 2) throw new IllegalArgumentException("BOOL地址需包含位偏移:" + address); - byteOffset = parseIntCheck(byteBit[0], "字节偏移"); - bitOffset = parseIntCheck(byteBit[1], "位偏移"); - if (bitOffset < 0 || bitOffset > 7) throw new IllegalArgumentException("位偏移需0-7:" + bitOffset); - size = 1; - break; - case 'B': // BYTE - byteOffset = parseIntCheck(offsetStr, "字节偏移"); - size = 1; - break; - case 'W': // WORD - byteOffset = parseIntCheck(offsetStr, "字节偏移"); - size = 2; - break; - case 'D': // DWORD/REAL - byteOffset = parseIntCheck(offsetStr, "字节偏移"); - size = 4; - break; - case 'R': // REAL - byteOffset = parseIntCheck(offsetStr, "字节偏移"); - size = 4; - break; - default: - throw new IllegalArgumentException("不支持的DB类型后缀:" + typeSuffix); - } - - // ========== 分支2:解析S7-200的VB/VD/V1.3格式 ========== - } else { - // 第一步:判断是BOOL位(V1.3、I0.1)还是字节/双字(VB1、VD4) - if (cleanAddress.contains(".")) { - // 处理BOOL位(V1.3、I0.1、M2.5) - String[] areaByteBit = cleanAddress.split("\\."); - if (areaByteBit.length != 2) throw new IllegalArgumentException("S7-200 BOOL地址格式错误:" + address); - // 拆分存储区和字节偏移(V1 → V 和 1) - String areaByteStr = areaByteBit[0]; - area = areaByteStr.substring(0, 1); // 取第一个字符(V/I/Q/M/SM) - String byteOffsetStr = areaByteStr.substring(1); - // 校验存储区合法性 - if (!"VIQMSM".contains(area)) throw new IllegalArgumentException("不支持的S7-200存储区:" + area); - // 解析字节和位偏移 - byteOffset = parseIntCheck(byteOffsetStr, "字节偏移"); - bitOffset = parseIntCheck(areaByteBit[1], "位偏移"); - if (bitOffset < 0 || bitOffset > 7) throw new IllegalArgumentException("位偏移需0-7:" + bitOffset); - size = 1; // BOOL占1字节 - - } else { - // 处理字节/双字(VB1、VD4、IW2、MB3) - // 拆分存储区+类型 和 地址(VB1 → VB 和 1;VD4 → VD 和 4) - char typeSuffix = cleanAddress.charAt(1); // 第二个字符是类型(B/W/D) - area = cleanAddress.substring(0, 1); // 第一个字符是存储区(V/I/Q/M/SM) - String offsetStr = cleanAddress.substring(2); - // 校验存储区和类型 - if (!"VIQMSM".contains(area)) throw new IllegalArgumentException("不支持的S7-200存储区:" + area); - if (!"BWD".contains(String.valueOf(typeSuffix))) throw new IllegalArgumentException("不支持的S7-200类型后缀:" + typeSuffix); - // 解析字节偏移和大小 - byteOffset = parseIntCheck(offsetStr, "字节偏移"); - switch (typeSuffix) { - case 'B': size = 1; break; // BYTE - case 'W': size = 2; break; // WORD - case 'D': size = 4; break; // DWORD/REAL - } - } - } - - // 校验字节偏移合法性 - if (byteOffset < 0) throw new IllegalArgumentException("字节偏移不能为负数:" + byteOffset); - return new PlcS7PointVariable(dbNum, byteOffset, bitOffset, size, type); - } - - // 工具方法:解析数字并捕获异常 - private static int parseIntCheck(String str, String fieldName) { + // 检查连接是否有效(核心修复:判断连接是否存在且未关闭) try { - return Integer.parseInt(str); - } catch (NumberFormatException e) { - throw new IllegalArgumentException(fieldName + "不是有效数字:" + str, e); + if (Objects.isNull(connection) || !connection.isConnected()) { + // 旧连接失效,先关闭再移除 + if (Objects.nonNull(connection)) { + try { + connection.close(); + } catch (Exception e) { + log.warn("关闭失效连接失败,deviceId: {}", deviceId, e); + } + connectMap.remove(deviceId); + } + // 创建新连接 + String connectionUrl = PlcS7Utils.getS7ConnectionUrl(deviceBO); + log.info("创建S7连接,deviceId: {}, url: {}", deviceId, connectionUrl); + connection = new DefaultPlcDriverManager().getConnection(connectionUrl); + connectMap.put(deviceId, connection); + } + } catch (Exception e) { + log.error("创建S7连接失败,deviceId: {}", deviceId, e); + throw new CommonException("PLC S7 连接失败:" + e.getMessage()); } + return connection; + } + + public static PlcReadRequest.Builder doBuildReadRequest(PlcConnection s7Connection, List points) { + PlcReadRequest.Builder readBuilder = s7Connection.readRequestBuilder(); + for (SiteBO point : points) { + String tagAddress = "%" + point.getRegisterAddress() + ":" + point.getDataType(); + // 构建读取请求 + String tagName = point.getAlias(); + // 校验地址格式 + if (!ModbusPlcValueConvertUtil.containerType(point.getDataType())) { + log.warn("S7数据类型错误:设备编码:{}", tagName); + continue; + } + readBuilder.addTagAddress(tagName, tagAddress); + } + return readBuilder; } /** - * MyS7Connector 内部类 - *

- * 该类用于封装与 PLC S7 连接相关的信息, 包括读写锁和 S7 连接器。 - * 读写锁 {@link ReentrantReadWriteLock} 用于确保在多线程环境下对 S7 连接器的操作是线程安全的。 - * S7 连接器 {@link S7Connector} 用于与 PLC S7 设备进行通信。 - *

- * 该类提供了无参构造函数和全参构造函数, 并使用了 Lombok 注解自动生成 getter 和 setter 方法。 + * 实现批量读取 + * @param s7Connection + * @param deviceBO + * @param points + * @return */ - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - private static class MyS7Connector { - private ReentrantReadWriteLock lock; - private S7Connector connector; + @SneakyThrows + public List batchReadValue(PlcConnection s7Connection, DeviceBO deviceBO, List points) { + // 1. 解析配置 + PlcReadRequest.Builder readBuilder = doBuildReadRequest(s7Connection, points); + // 3. 执行请求 + PlcReadRequest readRequest = readBuilder.build(); + CompletableFuture 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( + "读取S7失败,设备编码:%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( + "读取S7失败,设备编码:%s,地址:%s,响应码:%s", point.getAlias(), point.getRegisterAddress(), e.getMessage() + ))); + } + } + return list; } } diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/util/PlcS7Utils.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/util/PlcS7Utils.java new file mode 100644 index 0000000..4055696 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/plcs7/util/PlcS7Utils.java @@ -0,0 +1,52 @@ +package org.nl.iot.core.driver.protocol.plcs7.util; + +import cn.hutool.core.util.ObjectUtil; +import com.alibaba.fastjson.JSONObject; +import org.nl.iot.core.driver.bo.DeviceBO; + +/** + * plc s7 工具类 + * @author: lyd + * @date: 2026/3/12 + */ +public class PlcS7Utils { + public static final String S7_CONN_PRX = "s7://"; + + public static String getS7ConnectionUrl(DeviceBO deviceBO) { + // 包含:remote-rack、remote-slot、controller-type + JSONObject propertiesMap = JSONObject.parseObject(deviceBO.getProperties()); + + // 获取设备IP地址 + String ip = deviceBO.getHost(); + if (ip == null || ip.trim().isEmpty()) { + throw new IllegalArgumentException("设备IP地址不能为空"); + } + String port = deviceBO.getPort().toString(); + if (ObjectUtil.isEmpty(port.trim().isEmpty()) || port.equals("null")) { + port = "102"; + } + + // 获取连接参数,设置默认值 + String remoteRack = propertiesMap.getString("remote-rack"); + if (remoteRack == null || remoteRack.trim().isEmpty()) { + remoteRack = "0"; // 默认值 + } + + String remoteSlot = propertiesMap.getString("remote-slot"); + if (remoteSlot == null || remoteSlot.trim().isEmpty()) { + remoteSlot = "1"; // 默认值 + } + + String controllerType = propertiesMap.getString("controller-type"); + if (controllerType == null || controllerType.trim().isEmpty()) { + controllerType = "S7_1200"; // 默认值 + } + + // 构建S7连接URL +// return String.format("%s%s:%s?local-rack=0&local-slot=0&remote-rack=%s&remote-slot=%s&controller-type=%s", +// S7_CONN_PRX, ip, port, remoteRack, remoteSlot, controllerType); +// return String.format("%s%s:%s?local-rack=%s&local-slot=%s&controller-type=%s", +// S7_CONN_PRX, ip, port, remoteRack, remoteSlot, controllerType); + return S7_CONN_PRX + ip; + } +} 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 dd9a3e0..ba291f5 100644 --- a/nl-web-app/src/test/java/org/nl/ApiTest.java +++ b/nl-web-app/src/test/java/org/nl/ApiTest.java @@ -9,6 +9,7 @@ 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.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; @@ -463,4 +464,138 @@ public class ApiTest { } } + @Autowired + private PlcS7ProtocolDriverImpl plcS7ProtocolDriver; + + @Test + public void plcS7ReadTest() { + // 构建PLC S7连接对象 + JSONObject properties = new JSONObject(); + properties.put("remote-rack", "0"); + properties.put("remote-slot", "0"); + properties.put("controller-type", "S7_1200"); + + IotConnect connect = IotConnect.builder() + .id(2) + .code("PLC_S7_001") + .host("192.168.81.251") + .port(102) + .properties(JSONObject.toJSONString(properties)) + .protocol("plc-s7") + .enabled(true) + .description("测试PLC S7连接") + .build(); + + // 构建配置对象 - 读取DB1数据块的不同类型数据 + IotConfig config1 = IotConfig.builder() + .id(1) + .connectId(2) + .alias("move") + .aliasName("DB1布尔值") + .registerAddress("DB1.DB0.0") + .dataType("WORD") + .readonly(true) + .enabled(true) + .description("测试DB1布尔值读取") + .build(); + + IotConfig config2 = IotConfig.builder() + .id(2) + .connectId(2) + .alias("db1_int") + .aliasName("DB1整数值") +// .registerAddress("DB1.DB0.0") + .registerAddress("DB1.DBD0") + .dataType("DINT") + .readonly(true) + .enabled(true) + .description("测试DB1整数读取") + .build(); +// +// IotConfig config3 = IotConfig.builder() +// .id(3) +// .connectId(2) +// .alias("db1_real") +// .aliasName("DB1实数值") +// .registerAddress("DB1.DBD4") +// .dataType("REAL") +// .readonly(true) +// .enabled(true) +// .description("测试DB1实数读取") +// .build(); + + // 执行读取操作 + try { + System.out.println("========== 开始测试PLC S7读取 =========="); + System.out.println("连接信息: " + connect.getHost() + ":" + connect.getPort()); + System.out.println("控制器类型: " + properties.getString("controller-type")); + System.out.println("机架号: " + properties.getString("remote-rack")); + System.out.println("插槽号: " + properties.getString("remote-slot")); + + // 转换为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(); + +// // 测试读取布尔值 +// System.out.println("\n--- 测试读取布尔值 ---"); +// SiteBO siteBO1 = SiteBO.builder() +// .deviceCode(connect.getCode()) +// .alias(config1.getAlias()) +// .aliasName(config1.getAliasName()) +// .registerAddress(config1.getRegisterAddress()) +// .dataType(config1.getDataType()) +// .readonly(config1.getReadonly()) +// .build(); +// +// RValue result1 = plcS7ProtocolDriver.read(deviceBO, siteBO1); +// System.out.println("地址: " + config1.getRegisterAddress()); +// System.out.println("数据类型: " + config1.getDataType()); +// System.out.println("读取结果: " + result1.getValue()); + + // 测试读取整数值 + System.out.println("\n--- 测试读取整数值 ---"); + SiteBO siteBO2 = SiteBO.builder() + .deviceCode(connect.getCode()) + .alias(config2.getAlias()) + .aliasName(config2.getAliasName()) + .registerAddress(config2.getRegisterAddress()) + .dataType(config2.getDataType()) + .readonly(config2.getReadonly()) + .build(); + + RValue result2 = plcS7ProtocolDriver.read(deviceBO, siteBO2); + System.out.println("地址: " + config2.getRegisterAddress()); + System.out.println("数据类型: " + config2.getDataType()); + System.out.println("读取结果: " + result2.getValue()); + + // 测试读取实数值 +// System.out.println("\n--- 测试读取实数值 ---"); +// SiteBO siteBO3 = SiteBO.builder() +// .deviceCode(connect.getCode()) +// .alias(config3.getAlias()) +// .aliasName(config3.getAliasName()) +// .registerAddress(config3.getRegisterAddress()) +// .dataType(config3.getDataType()) +// .readonly(config3.getReadonly()) +// .build(); +// +// RValue result3 = plcS7ProtocolDriver.read(deviceBO, siteBO3); +// System.out.println("地址: " + config3.getRegisterAddress()); +// System.out.println("数据类型: " + config3.getDataType()); +// System.out.println("读取结果: " + result3.getValue()); + + System.out.println("\n========== PLC S7读取测试完成 =========="); + + } catch (Exception e) { + System.err.println("PLC S7读取测试失败: " + e.getMessage()); + e.printStackTrace(); + } + } + }