Commit ec6ffe29 authored by wanghao's avatar wanghao

1 上料必须是 绑定状态。

2 解绑时 必须解绑 异常的后才能 一键解绑 托盘状态。
3 上传MES 后 先不删除历史数据。
4  历史界面 导出功能。 根据  托盘号,设备号
5 增加一个任务 去执行 继电器状态,脉冲状态,模组状态,SIM卡状态,网络状态 这几个状态的 单独读 以及 写,科强说网络需要监控两次。
6 继电器状态,脉冲状态,模组状态,SIM卡状态,网络状态 涉及到的 校验 以及记录都放开了,待联调。
parent 3d775b4d
......@@ -23,4 +23,9 @@ public class RoboticArmConstans {
* 老化流程第三阶段执行时间(单位:分钟)
*/
public static final String AGING_STAGE3_TIME = "agingStage3Time";
/**
* 老化流程第四阶段执行时间(单位:分钟)
*/
public static final String AGING_STAGE4_TIME = "agingStage4Time";
}
......@@ -22,14 +22,14 @@ public class PalletDeviceUploadHistory extends BaseEntity
private Long id;
/** 托盘id */
@Excel(name = "托盘id")
private Long trayId;
/** 托盘编号 */
@Excel(name = "托盘编号")
private String trayCode;
/** 绑定的设备编号 */
@Excel(name = "绑定的设备编号")
@Excel(name = "设备编号")
private String deviceCode;
/** 行 */
......@@ -59,7 +59,7 @@ public class PalletDeviceUploadHistory extends BaseEntity
private Date unbindingTime;
/** 0-预热;1-正常;3-传感器故障;4-报警;5-通讯故障 */
@Excel(name = "0-预热;1-正常;3-传感器故障;4-报警;5-通讯故障")
@Excel(name = "状态")
private String status;
/** 设置-年 */
......@@ -91,7 +91,7 @@ public class PalletDeviceUploadHistory extends BaseEntity
private String adjustmentZeroAd;
/** 合格;不合格 */
@Excel(name = "合格;不合格")
@Excel(name = "调零状态")
private String zeroStatus;
/** 标定AD */
......@@ -99,7 +99,7 @@ public class PalletDeviceUploadHistory extends BaseEntity
private String calibrationAd;
/** 合格;不合格 */
@Excel(name = "合格;不合格")
@Excel(name = "标定状态0-不合格;1-合格")
private String calibrationStatus;
/** 浓度值 */
......@@ -115,48 +115,56 @@ public class PalletDeviceUploadHistory extends BaseEntity
private Integer realTimeAd;
/** 实时AD状态;0-异常;1-正常 */
@Excel(name = "实时AD状态;0-异常;1-正常")
@Excel(name = "实时AD状态;0-异常;1-正常 ")
private String realTimeAdStatus;
/**
* 传感器校准浓度
*/
@Excel(name = "校准浓度")
private BigDecimal calibrationConcentration;
/**
* 传感器校准状态 4-正常,其他都是异常
* 0-预热;1-正常;3-传感器故障;4-报警;5-通讯故障; 只有是4的时候显示正常,其他的都是异常
*/
@Excel(name = "校准浓度状态;0-预热;1-正常;3-传感器故障;4-报警;5-通讯故障; 只有是4的时候显示正常")
private String calibrationConcentrationStatus;
/**
* 写自检状态 空 是没写 0-失败;1-成功
*/
@Excel(name = "写自检状态;0-失败;1-成功")
private Integer writeSelfCheckStatus;
/**
* 继电器状态 0:初始 1:动作
*/
@Excel(name = "继电器状态;0:初始 1:动作")
private Integer relayStatus;
/**
* 脉冲状态 0:初始 1:动作
*/
@Excel(name = "脉冲状态;0:初始 1:动作")
private Integer pulseStatus;
/**
* 模块状态 0:异常 1:正常
*/
@Excel(name = "模块状态;0:异常 1:正常")
private Integer moduleStatus;
/**
* SIM卡状态 0:异常 1:正常
*/
@Excel(name = "SIM卡状态;0:异常 1:正常")
private Integer simCardStatus;
/**
* 网络状态 0:异常 1:正常
*/
@Excel(name = "网络状态;0:异常 1:正常")
private Integer networkStatus;
public void setId(Long id)
{
......
......@@ -137,14 +137,16 @@ public class CalibrationResultEventHandler {
uploadMesResultHistoryService.insertUploadMesResultHistory(uploadMesResultHistory);
if(StringUtils.isNotBlank(result)) {
JSONObject jsonObject = JSON.parseObject(result);
if(jsonObject.getInteger("code") != 200) {
String data = jsonObject.getString("data");
if(StringUtils.isNotBlank(data)) {
processPalletDeviceUploadHistory(palletDeviceBindings,data);
} else {
directProcessPaalletDeviceUploadHistory(palletDeviceBindings);
}
}
// if(jsonObject.getInteger("code") != 200) {
// String data = jsonObject.getString("data");
// if(StringUtils.isNotBlank(data)) {
// processPalletDeviceUploadHistory(palletDeviceBindings,data);
// } else {
// directProcessPaalletDeviceUploadHistory(palletDeviceBindings);
// }
// }
// 20251210 领导说 先 保存所有历史数据
directProcessPaalletDeviceUploadHistory(palletDeviceBindings);
} else {
directProcessPaalletDeviceUploadHistory(palletDeviceBindings);
}
......
......@@ -501,9 +501,10 @@ public class RobotArmCommandServiceImpl implements IRobotArmCommandService
}
// 20251104 上料 不需要看托盘状态了
// if(!"0".equals(tTrayInfo.getfStatus())) {
// throw new RuntimeException("托盘未解绑,请联系管理员");
// }
// 20251210 上料 又需要看托盘状态了
if(!"4".equals(tTrayInfo.getfStatus())) {
throw new RuntimeException("托盘状态异常,请联系管理员");
}
TStoreyInfo tStoreyInfo = storeyInfoMapper.selectNearestFreeStorey();
if(tStoreyInfo != null) {
......
package com.zehong.system.task;
import com.serotonin.modbus4j.ModbusFactory;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
......@@ -22,7 +21,6 @@ import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;
......@@ -35,8 +33,8 @@ import java.util.stream.Collectors;
* @description 老化最终执行任务
*/
@Component
public class FinalExecutionJob implements Job {
private static final Logger log = LoggerFactory.getLogger(FinalExecutionJob.class);
public class AgingStageFourProcessJob implements Job {
private static final Logger log = LoggerFactory.getLogger(AgingStageFourProcessJob.class);
// -------------------------- 常量配置(统一管理,避免魔法值)--------------------------
// 超时控制:必须小于Cron周期(假设Cron为5分钟,这里留1分钟缓冲)
......
......@@ -31,8 +31,8 @@ import java.util.stream.Collectors;
*/
@Component
@DisallowConcurrentExecution // 禁止同一任务并行执行(必须保留)
public class DeviceCommunicationJob implements Job {
private static final Logger log = LoggerFactory.getLogger(DeviceCommunicationJob.class);
public class AgingStageOneProcessJob implements Job {
private static final Logger log = LoggerFactory.getLogger(AgingStageOneProcessJob.class);
// -------------------------- 常量配置(统一管理,避免魔法值)--------------------------
// 超时控制:必须小于Cron周期(假设Cron为5分钟,这里留1分钟缓冲)
......@@ -157,16 +157,18 @@ public class DeviceCommunicationJob implements Job {
// 3. 更新浓度值
binding.setConcentration(String.valueOf(result[0]));
// 重用之前的master连接进行写操作
master = Modbus4jUtils.createModbusMaster(ip, port);
// 4. 条件写入时间
if (result[1] == 1 || result[1] == 3 || result[1] == 4) {
// 重用之前的master连接进行写操作
master = Modbus4jUtils.createModbusMaster(ip, port);
writeCurrentTimeToDevice(master, deviceId, binding);
}
// 5. 写入自检让设备开始自检,跟 上面的状态没关系
writeSelfCheckStatus(master, deviceId, binding);
binding.setWriteTimeStatus("1");
// 5. 更新数据库
// 6. 更新数据库
palletDeviceBindingMapper.updatePalletDeviceBinding(binding);
log.debug("设备{}处理完成: ip={}, port={}, status={}", deviceId, ip, port, result[1]);
......@@ -214,6 +216,20 @@ public class DeviceCommunicationJob implements Job {
}
}
/**
* 写入自检状态 让设备开始自检
*/
private void writeSelfCheckStatus(ModbusMaster master, int deviceId,
PalletDeviceBinding binding){
// 20251206 写完时间写自检,写自检就在时间后边写就行,不管时间写不写成功
try {
Modbus4jUtils.writeRegister(master, deviceId, 15, (short) 1);
binding.setWriteSelfCheckStatus(1);
} catch (Exception e) {
binding.setWriteSelfCheckStatus(0);
}
}
/**
* 写入当前时间到设备
*/
......@@ -251,15 +267,6 @@ public class DeviceCommunicationJob implements Job {
log.error("设备{}时间写入异常", deviceId, e);
recordAlarmByBinding(binding, "设备时间写入异常: " + e.getMessage());
}
// 20251206 写完时间写自检,写自检就在时间后边写就行,不管时间写不写成功
try {
Modbus4jUtils.writeRegister(master, deviceId, 15, (short) 1);
binding.setWriteSelfCheckStatus(1);
} catch (Exception e) {
binding.setWriteSelfCheckStatus(0);
}
}
// -------------------------- 辅助方法(日志/告警)--------------------------
......
......@@ -36,8 +36,8 @@ import java.util.stream.Collectors;
*/
@Component
@DisallowConcurrentExecution // 禁止同一任务并行执行(必须保留)
public class PrepareFinalExecutionJob implements Job {
private static final Logger log = LoggerFactory.getLogger(DeviceCommunicationJob.class);
public class AgingStageThreeProcessJob implements Job {
private static final Logger log = LoggerFactory.getLogger(AgingStageThreeProcessJob.class);
// -------------------------- 常量配置(统一管理,避免魔法值)--------------------------
// 超时控制:必须小于Cron周期(假设Cron为5分钟,这里留1分钟缓冲)
......@@ -190,6 +190,23 @@ public class PrepareFinalExecutionJob implements Job {
binding.setStatus(result[1] +"");
}
// 处理 继电器状态,脉冲状态,模组状态,SIM卡状态,网络状态
if(result[10] == 1){
binding.setRelayStatus(1);
}
if(result[11] == 1){
binding.setPulseStatus(1);
}
if(result[12] == 1){
binding.setModuleStatus(1);
}
if(result[13] == 1){
binding.setSimCardStatus(1);
}
if(result[14] == 1){
binding.setNetworkStatus(1);
}
// 5. 更新数据库
palletDeviceBindingMapper.updatePalletDeviceBinding(binding);
......
package com.zehong.system.task;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.zehong.common.utils.StringUtils;
import com.zehong.system.domain.PalletDeviceBinding;
import com.zehong.system.domain.TEquipmentAlarmData;
import com.zehong.system.domain.TStoreyInfo;
import com.zehong.system.mapper.PalletDeviceBindingMapper;
import com.zehong.system.mapper.TStoreyInfoMapper;
import com.zehong.system.modbus.util.Modbus4jUtils;
import com.zehong.system.service.ITEquipmentAlarmDataService;
import com.zehong.system.service.websocket.RobotArmWebSocketHandler;
import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
/**
* @author lenovo
* @date 2025/12/10
* @description 老化第二阶段 读 继电器状态 脉冲状态 模组状态 SIM卡状态 网络状态 并再写入 1执行自检
*/
@Component
public class AgingStageTwoProcessJob implements Job {
private static final Logger log = LoggerFactory.getLogger(AgingStageTwoProcessJob.class);
// -------------------------- 常量配置(统一管理,避免魔法值)--------------------------
// 超时控制:必须小于Cron周期(假设Cron为5分钟,这里留1分钟缓冲)
private static final int TOTAL_TASK_TIMEOUT_SEC = 240; // 任务总超时:4分钟
private static final int SINGLE_DEVICE_TIMEOUT_SEC = 10; // 单个设备超时:10秒
@Resource
private RobotArmWebSocketHandler robotArmWebSocketHandler;
@Resource
private ITEquipmentAlarmDataService alarmDataService;
@Resource
private TStoreyInfoMapper tStoreyInfoMapper;
@Resource
private Scheduler scheduler;
@Resource
private PalletDeviceBindingMapper palletDeviceBindingMapper;
// 全局线程池 - 避免重复创建
private static final ExecutorService GLOBAL_DEVICE_EXECUTOR = new ThreadPoolExecutor(
50, 100, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(500),
r -> new Thread(r, "final-global-modbus-device"),
new ThreadPoolExecutor.CallerRunsPolicy()
);
@Override
public void execute(JobExecutionContext context) {
// 1. 初始化变量,避免空指针
JobDataMap data;
String fPowerOutageIp;
Long fStoreyId = null;
int fPowerOutagePort;
TStoreyInfo tStoreyInfo = null;
long startTime = System.currentTimeMillis();
try {
// 2. 提取并校验所有参数
data = context.getJobDetail().getJobDataMap();
if (data == null) {
log.info("JobDataMap为空,终止执行");
return;
}
fStoreyId = data.getLong("fStoreyId");
fPowerOutageIp = data.getString("fPowerOutageIp");
fPowerOutagePort = data.getInt("fPowerOutagePort");
if(StringUtils.isBlank(fPowerOutageIp)) {
log.info("参数缺失:fStoreyId={}, ip={}, port={},终止执行", fStoreyId, fPowerOutageIp, fPowerOutagePort);
return;
}
// 4. 查询设备信息
tStoreyInfo = tStoreyInfoMapper.selectTStoreyInfoById(fStoreyId);
if (tStoreyInfo == null) {
log.info("未查询到设备信息:fStoreyId={},终止执行", fStoreyId);
return;
}
// 5. 执行业务逻辑(Modbus写操作,单独捕获异常)
String storeyCode = tStoreyInfo.getfStoreyCode();
if (StringUtils.isBlank(storeyCode)) {
log.info("设备编码为空:fStoreyId={},终止执行", fStoreyId);
return;
}
// 20251206 读取 继电器状态 脉冲状态 模组状态 SIM卡状态 网络状态
// 并行处理3个端口
List<CompletableFuture<Void>> portFutures = Arrays.asList(
processPort(tStoreyInfo, 501, Arrays.asList(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27)),
processPort(tStoreyInfo, 502, Arrays.asList(28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54)),
processPort(tStoreyInfo, 503, Arrays.asList(55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72))
);
// 等待所有端口完成,带总超时
CompletableFuture<Void> allPorts = CompletableFuture.allOf(
portFutures.toArray(new CompletableFuture[0])
);
allPorts.get(TOTAL_TASK_TIMEOUT_SEC, TimeUnit.SECONDS);
log.info("AgingStageTwoProcessJob 任务执行成功: fStoreyId={}, 耗时={}ms",
tStoreyInfo.getfStoreyId(), System.currentTimeMillis() - startTime);
log.info("=== AgingStageTwoProcessJob 执行完成:fStoreyId={} ===", fStoreyId);
} catch (Throwable e) {
// 9. 捕获所有异常(包括Error)
log.error("=== AgingStageTwoProcessJob 致命异常:fStoreyId={} ===", fStoreyId, e);
// 记录告警(即使设备信息为空,也尝试记录)
try {
if (tStoreyInfo != null && StringUtils.isNotBlank(tStoreyInfo.getfStoreyCode())) {
recordAlarm(tStoreyInfo, "老化第二阶段致命异常:" + e.getMessage());
} else {
TEquipmentAlarmData alarm = new TEquipmentAlarmData();
alarm.setfAlarmType("03");
alarm.setfEquipmentCode(fStoreyId + "");
alarm.setfAlarmData("老化第二阶段异常:" + e.getMessage());
alarm.setfCreateTime(new Date());
alarmDataService.insertTEquipmentAlarmData(alarm);
}
} catch (Exception alarmEx) {
log.error("=== 告警记录失败 ===", alarmEx);
}
// 禁止抛出任何异常!!!
} finally {
log.error("=== AgingStageTwoProcessJob finally:fStoreyId={} ===", fStoreyId);
}
}
/**
* 处理单个端口的所有设备
*/
private CompletableFuture<Void> processPort(TStoreyInfo storeyInfo, int port, List<Integer> deviceIds) {
return CompletableFuture.runAsync(() -> {
String ip = storeyInfo.getfIp();
String storeyIdStr = storeyInfo.getfStoreyId().toString();
log.info("开始端口通信: ip={}, port={}, 设备数={}", ip, port, deviceIds.size());
AtomicInteger errorCount = new AtomicInteger(0);
// 并行处理该端口的所有设备
List<CompletableFuture<Boolean>> deviceFutures = deviceIds.stream()
.map(deviceId -> processDeviceWithWrite(ip, port, deviceId, errorCount))
.collect(Collectors.toList());
try {
// 等待该端口所有设备完成
CompletableFuture<Void> allDevices = CompletableFuture.allOf(
deviceFutures.toArray(new CompletableFuture[0])
);
// 端口超时 = 设备数 * 单设备超时 / 并发因子
int portTimeout = Math.max(30, deviceIds.size() * SINGLE_DEVICE_TIMEOUT_SEC / 5);
allDevices.get(portTimeout, TimeUnit.SECONDS);
} catch (TimeoutException e) {
log.warn("端口{}通信超时: ip={}, fStoreyId={}", port, ip, storeyIdStr);
recordAlarm(storeyInfo, "端口" + port + "通信超时");
} catch (Exception e) {
log.error("端口{}通信异常: ip={}, fStoreyId={}", port, ip, storeyIdStr, e);
}
if (errorCount.get() > 0) {
log.warn("端口{}部分设备失败: 失败数={}, fStoreyId={}",
port, errorCount.get(), storeyIdStr);
}
log.info("端口通信完成: ip={}, port={}, fStoreyId={}", ip, port, storeyIdStr);
}, GLOBAL_DEVICE_EXECUTOR);
}
/**
* 处理单个设备(读取 + 条件写入)
*/
private CompletableFuture<Boolean> processDeviceWithWrite(String ip, int port, int deviceId, AtomicInteger errorCount) {
return CompletableFuture.supplyAsync(() -> {
PalletDeviceBinding binding;
ModbusMaster master = null;
try {
// 1. 读取设备数据
int[] result = Modbus4jUtils.readDeviceWithRetry(ip, port, deviceId);
// 2. 查询设备绑定信息
binding = palletDeviceBindingMapper.selectByTrayIdAndIndex(ip, deviceId);
if (binding == null) {
log.warn("未找到设备绑定: ip={}, deviceId={}", ip, deviceId);
recordAlarm(null, "ip:" + ip + ",port:" + port + ",deviceId:" + deviceId, "未找到设备绑定");
errorCount.incrementAndGet();
return false;
}
binding.setRelayStatus(result[10]);
binding.setPulseStatus(result[11]);
binding.setModuleStatus(result[12]);
binding.setSimCardStatus(result[13]);
binding.setNetworkStatus(result[14]);
// 重用之前的master连接进行写操作
master = Modbus4jUtils.createModbusMaster(ip, port);
writeSelfCheckStatus(master, deviceId, binding);
// 5. 更新数据库
palletDeviceBindingMapper.updatePalletDeviceBinding(binding);
log.debug("设备{}处理完成: ip={}, port={}, status={}", deviceId, ip, port, result[1]);
return true;
} catch (Exception e) {
log.info("设备{}处理异常: ip={}, port={}", deviceId, ip, port, e);
errorCount.incrementAndGet();
return false;
}finally {
Modbus4jUtils.destroyModbusMaster(master, deviceId);
}
}, GLOBAL_DEVICE_EXECUTOR);
}
/**
* 写入自检状态 让设备开始自检
*/
private void writeSelfCheckStatus(ModbusMaster master, int deviceId,
PalletDeviceBinding binding){
// 20251206 写完时间写自检,写自检就在时间后边写就行,不管时间写不写成功
try {
Modbus4jUtils.writeRegister(master, deviceId, 15, (short) 1);
binding.setWriteSelfCheckStatus(1);
} catch (Exception e) {
binding.setWriteSelfCheckStatus(0);
}
}
/**
* 记录告警(兼容设备信息为空的场景)
*/
private void recordAlarm(TStoreyInfo storeyInfo, String equipmentCode, String alarmData) {
try {
TEquipmentAlarmData alarm = new TEquipmentAlarmData();
alarm.setfAlarmType("03"); // 老化层告警
alarm.setfEquipmentCode(storeyInfo != null ? storeyInfo.getfStoreyCode() : equipmentCode);
alarm.setfAlarmData(alarmData);
alarm.setfCreateTime(new Date());
alarmDataService.insertTEquipmentAlarmData(alarm);
log.debug("告警记录成功:设备编码={},内容={}", alarm.getfEquipmentCode(), alarmData);
} catch (Exception e) {
log.error("告警记录失败:设备编码={},内容={}", equipmentCode, alarmData, e);
}
}
// 辅助方法:记录告警(抽离,避免代码重复)
private void recordAlarm(TStoreyInfo tStoreyInfo, String alarmMsg) {
try {
TEquipmentAlarmData alarm = new TEquipmentAlarmData();
alarm.setfAlarmType("03");
alarm.setfEquipmentCode(tStoreyInfo.getfStoreyCode());
alarm.setfAlarmData(alarmMsg);
alarm.setfCreateTime(new Date());
alarmDataService.insertTEquipmentAlarmData(alarm);
log.info("告警记录完成:{}", tStoreyInfo.getfStoreyCode());
} catch (Exception e) {
log.error("告警记录失败:{}", tStoreyInfo.getfStoreyCode(), e);
}
}
private void cleanUpJobs(Long fStoreyId,JobExecutionContext context) throws SchedulerException {
// 清理通信任务
JobKey commJobKey = new JobKey("COMM_" + fStoreyId, "DEVICE_TASKS");
if (scheduler.checkExists(commJobKey)) {
boolean commDeleted = scheduler.deleteJob(commJobKey);
log.info("通信任务清理结果:{}({})", commDeleted ? "成功" : "失败", commJobKey.getName());
}
// 清理自身任务
JobKey selfJobKey = context.getJobDetail().getKey();
if (scheduler.checkExists(selfJobKey)) {
boolean selfDeleted = scheduler.deleteJob(selfJobKey);
log.info("自身任务清理结果:{}({})", selfDeleted ? "成功" : "失败", selfJobKey.getName());
}
}
// ... 业务逻辑方法
private void executeBusinessLogic(String fPowerOutageIp, int fPowerOutagePort,int registerOffsets) {
// 10 层
ModbusMaster master;
try {
master = Modbus4jUtils.createModbusMaster(fPowerOutageIp, fPowerOutagePort);
Boolean aBoolean = Modbus4jUtils.writeCoil(master, 1,registerOffsets,false );
} catch (ModbusInitException | ModbusTransportException e) {
throw new RuntimeException(e);
}
}
private void notifyCommandsUpdate() {
robotArmWebSocketHandler.broadcastCommandUpdate();
}
}
......@@ -9,7 +9,6 @@ import com.zehong.system.task.DeviceCommJob.DeviceComm501Device2Job;
import com.zehong.system.task.DeviceCommJob.DeviceComm501Device3Job;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.quartz.utils.Key;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -19,9 +18,7 @@ import javax.annotation.Resource;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @author lenovo
......@@ -74,6 +71,9 @@ public class DeviceTaskScheduler {
// 3. 创建5分钟开始读写时间的job
createHourlyCommunicationJob(fStoreyId);
// 4. 增加一个阶段 去处理 脉冲状态,模组状态,SIM卡状态,网络状态
createTworPortCommJobs(fStoreyId);
// 4. 创建71小时 执行的任务
prepareFinalExecutionJob(fStoreyId);
// 5. 创建72小时 最终任务
......@@ -223,6 +223,54 @@ public class DeviceTaskScheduler {
port, jobId, delayMin, nextFireTime);
}
/**
* 创建第二阶段任务
* @param fStoreyId 设备ID
*/
private void createTworPortCommJobs(Long fStoreyId) throws SchedulerException {
String jobId = "TWO_" + fStoreyId;
JobKey jobKey = new JobKey(jobId, JOB_GROUP);
TriggerKey triggerKey = new TriggerKey(jobId + "_TRIGGER", TRIGGER_GROUP);
// 先删除旧的触发器和任务(彻底清理)
if (scheduler.checkExists(triggerKey)) {
scheduler.unscheduleJob(triggerKey);
}
if (scheduler.checkExists(jobKey)) {
scheduler.deleteJob(jobKey);
}
JobDetail job = JobBuilder.newJob(AgingStageOneProcessJob.class)
.withIdentity(jobKey)
.usingJobData("fStoreyId", fStoreyId.toString())
.storeDurably(false)
.build();
String s = sysConfigService.directSelectConfigByKey(RoboticArmConstans.AGING_STAGE2_TIME);
int delayMin = 0;
if(StringUtils.isNotBlank(s)) {
delayMin = Integer.parseInt(s);
}
if(delayMin == 0) {
delayMin = 10;
}
Date executeTime = Date.from(Instant.now().plus(delayMin, ChronoUnit.MINUTES));
// 关键修复:使用StartAt而不是StartNow
SimpleTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity(triggerKey)
.forJob(jobKey)
.withDescription("设备" + fStoreyId + "第二阶段触发器,触发时间是:" + executeTime)
.startAt(executeTime)
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withMisfireHandlingInstructionFireNow() // 错过立即执行
.withRepeatCount(0)) // 仅一次
.build();
Date nextFireTime = scheduler.scheduleJob(job, trigger);
log.info("通信任务[{}]创建成功,下次执行:{}", jobId, nextFireTime);
}
/**
* 1. 创建每5分钟执行的通信任务(核心优化:简化调度逻辑、调整Misfire策略)
*/
......@@ -239,7 +287,7 @@ public class DeviceTaskScheduler {
scheduler.deleteJob(jobKey);
}
JobDetail job = JobBuilder.newJob(DeviceCommunicationJob.class)
JobDetail job = JobBuilder.newJob(AgingStageOneProcessJob.class)
.withIdentity(jobKey)
.usingJobData("fStoreyId", fStoreyId.toString())
.storeDurably(false)
......@@ -259,20 +307,12 @@ public class DeviceTaskScheduler {
SimpleTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity(triggerKey)
.forJob(jobKey)
.withDescription("设备" + fStoreyId + "最终任务触发器,触发时间是:" + executeTime)
.withDescription("设备" + fStoreyId + "第一阶段触发器,触发时间是:" + executeTime)
.startAt(executeTime)
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withMisfireHandlingInstructionFireNow() // 错过立即执行
.withRepeatCount(0)) // 仅一次
.build();
// CronTrigger trigger = TriggerBuilder.newTrigger()
// .withIdentity(triggerKey)
// .forJob(jobKey)
// .withDescription("设备" + fStoreyId + "写时间任务,触发时间是:" + executeTime)
// .withSchedule(CronScheduleBuilder.cronSchedule("0 0/3 * * * ?") // 改为3分钟
// .withMisfireHandlingInstructionDoNothing())
// .startAt(new Date()) // 显式设置开始时间:cite[1]
// .build();
Date nextFireTime = scheduler.scheduleJob(job, trigger);
log.info("通信任务[{}]创建成功,下次执行:{}", jobId, nextFireTime);
......@@ -295,19 +335,19 @@ public class DeviceTaskScheduler {
scheduler.deleteJob(jobKey);
}
JobDetail job = JobBuilder.newJob(PrepareFinalExecutionJob.class)
JobDetail job = JobBuilder.newJob(AgingStageThreeProcessJob.class)
.withIdentity(jobKey)
.usingJobData("fStoreyId", fStoreyId.toString())
.storeDurably(false)
.build();
String s = sysConfigService.directSelectConfigByKey(RoboticArmConstans.AGING_STAGE2_TIME);
String s = sysConfigService.directSelectConfigByKey(RoboticArmConstans.AGING_STAGE3_TIME);
int delayMin = 0;
if(StringUtils.isNotBlank(s)) {
delayMin = Integer.parseInt(s);
}
if(delayMin == 0) {
delayMin = 10;
delayMin = 15;
}
Date executeTime = Date.from(Instant.now().plus(delayMin, ChronoUnit.MINUTES));
......@@ -315,7 +355,7 @@ public class DeviceTaskScheduler {
SimpleTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity(triggerKey)
.forJob(jobKey)
.withDescription("设备" + fStoreyId + "最终任务触发器,触发时间是:" + executeTime)
.withDescription("设备" + fStoreyId + "第三阶段触发器,触发时间是:" + executeTime)
.startAt(executeTime)
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withMisfireHandlingInstructionFireNow() // 错过立即执行
......@@ -327,14 +367,14 @@ public class DeviceTaskScheduler {
}
/**
* 2. 创建15分钟后执行的最终任务(保持原逻辑,优化超时)
* 2. 创建20分钟后执行的最终任务(保持原逻辑,优化超时)
*/
private void createFinalExecutionJob(Long fStoreyId, String fPowerOutageIp, Integer fPowerOutagePort) throws SchedulerException {
String jobId = "FINAL_" + fStoreyId;
JobKey jobKey = new JobKey(jobId, JOB_GROUP);
TriggerKey triggerKey = new TriggerKey(jobId + "_TRIGGER", TRIGGER_GROUP);
JobDetail job = JobBuilder.newJob(FinalExecutionJob.class)
JobDetail job = JobBuilder.newJob(AgingStageFourProcessJob.class)
.withIdentity(jobKey)
.withDescription("设备" + fStoreyId + "最终执行任务(仅一次)")
.usingJobData("fStoreyId", fStoreyId.toString())
......@@ -344,13 +384,13 @@ public class DeviceTaskScheduler {
.requestRecovery(true)
.build();
String s = sysConfigService.directSelectConfigByKey(RoboticArmConstans.AGING_STAGE3_TIME);
String s = sysConfigService.directSelectConfigByKey(RoboticArmConstans.AGING_STAGE4_TIME);
int delayMin = 0;
if(StringUtils.isNotBlank(s)) {
delayMin = Integer.parseInt(s);
}
if(delayMin == 0) {
delayMin = 15;
delayMin = 20;
}
Date executeTime = Date.from(Instant.now().plus(delayMin, ChronoUnit.MINUTES));
SimpleTrigger trigger = TriggerBuilder.newTrigger()
......
......@@ -84,7 +84,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<select id="selectPalletDeviceUploadHistoryList" parameterType="PalletDeviceUploadHistory" resultMap="PalletDeviceUploadHistoryResult">
<include refid="selectPalletDeviceUploadHistoryVo"/>
<where>
<if test="deviceCode != null and deviceCode != ''"> and palDeviceBinding.f_device_code = #{deviceCode}</if>
<if test="deviceCode != null and deviceCode != ''"> and palDeviceBinding.f_device_code like concat('%',#{deviceCode},'%') </if>
<if test="trayCode != null and trayCode != ''"> and trayInfo.f_tray_code like concat('%',#{trayCode},'%') </if>
</where>
</select>
......
......@@ -39,7 +39,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select>
<select id="selectAgingStageTime" resultMap="SysConfigResult">
<include refid="selectConfigVo"/>
where config_key in ('agingStage1Time','agingStage2Time','agingStage3Time')
where config_key in ('agingStage1Time','agingStage2Time','agingStage3Time','agingStage4Time')
</select>
<select id="selectConfigList" parameterType="SysConfig" resultMap="SysConfigResult">
<include refid="selectConfigVo"/>
......
......@@ -10,6 +10,15 @@
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="托盘编号" prop="trayCode">
<el-input
v-model="queryParams.trayCode"
placeholder="请输入托盘编号"
clearable
size="small"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
......@@ -322,6 +331,7 @@ export default {
pageSize: 10,
trayId: null,
deviceCode: null,
trayCode: null,
row: null,
col: null,
index: null,
......@@ -435,10 +445,6 @@ export default {
this.ids = selection.map(item => item.id)
this.single = selection.length!==1
this.multiple = !selection.length
},
//单个设备重新上传
handleSingleUpload(row) {
},
/** 新增按钮操作 */
handleAdd() {
......
......@@ -34,10 +34,11 @@
</div>
<!-- 设备扫描区域 -->
<!-- 调整点1: 修改扫描区域的显示条件 -->
<div
class="scan-section"
style="align-items: center;"
v-show="trayStatus !== '1' && trayStatus !== '2'"
v-show="trayStatus !== '1' && trayStatus !== '2' && !(trayStatus === '3' && abnormalCount === 0)"
>
<input
type="text"
......@@ -46,12 +47,18 @@
v-model="deviceInput"
@keyup.enter="addDevice"
ref="deviceInput"
:disabled="trayStatus === '1'"
:disabled="trayStatus === '1' || (trayStatus === '3' && abnormalCount === 0)"
>
<div class="status-bar">
<i class="fas fa-info-circle"></i>
<!-- 调整点2: 修改状态栏显示逻辑 -->
<span v-if="trayStatus === '0'">已绑定设备: <span class="filled-count">{{ filledCount }}</span>/72</span>
<span v-if="trayStatus === '3'">待处理异常设备: <span class="filled-count">{{ abnormalCount }}</span>/{{ initialAbnormalCount }}</span>
<span v-if="trayStatus === '3' && abnormalCount > 0">
待处理异常设备: <span class="filled-count">{{ abnormalCount }}</span>/{{ initialAbnormalCount }}
</span>
<span v-if="trayStatus === '3' && abnormalCount === 0">
所有设备正常,可进行一键解绑
</span>
</div>
</div>
</div>
......@@ -111,9 +118,9 @@
<i class="fas fa-paper-plane"></i>
{{ '提交绑定' }}
</button>
<!-- 一键解绑按钮 - 仅status=3时显示 -->
<!-- 调整点3: 修改一键解绑按钮显示条件 -->
<button
v-if="trayStatus === '3'"
v-if="trayStatus === '3' && abnormalCount === 0"
class="unbind-btn"
@click="unbindAll"
>
......@@ -256,6 +263,10 @@ export default {
// 计算是否需要显示操作按钮
showControlButtons() {
// 当托盘状态为"老化中"(1)或"老化完成"(2)时,不显示操作按钮
// 调整点4: 在状态3时,只有当异常设备数量为0时才显示操作按钮(一键解绑)
if (this.trayStatus === '3') {
return this.abnormalCount === 0;
}
return this.trayStatus !== '1' && this.trayStatus !== '2';
},
// 计算已填充的设备数量
......@@ -300,7 +311,7 @@ export default {
// 扫描输入框提示语
scanPlaceholder() {
return this.trayStatus === '3'
? '解绑设备条码...'
? '扫描异常设备条码进行解绑...'
: '扫描设备条码...';
}
},
......@@ -353,7 +364,9 @@ export default {
(device.realTimeStatus != null && device.realTimeStatus === '0') ||
(device.calibrationConcentrationStatus != null && device.calibrationConcentrationStatus !== '4') ||
(device.writeTimeStatus != null && device.writeTimeStatus === '0') ||
(device.runTimeStatus != null && device.runTimeStatus === '0');
(device.runTimeStatus != null && device.runTimeStatus === '0') ||
device.relayStatus === 0 || device.pulseStatus === 0 ||
device.moduleStatus === 0 || device.simCardStatus === 0 || device.networkStatus === 0;
} else {
// 非标定完成状态(0,4,1,2等),只检查设备状态是否为 0 或 5
// 其他状态字段在绑定阶段可能为 null,不应该视为错误
......@@ -467,6 +480,15 @@ export default {
);
if (deviceIndex !== -1) {
// 调整点5: 在标定完成状态下,只有异常设备才允许解绑
const device = this.devices[deviceIndex];
if (!this.isDeviceError(device)) {
// 正常设备不允许解绑
this.$message.warning(`设备 ${this.deviceInput} 状态正常,不能解绑`);
this.deviceInput = '';
return;
}
// 找到设备,直接解绑(不弹确认对话框)
this.executeUnbind(deviceIndex);
} else {
......@@ -586,6 +608,11 @@ export default {
// 如果是异常设备,减少异常计数(使用完整的异常判断逻辑)
if (wasErrorDevice) {
this.abnormalCount--;
// 调整点6: 当异常设备全部解绑完成后,输入框会自动变为readonly(通过disabled属性控制)
if (this.abnormalCount === 0) {
this.$message.success('所有异常设备已解绑完成,可进行一键解绑');
}
}
this.$message.success(`设备 ${device.deviceCode} 解绑成功`);
......
......@@ -68,6 +68,23 @@
<div class="form-tips">必须大于等于第二阶段时间5分钟及以上,最大4320分钟</div>
</el-form-item>
<!-- 第四阶段 - 新增字段 -->
<el-form-item
label="老化流程第四阶段执行时间(单位:分钟)"
prop="agingStage4Time"
>
<el-input-number
v-model="form.agingStage4Time"
:min="Math.max(20, form.agingStage3Time + 5)"
:max="4325"
placeholder="请输入第四阶段执行时间"
controls-position="right"
style="width: 300px;"
@change="validateStage4"
/>
<div class="form-tips">必须大于等于第三阶段时间5分钟及以上,最大4325分钟</div>
</el-form-item>
<!-- 操作按钮 -->
<el-form-item>
<el-button
......@@ -130,14 +147,28 @@ export default {
}
};
// 第四阶段验证规则 - 新增验证规则
const validateStage4 = (rule, value, callback) => {
if (value === null || value === undefined || value === '') {
callback(new Error("第四阶段时间不能为空"));
} else if (value < this.form.agingStage3Time + 5) {
callback(new Error("第四阶段时间必须大于等于第三阶段时间5分钟及以上"));
} else if (value > 4325) {
callback(new Error("第四阶段时间不能超过4325分钟"));
} else {
callback();
}
};
return {
// 表单数据
// 表单数据 - 添加第四阶段字段
form: {
agingStage1Time: null,
agingStage2Time: null,
agingStage3Time: null,
agingStage4Time: null, // 新增第四阶段字段
},
// 验证规则
// 验证规则 - 添加第四阶段验证规则
rules: {
agingStage1Time: [
{ required: true, validator: validateStage1, trigger: ["blur", "change"] }
......@@ -148,14 +179,18 @@ export default {
agingStage3Time: [
{ required: true, validator: validateStage3, trigger: ["blur", "change"] }
],
agingStage4Time: [ // 新增第四阶段验证规则
{ required: true, validator: validateStage4, trigger: ["blur", "change"] }
],
},
// 加载状态
loading: false,
// 配置键名
// 配置键名 - 添加第四阶段键名
configKeys: {
stage1: "agingStage1Time",
stage2: "agingStage2Time",
stage3: "agingStage3Time",
stage4: "agingStage4Time", // 新增第四阶段键名
},
};
},
......@@ -170,7 +205,7 @@ export default {
const response = await getAgingStageTime();
if (response.code === 200 && response.data) {
// 假设返回的数据格式为数组,包含个配置项
// 假设返回的数据格式为数组,包含个配置项
const configs = response.data;
configs.forEach(item => {
switch (item.configKey) {
......@@ -183,6 +218,9 @@ export default {
case this.configKeys.stage3:
this.form.agingStage3Time = parseInt(item.configValue) || 15;
break;
case this.configKeys.stage4: // 新增第四阶段数据处理
this.form.agingStage4Time = parseInt(item.configValue) || 20;
break;
}
});
}
......@@ -201,7 +239,7 @@ export default {
try {
this.loading = true;
// 构建请求数据
// 构建请求数据 - 添加第四阶段数据
const requestData = [
{
configKey: this.configKeys.stage1,
......@@ -215,6 +253,10 @@ export default {
configKey: this.configKeys.stage3,
configValue: this.form.agingStage3Time.toString(),
},
{
configKey: this.configKeys.stage4, // 新增第四阶段数据
configValue: this.form.agingStage4Time.toString(),
},
];
const response = await updateAgingStageTime(requestData);
......@@ -246,19 +288,27 @@ export default {
// 第一阶段变化时触发验证
validateStage1() {
// 重新验证第二阶段和第三阶段
// 重新验证第二、三、四阶段
this.$refs.formRef.validateField("agingStage2Time");
this.$refs.formRef.validateField("agingStage3Time");
this.$refs.formRef.validateField("agingStage4Time");
},
// 第二阶段变化时触发验证
validateStage2() {
// 重新验证第三阶段
// 重新验证第三、四阶段
this.$refs.formRef.validateField("agingStage3Time");
this.$refs.formRef.validateField("agingStage4Time");
},
// 第三阶段变化时触发验证
validateStage3() {
// 重新验证第四阶段
this.$refs.formRef.validateField("agingStage4Time");
},
// 第四阶段变化时触发验证 - 新增方法
validateStage4() {
// 不需要重新验证其他阶段
},
......@@ -270,6 +320,11 @@ export default {
// 计算第三阶段的最小值
getStage3Min() {
return Math.max(15, (this.form.agingStage2Time || 10) + 5);
},
// 计算第四阶段的最小值 - 新增方法
getStage4Min() {
return Math.max(20, (this.form.agingStage3Time || 15) + 5);
}
},
watch: {
......@@ -296,6 +351,18 @@ export default {
}
},
immediate: true
},
'form.agingStage3Time': {
handler(newVal) {
// 当第三阶段变化时,确保第四阶段不小于第三阶段+5 - 新增监听
if (newVal !== null && this.form.agingStage4Time !== null) {
const minStage4 = newVal + 5;
if (this.form.agingStage4Time < minStage4) {
this.form.agingStage4Time = minStage4;
}
}
},
immediate: true
}
}
};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment