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 params = new ArrayList<>(); List bmlists = new ArrayList<>(); mesProductRecordService.updateRecordResult(sn, FIXED_OPRNO, "100000", "", "OK", "", params, FIXED_LINE_SN, true, bmlists, null); logger.info("FSW过程参数已保存并更新工位结果 OK sn={} oprno={}", sn, FIXED_OPRNO); } catch (Exception e) { logger.error("FSW过程参数保存失败 sn={}:{}", sn, e.getMessage()); } } /** * 3620 起连续 30 个保持寄存器,按 ASCII 拼接工件码 */ private String readWorkpieceCode(ModbusTcp plc) { byte[] bytes = plc.readHoldRegister(ADDR_SN_START, SN_REGISTER_COUNT); if (bytes == null || bytes.length == 0) { return null; } String raw = new String(bytes, StandardCharsets.US_ASCII); StringBuilder sb = new StringBuilder(raw.length()); for (int i = 0; i < raw.length(); i++) { char c = raw.charAt(i); if (c == 0 || c == ' ') { continue; } if (c >= 32 && c < 127) { sb.append(c); } } return sb.toString().trim(); } private ModbusTcp getClient() { ModbusTcp client = modbusTcp; if (client != null) { return client; } synchronized (this) { if (modbusTcp == null) { String host = Global.getConfig("mes.fsw.modbus.host"); if (StringUtils.isBlank(host)) { host = DEFAULT_HOST; } int port = parseInt(Global.getConfig("mes.fsw.modbus.port"), DEFAULT_PORT); int unitId = parseInt(Global.getConfig("mes.fsw.modbus.unitId"), DEFAULT_UNIT_ID); modbusTcp = new ModbusTcp(unitId, host, port); logger.info("FSW Modbus已连接 {}:{} unitId={}", host, port, unitId); } return modbusTcp; } } private void closeClient() { synchronized (this) { if (modbusTcp != null) { try { modbusTcp.close(); } catch (Exception ignore) { } modbusTcp = null; } } } private static int parseInt(String val, int defaultVal) { if (StringUtils.isBlank(val)) { return defaultVal; } try { return Integer.parseInt(val.trim()); } catch (Exception e) { return defaultVal; } } private static String fmt(float val) { return String.format("%.3f", val); } }