基础修改

联合利华-拍照
LAPTOP-S9HJSOEB\昊天 2 months ago
parent 21df529d49
commit 580aea4a0f

@ -0,0 +1,16 @@
# S7 PLC 配置文件
# 格式: ip, plcNumber, rack, slot, writeDataBlock, readDataBlock,
# palletName, palletDataType, palletOffset,
# batchName, batchDataType, batchOffset,
# dateName, dateDataType, dateOffset,
# snapName, snapDataType, snapOffset,
# photoName, photoDataType, photoOffset
192.168.1.1,001,0,1,DB221,DB222,盘点号,String[50],0.0,批次号,String[20],52.0,日期,String[20],74.0,允许拍照,byte,96.0,拍照结果,byte,98.0
192.168.1.2,002,0,1,DB221,DB222,盘点号,String[50],0.0,批次号,String[20],52.0,日期,String[20],74.0,允许拍照,byte,96.0,拍照结果,byte,98.0
192.168.1.3,003,0,1,DB221,DB222,盘点号,String[50],0.0,批次号,String[20],52.0,日期,String[20],74.0,允许拍照,byte,96.0,拍照结果,byte,98.0
192.168.1.4,004,0,1,DB221,DB222,盘点号,String[50],0.0,批次号,String[20],52.0,日期,String[20],74.0,允许拍照,byte,96.0,拍照结果,byte,98.0
192.168.1.5,005,0,1,DB221,DB222,盘点号,String[50],0.0,批次号,String[20],52.0,日期,String[20],74.0,允许拍照,byte,96.0,拍照结果,byte,98.0
192.168.1.6,006,0,1,DB221,DB222,盘点号,String[50],0.0,批次号,String[20],52.0,日期,String[20],74.0,允许拍照,byte,96.0,拍照结果,byte,98.0
192.168.1.7,007,0,1,DB221,DB222,盘点号,String[50],0.0,批次号,String[20],52.0,日期,String[20],74.0,允许拍照,byte,96.0,拍照结果,byte,98.0
192.168.1.8,008,0,1,DB221,DB222,盘点号,String[50],0.0,批次号,String[20],52.0,日期,String[20],74.0,允许拍照,byte,96.0,拍照结果,byte,98.0

@ -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
* IPPLC
*/
@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));
}
}
Loading…
Cancel
Save