|
|
|
@ -0,0 +1,528 @@
|
|
|
|
|
|
|
|
package com.zhehekeji.web.service.cron;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import com.sourceforge.snap7.moka7.S7;
|
|
|
|
|
|
|
|
import com.sourceforge.snap7.moka7.S7Client;
|
|
|
|
|
|
|
|
import lombok.Data;
|
|
|
|
|
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
|
|
|
import org.springframework.context.annotation.Configuration;
|
|
|
|
|
|
|
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
|
|
|
|
|
|
|
import org.springframework.stereotype.Component;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import javax.annotation.PostConstruct;
|
|
|
|
|
|
|
|
import java.io.BufferedReader;
|
|
|
|
|
|
|
|
import java.io.FileReader;
|
|
|
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
|
|
|
import java.nio.charset.StandardCharsets;
|
|
|
|
|
|
|
|
import java.util.*;
|
|
|
|
|
|
|
|
import java.util.concurrent.*;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* S7 多PLC模拟服务
|
|
|
|
|
|
|
|
* 支持连接多个不同IP的PLC,读写分离
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
@Configuration
|
|
|
|
|
|
|
|
@Component
|
|
|
|
|
|
|
|
@EnableScheduling
|
|
|
|
|
|
|
|
@Slf4j
|
|
|
|
|
|
|
|
public class S7MultiPlcService {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// PLC连接池映射: plcNumber -> 连接池
|
|
|
|
|
|
|
|
private final Map<String, BlockingQueue<S7Client>> plcConnectionPools = new ConcurrentHashMap<>();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// PLC配置列表
|
|
|
|
|
|
|
|
private final List<PlcConfig> plcConfigs = new CopyOnWriteArrayList<>();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 地址偏移量映射: plcNumber+fieldName -> offset
|
|
|
|
|
|
|
|
private final Map<String, Double> offsetMap = new ConcurrentHashMap<>();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 数据类型映射: plcNumber+fieldName -> dataType
|
|
|
|
|
|
|
|
private final Map<String, String> dataTypeMap = new ConcurrentHashMap<>();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 读取数据缓存: plcNumber -> 读取的数据
|
|
|
|
|
|
|
|
private final Map<String, PlcReadData> readDataCache = new ConcurrentHashMap<>();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 连接池大小
|
|
|
|
|
|
|
|
private final int POOL_SIZE = 3;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 读取间隔(ms)
|
|
|
|
|
|
|
|
private static final int READ_INTERVAL = 1000;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 线程池
|
|
|
|
|
|
|
|
private ExecutorService executorService;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@PostConstruct
|
|
|
|
|
|
|
|
public void init() {
|
|
|
|
|
|
|
|
readPlcConfig();
|
|
|
|
|
|
|
|
initConnectionPools();
|
|
|
|
|
|
|
|
initExecutorService();
|
|
|
|
|
|
|
|
log.info("S7多PLC服务初始化完成,共配置 {} 个PLC", plcConfigs.size());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* PLC配置实体
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
@Data
|
|
|
|
|
|
|
|
public static class PlcConfig {
|
|
|
|
|
|
|
|
private String ip;
|
|
|
|
|
|
|
|
private String plcNumber;
|
|
|
|
|
|
|
|
private int rack;
|
|
|
|
|
|
|
|
private int slot;
|
|
|
|
|
|
|
|
private int writeDataBlock; // 如 DB221 -> 221
|
|
|
|
|
|
|
|
private int readDataBlock; // 如 DB222 -> 222
|
|
|
|
|
|
|
|
private String palletName;
|
|
|
|
|
|
|
|
private String palletDataType;
|
|
|
|
|
|
|
|
private double palletOffset;
|
|
|
|
|
|
|
|
private String batchName;
|
|
|
|
|
|
|
|
private String batchDataType;
|
|
|
|
|
|
|
|
private double batchOffset;
|
|
|
|
|
|
|
|
private String dateName;
|
|
|
|
|
|
|
|
private String dateDataType;
|
|
|
|
|
|
|
|
private double dateOffset;
|
|
|
|
|
|
|
|
private String snapName;
|
|
|
|
|
|
|
|
private String snapDataType;
|
|
|
|
|
|
|
|
private double snapOffset;
|
|
|
|
|
|
|
|
private String photoName;
|
|
|
|
|
|
|
|
private String photoDataType;
|
|
|
|
|
|
|
|
private double photoOffset;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* PLC读取数据
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
@Data
|
|
|
|
|
|
|
|
public static class PlcReadData {
|
|
|
|
|
|
|
|
private String palletNo; // 盘点号
|
|
|
|
|
|
|
|
private String batchNo; // 批次号
|
|
|
|
|
|
|
|
private String date; // 日期
|
|
|
|
|
|
|
|
private byte snapFlag; // 允许拍照标志
|
|
|
|
|
|
|
|
private byte photoResult; // 拍照结果
|
|
|
|
|
|
|
|
private long updateTime; // 更新时间
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 读取PLC配置文件
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private void readPlcConfig() {
|
|
|
|
|
|
|
|
try (BufferedReader reader = new BufferedReader(new FileReader("./s7PlcConfig.txt"))) {
|
|
|
|
|
|
|
|
String line;
|
|
|
|
|
|
|
|
while ((line = reader.readLine()) != null) {
|
|
|
|
|
|
|
|
if (line.startsWith("#") || line.trim().isEmpty()) {
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
String[] parts = line.split(",");
|
|
|
|
|
|
|
|
if (parts.length >= 20) {
|
|
|
|
|
|
|
|
PlcConfig config = new PlcConfig();
|
|
|
|
|
|
|
|
config.setIp(parts[0].trim());
|
|
|
|
|
|
|
|
config.setPlcNumber(parts[1].trim());
|
|
|
|
|
|
|
|
config.setRack(Integer.parseInt(parts[2].trim()));
|
|
|
|
|
|
|
|
config.setSlot(Integer.parseInt(parts[3].trim()));
|
|
|
|
|
|
|
|
config.setWriteDataBlock(Integer.parseInt(parts[4].trim().replace("DB", "")));
|
|
|
|
|
|
|
|
config.setReadDataBlock(Integer.parseInt(parts[5].trim().replace("DB", "")));
|
|
|
|
|
|
|
|
config.setPalletName(parts[6].trim());
|
|
|
|
|
|
|
|
config.setPalletDataType(parts[7].trim());
|
|
|
|
|
|
|
|
config.setPalletOffset(Double.parseDouble(parts[8].trim()));
|
|
|
|
|
|
|
|
config.setBatchName(parts[9].trim());
|
|
|
|
|
|
|
|
config.setBatchDataType(parts[10].trim());
|
|
|
|
|
|
|
|
config.setBatchOffset(Double.parseDouble(parts[11].trim()));
|
|
|
|
|
|
|
|
config.setDateName(parts[12].trim());
|
|
|
|
|
|
|
|
config.setDateDataType(parts[13].trim());
|
|
|
|
|
|
|
|
config.setDateOffset(Double.parseDouble(parts[14].trim()));
|
|
|
|
|
|
|
|
config.setSnapName(parts[15].trim());
|
|
|
|
|
|
|
|
config.setSnapDataType(parts[16].trim());
|
|
|
|
|
|
|
|
config.setSnapOffset(Double.parseDouble(parts[17].trim()));
|
|
|
|
|
|
|
|
config.setPhotoName(parts[18].trim());
|
|
|
|
|
|
|
|
config.setPhotoDataType(parts[19].trim());
|
|
|
|
|
|
|
|
config.setPhotoOffset(Double.parseDouble(parts[20].trim()));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
plcConfigs.add(config);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 注册映射
|
|
|
|
|
|
|
|
String prefix = config.getPlcNumber();
|
|
|
|
|
|
|
|
offsetMap.put(prefix + "_pallet", config.getPalletOffset());
|
|
|
|
|
|
|
|
offsetMap.put(prefix + "_batch", config.getBatchOffset());
|
|
|
|
|
|
|
|
offsetMap.put(prefix + "_date", config.getDateOffset());
|
|
|
|
|
|
|
|
offsetMap.put(prefix + "_snap", config.getSnapOffset());
|
|
|
|
|
|
|
|
offsetMap.put(prefix + "_photo", config.getPhotoOffset());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
dataTypeMap.put(prefix + "_pallet", config.getPalletDataType());
|
|
|
|
|
|
|
|
dataTypeMap.put(prefix + "_batch", config.getBatchDataType());
|
|
|
|
|
|
|
|
dataTypeMap.put(prefix + "_date", config.getDateDataType());
|
|
|
|
|
|
|
|
dataTypeMap.put(prefix + "_snap", config.getSnapDataType());
|
|
|
|
|
|
|
|
dataTypeMap.put(prefix + "_photo", config.getPhotoDataType());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
|
|
|
log.error("读取PLC配置文件失败", e);
|
|
|
|
|
|
|
|
throw new RuntimeException("读取PLC配置文件失败", e);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 初始化所有PLC连接池
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private void initConnectionPools() {
|
|
|
|
|
|
|
|
for (PlcConfig config : plcConfigs) {
|
|
|
|
|
|
|
|
BlockingQueue<S7Client> pool = new ArrayBlockingQueue<>(POOL_SIZE);
|
|
|
|
|
|
|
|
for (int i = 0; i < POOL_SIZE; i++) {
|
|
|
|
|
|
|
|
S7Client client = new S7Client();
|
|
|
|
|
|
|
|
int result = client.ConnectTo(config.getIp(), config.getRack(), config.getSlot());
|
|
|
|
|
|
|
|
if (result == 0) {
|
|
|
|
|
|
|
|
pool.offer(client);
|
|
|
|
|
|
|
|
log.info("PLC {} 连接成功: {}", config.getPlcNumber(), config.getIp());
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
log.error("PLC {} 连接失败: {}, 错误码: {}", config.getPlcNumber(), config.getIp(), result);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
plcConnectionPools.put(config.getPlcNumber(), pool);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 初始化线程池
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private void initExecutorService() {
|
|
|
|
|
|
|
|
executorService = new ThreadPoolExecutor(
|
|
|
|
|
|
|
|
plcConfigs.size(),
|
|
|
|
|
|
|
|
plcConfigs.size() * 2,
|
|
|
|
|
|
|
|
60L, TimeUnit.SECONDS,
|
|
|
|
|
|
|
|
new LinkedBlockingQueue<>()
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 获取PLC连接
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private S7Client getConnection(String plcNumber) {
|
|
|
|
|
|
|
|
BlockingQueue<S7Client> pool = plcConnectionPools.get(plcNumber);
|
|
|
|
|
|
|
|
if (pool == null) {
|
|
|
|
|
|
|
|
log.error("未找到PLC {} 的连接池", plcNumber);
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
S7Client client = pool.take();
|
|
|
|
|
|
|
|
if (!client.Connected) {
|
|
|
|
|
|
|
|
// 重新连接
|
|
|
|
|
|
|
|
PlcConfig config = getPlcConfig(plcNumber);
|
|
|
|
|
|
|
|
if (config != null) {
|
|
|
|
|
|
|
|
client.ConnectTo(config.getIp(), config.getRack(), config.getSlot());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return client;
|
|
|
|
|
|
|
|
} catch (InterruptedException e) {
|
|
|
|
|
|
|
|
Thread.currentThread().interrupt();
|
|
|
|
|
|
|
|
log.error("获取PLC {} 连接失败", plcNumber, e);
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 归还PLC连接
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private void returnConnection(String plcNumber, S7Client client) {
|
|
|
|
|
|
|
|
BlockingQueue<S7Client> pool = plcConnectionPools.get(plcNumber);
|
|
|
|
|
|
|
|
if (pool != null && client != null) {
|
|
|
|
|
|
|
|
if (client.Connected) {
|
|
|
|
|
|
|
|
pool.offer(client);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// 重新创建连接
|
|
|
|
|
|
|
|
PlcConfig config = getPlcConfig(plcNumber);
|
|
|
|
|
|
|
|
if (config != null) {
|
|
|
|
|
|
|
|
S7Client newClient = new S7Client();
|
|
|
|
|
|
|
|
int result = newClient.ConnectTo(config.getIp(), config.getRack(), config.getSlot());
|
|
|
|
|
|
|
|
if (result == 0) {
|
|
|
|
|
|
|
|
pool.offer(newClient);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 获取PLC配置
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private PlcConfig getPlcConfig(String plcNumber) {
|
|
|
|
|
|
|
|
return plcConfigs.stream()
|
|
|
|
|
|
|
|
.filter(c -> c.getPlcNumber().equals(plcNumber))
|
|
|
|
|
|
|
|
.findFirst()
|
|
|
|
|
|
|
|
.orElse(null);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 解析数据类型对应的字节长度
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private int getDataTypeSize(String dataType) {
|
|
|
|
|
|
|
|
if (dataType == null) return 0;
|
|
|
|
|
|
|
|
if (dataType.equals("byte")) return 1;
|
|
|
|
|
|
|
|
if (dataType.equals("String[20]")) return 20;
|
|
|
|
|
|
|
|
if (dataType.equals("String[50]")) return 50;
|
|
|
|
|
|
|
|
if (dataType.startsWith("String[")) {
|
|
|
|
|
|
|
|
String size = dataType.replace("String[", "").replace("]", "");
|
|
|
|
|
|
|
|
return Integer.parseInt(size);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 从buffer中读取字符串
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private String readString(byte[] buffer, int offset, int length) {
|
|
|
|
|
|
|
|
if (buffer == null || offset < 0 || offset + length > buffer.length) {
|
|
|
|
|
|
|
|
return "";
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// 查找字符串结束位置(通常以\0结尾或有长度前缀)
|
|
|
|
|
|
|
|
int end = offset;
|
|
|
|
|
|
|
|
for (int i = offset; i < offset + length && i < buffer.length; i++) {
|
|
|
|
|
|
|
|
if (buffer[i] == 0) {
|
|
|
|
|
|
|
|
end = i;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
end = i + 1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return new String(buffer, offset, end - offset, StandardCharsets.ISO_8859_1).trim();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 读取指定PLC的所有数据(读取线程)
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
public PlcReadData readPlcData(String plcNumber) {
|
|
|
|
|
|
|
|
PlcConfig config = getPlcConfig(plcNumber);
|
|
|
|
|
|
|
|
if (config == null) {
|
|
|
|
|
|
|
|
log.error("未找到PLC配置: {}", plcNumber);
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
S7Client client = getConnection(plcNumber);
|
|
|
|
|
|
|
|
if (client == null) {
|
|
|
|
|
|
|
|
return readDataCache.get(plcNumber);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
// 计算需要读取的总长度
|
|
|
|
|
|
|
|
int maxOffset = (int) Math.max(
|
|
|
|
|
|
|
|
Math.max(config.getPalletOffset(), config.getBatchOffset()),
|
|
|
|
|
|
|
|
Math.max(config.getDateOffset(), config.getSnapOffset())
|
|
|
|
|
|
|
|
) + getDataTypeSize(config.getPalletDataType());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
byte[] buffer = new byte[maxOffset + 50];
|
|
|
|
|
|
|
|
int result = client.ReadArea(S7.S7AreaDB, config.getReadDataBlock(), 0, buffer.length, buffer);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (result != 0) {
|
|
|
|
|
|
|
|
log.error("PLC {} 读取失败,错误码: {}", plcNumber, result);
|
|
|
|
|
|
|
|
returnConnection(plcNumber, client);
|
|
|
|
|
|
|
|
return readDataCache.get(plcNumber);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PlcReadData data = new PlcReadData();
|
|
|
|
|
|
|
|
data.setPalletNo(readString(buffer, (int) config.getPalletOffset(), getDataTypeSize(config.getPalletDataType())));
|
|
|
|
|
|
|
|
data.setBatchNo(readString(buffer, (int) config.getBatchOffset(), getDataTypeSize(config.getBatchDataType())));
|
|
|
|
|
|
|
|
data.setDate(readString(buffer, (int) config.getDateOffset(), getDataTypeSize(config.getDateDataType())));
|
|
|
|
|
|
|
|
data.setSnapFlag(buffer[(int) config.getSnapOffset()]);
|
|
|
|
|
|
|
|
data.setPhotoResult(buffer[(int) config.getPhotoOffset()]);
|
|
|
|
|
|
|
|
data.setUpdateTime(System.currentTimeMillis());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 更新缓存
|
|
|
|
|
|
|
|
readDataCache.put(plcNumber, data);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
log.debug("PLC {} 读取成功: 盘点号={}, 批次号={}, 日期={}, 拍照标志={}",
|
|
|
|
|
|
|
|
plcNumber, data.getPalletNo(), data.getBatchNo(), data.getDate(), data.getSnapFlag());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
returnConnection(plcNumber, client);
|
|
|
|
|
|
|
|
return data;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
|
|
log.error("PLC {} 读取异常", plcNumber, e);
|
|
|
|
|
|
|
|
returnConnection(plcNumber, client);
|
|
|
|
|
|
|
|
return readDataCache.get(plcNumber);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 写入拍照结果到PLC(写入线程)
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
public boolean writePhotoResult(String plcNumber, byte photoResult) {
|
|
|
|
|
|
|
|
PlcConfig config = getPlcConfig(plcNumber);
|
|
|
|
|
|
|
|
if (config == null) {
|
|
|
|
|
|
|
|
log.error("未找到PLC配置: {}", plcNumber);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
S7Client client = getConnection(plcNumber);
|
|
|
|
|
|
|
|
if (client == null) {
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
byte[] buffer = new byte[1];
|
|
|
|
|
|
|
|
buffer[0] = photoResult;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int result = client.WriteArea(
|
|
|
|
|
|
|
|
S7.S7AreaDB,
|
|
|
|
|
|
|
|
config.getWriteDataBlock(),
|
|
|
|
|
|
|
|
(int) config.getPhotoOffset(),
|
|
|
|
|
|
|
|
1,
|
|
|
|
|
|
|
|
buffer
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (result == 0) {
|
|
|
|
|
|
|
|
log.info("PLC {} 写入拍照结果成功: {}", plcNumber, photoResult);
|
|
|
|
|
|
|
|
returnConnection(plcNumber, client);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
log.error("PLC {} 写入拍照结果失败,错误码: {}", plcNumber, result);
|
|
|
|
|
|
|
|
returnConnection(plcNumber, client);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
|
|
log.error("PLC {} 写入异常", plcNumber, e);
|
|
|
|
|
|
|
|
returnConnection(plcNumber, client);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 写入byte类型数据
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
public boolean writeByteData(String plcNumber, String fieldName, byte value) {
|
|
|
|
|
|
|
|
PlcConfig config = getPlcConfig(plcNumber);
|
|
|
|
|
|
|
|
if (config == null) {
|
|
|
|
|
|
|
|
log.error("未找到PLC配置: {}", plcNumber);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Double offset = offsetMap.get(plcNumber + "_" + fieldName);
|
|
|
|
|
|
|
|
if (offset == null) {
|
|
|
|
|
|
|
|
log.error("未找到字段偏移量: {}_{}", plcNumber, fieldName);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
S7Client client = getConnection(plcNumber);
|
|
|
|
|
|
|
|
if (client == null) {
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
byte[] buffer = new byte[1];
|
|
|
|
|
|
|
|
buffer[0] = value;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int result = client.WriteArea(
|
|
|
|
|
|
|
|
S7.S7AreaDB,
|
|
|
|
|
|
|
|
config.getWriteDataBlock(),
|
|
|
|
|
|
|
|
offset.intValue(),
|
|
|
|
|
|
|
|
1,
|
|
|
|
|
|
|
|
buffer
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (result == 0) {
|
|
|
|
|
|
|
|
log.info("PLC {} 写入 {} 成功: {}", plcNumber, fieldName, value);
|
|
|
|
|
|
|
|
returnConnection(plcNumber, client);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
log.error("PLC {} 写入 {} 失败,错误码: {}", plcNumber, fieldName, result);
|
|
|
|
|
|
|
|
returnConnection(plcNumber, client);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
|
|
log.error("PLC {} 写入异常", plcNumber, e);
|
|
|
|
|
|
|
|
returnConnection(plcNumber, client);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 获取所有PLC的最新读取数据
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
public Map<String, PlcReadData> getAllPlcReadData() {
|
|
|
|
|
|
|
|
return new HashMap<>(readDataCache);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 获取指定PLC的最新读取数据
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
public PlcReadData getPlcReadData(String plcNumber) {
|
|
|
|
|
|
|
|
return readDataCache.get(plcNumber);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 定时读取所有PLC数据(定时任务)
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
@Scheduled(fixedDelay = READ_INTERVAL)
|
|
|
|
|
|
|
|
public void scheduledReadAllPlc() {
|
|
|
|
|
|
|
|
for (PlcConfig config : plcConfigs) {
|
|
|
|
|
|
|
|
final String plcNumber = config.getPlcNumber();
|
|
|
|
|
|
|
|
executorService.submit(() -> {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
readPlcData(plcNumber);
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
|
|
log.error("定时读取PLC {} 异常", plcNumber, e);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 清理无效连接(定时任务)
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
@Scheduled(fixedRate = 60000)
|
|
|
|
|
|
|
|
public void cleanUpConnections() {
|
|
|
|
|
|
|
|
log.info("开始清理PLC连接池...");
|
|
|
|
|
|
|
|
for (Map.Entry<String, BlockingQueue<S7Client>> entry : plcConnectionPools.entrySet()) {
|
|
|
|
|
|
|
|
String plcNumber = entry.getKey();
|
|
|
|
|
|
|
|
BlockingQueue<S7Client> pool = entry.getValue();
|
|
|
|
|
|
|
|
int cleaned = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
List<S7Client> validClients = new ArrayList<>();
|
|
|
|
|
|
|
|
S7Client[] clients = pool.toArray(new S7Client[0]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (S7Client client : clients) {
|
|
|
|
|
|
|
|
pool.remove(client);
|
|
|
|
|
|
|
|
if (client != null && client.Connected) {
|
|
|
|
|
|
|
|
pool.offer(client);
|
|
|
|
|
|
|
|
validClients.add(client);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
if (client != null) {
|
|
|
|
|
|
|
|
client.Disconnect();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// 创建新连接
|
|
|
|
|
|
|
|
PlcConfig config = getPlcConfig(plcNumber);
|
|
|
|
|
|
|
|
if (config != null) {
|
|
|
|
|
|
|
|
S7Client newClient = new S7Client();
|
|
|
|
|
|
|
|
int result = newClient.ConnectTo(config.getIp(), config.getRack(), config.getSlot());
|
|
|
|
|
|
|
|
if (result == 0) {
|
|
|
|
|
|
|
|
pool.offer(newClient);
|
|
|
|
|
|
|
|
validClients.add(newClient);
|
|
|
|
|
|
|
|
cleaned++;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (cleaned > 0) {
|
|
|
|
|
|
|
|
log.info("PLC {} 替换了 {} 个无效连接", plcNumber, cleaned);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
log.info("PLC连接池清理完成");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 获取配置列表
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
public List<PlcConfig> getPlcConfigs() {
|
|
|
|
|
|
|
|
return new ArrayList<>(plcConfigs);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 异步写入拍照结果
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
public void writePhotoResultAsync(String plcNumber, byte photoResult) {
|
|
|
|
|
|
|
|
executorService.submit(() -> writePhotoResult(plcNumber, photoResult));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 异步读取指定PLC数据
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
public void readPlcDataAsync(String plcNumber) {
|
|
|
|
|
|
|
|
executorService.submit(() -> readPlcData(plcNumber));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|