FswModbusScheduling.java 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. package com.jeesite.modules.scheduling;
  2. import com.github.xingshuangs.iot.protocol.common.buff.EByteBuffFormat;
  3. import com.github.xingshuangs.iot.protocol.modbus.service.ModbusTcp;
  4. import com.jeesite.common.config.Global;
  5. import com.jeesite.common.lang.ObjectUtils;
  6. import com.jeesite.common.lang.StringUtils;
  7. import com.jeesite.modules.mes.entity.MesLine;
  8. import com.jeesite.modules.mes.entity.MesProduct;
  9. import com.jeesite.modules.mes.entity.MesProductFsw;
  10. import com.jeesite.modules.mes.resp.BindMaterialResp;
  11. import com.jeesite.modules.mes.resp.ParamsResp;
  12. import com.jeesite.modules.mes.service.MesLineService;
  13. import com.jeesite.modules.mes.service.MesProductFswService;
  14. import com.jeesite.modules.mes.service.MesProductRecordService;
  15. import com.jeesite.modules.mes.service.MesProductService;
  16. import com.jeesite.modules.mes.web.MesProductRecordController;
  17. import org.slf4j.Logger;
  18. import org.slf4j.LoggerFactory;
  19. import org.springframework.beans.factory.annotation.Autowired;
  20. import org.springframework.context.annotation.Configuration;
  21. import org.springframework.scheduling.annotation.EnableScheduling;
  22. import org.springframework.scheduling.annotation.Scheduled;
  23. import java.nio.charset.StandardCharsets;
  24. import java.util.ArrayList;
  25. import java.util.List;
  26. import java.util.concurrent.atomic.AtomicBoolean;
  27. /**
  28. * FSW 设备 Modbus TCP 握手与过程参数采集。
  29. * <pre>
  30. * 寄存器约定:
  31. * 4740 总使能(1=执行整套逻辑,其它值跳过)
  32. * 3600 数据采集触发(1=请求采集,MES读完后置0)
  33. * 3610 质量校验结果(1=合格,2=不合格)
  34. * 3620~3649 工件码(30个字,ASCII拼接)
  35. * 3740 进给速度 → feedSpeed
  36. * 3780 进给倍率 → feedRate
  37. * 3820 主轴转速 → spindleSpeed
  38. * 3860 主轴倍率 → spindleRate
  39. * 4040 指定焊接点压力 → pressure
  40. * 4590 指定焊接点偏移量 → pyl
  41. * 4742 B轴角度 → jd
  42. *
  43. * 配置(application.yml):
  44. * mes.fsw.modbus.enabled 是否启用(默认 false)
  45. * mes.fsw.modbus.host PLC IP
  46. * mes.fsw.modbus.port 端口,默认 502
  47. * mes.fsw.modbus.unitId 从站号,默认 1
  48. * mes.fsw.modbus.oprno 工位号
  49. * mes.fsw.modbus.lineSn 产线编号(可空,空则按工件类型自动解析)
  50. * </pre>
  51. */
  52. @Configuration
  53. @EnableScheduling
  54. public class FswModbusScheduling {
  55. private static final Logger logger = LoggerFactory.getLogger(FswModbusScheduling.class);
  56. /** 总开关:为 1 时才执行质量校验与过程采集 */
  57. private static final int ADDR_ENABLE_FLAG = 4740;
  58. private static final int ADDR_COLLECT_FLAG = 3600;
  59. private static final int ADDR_QUALITY_RESULT = 3610;
  60. private static final int ADDR_SN_START = 3620;
  61. private static final int SN_REGISTER_COUNT = 60;
  62. private static final int ADDR_FEED_SPEED = 3740;
  63. private static final int ADDR_FEED_RATE = 3780;
  64. private static final int ADDR_SPINDLE_SPEED = 3820;
  65. private static final int ADDR_SPINDLE_RATE = 3860;
  66. private static final int ADDR_WELD_PRESSURE = 4040;
  67. private static final int ADDR_WELD_OFFSET = 4590;
  68. private static final int ADDR_B_AXIS_ANGLE = 4742;
  69. private static final String DEFAULT_HOST = "10.0.20.220";
  70. private static final int DEFAULT_PORT = 502;
  71. private static final int DEFAULT_UNIT_ID = 1;
  72. /** 过程参数落库固定产线 / 工位 */
  73. private static final String FIXED_LINE_SN = "XT";
  74. private static final String FIXED_OPRNO = "OP230B";
  75. @Autowired
  76. private MesProductRecordController mesProductRecordController;
  77. @Autowired
  78. private MesProductService mesProductService;
  79. @Autowired
  80. private MesLineService mesLineService;
  81. @Autowired
  82. private MesProductFswService mesProductFswService;
  83. @Autowired
  84. private MesProductRecordService mesProductRecordService;
  85. private final AtomicBoolean running = new AtomicBoolean(false);
  86. private volatile ModbusTcp modbusTcp;
  87. private volatile String lastCheckedSn;
  88. /**
  89. * 每秒执行:工件码质量校验 + 过程参数采集握手
  90. */
  91. @Scheduled(fixedRate = 1000)
  92. public void collect() {
  93. String enabled = Global.getConfig("mes.fsw.modbus.enabled");
  94. if (StringUtils.isBlank(enabled) || !"true".equalsIgnoreCase(enabled.trim())) {
  95. return;
  96. }
  97. runOnce();
  98. }
  99. /**
  100. * 直接执行一轮完整逻辑(供测试 / 手工触发)。
  101. * 不受 mes.fsw.modbus.enabled 开关限制,仍会检查 4740 使能点位。
  102. */
  103. public void runOnce() {
  104. if (!running.compareAndSet(false, true)) {
  105. logger.warn("FSW Modbus上一轮尚未结束,跳过本次调用");
  106. return;
  107. }
  108. try {
  109. ModbusTcp plc = getClient();
  110. Short enable = plc.readInt16(ADDR_ENABLE_FLAG);
  111. if (enable == null || enable != 1) {
  112. logger.info("FSW Modbus 4740={},未使能,跳过", enable);
  113. return;
  114. }
  115. handleQualityCheck(plc);
  116. handleCollectTrigger(plc);
  117. } catch (Exception e) {
  118. logger.error("FSW Modbus任务失败:{}", e.getMessage());
  119. closeClient();
  120. } finally {
  121. running.set(false);
  122. }
  123. }
  124. /**
  125. * 读取工件码 → 质量校验 → 写回 3610(1合格 / 2不合格)
  126. */
  127. private void handleQualityCheck(ModbusTcp plc) {
  128. String sn = readWorkpieceCode(plc);
  129. if (StringUtils.isBlank(sn)) {
  130. lastCheckedSn = null;
  131. return;
  132. }
  133. if (sn.equals(lastCheckedSn)) {
  134. return;
  135. }
  136. boolean pass = doQualityCheck(sn);
  137. plc.writeInt16(ADDR_QUALITY_RESULT, pass ? (short) 1 : (short) 2);
  138. lastCheckedSn = sn;
  139. logger.info("FSW质量校验 sn={} result={}", sn, pass ? 1 : 2);
  140. }
  141. /**
  142. * 3600=1 时采集过程参数,完成后置 0
  143. */
  144. private void handleCollectTrigger(ModbusTcp plc) {
  145. Short flag = plc.readInt16(ADDR_COLLECT_FLAG);
  146. if (flag == null || flag != 1) {
  147. return;
  148. }
  149. String sn = readWorkpieceCode(plc);
  150. float feedSpeed = plc.readFloat32(ADDR_FEED_SPEED, EByteBuffFormat.BA_DC);
  151. float feedRate = plc.readFloat32(ADDR_FEED_RATE, EByteBuffFormat.BA_DC);
  152. float spindleSpeed = plc.readFloat32(ADDR_SPINDLE_SPEED, EByteBuffFormat.BA_DC);
  153. float spindleRate = plc.readFloat32(ADDR_SPINDLE_RATE, EByteBuffFormat.BA_DC);
  154. float weldPressure = plc.readFloat32(ADDR_WELD_PRESSURE, EByteBuffFormat.BA_DC);
  155. float weldOffset = plc.readFloat32(ADDR_WELD_OFFSET, EByteBuffFormat.BA_DC);
  156. float bAxisAngle = plc.readFloat32(ADDR_B_AXIS_ANGLE, EByteBuffFormat.BA_DC);
  157. logger.info("FSW过程采集 sn={} 进给速度={} 进给倍率={} 主轴转速={} 主轴倍率={} 焊接压力={} 偏移量={} B轴角度={}",
  158. sn, feedSpeed, feedRate, spindleSpeed, spindleRate, weldPressure, weldOffset, bAxisAngle);
  159. saveProcessData(sn, feedSpeed, feedRate, spindleSpeed, spindleRate, weldPressure, weldOffset, bAxisAngle);
  160. plc.writeInt16(ADDR_COLLECT_FLAG, (short) 0);
  161. }
  162. private boolean doQualityCheck(String sn) {
  163. String oprno = Global.getConfig("mes.fsw.modbus.oprno");
  164. if (StringUtils.isBlank(oprno)) {
  165. logger.warn("FSW质量校验失败:未配置 mes.fsw.modbus.oprno");
  166. return false;
  167. }
  168. String oldOprno = oprno;
  169. if (oldOprno.length() == 5) {
  170. oldOprno = oldOprno + "A";
  171. }
  172. String lineSn = Global.getConfig("mes.fsw.modbus.lineSn");
  173. if (StringUtils.isBlank(lineSn)) {
  174. lineSn = resolveLineSn(sn);
  175. }
  176. if (StringUtils.isBlank(lineSn)) {
  177. logger.warn("FSW质量校验失败:无法解析产线编号 sn={}", sn);
  178. return false;
  179. }
  180. try {
  181. String ret = mesProductRecordController.checkQualityCommon(oldOprno, sn, lineSn, "");
  182. if (StringUtils.isBlank(ret) || ret.length() < 5) {
  183. return false;
  184. }
  185. return "UD".equals(ret.substring(3, 5));
  186. } catch (Exception e) {
  187. logger.error("FSW质量校验异常 sn={}:{}", sn, e.getMessage());
  188. return false;
  189. }
  190. }
  191. private String resolveLineSn(String sn) {
  192. MesProduct product = mesProductService.findBySn(sn);
  193. if (ObjectUtils.isEmpty(product) || StringUtils.isBlank(product.getCate())) {
  194. return null;
  195. }
  196. MesLine query = new MesLine();
  197. query.setProductCate(product.getCate());
  198. query.setStatus("0");
  199. MesLine line = mesLineService.findInfo(query);
  200. if (ObjectUtils.isEmpty(line)) {
  201. return null;
  202. }
  203. return line.getSn();
  204. }
  205. private void saveProcessData(String sn, float feedSpeed, float feedRate,
  206. float spindleSpeed, float spindleRate, float weldPressure, float weldOffset, float bAxisAngle) {
  207. if (StringUtils.isBlank(sn)) {
  208. return;
  209. }
  210. try {
  211. MesProductFsw entity = new MesProductFsw();
  212. entity.setSn(sn);
  213. entity.setOprno(FIXED_OPRNO);
  214. entity.setLineSn(FIXED_LINE_SN);
  215. entity.setFeedSpeed(fmt(feedSpeed));
  216. entity.setFeedRate(fmt(feedRate));
  217. entity.setSpindleSpeed(fmt(spindleSpeed));
  218. entity.setSpindleRate(fmt(spindleRate));
  219. entity.setPressure(fmt(weldPressure));
  220. entity.setPyl(fmt(weldOffset));
  221. entity.setJd(fmt(bAxisAngle));
  222. mesProductFswService.save(entity);
  223. // 保存完成后提交工位总结果为 OK
  224. List<ParamsResp> params = new ArrayList<>();
  225. List<BindMaterialResp> bmlists = new ArrayList<>();
  226. mesProductRecordService.updateRecordResult(sn, FIXED_OPRNO, "100000", "", "OK",
  227. "", params, FIXED_LINE_SN, true, bmlists, null);
  228. logger.info("FSW过程参数已保存并更新工位结果 OK sn={} oprno={}", sn, FIXED_OPRNO);
  229. } catch (Exception e) {
  230. logger.error("FSW过程参数保存失败 sn={}:{}", sn, e.getMessage());
  231. }
  232. }
  233. /**
  234. * 3620 起连续 30 个保持寄存器,按 ASCII 拼接工件码
  235. */
  236. private String readWorkpieceCode(ModbusTcp plc) {
  237. byte[] bytes = plc.readHoldRegister(ADDR_SN_START, SN_REGISTER_COUNT);
  238. if (bytes == null || bytes.length == 0) {
  239. return null;
  240. }
  241. String raw = new String(bytes, StandardCharsets.US_ASCII);
  242. StringBuilder sb = new StringBuilder(raw.length());
  243. for (int i = 0; i < raw.length(); i++) {
  244. char c = raw.charAt(i);
  245. if (c == 0 || c == ' ') {
  246. continue;
  247. }
  248. if (c >= 32 && c < 127) {
  249. sb.append(c);
  250. }
  251. }
  252. return sb.toString().trim();
  253. }
  254. private ModbusTcp getClient() {
  255. ModbusTcp client = modbusTcp;
  256. if (client != null) {
  257. return client;
  258. }
  259. synchronized (this) {
  260. if (modbusTcp == null) {
  261. String host = Global.getConfig("mes.fsw.modbus.host");
  262. if (StringUtils.isBlank(host)) {
  263. host = DEFAULT_HOST;
  264. }
  265. int port = parseInt(Global.getConfig("mes.fsw.modbus.port"), DEFAULT_PORT);
  266. int unitId = parseInt(Global.getConfig("mes.fsw.modbus.unitId"), DEFAULT_UNIT_ID);
  267. modbusTcp = new ModbusTcp(unitId, host, port);
  268. logger.info("FSW Modbus已连接 {}:{} unitId={}", host, port, unitId);
  269. }
  270. return modbusTcp;
  271. }
  272. }
  273. private void closeClient() {
  274. synchronized (this) {
  275. if (modbusTcp != null) {
  276. try {
  277. modbusTcp.close();
  278. } catch (Exception ignore) {
  279. }
  280. modbusTcp = null;
  281. }
  282. }
  283. }
  284. private static int parseInt(String val, int defaultVal) {
  285. if (StringUtils.isBlank(val)) {
  286. return defaultVal;
  287. }
  288. try {
  289. return Integer.parseInt(val.trim());
  290. } catch (Exception e) {
  291. return defaultVal;
  292. }
  293. }
  294. private static String fmt(float val) {
  295. return String.format("%.3f", val);
  296. }
  297. }