package com.jeesite.modules.scheduling; import com.github.xingshuangs.iot.protocol.s7.enums.EPlcType; import com.github.xingshuangs.iot.protocol.s7.service.S7PLC; import com.jeesite.common.config.Global; import com.jeesite.common.lang.ObjectUtils; import com.jeesite.common.lang.StringUtils; import com.jeesite.modules.mes.entity.MesLine; import com.jeesite.modules.mes.entity.MesProduct; import com.jeesite.modules.mes.entity.MesProductRecord; import com.jeesite.modules.mes.entity.MesProductTj; import com.jeesite.modules.mes.resp.BindMaterialResp; import com.jeesite.modules.mes.resp.ParamsResp; import com.jeesite.modules.mes.service.MesLineService; import com.jeesite.modules.mes.service.MesProductRecordService; import com.jeesite.modules.mes.service.MesProductService; import com.jeesite.modules.mes.service.MesProductTjService; import com.jeesite.modules.mes.util.CommonUitl; import com.jeesite.modules.mes.web.MesProductRecordController; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; /** * 涂胶 S7 采集与质量握手(OP220,固定工位号)。 *
* DB1300 点位: * 0.0 框架码 String[30] * 64.0 扫码反馈信号 * 64.2 质量校验通过反馈(校验通过 MES 写 true,工位完成后 MES 写 false) * 64.4 涂胶数据采集触发 * 64.5 整体完成信号(true 时提交工位 OK 并回查确认落库,成功后将 64.2 置 false) * 66.0 涂胶序号 Word * 68.0 涂胶速度 Real * 72.0 涂胶长度 Word * * 配置(application.yml): * mes.tj.enabled 是否启用(默认 false) * mes.tj.plc.ip PLC IP,默认 10.0.40.100 * mes.tj.lineSn 产线编号,默认 XT **/ @Configuration @EnableScheduling public class ProductTjScheduling { private static final Logger logger = LoggerFactory.getLogger(ProductTjScheduling.class); private static final String ADDR_SN = "DB1300.0.0"; private static final int SN_LENGTH = 30; private static final String ADDR_SCAN_FEEDBACK = "DB1300.64.0"; private static final String ADDR_QUALITY_PASS = "DB1300.64.2"; private static final String ADDR_GLUE_COLLECT = "DB1300.64.4"; private static final String ADDR_COMPLETE = "DB1300.64.5"; private static final String ADDR_GLUE_SEQ = "DB1300.66.0"; private static final String ADDR_GLUE_SPEED = "DB1300.68.0"; private static final String ADDR_GLUE_LENGTH = "DB1300.72.0"; private static final String DEFAULT_PLC_IP = "10.0.40.100"; private static final String OPRNO = "OP220"; private static final String DEFAULT_LINE_SN = "XT"; @Autowired private MesProductRecordController mesProductRecordController; @Autowired private MesProductService mesProductService; @Autowired private MesLineService mesLineService; @Autowired private MesProductTjService mesProductTjService; @Autowired private MesProductRecordService mesProductRecordService; private final AtomicBoolean running = new AtomicBoolean(false); private volatile String lastKnownSn; private volatile String lastCompletedSn; @Scheduled(fixedRate = 100) public void collect() { String enabled = Global.getConfig("mes.tj.enabled"); if (StringUtils.isBlank(enabled) || !"true".equalsIgnoreCase(enabled.trim())) { return; } runOnce(); } /** * 直接执行一轮(供测试调用) */ public void runOnce() { if (!running.compareAndSet(false, true)) { return; } String ip = getPlcIp(); S7PLC s7PLC = null; try { s7PLC = new S7PLC(EPlcType.S1200, ip); handleScanFeedback(s7PLC); handleGlueCollect(s7PLC); handleComplete(s7PLC); } catch (Exception e) { logger.error("涂胶S7任务失败[{}]:{}", ip, e.getMessage()); } finally { if (s7PLC != null) { try { s7PLC.close(); } catch (Exception ignore) { } } running.set(false); } } /** * 扫码反馈为 true:读框架码 → 质量校验 → 通过则写 DB1300.64.2=true */ private void handleScanFeedback(S7PLC s7PLC) { if (!s7PLC.readBoolean(ADDR_SCAN_FEEDBACK)) { lastCompletedSn = null; return; } String sn = readWorkpieceCode(s7PLC); if (StringUtils.isBlank(sn)) { return; } lastKnownSn = sn; boolean pass = doQualityCheck(sn); if (pass) { s7PLC.writeBoolean(ADDR_QUALITY_PASS, true); logger.info("涂胶质量校验通过 sn={},已写 {}=true", sn, ADDR_QUALITY_PASS); } else { logger.warn("涂胶质量校验未通过 sn={}", sn); } } /** * 涂胶采集触发为 true:读涂胶参数 → 落库 mes_product_tj → 清触发位 */ private void handleGlueCollect(S7PLC s7PLC) { if (!s7PLC.readBoolean(ADDR_GLUE_COLLECT)) { return; } String sn = readWorkpieceCode(s7PLC); if (StringUtils.isBlank(sn)) { sn = lastKnownSn; } if (StringUtils.isBlank(sn)) { logger.error("涂胶数据采集失败:无工件码,保持 {}=true 待重试", ADDR_GLUE_COLLECT); return; } lastKnownSn = sn; int glueSeq = s7PLC.readUInt16(ADDR_GLUE_SEQ); float glueSpeed = s7PLC.readFloat32(ADDR_GLUE_SPEED); int glueLength = s7PLC.readUInt16(ADDR_GLUE_LENGTH); logger.info("涂胶数据采集 sn={} 序号={} 速度={} 长度={}", sn, glueSeq, glueSpeed, glueLength); if (!saveTjData(sn, glueSeq, glueSpeed, glueLength)) { logger.error("涂胶数据保存失败,保持 {}=true 待重试 sn={}", ADDR_GLUE_COLLECT, sn); return; } s7PLC.writeBoolean(ADDR_GLUE_COLLECT, false); logger.info("涂胶数据采集完成 sn={} 序号={},已将 {} 置 false", sn, glueSeq, ADDR_GLUE_COLLECT); } /** * DB1300.64.5 整体完成信号为 true:提交工位 OK → 成功后将 DB1300.64.2 置 false */ private void handleComplete(S7PLC s7PLC) { if (!s7PLC.readBoolean(ADDR_COMPLETE)) { return; } String sn = readWorkpieceCode(s7PLC); if (StringUtils.isBlank(sn)) { sn = lastKnownSn; } if (StringUtils.isBlank(sn)) { logger.error("涂胶工位完成提交失败:无工件码,保持 {}=true 待重试", ADDR_COMPLETE); return; } if (sn.equals(lastCompletedSn)) { return; } if (!submitStationResult(sn, "OK")) { logger.error("涂胶工位结果提交失败,保持信号待重试 sn={}", sn); return; } s7PLC.writeBoolean(ADDR_QUALITY_PASS, false); lastCompletedSn = sn; logger.info("涂胶工位完成 sn={},已提交 OK 并将 {} 置 false", sn, ADDR_QUALITY_PASS); } private boolean saveTjData(String sn, int glueSeq, float glueSpeed, int glueLength) { try { MesProductTj entity = new MesProductTj(); entity.setSn(sn); entity.setOprno(OPRNO); entity.setTjData(String.valueOf(glueSeq)); entity.setTjSpeed(fmt(glueSpeed)); entity.setTjLength(String.valueOf(glueLength)); mesProductTjService.save(entity); return true; } catch (Exception e) { logger.error("涂胶数据保存异常 sn={}:{}", sn, e.getMessage()); return false; } } /** * 直接提交工位结果(供测试 / 手工触发),不读写 PLC 信号。 * 逻辑与 64.5 完成时的 submitStationResult 相同(含回查与强制落库)。 * * @param sn 工件码 * @param result 结果,通常为 OK * @return true=craft=100000 已确认为目标结果 */ public boolean submitStationResultNow(String sn, String result) { if (StringUtils.isBlank(sn)) { logger.error("涂胶工位结果提交跳过:工件码为空"); return false; } return submitStationResult(sn.trim(), StringUtils.isBlank(result) ? "OK" : result.trim()); } /** * 提交工位结果为 OK,并回查 craft=100000 确认已落库。 * updateRecordResult 在单部件工位或缺少总结果行时可能静默跳过,此时改走 updateRecord 强制更新。 */ private boolean submitStationResult(String sn, String result) { String oldOprno = OPRNO + "A"; String lineSn = getLineSn(sn); if (StringUtils.isBlank(lineSn)) { logger.warn("涂胶工位结果提交失败:无法解析产线编号 sn={}", sn); return false; } try { List