WorkJPanelCnc.java 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548
  1. package com.mes.component;
  2. import com.alibaba.fastjson2.JSON;
  3. import com.alibaba.fastjson2.JSONObject;
  4. import com.github.xingshuangs.iot.protocol.s7.enums.EPlcType;
  5. import com.github.xingshuangs.iot.protocol.s7.service.S7PLC;
  6. import com.mes.ui.CacheData;
  7. import com.mes.ui.DataUtil;
  8. import com.mes.ui.MesClient;
  9. import com.mes.util.JdbcUtils;
  10. import com.mes.util.SnSlot;
  11. import org.slf4j.Logger;
  12. import org.slf4j.LoggerFactory;
  13. import javax.swing.*;
  14. import java.awt.*;
  15. import java.awt.event.MouseAdapter;
  16. import java.awt.event.MouseEvent;
  17. import java.util.Timer;
  18. import java.util.TimerTask;
  19. /**
  20. * 总成CNC工位面板:OP130A-E 正面(PLC 92),OP135A-E 反面(PLC 93),A-E 对应 DB7000-DB7004
  21. *
  22. * 点位 DB700X:0.0 请求上料 / 0.1 请求下料 / 0.2 运行中 / 0.3 设备状态 / 0.4 MES允许上料
  23. * 0.5 MES允许下料 / 0.6 心跳 / 0.7 上料到设备;1.0、1.1、2、4 为预留,不读不写
  24. *
  25. * 上料:请求上料时从 sourceSlot 取码(正面=扫码槽,反面=反面待上料码),校验通过写 0.4
  26. * 进设备:0.7 上升沿或 0.2 运行中,码从「上料」移到「加工」
  27. * 下料:请求下料时上传结果;正面要等反面待上料码为空,上传成功后存码给反面,再写 0.5
  28. */
  29. public class WorkJPanelCnc extends JPanel {
  30. public static final Logger log = LoggerFactory.getLogger(WorkJPanelCnc.class);
  31. public static final String PLC_FRONT = "192.168.90.92"; // 正面 OP130
  32. public static final String PLC_BACK = "192.168.90.93"; // 反面 OP135
  33. private static final String[] DBS = {"DB7000", "DB7001", "DB7002", "DB7003", "DB7004"};
  34. private static final String[] CN_NO = {"一", "二", "三", "四", "五", "六", "七", "八", "九", "十"};
  35. public String oprno; // OP130 / OP135
  36. public String pos; // A-E
  37. public boolean front; // true=正面
  38. public String plcIp;
  39. public String plcDb;
  40. public String deviceName;
  41. public SnSlot sourceSlot; // 上料取码来源
  42. public SnSlot targetSlot; // 下料存码目标,反面为 null
  43. public S7PLC s7PLC;
  44. public Integer workWidth = 470;
  45. public Integer workHeight = 300;
  46. public Integer tjFeedingFlag = 0; // 0=未取码 1=已取码校验通过
  47. public Integer tjFlag = 0; // 0=空闲 1=已上料 2=加工中 3=加工结束已上传
  48. public Integer tjStatus = 0; // 下料上传标识 0=未上传 1=已上传
  49. public String lastSn = "";
  50. private boolean feedingReleased = true; // 请求上料复位过,才处理下一次上料
  51. private boolean lastToDevice = false; // 上一轮 0.7,用于取上升沿
  52. private boolean plcHeart = false;
  53. public JButton status_menu;
  54. public JTextField productSnFeeding; // 上料码
  55. public JTextField productSn; // 加工码
  56. public JButton resetFeedingBtn;
  57. public JButton resetBtn;
  58. public JLabel feedinglabel; // 0.0 请求上料
  59. public JLabel feedingmeslabel; // 0.4 MES允许上料
  60. public JLabel runlabel; // 0.2 运行中
  61. public JLabel blankinglabel; // 0.1 请求下料
  62. public JLabel blankingmeslabel; // 0.5 MES允许下料
  63. public JLabel feedingtodevicelabel; // 0.7 上料到设备
  64. public ImageIcon imageGreen;
  65. public ImageIcon imageGray;
  66. public JButton signalBtn;
  67. public PlcSignalMonitor signalMonitor;
  68. public CacheData cacheData;
  69. public String cacheName;
  70. private String lastCacheJson = "";
  71. public Timer oprnoTimer;
  72. public Timer heartTimer;
  73. public WorkJPanelCnc(String oprno, String pos) {
  74. this.setLayout(null);
  75. this.setBackground(Color.WHITE);
  76. this.oprno = oprno;
  77. this.pos = pos;
  78. this.front = "OP130".equals(oprno);
  79. int idx = pos.charAt(0) - 'A';
  80. plcDb = DBS[idx];
  81. plcIp = front ? PLC_FRONT : PLC_BACK;
  82. deviceName = "东侧" + CN_NO[front ? idx : idx + 5] + "号机";
  83. sourceSlot = front ? MesClient.frontSlot : MesClient.backSlot;
  84. targetSlot = front ? MesClient.backSlot : null;
  85. s7PLC = new S7PLC(EPlcType.S1500, plcIp);
  86. s7PLC.setReceiveTimeout(1000);
  87. s7PLC.setConnectTimeout(1000);
  88. cacheName = "cache_" + oprno + pos;
  89. cacheData = JdbcUtils.getCache(cacheName);
  90. if (cacheData == null) {
  91. cacheData = new CacheData();
  92. cacheData.setLastSn("");
  93. cacheData.setSn("");
  94. cacheData.setFeedingSn("");
  95. cacheData.setTjStatus(0);
  96. cacheData.setTjFlag(0);
  97. cacheData.setTjFeedingFlag(0);
  98. cacheData.setError(-1);
  99. cacheData.setMsg("");
  100. JdbcUtils.setCache(cacheName, JSON.toJSONString(cacheData));
  101. }
  102. initUI();
  103. formatElementUI();
  104. startOprnoTimer();
  105. heartData();
  106. }
  107. // 界面 -----------------------------------------------------------------------------------------------------
  108. private void initUI() {
  109. JLabel oprnoLabel = new JLabel(oprno + pos);
  110. oprnoLabel.setHorizontalAlignment(SwingConstants.CENTER);
  111. oprnoLabel.setForeground(Color.BLACK);
  112. oprnoLabel.setFont(new Font("微软雅黑", Font.PLAIN, 20));
  113. oprnoLabel.setBounds(0, 0, workWidth / 5, 40);
  114. this.add(oprnoLabel);
  115. JLabel nameLabel = new JLabel(deviceName);
  116. nameLabel.setHorizontalAlignment(SwingConstants.CENTER);
  117. nameLabel.setForeground(Color.BLACK);
  118. nameLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
  119. nameLabel.setBounds(0, 17, workWidth / 5, 40);
  120. this.add(nameLabel);
  121. status_menu = new JButton("等待请求上料");
  122. status_menu.setBounds(workWidth / 5, 0, workWidth * 4 / 5, 40);
  123. status_menu.setForeground(Color.GREEN);
  124. status_menu.setFont(new Font("微软雅黑", Font.PLAIN, 16));
  125. status_menu.setBackground(Color.BLACK);
  126. this.add(status_menu);
  127. addRowLabel("上料", 50);
  128. productSnFeeding = createSnField(50);
  129. resetFeedingBtn = createResetBtn(50, () -> {
  130. String sn = productSnFeeding.getText().trim();
  131. if (confirm("确定刷新上料工件" + sn + "吗?\n刷新后该码不会退回待上料码")) {
  132. resetFeeding();
  133. }
  134. });
  135. addRowLabel("加工", 100);
  136. productSn = createSnField(100);
  137. resetBtn = createResetBtn(100, () -> {
  138. String sn = productSn.getText().trim();
  139. String tip = front ? "\n刷新后不上传该件结果,也不会存入反面待上料码" : "\n刷新后不上传该件结果";
  140. if (confirm("确定刷新加工工件" + sn + "吗?" + tip)) {
  141. resetBlanking("已手动清除加工工件");
  142. }
  143. });
  144. imageGreen = scaledIcon("/bg/green_dot.png");
  145. imageGray = scaledIcon("/bg/grey_dot.png");
  146. feedinglabel = createLight("请求上料", 0, 150);
  147. feedingmeslabel = createLight("MES允许上料", workWidth / 3, 150);
  148. runlabel = createLight("运行中", workWidth * 2 / 3, 150);
  149. blankinglabel = createLight("请求下料", 0, 190);
  150. blankingmeslabel = createLight("MES允许下料", workWidth / 3, 190);
  151. feedingtodevicelabel = createLight("上料到设备", workWidth * 2 / 3, 190);
  152. // 点击 MES 写入的灯可手动切换信号
  153. feedingmeslabel.addMouseListener(toggleSignal("MES允许上料", plcDb + ".0.4"));
  154. blankingmeslabel.addMouseListener(toggleSignal("MES允许下料", plcDb + ".0.5"));
  155. JLabel ipLabel = new JLabel("PLC " + plcIp + " | " + plcDb);
  156. ipLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12));
  157. ipLabel.setForeground(Color.DARK_GRAY);
  158. ipLabel.setBounds(5, 230, workWidth * 2 / 3 + 55, 26);
  159. this.add(ipLabel);
  160. signalMonitor = new PlcSignalMonitor(s7PLC, plcDb, oprno + pos, MesClient.mesClientFrame);
  161. signalBtn = signalMonitor.createSignalButton();
  162. signalBtn.setBounds(workWidth * 2 / 3 + 63, 230, 90, 26);
  163. this.add(signalBtn);
  164. }
  165. private void addRowLabel(String text, int y) {
  166. JLabel label = new JLabel(text);
  167. label.setHorizontalAlignment(SwingConstants.CENTER);
  168. label.setForeground(Color.BLACK);
  169. label.setFont(new Font("微软雅黑", Font.PLAIN, 20));
  170. label.setBounds(0, y, workWidth / 5, 40);
  171. this.add(label);
  172. }
  173. private JTextField createSnField(int y) {
  174. JTextField field = new JTextField("");
  175. field.setHorizontalAlignment(SwingConstants.CENTER);
  176. field.setForeground(Color.BLACK);
  177. field.setFont(new Font("微软雅黑", Font.PLAIN, 16));
  178. field.setEditable(false);
  179. field.setBounds(workWidth / 5, y, workWidth * 3 / 5, 40);
  180. field.addMouseListener(new MouseAdapter() {
  181. public void mouseClicked(MouseEvent e) {
  182. String sn = field.getText().trim();
  183. if (!sn.isEmpty()) {
  184. MesClient.createQRCode(sn, oprno + pos);
  185. }
  186. }
  187. });
  188. this.add(field);
  189. return field;
  190. }
  191. private JButton createResetBtn(int y, Runnable action) {
  192. JButton btn = new JButton("刷新");
  193. btn.setFont(new Font("微软雅黑", Font.PLAIN, 22));
  194. btn.setBounds(workWidth * 4 / 5 + 10, y, workWidth / 5 - 10, 40);
  195. btn.addActionListener(e -> action.run());
  196. this.add(btn);
  197. return btn;
  198. }
  199. private JLabel createLight(String text, int x, int y) {
  200. JLabel label = new JLabel(text);
  201. label.setHorizontalAlignment(SwingConstants.LEFT);
  202. label.setIcon(imageGray);
  203. label.setForeground(Color.BLACK);
  204. label.setBounds(x, y, workWidth / 3, 30);
  205. this.add(label);
  206. return label;
  207. }
  208. private MouseAdapter toggleSignal(String name, String addr) {
  209. return new MouseAdapter() {
  210. public void mouseClicked(MouseEvent e) {
  211. boolean ret = readBoolean(addr);
  212. if (confirm("确定修改" + name + "(" + addr + ")为" + !ret + "吗?")) {
  213. writeBoolean(addr, !ret);
  214. }
  215. }
  216. };
  217. }
  218. private static ImageIcon scaledIcon(String path) {
  219. ImageIcon icon = new ImageIcon(MesClient.class.getResource(path));
  220. icon.setImage(icon.getImage().getScaledInstance(20, 20, Image.SCALE_SMOOTH));
  221. return icon;
  222. }
  223. private static boolean confirm(String msg) {
  224. return JOptionPane.showConfirmDialog(null, msg, "操作确认", JOptionPane.YES_NO_OPTION,
  225. JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION;
  226. }
  227. private ImageIcon light(boolean on) {
  228. return on ? imageGreen : imageGray;
  229. }
  230. public void formatElementUI() {
  231. cacheData = JdbcUtils.getCache(cacheName);
  232. if (cacheData == null) {
  233. return;
  234. }
  235. productSn.setText(nvl(cacheData.getSn()));
  236. productSnFeeding.setText(nvl(cacheData.getFeedingSn()));
  237. tjFlag = nvl(cacheData.getTjFlag(), 0);
  238. tjFeedingFlag = nvl(cacheData.getTjFeedingFlag(), 0);
  239. lastSn = nvl(cacheData.getLastSn());
  240. setMenuStatus(nvl(cacheData.getMsg()), nvl(cacheData.getError(), -1));
  241. this.repaint();
  242. }
  243. public void setMenuStatus(String msg, int error) {
  244. status_menu.setForeground(error == 0 ? Color.GREEN : Color.RED);
  245. status_menu.setText(msg);
  246. cacheData.setMsg(msg);
  247. cacheData.setError(error);
  248. cacheData.setTjFlag(tjFlag);
  249. cacheData.setTjFeedingFlag(tjFeedingFlag);
  250. cacheData.setTjStatus(tjStatus);
  251. cacheData.setLastSn(lastSn);
  252. cacheData.setSn(productSn.getText());
  253. cacheData.setFeedingSn(productSnFeeding.getText());
  254. // 内容没变不重复写库(等待取码时每轮都会刷新状态)
  255. String json = JSON.toJSONString(cacheData);
  256. if (!json.equals(lastCacheJson)) {
  257. JdbcUtils.setCache(cacheName, json);
  258. lastCacheJson = json;
  259. }
  260. }
  261. // 定时任务 -----------------------------------------------------------------------------------------------------
  262. public void heartData() {
  263. if (heartTimer != null) {
  264. heartTimer.cancel();
  265. }
  266. heartTimer = new Timer();
  267. heartTimer.schedule(new TimerTask() {
  268. public void run() {
  269. if (MesClient.isLogin == 1) {
  270. plcHeart = !plcHeart;
  271. writeBoolean(plcDb + ".0.6", plcHeart, false);
  272. }
  273. }
  274. }, 1000, 1000);
  275. }
  276. public void startOprnoTimer() {
  277. if (oprnoTimer != null) {
  278. oprnoTimer.cancel();
  279. }
  280. oprnoTimer = new Timer();
  281. oprnoTimer.schedule(new TimerTask() {
  282. public void run() {
  283. try {
  284. if (MesClient.isLogin == 1) {
  285. cycle();
  286. }
  287. } catch (Exception e) {
  288. log.error("<{}{}>轮询异常", oprno, pos, e);
  289. }
  290. }
  291. }, 100, 3000);
  292. }
  293. private void cycle() {
  294. boolean retFeeding = readBoolean(plcDb + ".0.0");
  295. boolean retBlanking = readBoolean(plcDb + ".0.1");
  296. boolean retRunning = readBoolean(plcDb + ".0.2");
  297. boolean retAllowFeeding = readBoolean(plcDb + ".0.4");
  298. boolean retAllowBlanking = readBoolean(plcDb + ".0.5");
  299. boolean retToDevice = readBoolean(plcDb + ".0.7");
  300. boolean toDeviceRise = retToDevice && !lastToDevice;
  301. lastToDevice = retToDevice;
  302. feedinglabel.setIcon(light(retFeeding));
  303. feedingmeslabel.setIcon(light(retAllowFeeding));
  304. runlabel.setIcon(light(retRunning));
  305. blankinglabel.setIcon(light(retBlanking));
  306. blankingmeslabel.setIcon(light(retAllowBlanking));
  307. feedingtodevicelabel.setIcon(light(retToDevice));
  308. // 请求上料撤销后复位 MES允许上料
  309. if (!retFeeding && retAllowFeeding) {
  310. writeAllowFeeding(false);
  311. }
  312. // ---------- 上料 ----------
  313. if (!retFeeding) {
  314. feedingReleased = true;
  315. }
  316. if (tjFeedingFlag == 0) {
  317. if (retFeeding && feedingReleased) {
  318. takeSn();
  319. }
  320. } else if (tjFlag == 0 && (toDeviceRise || retRunning)) {
  321. // 工件已进设备:码从上料移到加工
  322. tjFlag = 1;
  323. tjFeedingFlag = 0;
  324. productSn.setText(productSnFeeding.getText());
  325. productSnFeeding.setText("");
  326. feedingReleased = false; // 本次请求上料复位后才处理下一次,防止拿走下一件的码
  327. setMenuStatus("工件已上料到设备", 0);
  328. }
  329. // ---------- 下料 ----------
  330. // 加工码为空时直接放行下料(无码件或已上传完成)
  331. if (retBlanking && productSn.getText().isEmpty() && !retAllowBlanking) {
  332. writeAllowBlanking(true);
  333. }
  334. if (tjFlag == 0 && !retBlanking && retAllowBlanking) {
  335. writeAllowBlanking(false);
  336. }
  337. if (tjFlag == 1 && retRunning) {
  338. tjFlag = 2;
  339. writeAllowFeeding(false);
  340. writeAllowBlanking(false);
  341. setMenuStatus("工件加工中", 0);
  342. } else if (tjFlag == 2 && retBlanking) {
  343. finishWork();
  344. }
  345. }
  346. // 取码并校验:通过后清掉码槽里的码,写 MES允许上料
  347. private void takeSn() {
  348. // 同一个码校验失败后不重复请求,等人工刷新或码槽换成新码
  349. String failedSn = productSnFeeding.getText().trim();
  350. if (!failedSn.isEmpty() && (sourceSlot.isEmpty() || failedSn.equals(sourceSlot.getSn()))) {
  351. return;
  352. }
  353. if (!sourceSlot.tryLock()) {
  354. return; // 其他机台正在取码,下一轮再试
  355. }
  356. try {
  357. String sn = sourceSlot.getSn();
  358. if (sn.isEmpty()) {
  359. setMenuStatus(front ? "暂无扫码结果,等待扫码" : "反面待上料码为空,等待正面下料", -1);
  360. return;
  361. }
  362. productSnFeeding.setText(sn);
  363. lastSn = sn;
  364. JSONObject retObj = DataUtil.checkQuality(sn, oprno + pos, MesClient.user_menu.getText());
  365. if (!isOk(retObj)) {
  366. writeAllowFeeding(false);
  367. setMenuStatus(failMsg(retObj), -1);
  368. return;
  369. }
  370. sourceSlot.clearIf(sn, oprno + pos + "取码");
  371. tjFeedingFlag = 1;
  372. writeAllowFeeding(true);
  373. setMenuStatus("工件可以加工", 0);
  374. } finally {
  375. sourceSlot.unlock();
  376. }
  377. }
  378. // 请求下料:上传结果,正面成功后存码给反面
  379. private void finishWork() {
  380. String sn = productSn.getText().trim();
  381. if (targetSlot == null) {
  382. if (upload(sn)) {
  383. resetBlanking("工件结果已提交,等待下一件");
  384. writeAllowBlanking(true);
  385. }
  386. return;
  387. }
  388. if (!targetSlot.tryLock()) {
  389. return; // 反面正在取码或其他正面机正在存码,下一轮再试
  390. }
  391. try {
  392. if (!targetSlot.isEmpty()) {
  393. setMenuStatus("反面待上料码" + targetSlot.getSn() + "未取走,暂不允许下料", -1);
  394. return;
  395. }
  396. if (upload(sn)) {
  397. targetSlot.put(sn, oprno + pos);
  398. resetBlanking("结果已提交,码已存入反面待上料码");
  399. writeAllowBlanking(true);
  400. }
  401. } finally {
  402. targetSlot.unlock();
  403. }
  404. }
  405. // 上传加工结果,失败停在 tjFlag=3 等人工刷新(与原逻辑一致)
  406. private boolean upload(String sn) {
  407. tjFlag = 3;
  408. tjStatus = 1;
  409. setMenuStatus("工件加工结束,结果上传中", 0);
  410. log.info("<{}{}>上传加工结果 sn={}", oprno, pos, sn);
  411. JSONObject retObj = DataUtil.sendQuality(sn, oprno + pos, "OK", MesClient.user_menu.getText());
  412. if (isOk(retObj)) {
  413. return true;
  414. }
  415. setMenuStatus(failMsg(retObj), -1);
  416. return false;
  417. }
  418. public void resetBlanking(String msg) {
  419. tjFlag = 0;
  420. tjStatus = 0;
  421. productSn.setText("");
  422. setMenuStatus(msg, 0);
  423. }
  424. public void resetFeeding() {
  425. tjFeedingFlag = 0;
  426. productSnFeeding.setText("");
  427. writeAllowFeeding(false);
  428. setMenuStatus("等待请求上料", 0);
  429. }
  430. private static boolean isOk(JSONObject retObj) {
  431. return retObj != null && "true".equalsIgnoreCase(String.valueOf(retObj.get("result")));
  432. }
  433. private static String failMsg(JSONObject retObj) {
  434. if (retObj == null || retObj.get("result") == null) {
  435. return "请求失败,请重试";
  436. }
  437. String msg = retObj.getString("message");
  438. return msg == null || msg.isEmpty() ? "校验失败" : msg;
  439. }
  440. private static String nvl(String s) {
  441. return s == null ? "" : s;
  442. }
  443. private static int nvl(Integer v, int def) {
  444. return v == null ? def : v;
  445. }
  446. // 与设备交互 -----------------------------------------------------------------------------------------
  447. public void writeAllowFeeding(boolean b) {
  448. writeBoolean(plcDb + ".0.4", b);
  449. }
  450. public void writeAllowBlanking(boolean b) {
  451. writeBoolean(plcDb + ".0.5", b);
  452. }
  453. public boolean readBoolean(String addr) {
  454. try {
  455. boolean ret = s7PLC.readBoolean(addr);
  456. log.info("<{}{}>读取{}-->成功,结果:{}", oprno, pos, addr, ret);
  457. return ret;
  458. } catch (Exception e) {
  459. log.error("<{}{}>读取{}-->失败:{}", oprno, pos, addr, e.getMessage());
  460. return false;
  461. }
  462. }
  463. public boolean writeBoolean(String addr, boolean value) {
  464. return writeBoolean(addr, value, true);
  465. }
  466. // 心跳不打成功日志
  467. private boolean writeBoolean(String addr, boolean value, boolean logOk) {
  468. try {
  469. s7PLC.writeBoolean(addr, value);
  470. if (logOk) {
  471. log.info("<{}{}>写入{}-->成功,写入内容:{}", oprno, pos, addr, value);
  472. }
  473. return true;
  474. } catch (Exception e) {
  475. log.error("<{}{}>写入{}-->失败,写入内容:{},{}", oprno, pos, addr, value, e.getMessage());
  476. return false;
  477. }
  478. }
  479. }