package com.jeesite.modules.scheduling; import com.github.xingshuangs.iot.protocol.common.buff.EByteBuffFormat; import com.github.xingshuangs.iot.protocol.modbus.service.ModbusTcp; 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.MesProductFsw; 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.MesProductFswService; import com.jeesite.modules.mes.service.MesProductRecordService; import com.jeesite.modules.mes.service.MesProductService; 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.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; /** * FSW 设备 Modbus TCP 握手与过程参数采集。 *
* 寄存器约定: * 4740 总使能(1=执行整套逻辑,其它值跳过) * 3600 数据采集触发(1=请求采集,MES读完后置0) * 3610 质量校验结果(1=合格,2=不合格) * 3620~3649 工件码(30个字,ASCII拼接) * 3740 进给速度 → feedSpeed * 3780 进给倍率 → feedRate * 3820 主轴转速 → spindleSpeed * 3860 主轴倍率 → spindleRate * 4040 指定焊接点压力 → pressure * 4590 指定焊接点偏移量 → pyl * 4742 B轴角度 → jd * * 配置(application.yml): * mes.fsw.modbus.enabled 是否启用(默认 false) * mes.fsw.modbus.host PLC IP * mes.fsw.modbus.port 端口,默认 502 * mes.fsw.modbus.unitId 从站号,默认 1 * mes.fsw.modbus.oprno 工位号 * mes.fsw.modbus.lineSn 产线编号(可空,空则按工件类型自动解析) **/ @Configuration @EnableScheduling public class FswModbusScheduling { private static final Logger logger = LoggerFactory.getLogger(FswModbusScheduling.class); /** 总开关:为 1 时才执行质量校验与过程采集 */ private static final int ADDR_ENABLE_FLAG = 4740; private static final int ADDR_COLLECT_FLAG = 3600; private static final int ADDR_QUALITY_RESULT = 3610; private static final int ADDR_SN_START = 3620; private static final int SN_REGISTER_COUNT = 60; private static final int ADDR_FEED_SPEED = 3740; private static final int ADDR_FEED_RATE = 3780; private static final int ADDR_SPINDLE_SPEED = 3820; private static final int ADDR_SPINDLE_RATE = 3860; private static final int ADDR_WELD_PRESSURE = 4040; private static final int ADDR_WELD_OFFSET = 4590; private static final int ADDR_B_AXIS_ANGLE = 4742; private static final String DEFAULT_HOST = "10.0.20.220"; private static final int DEFAULT_PORT = 502; private static final int DEFAULT_UNIT_ID = 1; /** 过程参数落库固定产线 / 工位 */ private static final String FIXED_LINE_SN = "XT"; private static final String FIXED_OPRNO = "OP230B"; @Autowired private MesProductRecordController mesProductRecordController; @Autowired private MesProductService mesProductService; @Autowired private MesLineService mesLineService; @Autowired private MesProductFswService mesProductFswService; @Autowired private MesProductRecordService mesProductRecordService; private final AtomicBoolean running = new AtomicBoolean(false); private volatile ModbusTcp modbusTcp; private volatile String lastCheckedSn; /** * 每秒执行:工件码质量校验 + 过程参数采集握手 */ @Scheduled(fixedRate = 1000) public void collect() { String enabled = Global.getConfig("mes.fsw.modbus.enabled"); if (StringUtils.isBlank(enabled) || !"true".equalsIgnoreCase(enabled.trim())) { return; } runOnce(); } /** * 直接执行一轮完整逻辑(供测试 / 手工触发)。 * 不受 mes.fsw.modbus.enabled 开关限制,仍会检查 4740 使能点位。 */ public void runOnce() { if (!running.compareAndSet(false, true)) { logger.warn("FSW Modbus上一轮尚未结束,跳过本次调用"); return; } try { ModbusTcp plc = getClient(); Short enable = plc.readInt16(ADDR_ENABLE_FLAG); if (enable == null || enable != 1) { logger.info("FSW Modbus 4740={},未使能,跳过", enable); return; } handleQualityCheck(plc); handleCollectTrigger(plc); } catch (Exception e) { logger.error("FSW Modbus任务失败:{}", e.getMessage()); closeClient(); } finally { running.set(false); } } /** * 读取工件码 → 质量校验 → 写回 3610(1合格 / 2不合格) */ private void handleQualityCheck(ModbusTcp plc) { String sn = readWorkpieceCode(plc); if (StringUtils.isBlank(sn)) { lastCheckedSn = null; return; } if (sn.equals(lastCheckedSn)) { return; } boolean pass = doQualityCheck(sn); plc.writeInt16(ADDR_QUALITY_RESULT, pass ? (short) 1 : (short) 2); lastCheckedSn = sn; logger.info("FSW质量校验 sn={} result={}", sn, pass ? 1 : 2); } /** * 3600=1 时采集过程参数,完成后置 0 */ private void handleCollectTrigger(ModbusTcp plc) { Short flag = plc.readInt16(ADDR_COLLECT_FLAG); if (flag == null || flag != 1) { return; } String sn = readWorkpieceCode(plc); float feedSpeed = plc.readFloat32(ADDR_FEED_SPEED, EByteBuffFormat.BA_DC); float feedRate = plc.readFloat32(ADDR_FEED_RATE, EByteBuffFormat.BA_DC); float spindleSpeed = plc.readFloat32(ADDR_SPINDLE_SPEED, EByteBuffFormat.BA_DC); float spindleRate = plc.readFloat32(ADDR_SPINDLE_RATE, EByteBuffFormat.BA_DC); float weldPressure = plc.readFloat32(ADDR_WELD_PRESSURE, EByteBuffFormat.BA_DC); float weldOffset = plc.readFloat32(ADDR_WELD_OFFSET, EByteBuffFormat.BA_DC); float bAxisAngle = plc.readFloat32(ADDR_B_AXIS_ANGLE, EByteBuffFormat.BA_DC); logger.info("FSW过程采集 sn={} 进给速度={} 进给倍率={} 主轴转速={} 主轴倍率={} 焊接压力={} 偏移量={} B轴角度={}", sn, feedSpeed, feedRate, spindleSpeed, spindleRate, weldPressure, weldOffset, bAxisAngle); saveProcessData(sn, feedSpeed, feedRate, spindleSpeed, spindleRate, weldPressure, weldOffset, bAxisAngle); plc.writeInt16(ADDR_COLLECT_FLAG, (short) 0); } private boolean doQualityCheck(String sn) { String oprno = Global.getConfig("mes.fsw.modbus.oprno"); if (StringUtils.isBlank(oprno)) { logger.warn("FSW质量校验失败:未配置 mes.fsw.modbus.oprno"); return false; } String oldOprno = oprno; if (oldOprno.length() == 5) { oldOprno = oldOprno + "A"; } String lineSn = Global.getConfig("mes.fsw.modbus.lineSn"); if (StringUtils.isBlank(lineSn)) { lineSn = resolveLineSn(sn); } if (StringUtils.isBlank(lineSn)) { logger.warn("FSW质量校验失败:无法解析产线编号 sn={}", sn); return false; } try { String ret = mesProductRecordController.checkQualityCommon(oldOprno, sn, lineSn, ""); if (StringUtils.isBlank(ret) || ret.length() < 5) { return false; } return "UD".equals(ret.substring(3, 5)); } catch (Exception e) { logger.error("FSW质量校验异常 sn={}:{}", sn, e.getMessage()); return false; } } private String resolveLineSn(String sn) { MesProduct product = mesProductService.findBySn(sn); if (ObjectUtils.isEmpty(product) || StringUtils.isBlank(product.getCate())) { return null; } MesLine query = new MesLine(); query.setProductCate(product.getCate()); query.setStatus("0"); MesLine line = mesLineService.findInfo(query); if (ObjectUtils.isEmpty(line)) { return null; } return line.getSn(); } private void saveProcessData(String sn, float feedSpeed, float feedRate, float spindleSpeed, float spindleRate, float weldPressure, float weldOffset, float bAxisAngle) { if (StringUtils.isBlank(sn)) { return; } try { MesProductFsw entity = new MesProductFsw(); entity.setSn(sn); entity.setOprno(FIXED_OPRNO); entity.setLineSn(FIXED_LINE_SN); entity.setFeedSpeed(fmt(feedSpeed)); entity.setFeedRate(fmt(feedRate)); entity.setSpindleSpeed(fmt(spindleSpeed)); entity.setSpindleRate(fmt(spindleRate)); entity.setPressure(fmt(weldPressure)); entity.setPyl(fmt(weldOffset)); entity.setJd(fmt(bAxisAngle)); mesProductFswService.save(entity); // 保存完成后提交工位总结果为 OK List