25 changed files with 999 additions and 30 deletions
@ -0,0 +1,63 @@
|
||||
package com.mh.common.core.redis; |
||||
|
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.data.redis.core.StringRedisTemplate; |
||||
import org.springframework.data.redis.core.script.RedisScript; |
||||
import org.springframework.stereotype.Component; |
||||
|
||||
import java.util.Collections; |
||||
import java.util.concurrent.TimeUnit; |
||||
|
||||
/** |
||||
* @author LJF |
||||
* @version 1.0 |
||||
* @project EEMCS |
||||
* @description 锁 |
||||
* @date 2025-06-06 16:08:13 |
||||
*/ |
||||
@Slf4j |
||||
@Component |
||||
public class RedisLock { |
||||
|
||||
private final StringRedisTemplate redisTemplate; |
||||
|
||||
public RedisLock(StringRedisTemplate redisTemplate) { |
||||
this.redisTemplate = redisTemplate; |
||||
} |
||||
|
||||
/** |
||||
* 获取锁 |
||||
*/ |
||||
public boolean lock(String key, String requestId, long expireTimeInSeconds) { |
||||
Boolean success = redisTemplate.opsForValue().setIfAbsent(key, requestId, expireTimeInSeconds, TimeUnit.SECONDS); |
||||
return Boolean.TRUE.equals(success); |
||||
} |
||||
|
||||
/** |
||||
* 尝试获取锁(带超时) |
||||
*/ |
||||
public boolean tryLock(String key, String requestId, long expireTime, long timeoutMs) throws InterruptedException { |
||||
long startTime = System.currentTimeMillis(); |
||||
while (System.currentTimeMillis() - startTime < timeoutMs) { |
||||
if (lock(key, requestId, expireTime)) { |
||||
return true; |
||||
} |
||||
Thread.sleep(50); |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* 释放锁(使用 Lua 脚本保证原子性) |
||||
*/ |
||||
public void unlock(String key, String requestId) { |
||||
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; |
||||
RedisScript<Long> redisScript = RedisScript.of(script, Long.class); |
||||
|
||||
Long result = redisTemplate.execute(redisScript, Collections.singletonList(key), requestId); |
||||
|
||||
if (result == null || result == 0) { |
||||
log.warn("释放锁失败,可能已被其他线程释放 key={}", key); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,47 @@
|
||||
package com.mh.common.utils; |
||||
|
||||
import io.netty.buffer.ByteBuf; |
||||
import io.netty.buffer.Unpooled; |
||||
import io.netty.channel.ChannelHandlerContext; |
||||
|
||||
/** |
||||
* @author LJF |
||||
* @version 1.0 |
||||
* @project EEMCS |
||||
* @description Modbus协议工具类 |
||||
* @date 2025-06-06 14:40:24 |
||||
*/ |
||||
public class ModbusUtils { |
||||
public static String createControlCode(String mtCode, Integer type, String registerAddr, String param) { |
||||
String orderStr; |
||||
mtCode = ExchangeStringUtil.addZeroForNum(mtCode, 2); |
||||
registerAddr = ExchangeStringUtil.addZeroForNum(registerAddr, 4); |
||||
param = ExchangeStringUtil.addZeroForNum(ExchangeStringUtil.decToHex(param), 4); |
||||
orderStr = mtCode + "06" + registerAddr + param; |
||||
byte[] strOrder = ExchangeStringUtil.hexStrToBinaryStr(orderStr); |
||||
int checkNum = CRC16.CRC16_MODBUS(strOrder); |
||||
String checkWord = ExchangeStringUtil.decToHex(String.valueOf(checkNum)); |
||||
checkWord = checkWord.substring(2, 4) + checkWord.substring(0, 2); |
||||
return orderStr + checkWord; |
||||
} |
||||
|
||||
public static ByteBuf getByteBuf(ChannelHandlerContext ctx, String sendStr) { |
||||
// byte类型的数据
|
||||
// String sendStr = "5803004900021914"; // 冷量计
|
||||
// 申请一个数据结构存储信息
|
||||
ByteBuf buffer = ctx.alloc().buffer(); |
||||
// 将信息放入数据结构中
|
||||
buffer.writeBytes(ExchangeStringUtil.hexStrToBinaryStr(sendStr));//对接需要16进制
|
||||
return buffer; |
||||
} |
||||
|
||||
public static ByteBuf createByteBuf(String sendStr) { |
||||
// byte类型的数据
|
||||
// String sendStr = "5803004900021914"; // 冷量计
|
||||
// 申请一个数据结构存储信息
|
||||
ByteBuf buffer = Unpooled.buffer(); |
||||
// 将信息放入数据结构中
|
||||
buffer.writeBytes(ExchangeStringUtil.hexStrToBinaryStr(sendStr));//对接需要16进制
|
||||
return buffer; |
||||
} |
||||
} |
@ -0,0 +1,77 @@
|
||||
package com.mh.common.utils; |
||||
|
||||
|
||||
import com.google.common.cache.Cache; |
||||
import com.google.common.cache.CacheBuilder; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.apache.commons.lang3.StringUtils; |
||||
|
||||
import java.util.Objects; |
||||
import java.util.concurrent.BlockingQueue; |
||||
import java.util.concurrent.LinkedBlockingQueue; |
||||
import java.util.concurrent.TimeUnit; |
||||
|
||||
/** |
||||
* @author LJF |
||||
* @version 1.0 |
||||
* @project TAD_Server |
||||
* @description 缓存等待数据 |
||||
* @date 2023/7/4 08:45:16 |
||||
*/ |
||||
@Slf4j |
||||
public class NettyTools { |
||||
|
||||
/** |
||||
* 响应消息缓存 |
||||
*/ |
||||
private static final Cache<String, BlockingQueue<String>> responseMsgCache = CacheBuilder.newBuilder() |
||||
.maximumSize(500) |
||||
.expireAfterWrite(1000, TimeUnit.SECONDS) |
||||
.build(); |
||||
|
||||
|
||||
/** |
||||
* 等待响应消息 |
||||
* @param key 消息唯一标识 |
||||
* @return ReceiveDdcMsgVo |
||||
*/ |
||||
public static boolean waitReceiveMsg(String key) { |
||||
|
||||
try { |
||||
//设置超时时间
|
||||
String vo = Objects.requireNonNull(responseMsgCache.getIfPresent(key)) |
||||
.poll(1000 * 10, TimeUnit.MILLISECONDS); |
||||
|
||||
//删除key
|
||||
responseMsgCache.invalidate(key); |
||||
return StringUtils.isNotBlank(vo); |
||||
} catch (Exception e) { |
||||
log.error("获取数据异常,sn={},msg=null",key); |
||||
return false; |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* 初始化响应消息的队列 |
||||
* @param key 消息唯一标识 |
||||
*/ |
||||
public static void initReceiveMsg(String key) { |
||||
responseMsgCache.put(key,new LinkedBlockingQueue<String>(1)); |
||||
} |
||||
|
||||
/** |
||||
* 设置响应消息 |
||||
* @param key 消息唯一标识 |
||||
*/ |
||||
public static void setReceiveMsg(String key, String msg) { |
||||
|
||||
if(responseMsgCache.getIfPresent(key) != null){ |
||||
responseMsgCache.getIfPresent(key).add(msg); |
||||
return; |
||||
} |
||||
|
||||
log.warn("sn {}不存在",key); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,18 @@
|
||||
package com.mh.framework.netty; |
||||
|
||||
import com.mh.common.core.domain.entity.OrderEntity; |
||||
|
||||
import java.util.List; |
||||
|
||||
/** |
||||
* @author LJF |
||||
* @version 1.0 |
||||
* @project EEMCS |
||||
* @description netty |
||||
* @date 2025-06-06 15:13:06 |
||||
*/ |
||||
public interface INettyService { |
||||
|
||||
boolean sendOrder(List<OrderEntity> changeValues); |
||||
|
||||
} |
@ -0,0 +1,119 @@
|
||||
package com.mh.framework.netty; |
||||
|
||||
import com.mh.common.core.domain.AjaxResult; |
||||
import com.mh.common.core.domain.entity.CollectionParamsManage; |
||||
import com.mh.common.core.domain.entity.GatewayManage; |
||||
import com.mh.common.core.domain.entity.OrderEntity; |
||||
import com.mh.common.core.redis.RedisCache; |
||||
import com.mh.common.core.redis.RedisLock; |
||||
import com.mh.common.utils.ModbusUtils; |
||||
import com.mh.common.utils.NettyTools; |
||||
import com.mh.common.utils.StringUtils; |
||||
import com.mh.framework.netty.session.ServerSession; |
||||
import com.mh.framework.netty.session.SessionMap; |
||||
import com.mh.system.mapper.device.CollectionParamsManageMapper; |
||||
import com.mh.system.mapper.device.GatewayManageMapper; |
||||
import jakarta.annotation.Resource; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.stereotype.Service; |
||||
|
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
import java.util.UUID; |
||||
import java.util.concurrent.ConcurrentHashMap; |
||||
import java.util.concurrent.TimeUnit; |
||||
|
||||
/** |
||||
* @author LJF |
||||
* @version 1.0 |
||||
* @project EEMCS |
||||
* @description netty实现类 |
||||
* @date 2025-06-06 15:13:23 |
||||
*/ |
||||
@Slf4j |
||||
@Service |
||||
public class NettyServiceImpl implements INettyService { |
||||
|
||||
@Resource |
||||
private CollectionParamsManageMapper collectionParamsManageMapper; |
||||
|
||||
@Resource |
||||
private GatewayManageMapper gatewayManageMapper; |
||||
|
||||
@Resource |
||||
private RedisCache redisCache; |
||||
|
||||
@Resource |
||||
private RedisLock redisLock; |
||||
|
||||
@Override |
||||
public boolean sendOrder(List<OrderEntity> changeValues) { |
||||
for (OrderEntity changeValue : changeValues) { |
||||
String cpmId = changeValue.getId(); |
||||
CollectionParamsManage collectionParamsManage = collectionParamsManageMapper.selectById(cpmId); |
||||
if (null == collectionParamsManage) { |
||||
return false; |
||||
} |
||||
GatewayManage gatewayManage = gatewayManageMapper.selectById(collectionParamsManage.getGatewayId()); |
||||
if (null == gatewayManage || StringUtils.isEmpty(gatewayManage.getHeartBeat())) { |
||||
return false; |
||||
} |
||||
ConcurrentHashMap<String, ServerSession> map = SessionMap.inst().getMap(); |
||||
Set<Map.Entry<String, ServerSession>> entries = map.entrySet(); |
||||
boolean flag = false; |
||||
String keyVal = null; |
||||
for (Map.Entry<String, ServerSession> entry : entries) { |
||||
String key = entry.getKey(); |
||||
if (key.contains(gatewayManage.getHeartBeat())){ |
||||
flag = true; |
||||
keyVal = key; |
||||
break; |
||||
} |
||||
} |
||||
if (flag) { |
||||
ServerSession serverSession = map.get(keyVal); |
||||
// 目前只有DTU,modbus方式,只创建modbus先
|
||||
String controlCode = ModbusUtils.createControlCode(collectionParamsManage.getMtCode(), |
||||
changeValue.getType(), |
||||
collectionParamsManage.getRegisterAddr(), |
||||
changeValue.getParam()); |
||||
if (StringUtils.isEmpty(controlCode)) { |
||||
log.error("创建控制码失败"); |
||||
return false; |
||||
} |
||||
|
||||
String requestId = UUID.randomUUID().toString(); // 唯一标识当前请求
|
||||
String lockKey = "lock:order_send:" + gatewayManage.getHeartBeat(); // 按网关分锁
|
||||
|
||||
try { |
||||
if (!redisLock.tryLock(lockKey, requestId, 10, 10)) { |
||||
log.warn("获取锁失败,当前操作繁忙"); |
||||
return false; |
||||
} |
||||
// 初始化发送指令
|
||||
NettyTools.initReceiveMsg("order_wait"); |
||||
// 设置缓存,方便在netty中判断发送的指令
|
||||
redisCache.setCacheObject("order_send", controlCode, 10, TimeUnit.SECONDS); |
||||
// 发送指令
|
||||
serverSession.getChannel().writeAndFlush(ModbusUtils.createByteBuf(controlCode)); |
||||
// 等待指令
|
||||
if (NettyTools.waitReceiveMsg("order_wait")) { |
||||
log.error("发送指令成功,心跳包:{}", gatewayManage.getHeartBeat()); |
||||
return true; |
||||
} else { |
||||
log.error("发送指令异常,心跳包:{}", gatewayManage.getHeartBeat()); |
||||
return false; |
||||
} |
||||
} catch (InterruptedException e) { |
||||
log.error("发送指令异常", e); |
||||
} finally { |
||||
redisLock.unlock(lockKey, requestId); |
||||
} |
||||
} |
||||
log.error("当前设备不在线,心跳包:{}",gatewayManage.getHeartBeat()); |
||||
return false; |
||||
} |
||||
return false; |
||||
} |
||||
} |
@ -0,0 +1,66 @@
|
||||
package com.mh.framework.netty.session; |
||||
|
||||
import io.netty.channel.Channel; |
||||
import io.netty.channel.ChannelFuture; |
||||
import io.netty.channel.ChannelFutureListener; |
||||
import io.netty.channel.ChannelHandlerContext; |
||||
import io.netty.util.AttributeKey; |
||||
import lombok.Data; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
|
||||
@Data |
||||
@Slf4j |
||||
public class ServerSession { |
||||
public static final AttributeKey<ServerSession> SESSION_KEY = |
||||
AttributeKey.valueOf("SESSION_KEY"); |
||||
//通道
|
||||
private Channel channel; |
||||
private final String sessionId; |
||||
private boolean isLogin = false; |
||||
|
||||
public ServerSession(Channel channel, String deviceCode){ |
||||
this.channel = channel; |
||||
this.sessionId = deviceCode; |
||||
} |
||||
|
||||
//session需要和通道进行一定的关联,他是在构造函数中关联上的;
|
||||
//session还需要通过sessionkey和channel进行再次的关联;channel.attr方法.set当前的
|
||||
// serverSession
|
||||
//session需要被添加到我们的SessionMap中
|
||||
public void bind(){ |
||||
log.info("server Session 会话进行绑定 :" + channel.remoteAddress()); |
||||
channel.attr(SESSION_KEY).set(this); |
||||
SessionMap.inst().addSession(sessionId, this); |
||||
this.isLogin = true; |
||||
} |
||||
|
||||
//通过channel获取session
|
||||
public static ServerSession getSession(ChannelHandlerContext ctx){ |
||||
Channel channel = ctx.channel(); |
||||
return channel.attr(SESSION_KEY).get(); |
||||
} |
||||
|
||||
//关闭session,新增返回一个meterNum用于纪录设备下线时间2024-05-08
|
||||
public static String closeSession(ChannelHandlerContext ctx){ |
||||
String meterNum = null; |
||||
ServerSession serverSession = ctx.channel().attr(SESSION_KEY).get(); |
||||
if(serverSession != null && serverSession.getSessionId() != null) { |
||||
ChannelFuture future = serverSession.channel.close(); |
||||
future.addListener((ChannelFutureListener) future1 -> { |
||||
if(!future1.isSuccess()) { |
||||
log.info("Channel close error!"); |
||||
} |
||||
}); |
||||
ctx.close(); |
||||
meterNum = serverSession.sessionId; |
||||
SessionMap.inst().removeSession(serverSession.sessionId); |
||||
log.info(ctx.channel().remoteAddress()+" "+serverSession.sessionId + "==>移除会话"); |
||||
} |
||||
return meterNum; |
||||
} |
||||
|
||||
//写消息
|
||||
public void writeAndFlush(Object msg) { |
||||
channel.writeAndFlush(msg); |
||||
} |
||||
} |
@ -0,0 +1,96 @@
|
||||
package com.mh.framework.netty.session; |
||||
|
||||
import lombok.Data; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
|
||||
import java.util.Iterator; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.concurrent.ConcurrentHashMap; |
||||
import java.util.stream.Collectors; |
||||
|
||||
@Data |
||||
@Slf4j |
||||
public class SessionMap { |
||||
|
||||
private ThreadLocal<Boolean> sceneThreadLocal = new ThreadLocal<>(); |
||||
|
||||
//用单例模式进行sessionMap的创建
|
||||
private SessionMap(){} |
||||
|
||||
private static SessionMap singleInstance = new SessionMap(); |
||||
|
||||
public static SessionMap inst() { |
||||
return singleInstance; |
||||
} |
||||
|
||||
//进行会话的保存
|
||||
//key 我们使用 sessionId;value 需要是 serverSession
|
||||
private ConcurrentHashMap<String, ServerSession> map = new ConcurrentHashMap<>(256); |
||||
//添加session
|
||||
public void addSession(String sessionId, ServerSession s) { |
||||
map.put(sessionId, s); |
||||
log.info("IP地址:"+s.getChannel().remoteAddress()+" "+ sessionId + " 表具上线,总共表具:" + map.size()); |
||||
} |
||||
|
||||
//删除session
|
||||
public void removeSession(String sessionId) { |
||||
if(map.containsKey(sessionId)) { |
||||
ServerSession s = map.get(sessionId); |
||||
map.remove(sessionId); |
||||
log.info("设备id下线:{},在线设备:{}", s.getSessionId(), map.size() ); |
||||
} |
||||
return; |
||||
} |
||||
|
||||
public boolean hasLogin(String sessionId) { |
||||
Iterator<Map.Entry<String, ServerSession>> iterator = map.entrySet().iterator(); |
||||
while(iterator.hasNext()) { |
||||
Map.Entry<String, ServerSession> next = iterator.next(); |
||||
if(sessionId != null && sessionId.equalsIgnoreCase(next.getValue().getSessionId())) { |
||||
return true ; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
//如果在线,肯定有sessionMap里保存的 serverSession
|
||||
//如果不在线,serverSession也没有。用这个来判断是否在线
|
||||
public List<ServerSession> getSessionBy(String sessionId) { |
||||
return map.values().stream(). |
||||
filter(s -> s.getSessionId().equals(sessionId)). |
||||
collect(Collectors.toList()); |
||||
} |
||||
|
||||
public boolean getScene() { |
||||
return sceneThreadLocal.get(); |
||||
} |
||||
|
||||
public void initScene(Boolean status) { |
||||
if (sceneThreadLocal == null) { |
||||
log.info("======创建ThreadLocal======"); |
||||
sceneThreadLocal = new ThreadLocal<>(); |
||||
} |
||||
log.info("设置状态==>" + status); |
||||
sceneThreadLocal.set(status); |
||||
} |
||||
|
||||
public void clearScene() { |
||||
initScene(null); |
||||
sceneThreadLocal.remove(); |
||||
} |
||||
|
||||
public void updateSession(String sessionId, ServerSession session, String meterNum) { |
||||
Iterator<Map.Entry<String, ServerSession>> iterator = map.entrySet().iterator(); |
||||
while(iterator.hasNext()) { |
||||
Map.Entry<String, ServerSession> next = iterator.next(); |
||||
if (next.getKey().contains(meterNum)){ |
||||
iterator.remove(); |
||||
} |
||||
if(sessionId != null && sessionId.equalsIgnoreCase(next.getValue().getSessionId())) { |
||||
next.setValue(session); |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,20 @@
|
||||
package com.mh.framework.netty.task; |
||||
|
||||
/** |
||||
* @author LJF |
||||
* @version 1.0 |
||||
* @project TAD_Server |
||||
* @description 回调任务 |
||||
* @date 2023/7/3 15:34:11 |
||||
*/ |
||||
public interface CallbackTask<T> { |
||||
T execute() throws Exception; |
||||
|
||||
/** |
||||
* // 执行没有 异常的情况下的 返回值
|
||||
* @param t |
||||
*/ |
||||
void onBack(T t); |
||||
|
||||
void onException(Throwable t); |
||||
} |
@ -0,0 +1,78 @@
|
||||
package com.mh.framework.netty.task; |
||||
|
||||
import com.google.common.util.concurrent.*; |
||||
|
||||
import java.util.concurrent.Callable; |
||||
import java.util.concurrent.ConcurrentLinkedQueue; |
||||
import java.util.concurrent.ExecutorService; |
||||
import java.util.concurrent.Executors; |
||||
|
||||
/** |
||||
* @author LJF |
||||
* @version 1.0 |
||||
* @project TAD_Server |
||||
* @description 回调任务 |
||||
* @date 2023/7/3 15:34:11 |
||||
*/ |
||||
public class CallbackTaskScheduler extends Thread { |
||||
private ConcurrentLinkedQueue<CallbackTask> executeTaskQueue = |
||||
new ConcurrentLinkedQueue<>(); |
||||
private long sleepTime = 1000 * 10; |
||||
private final ExecutorService pool = Executors.newCachedThreadPool(); |
||||
ListeningExecutorService lpool = MoreExecutors.listeningDecorator(pool); |
||||
private static CallbackTaskScheduler inst = new CallbackTaskScheduler(); |
||||
private CallbackTaskScheduler() { |
||||
this.start(); |
||||
} |
||||
//add task
|
||||
public static <T> void add(CallbackTask<T> executeTask) { |
||||
inst.executeTaskQueue.add(executeTask); |
||||
} |
||||
|
||||
@Override |
||||
public void run() { |
||||
while (true) { |
||||
handleTask(); |
||||
//为了避免频繁连接服务器,但是当前连接服务器过长导致失败
|
||||
//threadSleep(sleepTime);
|
||||
} |
||||
} |
||||
|
||||
private void threadSleep(long sleepTime) { |
||||
try { |
||||
Thread.sleep(sleepTime); |
||||
}catch (Exception e) { |
||||
e.printStackTrace(); |
||||
} |
||||
} |
||||
|
||||
//任务执行
|
||||
private void handleTask() { |
||||
CallbackTask executeTask = null; |
||||
while (executeTaskQueue.peek() != null) { |
||||
executeTask = executeTaskQueue.poll(); |
||||
handleTask(executeTask); |
||||
} |
||||
} |
||||
private <T> void handleTask(CallbackTask<T> executeTask) { |
||||
ListenableFuture<T> future = lpool.submit(new Callable<T>() { |
||||
public T call() throws Exception { |
||||
return executeTask.execute(); |
||||
} |
||||
}); |
||||
Futures.addCallback(future, new FutureCallback<T>() { |
||||
@Override |
||||
public void onSuccess(T t) { |
||||
executeTask.onBack(t); |
||||
} |
||||
|
||||
@Override |
||||
public void onFailure(Throwable throwable) { |
||||
executeTask.onException(throwable); |
||||
} |
||||
|
||||
|
||||
}, pool); |
||||
} |
||||
} |
||||
|
@ -0,0 +1,6 @@
|
||||
package com.mh.framework.netty.task; |
||||
|
||||
//不需要知道异步线程的 返回值
|
||||
public interface ExecuteTask { |
||||
void execute(); |
||||
} |
@ -0,0 +1,67 @@
|
||||
package com.mh.framework.netty.task; |
||||
|
||||
import java.util.concurrent.ConcurrentLinkedQueue; |
||||
import java.util.concurrent.ExecutorService; |
||||
import java.util.concurrent.Executors; |
||||
|
||||
/** |
||||
* @author LJF |
||||
* @version 1.0 |
||||
* @project TAD_Server |
||||
* @description 任务定时 |
||||
* @date 2023/7/3 15:34:11 |
||||
*/ |
||||
public class FutureTaskScheduler extends Thread{ |
||||
private ConcurrentLinkedQueue<ExecuteTask> executeTaskQueue = |
||||
new ConcurrentLinkedQueue<>(); |
||||
private long sleepTime = 200; |
||||
private ExecutorService pool = Executors.newFixedThreadPool(10); |
||||
private static FutureTaskScheduler inst = new FutureTaskScheduler(); |
||||
public FutureTaskScheduler() { |
||||
this.start(); |
||||
} |
||||
//任务添加
|
||||
public static void add(ExecuteTask executeTask) { |
||||
inst.executeTaskQueue.add(executeTask); |
||||
} |
||||
|
||||
@Override |
||||
public void run() { |
||||
while (true) { |
||||
handleTask(); |
||||
//threadSleep(sleepTime);
|
||||
} |
||||
} |
||||
|
||||
private void threadSleep(long sleepTime) { |
||||
try { |
||||
Thread.sleep(sleepTime); |
||||
} catch (InterruptedException e) { |
||||
e.printStackTrace(); |
||||
} |
||||
} |
||||
|
||||
//执行任务
|
||||
private void handleTask() { |
||||
ExecuteTask executeTask; |
||||
while (executeTaskQueue.peek() != null) { |
||||
executeTask = executeTaskQueue.poll(); |
||||
handleTask(executeTask); |
||||
} |
||||
//刷新心跳时间
|
||||
} |
||||
private void handleTask(ExecuteTask executeTask) { |
||||
pool.execute(new ExecuteRunnable(executeTask)); |
||||
} |
||||
|
||||
class ExecuteRunnable implements Runnable { |
||||
ExecuteTask executeTask; |
||||
public ExecuteRunnable(ExecuteTask executeTask) { |
||||
this.executeTask = executeTask; |
||||
} |
||||
@Override |
||||
public void run() { |
||||
executeTask.execute(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,152 @@
|
||||
package com.mh.quartz.util; |
||||
|
||||
/** |
||||
* @author LJF |
||||
* @version 1.0 |
||||
* @project EEMCS |
||||
* @description 模糊PID控制算法 |
||||
* @date 2025-06-04 09:47:16 |
||||
*/ |
||||
public class FuzzyPIDControlUtil { |
||||
// PID参数
|
||||
private final double kp; // 比例增益
|
||||
private final double ki; // 积分增益
|
||||
private final double kd; // 微分增益
|
||||
|
||||
// 控制器状态
|
||||
private double prevError; |
||||
private double integral; |
||||
private double derivative; |
||||
|
||||
// 模糊规则参数
|
||||
private final double[] errorLevels = {-6, -3, -1, 0, 1, 3, 6}; // 温度误差级别(℃)
|
||||
private final double[] dErrorLevels = {-3, -1, 0, 1, 3}; // 误差变化率级别(℃/min)
|
||||
private final double[] kpAdjust = {1.5, 2.0, 2.5, 3.0, 4.0}; // Kp调整因子 (增强)
|
||||
private final double[] kiAdjust = {0.3, 0.7, 1.0, 1.3, 1.7}; // Ki调整因子
|
||||
|
||||
// 阀门限制
|
||||
private static final double MIN_VALVE = 0.0; // 最小开度(0%)
|
||||
private static final double MAX_VALVE = 100.0; // 最大开度(100%)
|
||||
|
||||
public FuzzyPIDControlUtil(String kp, String ki, String kd) { |
||||
this.kp = Double.parseDouble(kp); |
||||
this.ki = Double.parseDouble(ki); |
||||
this.kd = Double.parseDouble(kd); |
||||
this.prevError = 0; |
||||
this.integral = 0; |
||||
this.derivative = 0; |
||||
} |
||||
|
||||
// 模糊推理计算PID参数调整因子
|
||||
private double[] fuzzyInference(double error, double dError) { |
||||
// 模糊化:计算误差和误差变化率的隶属度
|
||||
double[] errorMembership = calculateMembership(error, errorLevels); |
||||
double[] dErrorMembership = calculateMembership(dError, dErrorLevels); |
||||
|
||||
// 模糊规则库
|
||||
double kpAdjustSum = 0.0; |
||||
double kiAdjustSum = 0.0; |
||||
double weightSum = 0.0; |
||||
|
||||
// 应用模糊规则 (增强大误差时的响应)
|
||||
for (int i = 0; i < errorMembership.length; i++) { |
||||
for (int j = 0; j < dErrorMembership.length; j++) { |
||||
double weight = errorMembership[i] * dErrorMembership[j]; |
||||
if (weight > 0) { |
||||
// 增强大误差时的响应
|
||||
int kpIndex; |
||||
if (Math.abs(error) > 3) { // 大误差
|
||||
kpIndex = Math.min(Math.max(i + j, 0), kpAdjust.length - 1); |
||||
} else { |
||||
kpIndex = Math.min(Math.max(i + j - 1, 0), kpAdjust.length - 1); |
||||
} |
||||
|
||||
// Ki调整:小误差时增强积分作用
|
||||
int kiIndex; |
||||
if (Math.abs(error) < 1) { // 小误差
|
||||
kiIndex = Math.min(Math.max(3 + j, 0), kiAdjust.length - 1); |
||||
} else { |
||||
kiIndex = Math.min(Math.max(2 + j, 0), kiAdjust.length - 1); |
||||
} |
||||
|
||||
kpAdjustSum += weight * kpAdjust[kpIndex]; |
||||
kiAdjustSum += weight * kiAdjust[kiIndex]; |
||||
weightSum += weight; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 反模糊化 (加权平均)
|
||||
double kpFactor = weightSum > 0 ? kpAdjustSum / weightSum : 1.0; |
||||
double kiFactor = weightSum > 0 ? kiAdjustSum / weightSum : 1.0; |
||||
|
||||
return new double[]{kpFactor, kiFactor, 1.0}; // Kd不调整
|
||||
} |
||||
|
||||
// 计算隶属度 (三角隶属函数)
|
||||
private double[] calculateMembership(double value, double[] levels) { |
||||
double[] membership = new double[levels.length]; |
||||
|
||||
for (int i = 0; i < levels.length; i++) { |
||||
if (i == 0) { |
||||
membership[i] = (value <= levels[i]) ? 1.0 : |
||||
(value < levels[i+1]) ? (levels[i+1] - value) / (levels[i+1] - levels[i]) : 0.0; |
||||
} else if (i == levels.length - 1) { |
||||
membership[i] = (value >= levels[i]) ? 1.0 : |
||||
(value > levels[i-1]) ? (value - levels[i-1]) / (levels[i] - levels[i-1]) : 0.0; |
||||
} else { |
||||
if (value >= levels[i-1] && value <= levels[i]) { |
||||
membership[i] = (value - levels[i-1]) / (levels[i] - levels[i-1]); |
||||
} else if (value >= levels[i] && value <= levels[i+1]) { |
||||
membership[i] = (levels[i+1] - value) / (levels[i+1] - levels[i]); |
||||
} else { |
||||
membership[i] = 0.0; |
||||
} |
||||
} |
||||
} |
||||
|
||||
return membership; |
||||
} |
||||
|
||||
// 计算控制输出 (阀门开度) - 修复了符号问题
|
||||
public double calculate(double setpoint, double currentValue, double dt) { |
||||
// 计算误差项 - 修复:当前值高于设定值需要冷却,误差应为正
|
||||
double error = currentValue - setpoint; |
||||
|
||||
// 计算微分项 (基于误差变化率)
|
||||
if (dt > 0) { |
||||
derivative = (error - prevError) / dt; |
||||
} |
||||
|
||||
// 模糊调整PID参数
|
||||
double[] adjustments = fuzzyInference(error, derivative); |
||||
double adjKp = kp * adjustments[0]; |
||||
double adjKi = ki * adjustments[1]; |
||||
double adjKd = kd * adjustments[2]; |
||||
|
||||
// 计算积分项 (带抗饱和)
|
||||
integral += error * dt; |
||||
|
||||
// 抗饱和限制
|
||||
double maxIntegral = MAX_VALVE / (adjKi + 1e-5); |
||||
if (Math.abs(integral) > maxIntegral) { |
||||
integral = Math.signum(integral) * maxIntegral; |
||||
} |
||||
|
||||
// PID计算 - 修复:误差为正时需要正输出打开阀门
|
||||
double output = adjKp * error + adjKi * integral + adjKd * derivative; |
||||
|
||||
// 保存误差用于下次计算
|
||||
prevError = error; |
||||
|
||||
// 阀门开度限制
|
||||
return Math.min(Math.max(output, MIN_VALVE), MAX_VALVE); |
||||
} |
||||
|
||||
// 重置控制器状态
|
||||
public void reset() { |
||||
integral = 0; |
||||
prevError = 0; |
||||
derivative = 0; |
||||
} |
||||
} |
Loading…
Reference in new issue