|
|
|
|
@ -0,0 +1,372 @@
|
|
|
|
|
package com.zhehekeji.web.service.cron;
|
|
|
|
|
|
|
|
|
|
import com.sourceforge.snap7.moka7.S7;
|
|
|
|
|
import com.sourceforge.snap7.moka7.S7Client;
|
|
|
|
|
import io.swagger.models.auth.In;
|
|
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
import org.springframework.stereotype.Component;
|
|
|
|
|
|
|
|
|
|
import javax.annotation.PostConstruct;
|
|
|
|
|
import javax.annotation.Resource;
|
|
|
|
|
import java.io.BufferedReader;
|
|
|
|
|
import java.io.FileReader;
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
import java.util.*;
|
|
|
|
|
import java.util.concurrent.*;
|
|
|
|
|
|
|
|
|
|
@Slf4j
|
|
|
|
|
@Component
|
|
|
|
|
public class PLCMonitorService {
|
|
|
|
|
|
|
|
|
|
// 定义数据块和偏移量
|
|
|
|
|
int dbNumber = 121; // DB121
|
|
|
|
|
String plcIp = "10.69.105.122";
|
|
|
|
|
int plcRack = 0;
|
|
|
|
|
int plcSlot = 1;
|
|
|
|
|
int sizeToRead = 4; // 读取4个字节
|
|
|
|
|
|
|
|
|
|
// 地址映射
|
|
|
|
|
public static final Map<String, Integer> addressMap = new ConcurrentHashMap<>();
|
|
|
|
|
// 当前命令的上一个任务号
|
|
|
|
|
public static final Map<String, Integer> taskMap = new ConcurrentHashMap<>();
|
|
|
|
|
|
|
|
|
|
private final int POOL_SIZE = 5; // 连接池大小
|
|
|
|
|
private BlockingQueue<S7Client> connectionPool;
|
|
|
|
|
|
|
|
|
|
// 监控线程
|
|
|
|
|
private volatile boolean running = true;
|
|
|
|
|
private Thread monitorThread;
|
|
|
|
|
// 重启计数器
|
|
|
|
|
private volatile int restartCount = 0;
|
|
|
|
|
private static final int MAX_RESTART_COUNT = 10; // 最大重启次数
|
|
|
|
|
|
|
|
|
|
// 线程池
|
|
|
|
|
int corePoolSize = 15;
|
|
|
|
|
int maximumPoolSize = 40;
|
|
|
|
|
long keepAliveTime = 60;
|
|
|
|
|
TimeUnit unit = TimeUnit.SECONDS;
|
|
|
|
|
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>();
|
|
|
|
|
ExecutorService executorService = new ThreadPoolExecutor(
|
|
|
|
|
corePoolSize,
|
|
|
|
|
maximumPoolSize,
|
|
|
|
|
keepAliveTime,
|
|
|
|
|
unit,
|
|
|
|
|
workQueue
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// @PostConstruct
|
|
|
|
|
public void init() {
|
|
|
|
|
// 初始化连接池
|
|
|
|
|
connectionPool = new ArrayBlockingQueue<>(POOL_SIZE);
|
|
|
|
|
log.info("建立PLC监控连接池");
|
|
|
|
|
|
|
|
|
|
readDbConf();
|
|
|
|
|
// 创建连接池中的连接
|
|
|
|
|
for (int i = 0; i < POOL_SIZE; i++) {
|
|
|
|
|
S7Client client = new S7Client();
|
|
|
|
|
client.ConnectTo(plcIp, plcRack, plcSlot);
|
|
|
|
|
connectionPool.offer(client);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 启动监控线程
|
|
|
|
|
startMonitorThread();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 启动监控线程
|
|
|
|
|
*/
|
|
|
|
|
private void startMonitorThread() {
|
|
|
|
|
monitorThread = new Thread(() -> {
|
|
|
|
|
log.info("PLC监控线程启动");
|
|
|
|
|
while (running) {
|
|
|
|
|
try {
|
|
|
|
|
// 批量读取所有C1/C2/C3地址
|
|
|
|
|
Map<String, Integer> dataMap = readPlcDataTaskIds();
|
|
|
|
|
|
|
|
|
|
for (String key : dataMap.keySet()) {
|
|
|
|
|
int i = dataMap.get(key);
|
|
|
|
|
if (i == 0) continue;
|
|
|
|
|
if (taskMap.get(key) == null || taskMap.get(key) != i) {
|
|
|
|
|
log.info("任务号变化" + key + ":" + i);
|
|
|
|
|
taskMap.put(key, i);
|
|
|
|
|
taskMap.put(key + "-out", i);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Thread.sleep(300); // 查询间隔
|
|
|
|
|
} catch (InterruptedException e) {
|
|
|
|
|
log.info("PLC监控线程被中断");
|
|
|
|
|
Thread.currentThread().interrupt();
|
|
|
|
|
// 跳出循环后会检查是否需要重启
|
|
|
|
|
break;
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
log.error("PLC监控线程异常", e);
|
|
|
|
|
try {
|
|
|
|
|
Thread.sleep(1000);
|
|
|
|
|
} catch (InterruptedException ie) {
|
|
|
|
|
Thread.currentThread().interrupt();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
log.info("PLC监控线程结束");
|
|
|
|
|
|
|
|
|
|
// 线程异常退出时,检查是否需要重启
|
|
|
|
|
if (running && restartCount < MAX_RESTART_COUNT) {
|
|
|
|
|
restartCount++;
|
|
|
|
|
log.warn("PLC监控线程异常退出,{}秒后尝试第{}次重启", restartCount * 2, restartCount);
|
|
|
|
|
try {
|
|
|
|
|
Thread.sleep(restartCount * 2000L); // 递增延迟:2s, 4s, 6s...
|
|
|
|
|
// 重新初始化连接池
|
|
|
|
|
reinitializeConnectionPool();
|
|
|
|
|
startMonitorThread();
|
|
|
|
|
} catch (InterruptedException e) {
|
|
|
|
|
Thread.currentThread().interrupt();
|
|
|
|
|
log.info("重启PLC监控线程被中断");
|
|
|
|
|
}
|
|
|
|
|
} else if (restartCount >= MAX_RESTART_COUNT) {
|
|
|
|
|
log.error("PLC监控线程重启次数超过上限({}),停止自动重启", MAX_RESTART_COUNT);
|
|
|
|
|
}
|
|
|
|
|
}, "PLC-Monitor-Thread");
|
|
|
|
|
monitorThread.setDaemon(true);
|
|
|
|
|
monitorThread.start();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 重新初始化连接池
|
|
|
|
|
*/
|
|
|
|
|
private void reinitializeConnectionPool() {
|
|
|
|
|
log.info("重新初始化PLC连接池...");
|
|
|
|
|
// 清理旧连接
|
|
|
|
|
connectionPool.clear();
|
|
|
|
|
// 重建连接池
|
|
|
|
|
for (int i = 0; i < POOL_SIZE; i++) {
|
|
|
|
|
S7Client client = new S7Client();
|
|
|
|
|
client.ConnectTo(plcIp, plcRack, plcSlot);
|
|
|
|
|
connectionPool.offer(client);
|
|
|
|
|
}
|
|
|
|
|
log.info("PLC连接池重新初始化完成");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 停止监控线程
|
|
|
|
|
*/
|
|
|
|
|
public void stopMonitor() {
|
|
|
|
|
running = false;
|
|
|
|
|
restartCount = MAX_RESTART_COUNT; // 停止自动重启
|
|
|
|
|
if (monitorThread != null) {
|
|
|
|
|
monitorThread.interrupt();
|
|
|
|
|
}
|
|
|
|
|
executorService.shutdown();
|
|
|
|
|
log.info("PLC监控服务已停止");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取连接
|
|
|
|
|
*/
|
|
|
|
|
public S7Client getConnection() {
|
|
|
|
|
try {
|
|
|
|
|
return connectionPool.take();
|
|
|
|
|
} catch (InterruptedException e) {
|
|
|
|
|
Thread.currentThread().interrupt();
|
|
|
|
|
throw new RuntimeException("获取 PLC 连接失败", e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 更新连接
|
|
|
|
|
*/
|
|
|
|
|
public void updateConnection(S7Client client) {
|
|
|
|
|
try {
|
|
|
|
|
if (client != null) {
|
|
|
|
|
client.Disconnect();
|
|
|
|
|
}
|
|
|
|
|
connectionPool.remove(client);
|
|
|
|
|
|
|
|
|
|
S7Client newClient = new S7Client();
|
|
|
|
|
int result = newClient.ConnectTo(plcIp, plcRack, plcSlot);
|
|
|
|
|
if (result == 0) {
|
|
|
|
|
connectionPool.offer(newClient);
|
|
|
|
|
log.info("成功创建并添加新PLC连接到连接池");
|
|
|
|
|
} else {
|
|
|
|
|
log.error("创建新PLC连接失败,错误码: {}", result);
|
|
|
|
|
}
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
log.error("更新PLC连接失败", e);
|
|
|
|
|
throw new RuntimeException("归还 PLC 连接失败", e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 归还连接
|
|
|
|
|
*/
|
|
|
|
|
public void returnConnection(S7Client client) {
|
|
|
|
|
try {
|
|
|
|
|
if (client != null && client.Connected) {
|
|
|
|
|
connectionPool.offer(client);
|
|
|
|
|
} else {
|
|
|
|
|
updateConnection(client);
|
|
|
|
|
}
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
log.error("归还PLC连接时发生错误", e);
|
|
|
|
|
throw new RuntimeException("归还 PLC 连接失败", e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 读取s7配置
|
|
|
|
|
*/
|
|
|
|
|
void readDbConf() {
|
|
|
|
|
try (BufferedReader reader = new BufferedReader(new FileReader("./s7DB.txt"))) {
|
|
|
|
|
String line;
|
|
|
|
|
while ((line = reader.readLine()) != null) {
|
|
|
|
|
if (line.startsWith("#")) continue;
|
|
|
|
|
String[] parts = line.split(":", 2);
|
|
|
|
|
if (parts.length == 2) {
|
|
|
|
|
addressMap.put(parts[0].trim(), Integer.valueOf(parts[1].trim()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
throw new RuntimeException(e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 读取指定偏移量的任务号
|
|
|
|
|
*/
|
|
|
|
|
public int readPlcDataTaskId(int startOffset) {
|
|
|
|
|
S7Client client = getConnection();
|
|
|
|
|
try {
|
|
|
|
|
byte[] buffer = new byte[sizeToRead];
|
|
|
|
|
int result = client.ReadArea(S7.S7AreaDB, dbNumber, startOffset, sizeToRead, buffer);
|
|
|
|
|
int i = 0;
|
|
|
|
|
if (result == 0) {
|
|
|
|
|
i = ((buffer[0] & 0xFF) << 24) |
|
|
|
|
|
((buffer[1] & 0xFF) << 16) |
|
|
|
|
|
((buffer[2] & 0xFF) << 8) |
|
|
|
|
|
(buffer[3] & 0xFF);
|
|
|
|
|
} else {
|
|
|
|
|
updateConnection(client);
|
|
|
|
|
log.info("读取失败,错误码: " + result + " 位置: " + startOffset);
|
|
|
|
|
}
|
|
|
|
|
return i;
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
log.error("连接池报错", e);
|
|
|
|
|
} finally {
|
|
|
|
|
returnConnection(client);
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 写入任务号
|
|
|
|
|
*/
|
|
|
|
|
public synchronized boolean writePlcDataTaskId(String key) {
|
|
|
|
|
log.info("写入任务号:" + key);
|
|
|
|
|
int value = taskMap.get(key);
|
|
|
|
|
if (value == 0) {
|
|
|
|
|
return false;
|
|
|
|
|
} else {
|
|
|
|
|
writePlcDataTaskId(key, value);
|
|
|
|
|
taskMap.put(key, 0);
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 写入任务号到指定位置
|
|
|
|
|
*/
|
|
|
|
|
public boolean writePlcDataTaskId(String key, int value) {
|
|
|
|
|
S7Client client = getConnection();
|
|
|
|
|
try {
|
|
|
|
|
byte[] buffer = new byte[sizeToRead];
|
|
|
|
|
S7.SetDIntAt(buffer, 0, value);
|
|
|
|
|
int result = client.WriteArea(S7.S7AreaDB, dbNumber, addressMap.get(key), sizeToRead, buffer);
|
|
|
|
|
if (result == 0) {
|
|
|
|
|
log.info("写入成功" + key + ":" + value);
|
|
|
|
|
return true;
|
|
|
|
|
} else {
|
|
|
|
|
updateConnection(client);
|
|
|
|
|
log.info("写入失败,错误码: " + result);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
log.error("连接池报错", e);
|
|
|
|
|
} finally {
|
|
|
|
|
returnConnection(client);
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 批量读取所有C1/C2/C3地址的任务号
|
|
|
|
|
*/
|
|
|
|
|
public Map<String, Integer> readPlcDataTaskIds() {
|
|
|
|
|
S7Client client = getConnection();
|
|
|
|
|
try {
|
|
|
|
|
byte[] buffer = new byte[48];
|
|
|
|
|
int result = client.ReadArea(S7.S7AreaDB, dbNumber, 0, 48, buffer);
|
|
|
|
|
|
|
|
|
|
if (result != 0) {
|
|
|
|
|
updateConnection(client);
|
|
|
|
|
log.info("批量读取失败,错误码: " + result);
|
|
|
|
|
return new HashMap<>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Map<String, Integer> resultMap = new HashMap<>();
|
|
|
|
|
for (String key : addressMap.keySet()) {
|
|
|
|
|
if (key.contains("out") || !key.contains("C")) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
int offset = addressMap.get(key);
|
|
|
|
|
int value = S7.GetDIntAt(buffer, offset);
|
|
|
|
|
resultMap.put(key, value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return resultMap;
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
log.error("批量读取异常", e);
|
|
|
|
|
return new HashMap<>();
|
|
|
|
|
} finally {
|
|
|
|
|
returnConnection(client);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 写入PLC状态错误
|
|
|
|
|
*/
|
|
|
|
|
public boolean writePlcDataStatusErr(String plcId, int digit) {
|
|
|
|
|
if (digit == 1) {
|
|
|
|
|
return writePlcDataStatus((plcId + "-ET-out"), digit, true);
|
|
|
|
|
} else {
|
|
|
|
|
return writePlcDataStatus((plcId + "-ED-out"), digit, true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 写入PLC状态
|
|
|
|
|
*/
|
|
|
|
|
public boolean writePlcDataStatus(String startOffset, int digit, boolean value) {
|
|
|
|
|
S7Client client = getConnection();
|
|
|
|
|
try {
|
|
|
|
|
int sizeToRead = 1;
|
|
|
|
|
byte[] buffer = new byte[sizeToRead];
|
|
|
|
|
S7.SetBitAt(buffer, 0, digit, value);
|
|
|
|
|
int result = client.WriteArea(S7.S7AreaDB, dbNumber, addressMap.get(startOffset), sizeToRead, buffer);
|
|
|
|
|
if (result == 0) {
|
|
|
|
|
log.info("写入成功位置:" + startOffset + ":" + digit);
|
|
|
|
|
return true;
|
|
|
|
|
} else {
|
|
|
|
|
updateConnection(client);
|
|
|
|
|
log.info("写入失败,错误码: " + result);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
log.error("连接池报错", e);
|
|
|
|
|
} finally {
|
|
|
|
|
returnConnection(client);
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|