package com.ruoyi.sim.service.impl; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.sim.config.SimConfig; import com.ruoyi.sim.constant.CommConst; import com.ruoyi.sim.constant.FaultIdConst; import com.ruoyi.sim.domain.*; import com.ruoyi.sim.domain.vo.FaultCheckVo; import com.ruoyi.sim.domain.vo.ScanSeatVo; import com.ruoyi.sim.domain.vo.SimSocketParamVo; import com.ruoyi.sim.util.SimDateUtil; import org.apache.commons.collections4.SetUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.LoggerFactory; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.util.*; import static com.ruoyi.sim.constant.CommConst.*; /** * 硬件通信 * send service. * "commSendService" */ @Service public class CommSendService { private static final Logger l = LoggerFactory.getLogger(CommSendService.class); @Autowired private CommReceiveService simReceiveService; @Autowired private SeatService seatService; @Autowired private SimService simService; @Autowired private FaultService faultService; @Autowired private RealExamService realExamService; @Autowired private RealExamFaultService realExamFaultService; @Autowired private RealExamCollectionService realExamCollectionService; @Autowired private CommBuildService commBuildService; @Autowired private DebugFaultService debugFaultService; @Autowired private SocketService socketService; @Autowired private CommCheckService commCheckService; @Autowired private CommReceiveService commReceiveService; @Autowired private SimConfig config; @Autowired private CommStrategy commStrategy; @Autowired private CmdService cmdService; /** * 定时任务。 */ public void scheduledLoopRead() { readAll(); } public void readAll() { l.info("readAll"); List listRe = realExamService.listAllByStatus(RealExam.State.ANSWERING); listRe.forEach(e -> { if (e == null) { return; } List listRef = realExamFaultService.listAllType2State2and3ByExamId(e.getExamId()); listRef.forEach(ref -> { RealExam re = realExamService.selectRealExamByExamId(ref.getExamId()); Seat seat = seatService.selectSeatBySeatId(re.getSeatId()); Sim sim = simService.selectSimBySimId(e.getSimId()); Fault fault = faultService.selectFaultByFaultId(ref.getFaultId()); if (fault != null && Fault.Type.REAL_GZBW.equals(fault.getFaultType()) && Fault.State.ENABLE.equals(fault.getFaultState())) { readOneSimOneFaultResistance(seat, sim, ref, fault, null); } }); }); } /** * Async version. */ @Async("tp-comm") public void readAllAsync() { readAll(); } /** * 开始考试以后,交卷之前,定时任务中间读取值,作为答题值。有优先级高的任务可能跳过执行。 * * @param re */ public void readOneExamAtMiddle(RealExam re) { l.info("readOneExamAtMiddle getExamId = {}", re.getExamId()); List list = realExamFaultService.listAllType2State2and3ByExamId(re.getExamId()); for (RealExamFault ref : list) { { RealExamFault refQ = realExamFaultService.selectRealExamFaultByRefId(ref.getRefId()); if (refQ == null || refQ.getExamId() == 0L) { continue; } if (!RealExamFault.State.WRITTEN.equals(ref.getRefState()) && !RealExamFault.State.LOOP_READ.equals(ref.getRefState())) { continue; } } Seat seat = seatService.selectSeatBySeatId(re.getSeatId()); Sim sim = simService.selectSimBySimId(re.getSimId()); Fault fault = faultService.selectFaultByFaultId(ref.getFaultId()); readOneSimOneFaultResistance(seat, sim, ref, fault, RealExamFault.State.LOOP_READ); } } /** * 交卷最后读取值,作为答题值。 * * @param re */ public void readOneExamAtLast(RealExam re) { l.info("readOneExamAtLast getExamId = {}", re.getExamId()); List list = realExamFaultService.listAllType2State2and3ByExamId(re.getExamId()); for (RealExamFault ref : list) { Seat seat = seatService.selectSeatBySeatId(re.getSeatId()); Sim sim = simService.selectSimBySimId(re.getSimId()); Fault fault = faultService.selectFaultByFaultId(ref.getFaultId()); readOneSimOneFaultResistance(seat, sim, ref, fault, RealExamFault.State.FINISH); } // 计算扣分。 // 最后都读取到,才算扣分。 { // todo:暂时不算。 // calculateScore(re.getExamId()); } } public void readOneSimAtLastByDebug(Seat seat, Sim sim) { l.info("readOneSimAtLastByDebug"); List list = faultService.listType3(sim.getSimType()); for (Fault fault : list) { readOneSimOneFaultResistance(seat, sim, null, fault, null); } } public AjaxResult debugReadAllFaultResistanceBySimNum(final Long seatId) { // check if (seatId == null || seatId == 0L) { return AjaxResult.error("尚未选择座次。"); } // // 打开socket { socketService.tryOpenAll(); } // Seat seat = seatService.selectSeatBySeatId(seatId); Sim sim = getSimBySeatIdNewVer(seatId); { AjaxResult arE3 = checkOneSimStateActive(seat); if (arE3.isError()) { return arE3; } } sim = simService.uniqueBySimNum(sim.getSimNum()); // todo: aj改造 readOneSimAtLastByDebug(seat, sim); return AjaxResult.success("成功读取!"); } @Async("tp-comm") public void readOneExamAtLastAsync(RealExam re) { l.info("readOneExamAtLastAsync"); readOneExamAtLast(re); } /** * 计算减分(不包括超时)。汇总到deduction_total_score字段。 * * @param realExamId */ public void calculateScore(Long realExamId) { realExamService.updateOneState(realExamId, RealExam.State.CALCULATING_SCORE); int minus = 0; // 排除故障部分 + 维修报告选择部分 realExamFaultService.calculateMinusByRealExamId(realExamId); // 超时部分 int timeoutMinute = 0; // realExamService.updateOneState(realExamId, RealExam.State.GOT_REPORT); } /** * todo:???? * 定时任务。 */ public void scheduledReadSim() { l.info("scheduledReadSim"); if (!realExamCollectionService.existAtLeastOneOpened()) { l.info("没有open的考试集合"); return; } readAll(); // // AA010102010000000055 // AA010102010000000055 String simNum = "01"; String simType = "0002"; // for (int i = 0; i < 3; i++) { // debugReadSimType(simNum); // } // debugClearAllFault(simNum, simType); // debugWriteAllFault(simNum, simType); // for (int i = 0; i < 3; i++) { // debugReadAllFaultResistance(simNum, simType); // } // debugClearOneFault(simNum, "02"); // boolean bRea = isReachable("123.112.16.165"); // debugReadSimType(simNum); // l.info("bRea:" + bRea); // debugReadAllFaultResistance(simNum, simType); // debugWriteAllFault(simNum, simType); // for (int i = 0; i < 5; i++) { // debugReadAllFaultResistance(simNum, simType); // } // if (isSocketOk()) { // readAllAsync(); // } else { // scheduledConnect(); // } } /** * 连接情况 的 定时任务。 * 执行频率: 5min */ public void scheduledConnect() { l.info("scheduled####Connect 连接情况 的 定时任务"); // if (!SimDebugConfig.SCHEDULED_CONNECT) { if (true) { l.info("连接情况 的 定时任务被禁用!"); return; } // 暂时注释 // if (!realExamCollectionService.existAtLeastOneOpened()) { // l.info("没有open的任何集合"); // return; // } // { AjaxResult ar = commCheckService.checkRouterState(config.getRouterIp()); if (ar.isError()) { return; } } // { seatService.listAllRs485Ip().forEach(rs485Ip -> { commCheckService.checkPingRs485State(rs485Ip); }); } // SocketOldService实现 // if (socketOldService.isCachedSocketOk()) { // checkAllSeatAndSimState(); // } socketService.tryOpenAll(); commCheckService.checkAllSeatAndSimState(); } /** * 每天06:00/12:00/18:00/00:00执行 */ public void scheduledCloseAllSocket() { l.info("scheduledCloseAllSocket"); AjaxResult ar = socketService.closeAll(); l.info("AjaxResult = {}", ar); } /** * 每30min运行一次。 */ public void scheduledSystemAutoCleanExam() { l.info("scheduledSystemAutoCleanExam"); realExamService.systemAutoCleanExam(); } /** * 每6hour运行一次。 */ public void scheduledProjectRestart() { l.info("scheduledProjectRestart"); cmdService.restart(); } /** * 主动更新模拟器状态。 *

*

* 主动查询一次模拟器状态。更新模拟器在线/离线状态;否则返回对应错误。 * todo:需要重新考虑,不再使用。 * * @param seat * @return */ @Deprecated public AjaxResult checkOneSimStateActive(Seat seat) { // 这句可能会调整模拟器状态,后面需要重新查询。 commCheckService.checkOneSeatState(seat, true); AjaxResult ar1 = commCheckService.checkOneSimOnlineState(seat, true); if (ar1.isError()) { return ar1; } // 重新最新模拟器。 Sim sim = getSimBySeatIdNewVer(seat.getSeatId()); // 如果模拟器离线 if (sim != null && Sim.State.ONLINE.equals(sim.getSimState())) { return AjaxResult.success(); } else { return AjaxResult.error("未连接模拟器,请检查连接!"); } } @Async("tp-comm") public void checkAllSimStateAsync() { commCheckService.checkAllSeatAndSimState(); } /** * 清除一个考试的,对应的某型号一台模拟器的,所有故障部位。 * * @param re */ public void clearOneSimAllFaultByExam(RealExam re) { l.info("clearOneSimAllFaultByExam = {}", re); // check // 更新Exam状态。 { realExamService.updateOneState(re.getExamId(), RealExam.State.SIM_WRITING); } // List list = realExamFaultService.listAllType2InitStateByExamId(re.getExamId()); if (list != null) { l.info("清除exam list = {}", list.size()); } assert list != null; Seat seat = seatService.selectSeatBySeatId(re.getSeatId()); list.forEach(ref -> { Fault f = faultService.selectFaultByFaultId(ref.getFaultId()); if (faultService.isDisable(f.getFaultId())) { l.warn("故障 {} -被禁用", f.getName()); throw new IllegalArgumentException("故障被禁用"); } Sim sim = simService.selectSimBySimId(re.getSimId()); // check // clearOneSimOneFault(seat, sim, ref, f); }); } public void clearOneSimAllFaultBySim(Seat seat, Sim sim) { l.info("clearOneSimAllFaultBySim = {}", sim); faultService.listType3EnableBySimType(sim.getSimType()).forEach(f -> { clearOneSimOneFault(seat, sim, null, f); }); } /** * Async version. * * @param re */ @Async("tp-comm") public void clearListFaultByRealExamAsync(RealExam re) { clearOneSimAllFaultByExam(re); } public void clearAll() { // todo: // 根据Seat数据遍历 seatService.listAllEnable().forEach(seat -> { Sim sim = getSimBySeatIdNewVer(seat.getSeatId()); String simType = sim.getSimType(); List listF = faultService.listType3EnableBySimType(simType); listF.forEach(f -> { clearOneSimOneFault(seat, sim, null, f); }); }); } /** * debug读取模拟器类型序列号 * * @param seatId * @return */ public SimMsg debugReadSimType(final Long seatId) { Seat seat = seatService.selectSeatBySeatId(seatId); Sim sim = getSimBySeatIdNewVer(seatId); SimMsg sm = commBuildService.buildSendMsgReadSimType(sim.getSimNum()); return send(sm, seat, sim, CommConst.RETRY_COUNT_0, commStrategy.getSleepShort()); } /** * debug清除一个故障 * * @param seatId * @param bindHardwareMsg * @return */ public SimMsg debugClearOneFault(final Long seatId, final String bindHardwareMsg) { Seat seat = seatService.selectSeatBySeatId(seatId); // Sim sim = getSimBySeatIdNewVer(seatId); // 不需要重复调用 getSimBySeatIdNewVer。 Sim sim = simService.selectSimBySimId(seat.getCurrentSimId()); SimMsg sm = commBuildService.buildSendMsgClearFault(sim.getSimNum(), bindHardwareMsg); return send(sm, seat, sim, RETRY_COUNT_CLEAR_ONE_FAULT, commStrategy.getSleepLong()); } /** * debug清除所有座次上的模拟器的所有故障 * * @return */ public AjaxResult debugClearAllSeatAllFault() { debugFaultService.deleteAll(); List list = seatService.listAllEnable(); for (Seat seat : list) { if (seat == null) { AjaxResult.error("座次数据异常!"); } // commCheckService.checkOneSeatState(seat, true); // 重新查询 seat = seatService.selectSeatBySeatId(seat.getSeatId()); if (seat.getCurrentSimId() == null || seat.getCurrentSimId() == 0L) { continue; } AjaxResult ar = debugClearAllFaultBySeatId(seat.getSeatId()); if (ar.isError()) { return ar; } } return AjaxResult.success("清除成功,清除所有座次正确连接的模拟器的,所有的故障!" + SimDateUtil.getNow()); } /** * debug清除SeatId上模拟器所有故障 * * @param seatId * @return */ public AjaxResult debugClearAllFaultBySeatId(final Long seatId) { { AjaxResult ar = debugCheckSeatId(seatId); if (ar.isError()) { return ar; } } debugFaultService.deleteAll(); Seat seat = seatService.selectSeatBySeatId(seatId); Sim sim = getSimBySeatIdNewVer(seatId); if (sim == null) { return AjaxResult.error("清除失败,模拟器不存在!请检查模拟器线缆连接,模拟器线缆开关!"); } sim = simService.selectSimBySimId(sim.getSimId()); if (!Sim.State.ONLINE.equals(sim.getSimState())) { return AjaxResult.error("清除失败,模拟器尚未在线!"); } List list = new ArrayList<>(); for (String b : getGZBWBySimType(sim.getSimType())) { SimMsg sm = debugClearOneFault(seatId, b); if (sm != null && !Objects.equals(sm.getResult(), SimMsg.Result.SUCCESS)) { AjaxResult.error("清除失败!"); } list.add(sm); } return AjaxResult.success("清除成功,清除当前模拟器所有的故障!" + SimDateUtil.getNow()); } /** * @param sim * @param reF 可以为空,表示不关联考试的,单独执行的。调试模式下为空。 * @param f */ public void clearOneSimOneFault(Seat seat, Sim sim, RealExamFault reF, Fault f) { l.info("clearOneSimOneFault 清除One故障:getSimNum = {},getBindHardwareMsg = {},fault.getName = {}", sim.getSimNum(), f.getBindHardwareMsg(), f.getName()); // check todo: // step1 SimMsg smS = commBuildService.buildSendMsgClearFault(sim.getSimNum(), f.getBindHardwareMsg()); SimMsg smR = send(smS, seat, sim, RETRY_COUNT_CLEAR_ONE_FAULT, commStrategy.getSleepLong()); if (reF != null) { simReceiveService.clearOneFault(smR, sim, reF, f); } else { l.info("reF == null"); } // step2 // 下发故障独立运行。下面屏蔽。 // if (reF != null && // realExamFaultService.isState(reF.getRefId(), RealExamFault.State.CLEARED)) { // if (reF.getFlag().equals(RealExamFault.Flag.YES)) { // writeOneFault(s, reF, f); // } else if (reF.getFlag().equals(RealExamFault.Flag.NO)) { // RealExamFault f1 = realExamFaultService.selectRealExamFaultByRefId(reF.getRefId()); // f1.setRefState(RealExamFault.State.LOOP_READ); // } // } } /** * debug下发一个故障 * * @param seatId * @param bindHardwareMsg * @return * @throws IOException */ public SimMsg debugWriteOneFault(final Long seatId, final String bindHardwareMsg) { Seat seat = seatService.selectSeatBySeatId(seatId); Sim sim = getSimBySeatIdNewVer(seatId); SimMsg sm = commBuildService.buildSendMsgWriteFault(sim.getSimNum(), bindHardwareMsg); return send(sm, seat, sim, RETRY_COUNT_WRITE_ONE_FAULT, commStrategy.getSleepLong()); } /** * debug下发所有故障 * * @param seatId * @return * @throws IOException */ public List debugWriteAllFault(final Long seatId) { List list = new ArrayList<>(); Sim sim = getSimBySeatIdNewVer(seatId); String simType = sim.getSimType(); for (String bind : getGZBWBySimType(simType)) { list.add(debugWriteOneFault(seatId, bind)); } return list; } /** * todo:尚未实现 * 实现方式类似 studentStartRealExam方法。 * * @param seatId * @param faultIds * @param checkReplace 是否进行可换件检查 * @return */ @Transactional public AjaxResult debugWriteSelectedFaultBySeatId(final Long seatId, final String[] faultIds, final Boolean checkReplace) { // Check:seatId有效性 { AjaxResult ar = debugCheckSeatId(seatId); if (ar.isError()) { return ar; } } // Check:faultIds有效性 { if (faultIds == null || faultIds.length == 0) { return AjaxResult.error("下发故障数据为空!"); } } // 删除调试表故障数据。 debugFaultService.deleteAll(); // Seat seat = seatService.selectSeatBySeatId(seatId); Sim sim = getSimBySeatIdNewVer(seatId); // check sim // 打开socket { socketService.tryOpenAll(); } // Step 1 主动查询一次模拟器状态。 { AjaxResult arE3 = checkOneSimStateActive(seat); if (arE3.isError()) { return arE3; } } sim = simService.uniqueBySimNum(sim.getSimNum()); // Step 2 // msg判断,是否含有"故障部位"字符串 { if (checkReplace) { AjaxResult arE2 = readOneSimAllFaultStartCheck(seat, sim); if (arE2.isError()) { return arE2; } } } // Step 3 清除对应一台模拟器 所有故障部位故障。 { clearOneSimAllFaultBySim(seat, sim); } // Step 4 下发对应一台模拟器 出题选中的 故障位置故障。 { Fault[] faults = new Fault[faultIds.length]; for (int i = 0; i < faultIds.length; i++) { faults[i] = faultService.selectFaultByFaultId(faultIds[i]); } writeOneSimAllSelectFaultByDebug(seat, sim, faults); } // Step 5 读取 { readOneSimAllFaultFirstTimeBySim(seat, sim, faultIds); } return AjaxResult.success("下发故障流程执行成功!" + SimDateUtil.getNow()); } /** * debug校验seatId * * @param seatId * @return */ public AjaxResult debugCheckSeatId(final Long seatId) { if (seatId == null || seatId <= 0) { return AjaxResult.error("座次信息不正确!"); } Seat seat = seatService.selectSeatBySeatId(seatId); if (seat == null) { return AjaxResult.error("座次信息不存在!"); } return AjaxResult.success(); } /** * debug扫描所有座次模拟器 * * @return */ public AjaxResult debugScanAllSeat() { List list = new ArrayList<>(); seatService.listAllEnable().forEach(seat -> { AjaxResult ar = commCheckService.checkOneSeatState(seat, true); String text = ar.get(AjaxResult.MSG_TAG).toString(); // 新查询 seat = seatService.selectSeatBySeatId(seat.getSeatId()); // 座上sim Sim sim = simService.selectSimBySimId(seat.getCurrentSimId()); if (sim != null) { list.add(new ScanSeatVo(seat.getSeatId(), sim.getSimId(), sim.getSimType(), text)); } else { list.add(new ScanSeatVo(seat.getSeatId(), 0L, "", text)); } }); return AjaxResult.success(list); } private String[] getGZBWBySimType(String simType) { if (StringUtils.isBlank(simType)) { return new String[0]; } switch (simType) { case Sim.TYPE_0001 -> { return TYPE_1_BIND_MSG; } case Sim.TYPE_0002 -> { return TYPE_2_BIND_MSG; } case Sim.TYPE_0003 -> { return TYPE_3_BIND_MSG; } default -> { return new String[0]; } } } /** * 下发所有选中的真实的故障部位。 * * @param re */ public void writeOneSimAllSelectFaultByExam(RealExam re) { // 更新Exam状态。 realExamService.updateOneState(re.getExamId(), RealExam.State.SIM_WRITING); List list = realExamFaultService.listAllType2FlagYesClearedStateByExamId(re.getExamId()); for (RealExamFault ref : list) { Seat seat = seatService.selectSeatBySeatId(re.getSeatId()); Sim sim = simService.selectSimBySimId(re.getSimId()); Fault f = faultService.selectFaultByFaultId(ref.getFaultId()); // 选中的故障才下发。 writeOneSimOneFault(seat, sim, ref, f); } } public void writeOneSimAllSelectFaultByDebug(Seat seat, Sim sim, Fault[] faults) { for (Fault f : faults) { writeOneSimOneFault(seat, sim, null, f); } } public void writeOneSimOneFault(Seat seat, Sim sim, RealExamFault ref, Fault f) { l.info("下发故障:getSimId = {},fault.getName = {}", sim.getSimId(), f.getName()); // todo:ref is null. // 下发故障 SimMsg smA1 = commBuildService.buildSendMsgWriteFault(sim.getSimNum(), f.getBindHardwareMsg()); SimMsg smA2 = send(smA1, seat, sim, RETRY_COUNT_WRITE_ONE_FAULT, commStrategy.getSleepLong()); } /** * 一个模拟器 所有故障 开始考试前 检查读取。 * 有两处引用。正式开始和调试。 * * @param seat * @param sim * @return */ public AjaxResult readOneSimAllFaultStartCheck(final Seat seat, final Sim sim) { // 查询某型号所有真实的故障部位。 Fault q = new Fault(); q.setFaultType(Fault.Type.REAL_GZBW); q.setSimType(sim.getSimType()); List listF = faultService.selectFaultList(q); // 未正确安装可换件故障List // NG not good List listNGVo = new ArrayList<>(); // not good fault_id HashSet Set setNG = new HashSet<>(); // 单一故障部位检查 { for (Fault oneF : listF) { FaultCheckVo voOne = new FaultCheckVo(); voOne.setSeat(seat); voOne.setSim(sim); voOne.setFault(oneF); AjaxResult ar = readOneSimOneFaultStartSimpleCheck(voOne); // getDefaultErrorAR voReturn 可能为空 FaultCheckVo voReturn = (FaultCheckVo) ar.get(AjaxResult.DATA_TAG); if (voReturn != null) { if (voReturn.isCheckOk()) { l.info("log 故障部位[{}][{}]安装ok;", oneF.getBindHardwareMsg(), oneF.getReplaceName()); } else { l.info("log 故障部位[{}][{}]未正确安装;", oneF.getBindHardwareMsg(), oneF.getReplaceName()); listNGVo.add(voReturn); setNG.add(oneF.getFaultId()); } } // 得出ar有问题直接返回,不执行后面检查。 if (ar.isError()) { return ar; } } } // 复核条件检查 { AjaxResult ar = readOneSimAllFaultStartGlobalCheck(sim, setNG); if (ar.isError()) { return ar; } } // if (!listNGVo.isEmpty()) { StringBuilder sb = new StringBuilder(); for (FaultCheckVo oneVo : listNGVo) { sb.append(oneVo.getErrorMsg()); } sb.append("请正确安装可换件,检查后重新开始!"); return AjaxResult.error(sb.toString()); } return AjaxResult.success("所有故障部位检查没有问题。"); } private String buildKeyType0003(String bindHardwareMsg) { return buildKey(Sim.TYPE_0003, bindHardwareMsg); } private String buildKey(String simType, String bindHardwareMsg) { if (StringUtils.isAnyBlank(simType, bindHardwareMsg)) { throw new IllegalArgumentException("isAnyBlank"); } return simType + "@" + bindHardwareMsg; } /** * 一个模拟器 全部故障 开始考试前 复合读取检查。 * * @param sim * @param setNG * @return */ public AjaxResult readOneSimAllFaultStartGlobalCheck(Sim sim, Set setNG) { if (Objects.isNull(setNG) || setNG.isEmpty()) { return AjaxResult.success(""); } if (StringUtils.equals(sim.getSimType(), Sim.TYPE_0003)) { // if (map.containsKey(buildKeyType0003("01"))) { // SimMsg smR = map.get(buildKeyType0003("01")).getSimMsgReceive(); // String smRS = CommParseUtils.subContentData(smR); // // 没有连接FFC排线 // if (StringUtils.endsWith(smRS, "02")) { // return AjaxResult.error("请检查FFC排线连接!"); // } else if (StringUtils.endsWith(smRS, "01")) { // // } // } if (setNG.contains(FaultIdConst.T0003.F03) && setNG.contains(FaultIdConst.T0003.F04) && setNG.contains(FaultIdConst.T0003.F0B) && setNG.contains(FaultIdConst.T0003.F0E) ) { return AjaxResult.error("请检查 显控报警板与主板连接线 连接情况!"); } } return AjaxResult.success("复合读取检查 成功。"); } /** * 一个模拟器 一个故障 开始考试前 简单读取检查。 * * @param vo * @return */ public AjaxResult readOneSimOneFaultStartSimpleCheck(FaultCheckVo vo) { l.info("readOneSimOneFaultCheck vo = {}", vo); SimMsg smS = commBuildService.buildSendMsgReadFaultResistance(vo.getSim().getSimNum(), vo.getFault().getBindHardwareMsg()); SimMsg smR = send(smS, vo.getSeat(), vo.getSim(), RETRY_COUNT_CHECK_ONE_FAULT, commStrategy.getSleepLong()); if (smR.isResultNotOk()) { if (Objects.equals(smR.getResult(), SimMsg.Result.RECEIVE_CHECK_FAIL) || (Objects.equals(smR.getResult(), SimMsg.Result.RECEIVE_NOT_MATCH))) { l.info("reset connection!"); String ip = vo.getSeat().getSeatRs485Ip(); Integer port = vo.getSeat().getSeatRs485Port(); SimSocketParamVo sspv = new SimSocketParamVo(ip, port); socketService.closeOne(sspv, true); } return smR.getDefaultErrorAR(); } // set send and receive msg. vo.setSimMsgSend(smS); vo.setSimMsgReceive(smR); return simReceiveService.getOneFaultCheck(vo); } /** * 第一次读取,作为出题值。 * * @param re */ public void readOneSimAllFaultFirstTimeByExam(RealExam re) { l.info("readOneSimAllFaultFirstTimeByExam re = {}", re); List list = realExamFaultService.listAllType2(re.getExamId()); for (RealExamFault ref : list) { Seat seat = seatService.selectSeatBySeatId(re.getSeatId()); Sim sim = simService.selectSimBySimId(re.getSimId()); Fault f = faultService.selectFaultByFaultId(ref.getFaultId()); readOneSimOneFaultFirstTime(seat, sim, ref, f, null); } } /** * 第一次读取,作为出题值。debug模式。 * * @param seat * @param sim * @param faultIds debug模式必须有值 */ public void readOneSimAllFaultFirstTimeBySim(Seat seat, Sim sim, final String[] faultIds) { l.info("readOneSimAllFaultFirstTimeBySim s = {}", sim); List list = faultService.listType3(sim.getSimType()); for (Fault f : list) { readOneSimOneFaultFirstTime(seat, sim, null, f, faultIds); } } /** * 第一次读取,作为出题值。 * * @param sim * @param ref debug调试模式为空。可以为空。 * @param fault * @param faultIds debug模式必须有值 */ public void readOneSimOneFaultFirstTime(Seat seat, Sim sim, RealExamFault ref, Fault fault, String[] faultIds) { l.info("readOneSimOneFaultFirstTime"); // 读取一次当前电阻代表值作为出题值。 SimMsg smS = commBuildService.buildSendMsgReadFaultResistance(sim.getSimNum(), fault.getBindHardwareMsg()); SimMsg smR = send(smS, seat, sim, RETRY_COUNT_WRITE_ONE_FAULT, commStrategy.getSleepLong()); simReceiveService.setFaultQuestionValue(smR, sim, ref, fault, faultIds); } /** * debug读取一个故障位置数据 * * @param seatId * @param bindHardwareMsg * @return */ public SimMsg debugReadOneFaultResistance(final Long seatId, final String bindHardwareMsg) { Seat seat = seatService.selectSeatBySeatId(seatId); Sim sim = getSimBySeatIdNewVer(seatId); SimMsg sm = commBuildService.buildSendMsgReadFaultResistance(sim.getSimNum(), bindHardwareMsg); return send(sm, seat, null, RETRY_COUNT_0, commStrategy.getSleepShort()); } /** * debug读取全部故障位置数据 * * @param seatId * @return */ public List debugReadAllFaultResistance(final Long seatId) { Sim sim = getSimBySeatIdNewVer(seatId); List list = new ArrayList<>(); String simType = simService.uniqueBySimNum(sim.getSimNum()).getSimType(); for (String b : getGZBWBySimType(simType)) { list.add(debugReadOneFaultResistance(seatId, b)); } return list; } /** * 读取一个模拟器的一个故障点位的值。 * * @param sim * @param reF * @param fault * @param refState 修改的目标状态。debug模式下执行为null。 */ public void readOneSimOneFaultResistance(Seat seat, Sim sim, RealExamFault reF, Fault fault, final String refState) { l.info("readOneSimOneFaultResistance"); SimMsg smS = commBuildService.buildSendMsgReadFaultResistance(sim.getSimNum(), fault.getBindHardwareMsg()); SimMsg sm2 = null; if (reF != null && refState != null) { if (StringUtils.equals(refState, RealExamFault.State.LOOP_READ)) { // 是否是中间读取 sm2 = send(smS, seat, sim, RETRY_COUNT_READ_ONE_RESISTANCE_MIDDLE, commStrategy.getSleepShort(), false); } else if (StringUtils.equals(refState, RealExamFault.State.FINISH)) { // 是否最后一次读取。 sm2 = send(smS, seat, sim, RETRY_COUNT_READ_ONE_RESISTANCE_FINAL, commStrategy.getSleepShort()); } else { sm2 = send(smS, seat, sim, RETRY_COUNT_0, commStrategy.getSleepShort()); } } else { sm2 = send(smS, seat, sim, RETRY_COUNT_READ_ONE_RESISTANCE_FINAL, commStrategy.getSleepShort()); } simReceiveService.setFaultAnswerValue(sm2, sim, reF, fault, refState); } public SimMsg send(final SimMsg sm, final Seat seat, final Sim sim, final int retryTotalCount, final long sleep) { return send(sm, seat, sim, retryTotalCount, sleep, true); } /** * 最基本的通信方法。 * send hex message *

* 去掉synchronized * * @param sm 发送 * @param seat 不能为空 * @param sim 可以为空!更新最后发送/接收时间 用。 * @param retryTotalCount 重试次数 * @param sleep 不使用传入0,不进行挂起。 * @param importantTask 是否是重要的。 * @return */ public SimMsg send(final SimMsg sm, final Seat seat, final Sim sim, final int retryTotalCount, final long sleep, final boolean importantTask) { if (!config.isCommGlobal()) { l.warn("isCommGlobal == false [模拟器通信被禁用!]"); return sm; } if (sm == null || sm.getSendMsg() == null || StringUtils.isBlank(sm.getSendMsg())) { throw new IllegalArgumentException("SimMsg IllegalArgument"); } // sim if (seat == null) { throw new IllegalArgumentException("seat is null"); } if (sleep < 0) { throw new IllegalArgumentException("SimMsg sleep"); } // log. { l.info("####发送#### == Seat[{}],SimMsg[{}]", seat, sm); } SimSocketParamVo sspv = seat.toSimSocketParamVo(); try { // 如果没有打开socket,顺道打开。正好后面要sleep。 // 不强制重开Socket。 // 先进行Socket相关处理。 // 优先级高的在运行,跳过 if (importantTask == false && socketService.getImportantTaskRunning(sspv)) { sm.setResult(SimMsg.Result.SKIP); socketService.setImportantTaskRunning(sspv, false); l.warn("####跳过运行#### sm = {}", sm); return sm; } if (importantTask) { socketService.setImportantTaskRunning(sspv, true); } socketService.openOne(sspv); // Socket情况不正确,直接返回。 if (socketService.isNotOk(sspv)) { sm.setResult(SimMsg.Result.SOCKET_CONNECT_EXCEPTION); socketService.setImportantTaskRunning(sspv, false); return sm; } { // sleep挂起线程,追求顺序请求。 // 大于0才挂起。 if (sleep > 0 && socketService.getVo(sspv).getPreviousSendSleep() > 0L) { // 时间间隔挂起。 Thread.sleep(socketService.getVo(sspv).getPreviousSendSleep()); } } socketService.getVo(sspv).setPreviousSendSleep(sleep); Socket socket = socketService.getVo(sspv).getSocket(); InputStream is = socket.getInputStream(); OutputStream os = socket.getOutputStream(); Sim simSeat = simService.selectSimBySimId(seat.getCurrentSimId()); socket.setSoTimeout(commStrategy.getSoTimeout(simSeat)); os.write(hexStrToByteArrs(sm.getSendMsg())); sm.setSendTime(DateUtils.getNowDate()); if (sim != null) { simService.updateLastSentTime(sim); } byte[] buffer = new byte[LENGTH_24]; int length = is.read(buffer); StringBuilder sbHex = new StringBuilder(); for (int i = 0; i < length; i++) { sbHex.append(String.format("%02X", buffer[i])); } String receiveWith0 = sbHex.toString(); // 原始带0的收到报文。 sm.setReceiveOriginalMsg(receiveWith0); sm.setReceiveMsg(commReceiveService.removeRrdfix0(receiveWith0)); sm.setReceiveTime(DateUtils.getNowDate()); // log. { AjaxResult ar = commReceiveService.checkReceiveMsgFormat(sm.getReceiveMsg()); if (ar.isError()) { // todo: l.warn("####接收错误@格式错误#### = sm = {},ar = {}", sm, ar); sm.setResult(SimMsg.Result.RECEIVE_CHECK_FAIL); socketService.setImportantTaskRunning(sspv, false); return sm; } } { AjaxResult ar = commReceiveService.checkReceiveMsgMatch(sm); if (ar.isError()) { l.warn("####接收错误@匹配错误#### sm = {},ar = {}", sm, ar); sm.setResult(SimMsg.Result.RECEIVE_NOT_MATCH); socketService.setImportantTaskRunning(sspv, false); return sm; } } l.info("####接收成功#### = {}", sm); if (sim != null) { simService.updateLastReceivedTime(sim); } sm.setResult(SimMsg.Result.SUCCESS); socketService.setImportantTaskRunning(sspv, false); // 最后返回报文实体。 return sm; } catch (InterruptedException | IOException e) { // SocketTimeoutException l.error("SocketTimeoutException"); e.printStackTrace(); sm.setResult(SimMsg.Result.READ_TIMEOUT_EXCEPTION); socketService.setImportantTaskRunning(sspv, false); if (sim != null) { l.info("fail sim.getSimId() = {}", sim.getSimId()); } // Socket失败计数 socketService.failedPlus1(sspv); if (socketService.failedIsReachedMax(sspv, SocketService.SOCKET_CONNECT_RETRY_COUNT_LIMIT)) { // 达到重试次数上限,认为模拟器离线 if (sim != null) { simService.updateSimStateBySimId(sim.getSimId(), Sim.State.OFFLINE); } socketService.failedReset0(sspv); } // 先考虑一台模拟器演示。 // 进行重试 start if (sm.getRetryCount() == RETRY_COUNT_0) { l.warn("####RetryTotalCount不重试RETRY_COUNT_0#### = getSimMsgId = {}", sm.getSimMsgId()); return sm; } if (sm.getRetryCount() < retryTotalCount) { sm.retryCountPlus1(); send(sm, seat, sim, retryTotalCount, sleep); l.warn("####RetryTotalCount重试#### = {}", sm); } else { l.warn("####RetryTotalCount达到重试上限#### = {}", sm); } // 进行重试 end // 最后返回报文实体。 return sm; } } /** * todo: * * @param sm * @param s * @param retryTotalCount * @param sleep * @return */ public SimMsg sendRetry(final SimMsg sm, final Sim s, final int retryTotalCount, final long sleep) { return null; } /** * https://mvnrepository.com/artifact/com.infiniteautomation/modbus4j/3.0.3 */ public void test02() { byte[] data = {0x01, 0x03, 0x00, 0x00, 0x00, 0x02}; // 示例数据 // int crc = ModbusUtils.calculateCRC(data); // System.out.println("CRC-16/MODBUS 校验值: " + Integer.toHexString(crc)); } public static byte[] hexStrToByteArrs(String hexString) { // if (StringUtils.isEmpty(hexString)) { // return null; // } hexString = hexString.replaceAll(" ", ""); int len = hexString.length(); int index = 0; byte[] bytes = new byte[len / 2]; while (index < len) { String sub = hexString.substring(index, index + 2); bytes[index / 2] = (byte) Integer.parseInt(sub, 16); index += 2; } return bytes; } public String logBytesToString(byte[] bytes) { if (bytes == null || bytes.length == 0) { return ""; } StringBuffer sbHex = new StringBuffer(); byte[] buffer = new byte[bytes.length]; for (int i = 0; i < bytes.length; i++) { sbHex.append(String.format("%02X", buffer[i])); } return sbHex.toString(); } public static String bytesToHexV2(byte[] bytes) { StringBuilder hexString = new StringBuilder(); for (byte b : bytes) { String hex = Integer.toHexString(0xFF & b); // 将字节转换为十六进制字符串 if (hex.length() == 1) { hexString.append('0'); // 如果是单个字符,补零 } hexString.append(hex); } return hexString.toString(); } /** * checkOneSeatState 包装调用。 * seatId映射到Sim对象。 * * @param seatId * @return 可能为空 */ public Sim getSimBySeatIdNewVer(Long seatId) { Seat seat = seatService.selectSeatBySeatId(seatId); // 执行在线监测。 AjaxResult ar = commCheckService.checkOneSeatState(seat, true); if (ar != null) { return (Sim) ar.get(AjaxResult.DATA_TAG); } else { return null; } } /** * todo: * * @return */ public AjaxResult debugResetAnything() { // Step:关闭所有Socket连接 socketService.closeAll(); // Step:ping路由器,返回在线情况。 // Step:ping教员端主机,返回在线情况。 // Step:ping所有学员端主机,返回在线情况列表。 // Step:ping所有RS485,返回在线情况列表。 // Step:建立所有模拟器Socket,返回情况列表。 // Step:有Socket的前提下,尝试查询所有模拟器连接情况,得到连接的模拟器 型号、序列号信息 // Step:所有连接的模拟器,清除每一个模拟器的各个故障 // Step:所有连接的模拟器,读取每一个模拟器的各个故障,是否处于考试准备ok状态 // 删除debug表中所有数据。 debugFaultService.deleteAll(); return AjaxResult.success("全部重置成功。" + SimDateUtil.getNow()); } /** * 每30s执行一次。 */ public void scheduledChargingCount() { // 查询到唯一打开的 考试 或 训练 RealExamCollection rec = realExamCollectionService.selectRealExamCollectionOpenedNotSelfExercise(); // if (rec == null) { l.info("rec == null!"); return; } Long recId = rec.getExamCollectionId(); RealExam q = new RealExam(); q.setExamCollectionId(recId); // 只需要0002型模拟器。 q.setSimType(Sim.TYPE_0002); List list = realExamService.selectRealExamList(q); if (list == null || list.isEmpty()) { return; } list.forEach((RealExam re) -> { if (re == null || re.getExamId() == null || re.getExamId() == 0L) { return; } if (StringUtils.equals(re.getExamStatus(), RealExam.State.LOGGED_IN)) { Seat qSeat = seatService.selectSeatBySeatId(re.getSeatId()); // 检查后就知道seat上是否有sim AjaxResult ar = commCheckService.checkOneSeatState(qSeat, true); if (ar.isSuccess()) { // 获取当前simId Long currSimId = seatService.selectSeatBySeatId(re.getSeatId()).getCurrentSimId(); if (!currSimId.equals(Sim.ID_0)) { Integer count = simService.updateChargingCountPlusBySimId(currSimId); l.info("scheduledChargingCount count = {}", count); } else { l.info("scheduledChargingCount Sim.ID_0 seatId = {}", qSeat.getSeatId()); } } else { l.info("checkOneSeatState failed"); } } else { l.info("skip"); } }); } }