feat:s7读
This commit is contained in:
@@ -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<String, MyS7Connector> connectMap;
|
||||
private Map<String, PlcConnection> 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<? extends PlcReadResponse> 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<RValue> batchRead(DeviceBO device, List<SiteBO> points) {
|
||||
return batchReadValue(getS7Connector(device), device, points);
|
||||
}
|
||||
/**
|
||||
* 向 PLC S7 写入数据
|
||||
* <p>
|
||||
* 该方法用于将指定类型的数据写入到 PLC S7 的指定点位。
|
||||
* 1. 根据类型字符串获取对应的 {@link AttributeTypeFlagEnum} 枚举值。
|
||||
* 2. 如果类型不支持, 抛出 {@link UnSupportException} 异常。
|
||||
* 3. 根据类型将字符串值转换为相应的 Java 类型。
|
||||
* 4. 使用 {@link S7Serializer} 将数据写入到 PLC S7 的指定数据块和字节偏移量位置。
|
||||
* <p>
|
||||
* 支持的数据类型包括:
|
||||
* - 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<WResponse> batchWrite(DeviceBO device, List<WValue> wValue) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 PLC S7 连接器
|
||||
* <p>
|
||||
* 该方法用于从缓存中获取指定设备的 S7 连接器。如果缓存中不存在该设备的连接器,
|
||||
* 则会根据驱动配置信息创建一个新的连接器, 并将其缓存以供后续使用。
|
||||
* <p>
|
||||
* 连接器创建过程中, 会从驱动配置中获取主机地址和端口号, 并初始化读写锁以确保线程安全。
|
||||
* 如果连接器创建失败, 将抛出 {@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 点位变量信息
|
||||
* <p>
|
||||
* 该方法用于从点位配置中提取 PLC S7 点位变量信息, 并封装为 {@link PlcS7PointVariable} 对象。
|
||||
* 点位配置中应包含以下关键属性:
|
||||
* - dbNum: 数据块编号
|
||||
* - byteOffset: 字节偏移量
|
||||
* - bitOffset: 位偏移量
|
||||
* - blockSize: 数据块大小
|
||||
* - type: 点位数据类型
|
||||
* <p>
|
||||
* 如果点位配置中缺少上述任一属性, 将抛出 {@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<SiteBO> 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 内部类
|
||||
* <p>
|
||||
* 该类用于封装与 PLC S7 连接相关的信息, 包括读写锁和 S7 连接器。
|
||||
* 读写锁 {@link ReentrantReadWriteLock} 用于确保在多线程环境下对 S7 连接器的操作是线程安全的。
|
||||
* S7 连接器 {@link S7Connector} 用于与 PLC S7 设备进行通信。
|
||||
* <p>
|
||||
* 该类提供了无参构造函数和全参构造函数, 并使用了 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<RValue> batchReadValue(PlcConnection s7Connection, DeviceBO deviceBO, List<SiteBO> points) {
|
||||
// 1. 解析配置
|
||||
PlcReadRequest.Builder readBuilder = doBuildReadRequest(s7Connection, points);
|
||||
// 3. 执行请求
|
||||
PlcReadRequest readRequest = readBuilder.build();
|
||||
CompletableFuture<? extends PlcReadResponse> readFuture = readRequest.execute();
|
||||
PlcReadResponse readResponse = readFuture.get(10, TimeUnit.SECONDS);
|
||||
List<RValue> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user