自动关逻辑初步验证完毕
parent
fd0fd6e61e
commit
12fe073189
|
|
@ -1,7 +1,9 @@
|
|||
package com.agri.common.constant;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Locale;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
||||
/**
|
||||
* 通用常量信息
|
||||
|
|
@ -173,4 +175,8 @@ public class Constants
|
|||
|
||||
public static final String JM1G = "jm1g";
|
||||
|
||||
public static final BigDecimal PER_LAP_LEN = BigDecimal.valueOf(2.13);
|
||||
|
||||
public static final BigDecimal PER_LAP_SEC = BigDecimal.valueOf(18);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ package com.agri.common.utils;
|
|||
* @Version 1.0
|
||||
*/
|
||||
|
||||
import com.agri.common.constant.Constants;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
|
@ -17,12 +18,6 @@ import java.math.RoundingMode;
|
|||
* 卷膜时间计算工具类(企业标准写法:单例+常量固化)
|
||||
*/
|
||||
public class RollerTimeCalculator {
|
||||
// 基础常量:固化到工具类,统一维护
|
||||
@Value("${agri.per-lap.len}")
|
||||
private static BigDecimal perLapLen;
|
||||
|
||||
@Value("${agri.per-lap.sec}")
|
||||
private static BigDecimal perLapSec;
|
||||
|
||||
// 私有化构造器,禁止实例化
|
||||
private RollerTimeCalculator() {}
|
||||
|
|
@ -38,8 +33,8 @@ public class RollerTimeCalculator {
|
|||
return 0;
|
||||
}
|
||||
// 核心公式:时间 = (长度 / 每圈长度) × 每圈时间
|
||||
BigDecimal cycleCount = targetLen.divide(perLapLen,2, RoundingMode.HALF_UP);
|
||||
return cycleCount.multiply(perLapSec).setScale(2, RoundingMode.HALF_UP).intValue(); // 四舍五入取整
|
||||
BigDecimal cycleCount = targetLen.divide(Constants.PER_LAP_LEN,2, RoundingMode.HALF_UP);
|
||||
return cycleCount.multiply(Constants.PER_LAP_SEC).setScale(2, RoundingMode.HALF_UP).intValue(); // 四舍五入取整
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ public class FrontendConfigHandler {
|
|||
}
|
||||
// 转发前端指令
|
||||
String deviceTopic = "dtu/" + deviceId + "/down";
|
||||
// mqttMessageSender.publish(deviceTopic, payload);
|
||||
mqttMessageSender.publish(deviceTopic, payload);
|
||||
LocalDateTime currentTime = LocalDateTime.now();
|
||||
// 3. 记录日志
|
||||
log.info("【指令处理】前端{}于{}控制设备{}的{}功能,指令:{}",
|
||||
|
|
|
|||
|
|
@ -128,13 +128,13 @@ public class FrontendControlHandler {
|
|||
});
|
||||
} catch (Exception e) {
|
||||
log.error("【指令处理】功能码解析失败,payload={}", payload, e);
|
||||
// String errorTopic = "frontend/" + clientId + "/dtu/" + deviceId + "/listener";
|
||||
// mqttMessageSender.publish(errorTopic, "{\"msg\":\"指令格式错误\"}");
|
||||
String errorTopic = "frontend/" + clientId + "/dtu/" + deviceId + "/listener";
|
||||
mqttMessageSender.publish(errorTopic, "{\"msg\":\"指令格式错误\"}");
|
||||
return;
|
||||
}
|
||||
if (funcCodeMap == null || funcCodeMap.isEmpty()) {
|
||||
// String errorTopic = "frontend/" + clientId + "/dtu/" + deviceId + "/listener";
|
||||
// mqttMessageSender.publish(errorTopic, "{\"msg\":\"功能码不能为空\"}");
|
||||
String errorTopic = "frontend/" + clientId + "/dtu/" + deviceId + "/listener";
|
||||
mqttMessageSender.publish(errorTopic, "{\"msg\":\"功能码不能为空\"}");
|
||||
log.warn("【指令处理】前端{}操作设备{}失败:功能码为空", clientId, deviceId);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -280,25 +280,26 @@ public class MqttAutoOffManager {
|
|||
JSONObject down = new JSONObject();
|
||||
down.put(funcType, 0);
|
||||
log.info("触发自动化条件");
|
||||
// mqttMessageSender.publish(deviceTopic, down.toJSONString());
|
||||
// SysAgriInfo agriInfo = sysAgriInfoService.lambdaQuery()
|
||||
// .eq(SysAgriInfo::getImei, deviceId)
|
||||
// .one();
|
||||
// String agriName = (agriInfo!=null && ObjectUtils.isNotEmpty(agriInfo.getAgriName()))?agriInfo.getAgriName():null;
|
||||
// SysDevOperLog logDto = new SysDevOperLog();
|
||||
// logDto.setAgriName(agriName);
|
||||
// logDto.setImei(deviceId);
|
||||
// logDto.setFuncCode(funcType);
|
||||
// logDto.setOpType(0);
|
||||
// logDto.setOpSource(2);
|
||||
// logDto.setPayload(down.toJSONString());
|
||||
// logDto.setLockAcquired(1);
|
||||
// logDto.setLockHolder("autoOff");
|
||||
// logDto.setExecResult(1);
|
||||
// logDto.setLatestState(latest);
|
||||
// logDto.setCreateBy("自动关");
|
||||
// logDto.setTaskStatus(getFutureStatus().toString());
|
||||
// sysDevOperLogService.save(logDto);
|
||||
//todo
|
||||
mqttMessageSender.publish(deviceTopic, down.toJSONString());
|
||||
SysAgriInfo agriInfo = sysAgriInfoService.lambdaQuery()
|
||||
.eq(SysAgriInfo::getImei, deviceId)
|
||||
.one();
|
||||
String agriName = (agriInfo!=null && ObjectUtils.isNotEmpty(agriInfo.getAgriName()))?agriInfo.getAgriName():null;
|
||||
SysDevOperLog logDto = new SysDevOperLog();
|
||||
logDto.setAgriName(agriName);
|
||||
logDto.setImei(deviceId);
|
||||
logDto.setFuncCode(funcType);
|
||||
logDto.setOpType(0);
|
||||
logDto.setOpSource(2);
|
||||
logDto.setPayload(down.toJSONString());
|
||||
logDto.setLockAcquired(1);
|
||||
logDto.setLockHolder("autoOff");
|
||||
logDto.setExecResult(1);
|
||||
logDto.setLatestState(latest);
|
||||
logDto.setCreateBy("自动关");
|
||||
logDto.setTaskStatus(getFutureStatus().toString());
|
||||
sysDevOperLogService.save(logDto);
|
||||
log.info("【自动关任务】检测仍在运行,已下发关闭:deviceId={}, funcType={}, payload={}", deviceId, funcType, down.toJSONString());
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ public class MqttMessageDispatcher {
|
|||
public void handleMessage(String topic, String payload) {
|
||||
try {
|
||||
// log.info("【MQTT接收】topic={}, payload={}", topic, payload);
|
||||
// if (env.acceptsProfiles("dev")) return;
|
||||
if (env.acceptsProfiles("dev")) return;
|
||||
// 设备状态主题:dtu/{deviceId}/up
|
||||
if (topic.matches("dtu/\\w+/\\w+")) {
|
||||
deviceStatusHandler.handle(topic, payload);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.agri.quartz.task;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import com.agri.common.enums.TempCommandStatus;
|
||||
import com.agri.common.utils.*;
|
||||
import com.agri.common.utils.wechat.WxUtil;
|
||||
import com.agri.framework.config.MqttConfig;
|
||||
import com.agri.framework.manager.MqttAutoOffManager;
|
||||
import com.agri.system.domain.SysAgriInfo;
|
||||
|
|
@ -23,6 +25,8 @@ import org.springframework.stereotype.Component;
|
|||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
|
@ -73,6 +77,9 @@ public class RollerAutoTask {
|
|||
@Value("${spring.mqtt.dtu-ctl-lock-ttl}")
|
||||
private int dtuCtlLockTTL;
|
||||
|
||||
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");
|
||||
|
||||
|
||||
// ========== 常量定义(新增) ==========
|
||||
private static final int WORK_MODE_AUTO = 1; // 自动模式
|
||||
private static final int NOT_DELETED = 0; // 未删除
|
||||
|
|
@ -83,125 +90,151 @@ public class RollerAutoTask {
|
|||
private static final int LOCK_ACQUIRED = 1; // 是否获取锁
|
||||
|
||||
public void checkAutoTerm() {
|
||||
try {
|
||||
log.info("=============【定时任务】大棚自动模式监测开始执行,时间:{}=============", LocalDateTime.now());
|
||||
// 查询自动模式的大棚
|
||||
List<SysAgriInfo> agriInfos = agriInfoService.lambdaQuery()
|
||||
.select(SysAgriInfo::getImei, SysAgriInfo::getAgriName)
|
||||
.eq(SysAgriInfo::getWorkMode, WORK_MODE_AUTO)
|
||||
.eq(SysAgriInfo::getIsDeleted, NOT_DELETED)
|
||||
.list();
|
||||
if (CollectionUtils.isEmpty(agriInfos)) return;
|
||||
// 取imei集合
|
||||
List<String> imeiList = agriInfos.stream().map(SysAgriInfo::getImei).collect(Collectors.toList());
|
||||
if (CollectionUtils.isEmpty(imeiList)) return;
|
||||
|
||||
// 查询自动模式的大棚
|
||||
List<SysAgriInfo> agriInfos = agriInfoService.lambdaQuery()
|
||||
.select(SysAgriInfo::getImei, SysAgriInfo::getAgriName)
|
||||
.eq(SysAgriInfo::getWorkMode, WORK_MODE_AUTO)
|
||||
.eq(SysAgriInfo::getIsDeleted, NOT_DELETED)
|
||||
.list();
|
||||
if (CollectionUtils.isEmpty(agriInfos)) return;
|
||||
// 取imei集合
|
||||
List<String> imeiList = agriInfos.stream().map(SysAgriInfo::getImei).collect(Collectors.toList());
|
||||
if (CollectionUtils.isEmpty(imeiList)) return;
|
||||
// 根据imei 查询dtu_data最后一条温度数据
|
||||
List<Map<String, Object>> dtuDataList = dtuDataService.getLastDtuDataByImeiList(imeiList);
|
||||
if (CollectionUtils.isEmpty(dtuDataList)) return;
|
||||
// 根据温湿度imei分组
|
||||
Map<String, List<Map<String, Object>>> dtuDataByImeiMap
|
||||
= dtuDataList.stream().collect(Collectors.groupingBy(map -> (String) map.get("imei")));
|
||||
|
||||
// 根据imei 查询dtu_data最后一条温度数据
|
||||
List<Map<String, Object>> dtuDataList = dtuDataService.getLastDtuDataByImeiList(imeiList);
|
||||
if (CollectionUtils.isEmpty(dtuDataList)) return;
|
||||
// 根据温湿度imei分组
|
||||
Map<String, List<Map<String, Object>>> dtuDataByImeiMap
|
||||
= dtuDataList.stream().collect(Collectors.groupingBy(map -> (String) map.get("imei")));
|
||||
|
||||
// 获取所有开启自动化模式的大棚列表的卷膜参数和条件设置
|
||||
List<RollerTermVO> rollerTermList = rollerParamService.getRollerTerms(imeiList);
|
||||
if (CollectionUtils.isEmpty(rollerTermList)) {
|
||||
// todo 无参数设置和条件列表直接返回
|
||||
log.error("【定时任务-卷膜自动化控制】无参数设置和条件列表直接返回!");
|
||||
return;
|
||||
}
|
||||
// 按imei分组 → 再按roller分组(一步到位)
|
||||
Map<String, Map<String, List<RollerTermVO>>> rollerTermMap = rollerTermList.stream()
|
||||
.collect(Collectors.groupingBy(
|
||||
RollerTermVO::getImei,
|
||||
Collectors.groupingBy(RollerTermVO::getRoller)
|
||||
));
|
||||
|
||||
// 查询每个IMEI今天的第一条日志
|
||||
Map<String, Map<String, Integer>> todayLogCountByImeiMap = devOperLogService.getTodayLogCountByImeiMap(imeiList);
|
||||
// 循环所有开启自动化的大棚
|
||||
for (SysAgriInfo agriInfo : agriInfos) {
|
||||
String imei = agriInfo.getImei();
|
||||
String agriName = agriInfo.getAgriName();
|
||||
// 获取温湿度指定imei的数据
|
||||
List<Map<String, Object>> dtuDataInfo = dtuDataByImeiMap.get(imei);
|
||||
// 该大棚温湿度不存在
|
||||
if (CollectionUtils.isEmpty(dtuDataInfo)) {
|
||||
// todo 该大棚下1分钟内无最新温湿度,怀疑离线
|
||||
log.error("【定时任务-卷膜自动化控制】大棚『{}』1分钟内无最新温湿度,怀疑离线",imei);
|
||||
continue;
|
||||
// 获取所有开启自动化模式的大棚列表的卷膜参数和条件设置
|
||||
List<RollerTermVO> rollerTermList = rollerParamService.getRollerTerms(imeiList);
|
||||
if (CollectionUtils.isEmpty(rollerTermList)) {
|
||||
// todo 无参数设置和条件列表直接返回
|
||||
log.error("【定时任务-卷膜自动化控制】无参数设置和条件列表直接返回!");
|
||||
return;
|
||||
}
|
||||
// 按imei分组 → 再按roller分组(一步到位)
|
||||
Map<String, Map<String, List<RollerTermVO>>> rollerTermMap = rollerTermList.stream()
|
||||
.collect(Collectors.groupingBy(
|
||||
RollerTermVO::getImei,
|
||||
Collectors.groupingBy(RollerTermVO::getRoller)
|
||||
));
|
||||
|
||||
// 最后一条对应imei对应温度 及时间
|
||||
Map<String, Object> dtuData = dtuDataInfo.get(0);
|
||||
// 求温度上报时间
|
||||
LocalDateTime dtuTime = TimeConvertUtil.strToLocalDateTimeSafe((String) dtuData.get("time"));
|
||||
if (dtuTime == null) {
|
||||
// todo 当前大棚温湿度时间为空 跳过
|
||||
log.error("【定时任务-卷膜自动化控制】大棚『{}』温湿度时间「{}」为空, 跳过",imei, LocalDateTime.now().minusMinutes(1));
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取当前imei下的所有参数设置以及卷膜自动化条件设置
|
||||
Map<String, List<RollerTermVO>> configTermByRollerMap = rollerTermMap.get(imei);
|
||||
if (configTermByRollerMap.isEmpty()) {
|
||||
// todo 当前大棚下没有设置条件或者参数
|
||||
log.error("【定时任务-卷膜自动化控制】大棚『{}』当前大棚下没有设置条件或者参数",imei);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取今天对应imei的日志
|
||||
Map<String, Integer> todayLogByRoller = todayLogCountByImeiMap.get(imei);
|
||||
|
||||
for (Map.Entry<String, List<RollerTermVO>> configEntry : configTermByRollerMap.entrySet()) {
|
||||
String roller = configEntry.getKey(); // 当前卷膜
|
||||
List<RollerTermVO> terms = configEntry.getValue(); // 当前卷膜条件
|
||||
// 每个卷膜分组只会有一个卷膜参数设置,所有取第一个即可
|
||||
if (terms == null || terms.isEmpty()) {
|
||||
// todo 当前卷膜 无参数设置,跳过当前roller
|
||||
log.error("【定时任务-卷膜自动化控制】大棚『{}』当前卷膜「{}」无参数设置,跳过当前roller",imei, roller);
|
||||
// 查询每个IMEI今天的第一条日志
|
||||
Map<String, Map<String, Integer>> todayLogCountByImeiMap = devOperLogService.getTodayLogCountByImeiMap(imeiList);
|
||||
// 循环所有开启自动化的大棚
|
||||
for (SysAgriInfo agriInfo : agriInfos) {
|
||||
String imei = agriInfo.getImei();
|
||||
String agriName = agriInfo.getAgriName();
|
||||
// 获取温湿度指定imei的数据
|
||||
List<Map<String, Object>> dtuDataInfo = dtuDataByImeiMap.get(imei);
|
||||
// 该大棚温湿度不存在
|
||||
if (CollectionUtils.isEmpty(dtuDataInfo)) {
|
||||
// todo 该大棚下1分钟内无最新温湿度,怀疑离线
|
||||
log.error("【定时任务-卷膜自动化控制】大棚『{}』1分钟内无最新温湿度,怀疑离线", imei);
|
||||
continue;
|
||||
}
|
||||
// 获取卷膜参数
|
||||
RollerTermVO rollerConfig = terms.get(0);
|
||||
String refTempCode = rollerConfig.getRefTempCode(); // 参考温度
|
||||
BigDecimal ventTotalLen = rollerConfig.getVentTotalLen(); // 风口总长
|
||||
BigDecimal reservedLen = rollerConfig.getReservedLen(); // 预留风口
|
||||
|
||||
// 防御性判断:避免dtuData.get(refTempCode)为null
|
||||
Object tempObj = dtuData.get(refTempCode);
|
||||
if (tempObj == null) {
|
||||
// todo 当前卷膜参考温度设置为空
|
||||
log.error("【定时任务-卷膜自动化控制】大棚『{}』当前卷膜「{}」参考温度设置为空",imei, roller);
|
||||
// 最后一条对应imei对应温度 及时间
|
||||
Map<String, Object> dtuData = dtuDataInfo.get(0);
|
||||
// 求温度上报时间
|
||||
LocalDateTime dtuTime = (LocalDateTime) dtuData.get("time");
|
||||
if (dtuTime == null) {
|
||||
// todo 当前大棚温湿度时间为空 跳过
|
||||
log.error("【定时任务-卷膜自动化控制】大棚『{}』温湿度时间「{}」为空, 跳过", imei, LocalDateTime.now().minusMinutes(1));
|
||||
continue;
|
||||
}
|
||||
// 优化:明确标注为当前roller的参考温度快照(仅解析一次)
|
||||
BigDecimal currentTemp = new BigDecimal(tempObj.toString());
|
||||
|
||||
// 每个roller单独定义isFirstRun(作用域:当前roller)
|
||||
boolean isFirstRun = todayLogByRoller.getOrDefault(roller, 0) == 0;
|
||||
// 获取当前imei下的所有参数设置以及卷膜自动化条件设置
|
||||
Map<String, List<RollerTermVO>> configTermByRollerMap = rollerTermMap.get(imei);
|
||||
if (MapUtil.isEmpty(configTermByRollerMap)) {
|
||||
// todo 当前大棚下没有设置条件或者参数
|
||||
log.error("【定时任务-卷膜自动化控制】大棚『{}』当前大棚下没有设置条件或者参数, 跳过", imei);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 遍历当前roller的所有term(改用普通for循环,可读性更高)
|
||||
for (RollerTermVO term : terms) {
|
||||
// 判断该温度上报时间是否在该条件设置的时间范围内
|
||||
boolean inRange = TimeRangeUtil.isTimeInRange(dtuTime, term.getStartTime(), term.getEndTime());
|
||||
// 在范围内
|
||||
if (inRange) {
|
||||
// 获取今天对应imei的日志
|
||||
Map<String, Integer> todayLogByRoller = todayLogCountByImeiMap.get(imei);
|
||||
|
||||
for (Map.Entry<String, List<RollerTermVO>> configEntry : configTermByRollerMap.entrySet()) {
|
||||
String roller = configEntry.getKey(); // 当前卷膜
|
||||
List<RollerTermVO> terms = configEntry.getValue(); // 当前卷膜条件
|
||||
// 每个卷膜分组只会有一个卷膜参数设置,所有取第一个即可
|
||||
if (terms == null || terms.isEmpty()) {
|
||||
// todo 当前卷膜 无参数设置,跳过当前roller
|
||||
log.error("【定时任务-卷膜自动化控制】大棚『{}』当前卷膜「{}」无参数设置,跳过当前roller", imei, roller);
|
||||
continue;
|
||||
}
|
||||
LocalDateTime startTime = terms.stream()
|
||||
.max(Comparator.comparing(RollerTermVO::getStartTime))
|
||||
.get()
|
||||
.getStartTime();
|
||||
// 获取卷膜参数
|
||||
RollerTermVO rollerConfig = terms.get(0);
|
||||
String refTempCode = rollerConfig.getRefTempCode(); // 参考温度
|
||||
BigDecimal ventTotalLen = rollerConfig.getVentTotalLen(); // 风口总长
|
||||
BigDecimal reservedLen = rollerConfig.getReservedLen(); // 预留风口
|
||||
|
||||
// 防御性判断:避免dtuData.get(refTempCode)为null
|
||||
Object tempObj = dtuData.get(refTempCode);
|
||||
if (tempObj == null) {
|
||||
// todo 当前卷膜参考温度设置为空
|
||||
log.error("【定时任务-卷膜自动化控制】大棚『{}』当前卷膜「{}」参考温度设置为空,跳过!", imei, roller);
|
||||
continue;
|
||||
}
|
||||
// 优化:明确标注为当前roller的参考温度快照(仅解析一次)
|
||||
BigDecimal currentTemp = new BigDecimal(tempObj.toString());
|
||||
|
||||
// 遍历当前roller的所有term(改用普通for循环,可读性更高)
|
||||
for (RollerTermVO term : terms) {
|
||||
// 判断该温度上报时间是否在该条件设置的时间范围内
|
||||
boolean inRange = TimeRangeUtil.isTimeInRange(dtuTime, term.getStartTime(), term.getEndTime());
|
||||
boolean isCancelOff = startTime.equals(term.getStartTime());
|
||||
log.info("\n【定时任务-卷膜自动化控制】正在监测大棚『{}』-卷膜『{}』:" +
|
||||
"条件设置详情,监控时间范围:『{}~{}』,适宜温度:{}℃,运行风口:{}cm。「{}」",
|
||||
imei,roller,term.getStartTime().format(FORMATTER),term.getEndTime().format(FORMATTER),
|
||||
term.getTemp(),term.getVent(), isCancelOff?"当前为卷膜「"+roller+"」最后一条自动化规则":"");
|
||||
if (!inRange) {
|
||||
log.info("【定时任务-卷膜自动化控制】大棚『{}』当前卷膜「{}」温湿度时间:「{}」不在监控时间范围内「{}~{}」,跳过!",
|
||||
imei, roller, dtuTime,term.getStartTime().format(FORMATTER),term.getEndTime().format(FORMATTER));
|
||||
continue;
|
||||
}
|
||||
// 在范围内
|
||||
//判断温度是否在适宜温度内
|
||||
TempCommandStatus tempCommandStatus = TempJudgeUtil.judgeTempCommand(currentTemp, term.getTemp());
|
||||
// todo 开关指令需要通知用户 推送主题 && 更新数据 前端重新请求消息表
|
||||
if (tempCommandStatus == TempCommandStatus.OPEN) {
|
||||
log.info("【定时任务-卷膜自动化控制】大棚『{}』-卷膜『{}』当前温湿度:『{}℃』,适宜温度为:「{}℃」,触发自动化条件,即将执行『开』指令!",
|
||||
imei, roller, currentTemp, term.getTemp());
|
||||
// 判断是否首次开
|
||||
Integer openLen = todayLogByRoller.getOrDefault(roller + "k1", 0);
|
||||
// 开指令
|
||||
sendOpenCommand(imei,agriName, roller, isFirstRun, term.getVent(), reservedLen);
|
||||
isFirstRun = false;
|
||||
sendOpenCommand(imei, agriName, roller, openLen == 0, term.getVent(), reservedLen);
|
||||
// 每次后,数量累计+1 不需要因为只循环到一次
|
||||
} else if (tempCommandStatus == TempCommandStatus.CLOSE) {
|
||||
log.info("【定时任务-卷膜自动化控制】大棚『{}』-卷膜『{}』当前温湿度:『{}℃』,适宜温度为:「{}℃」,触发自动化条件,即将执行『关』指令!",
|
||||
imei, roller, currentTemp, term.getTemp());
|
||||
// 判断是否首次开
|
||||
Integer closeLen = todayLogByRoller.getOrDefault(roller + "g1", 0);
|
||||
// 关指令
|
||||
sendCloseCommand(imei, agriName, roller, isFirstRun, term.getVent());
|
||||
isFirstRun = false;
|
||||
sendCloseCommand(imei, agriName, roller, closeLen == 0, term.getVent(), isCancelOff);
|
||||
// 每次后,数量累计+1
|
||||
} else {
|
||||
log.info("【定时任务-卷膜自动化控制】大棚『{}』-卷膜『{}』当前温湿度:『{}℃』,适宜温度为:「{}℃」,温度适宜,无需操作!",
|
||||
imei, roller, currentTemp, term.getTemp());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
log.info("=============【定时任务】大棚自动模式监测执行完毕,时间:{}=============", LocalDateTime.now());
|
||||
} catch (Exception e) {
|
||||
WxUtil.pushText("【定时任务】大棚自动模式监测执行终端, \n发生错误.错误原因:"+e+",\n时间:"+LocalDateTime.now());
|
||||
log.error("\n=============【定时任务】大棚自动模式监测执行终端,\n发生错误.错误原因:{},\n时间:{}=============", e, LocalDateTime.now());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -238,7 +271,7 @@ public class RollerAutoTask {
|
|||
String message = buildMqttMessage(funcType);
|
||||
|
||||
// ========== 3. 执行核心指令逻辑 ==========
|
||||
executeCommand(imei, agriName, roller, funcType, message, openLen, true);
|
||||
executeCommand(imei, agriName, roller, funcType, message, openLen, true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -249,7 +282,8 @@ public class RollerAutoTask {
|
|||
* @param isFirstRun 是否第一次执行
|
||||
* @param vent 每次执行的风口大小
|
||||
*/
|
||||
private void sendCloseCommand(String imei, String agriName, String roller, boolean isFirstRun, BigDecimal vent) {
|
||||
private void sendCloseCommand(String imei, String agriName, String roller,
|
||||
boolean isFirstRun, BigDecimal vent, boolean isCancelOff) {
|
||||
// ========== 1. 前置参数校验 ==========
|
||||
validateBaseParams(imei, agriName, roller);
|
||||
if (isFirstRun) {
|
||||
|
|
@ -266,7 +300,7 @@ public class RollerAutoTask {
|
|||
String message = buildMqttMessage(funcType);
|
||||
|
||||
// ========== 3. 执行核心指令逻辑 ==========
|
||||
executeCommand(imei, agriName, roller, funcType, message, vent, false);
|
||||
executeCommand(imei, agriName, roller, funcType, message, vent, false, isCancelOff);
|
||||
}
|
||||
|
||||
// ========== 抽取公共方法:减少代码冗余 ==========
|
||||
|
|
@ -309,7 +343,7 @@ public class RollerAutoTask {
|
|||
* @param isOpen 是否开指令
|
||||
*/
|
||||
private void executeCommand(String imei, String agriName, String roller, String funcType,
|
||||
String message, BigDecimal len, boolean isOpen) {
|
||||
String message, BigDecimal len, boolean isOpen, boolean isCancelOff) {
|
||||
String lockKey = LOCK_PREFIX + imei + ":" + funcType;
|
||||
try {
|
||||
// ========== 1. 获取分布式锁 ==========
|
||||
|
|
@ -327,15 +361,18 @@ public class RollerAutoTask {
|
|||
isOpen ? "开启" : "关闭", imei, funcType, message);
|
||||
saveOperLog(imei, agriName, funcType, message, isOpen ? 1 : 0);
|
||||
|
||||
// ========== 3. 发布MQTT指令 ==========
|
||||
// mqttMessageSender.publish("dtu/" + imei + "/down", message);
|
||||
|
||||
// ========== 3. 发布MQTT指令 ==========todo
|
||||
mqttMessageSender.publish("dtu/" + imei + "/down", message);
|
||||
if (isCancelOff) {
|
||||
log.debug("【自动关调度】设备{}卷膜{}:触发最后一条自动化条件,下发关闭指令,无需暂停!时间:{}", imei, roller, LocalDateTime.now());
|
||||
return;
|
||||
}
|
||||
// ========== 4. 计算运行时间并调度自动关 ==========
|
||||
int runTime = RollerTimeCalculator.calculateRunTime(len);
|
||||
if (runTime > 0) {
|
||||
String autoOffKey = roller + (isOpen ? "k" : "g");
|
||||
autoOffManager.scheduleAutoOff(imei, autoOffKey, runTime);
|
||||
log.debug("【自动关调度】设备{}卷膜{}调度{}秒后自动关闭", imei, roller, runTime);
|
||||
log.debug("【自动关调度】设备{}卷膜「{}:{}」调度{}秒后自动关闭", imei, roller,(isOpen?"开":"关"), runTime);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
package com.agri.system.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.agri.system.domain.SysAgriAlarmRelation;
|
||||
import com.agri.system.domain.SysDtuData;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
/**
|
||||
* DTU温湿度上报数据Mapper接口
|
||||
|
|
@ -63,4 +65,6 @@ public interface SysDtuDataMapper extends BaseMapper<SysDtuData>
|
|||
* @return 结果
|
||||
*/
|
||||
public int deleteSysDtuDataByIds(Long[] ids);
|
||||
|
||||
List<Map<String, Object>> getLastDtuDataByImeiList(@Param("imeiList") List<String> imeiList);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -122,6 +122,7 @@ public class SysDevOperLogServiceImpl extends ServiceImpl<SysDevOperLogMapper, S
|
|||
.in("imei", imeiList) // 过滤固定IMEI列表(命中idx_imei_time/idx_func的imei前缀)
|
||||
.ge("create_time", todayStart) // 今日0点后(命中idx_imei_time的create_time)
|
||||
.lt("create_time", tomorrowStart) // 明日0点前(避免23:59:59.999漏查)
|
||||
.notIn("func_code","jbk1","jbg1")
|
||||
.groupBy("imei", "func_code"); // 按IMEI+func_code分组(利用idx_func索引)
|
||||
|
||||
// 3. 执行查询(返回Map列表,仅包含imei/func_code/count三个字段)
|
||||
|
|
@ -132,7 +133,7 @@ public class SysDevOperLogServiceImpl extends ServiceImpl<SysDevOperLogMapper, S
|
|||
|
||||
// 4.1 初始化所有固定IMEI的内层Map(避免取值时NPE)
|
||||
for (String imei : imeiList) {
|
||||
nestedCountMap.put(imei, Collections.emptyMap());
|
||||
nestedCountMap.put(imei, new HashMap<>());
|
||||
}
|
||||
// 4.2 填充统计结果
|
||||
for (Map<String, Object> row : sqlResult) {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
|
@ -115,28 +116,14 @@ public class SysDtuDataServiceImpl extends ServiceImpl<SysDtuDataMapper, SysDtuD
|
|||
*/
|
||||
@Override
|
||||
public List<Map<String, Object>> getLastDtuDataByImeiList(List<String> imeiList) {
|
||||
// 1. 计算最后一分钟的起始时间(当前时间 - 1分钟)
|
||||
LocalDateTime lastOneMinute = LocalDateTime.now().minusMinutes(1);
|
||||
|
||||
// 2. 子查询:按imei分组 + 最后一分钟过滤 + 取每组最大ID
|
||||
QueryWrapper<SysDtuData> subQuery = new QueryWrapper<>();
|
||||
subQuery.select("imei", "MAX(id) as max_id") // 雪花ID的MAX(id)就是最新记录
|
||||
.in("imei", imeiList)
|
||||
.ge("time", lastOneMinute)
|
||||
.groupBy("imei");
|
||||
if (imeiList == null || imeiList.isEmpty()) {
|
||||
log.error("imeiList列表为空");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// 3. 主查询:通过MAX(id)匹配完整数据 + 指定字段
|
||||
LambdaQueryWrapper<SysDtuData> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.inSql(SysDtuData::getId,
|
||||
"SELECT max_id FROM (" + subQuery.getCustomSqlSegment() + ") t")
|
||||
.select(SysDtuData::getImei,
|
||||
SysDtuData::getTemp1,
|
||||
SysDtuData::getTemp2,
|
||||
SysDtuData::getTemp3,
|
||||
SysDtuData::getTemp4,
|
||||
SysDtuData::getTime);
|
||||
|
||||
return baseMapper.selectMaps(queryWrapper);
|
||||
// 3. 执行查询(如果用 ServiceImpl,直接调 list(mainQueryWrapper) 即可)
|
||||
return baseMapper.getLastDtuDataByImeiList(imeiList);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,4 +136,20 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
</where>
|
||||
order by `time` desc limit 1
|
||||
</select>
|
||||
<select id="getLastDtuDataByImeiList" resultType="java.util.Map">
|
||||
SELECT
|
||||
id, imei, temp1, temp2, temp3, temp4, time
|
||||
FROM
|
||||
sys_dtu_data
|
||||
WHERE id IN (
|
||||
SELECT MAX(id) AS id
|
||||
FROM sys_dtu_data
|
||||
WHERE imei IN
|
||||
<foreach collection="imeiList" item="imei" open="(" separator="," close=")">
|
||||
#{imei}
|
||||
</foreach>
|
||||
AND time >= DATE_SUB(NOW(), INTERVAL 1 MINUTE)
|
||||
GROUP BY imei
|
||||
)
|
||||
</select>
|
||||
</mapper>
|
||||
|
|
@ -107,7 +107,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
at.start_time,
|
||||
at.end_time
|
||||
FROM sys_roller_param rp
|
||||
LEFT JOIN sys_auto_term at
|
||||
LEFT JOIN sys_auto_term `at`
|
||||
ON rp.imei = at.imei
|
||||
AND rp.roller = at.roller -- 关键:按imei+roller双字段关联(精准匹配)
|
||||
WHERE rp.imei IN
|
||||
|
|
|
|||
Loading…
Reference in New Issue