WorkJPanelOP40Transfer.java 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. package com.mes.component;
  2. import com.alibaba.fastjson2.JSONObject;
  3. import com.github.xingshuangs.iot.protocol.s7.enums.EPlcType;
  4. import com.github.xingshuangs.iot.protocol.s7.service.S7PLC;
  5. import com.mes.ui.DataUtil;
  6. import com.mes.ui.MesClient;
  7. import org.slf4j.Logger;
  8. import org.slf4j.LoggerFactory;
  9. import javax.swing.*;
  10. import java.awt.*;
  11. import java.awt.event.ActionEvent;
  12. import java.awt.event.ActionListener;
  13. import java.awt.event.MouseAdapter;
  14. import java.awt.event.MouseEvent;
  15. import java.util.Timer;
  16. import java.util.TimerTask;
  17. public class WorkJPanelOP40Transfer extends JPanel {
  18. public static final Logger log = LoggerFactory.getLogger(WorkJPanelOP40Transfer.class);
  19. public String oprno = "OP040";
  20. public String pos = "A";
  21. public String lastSn = ""; // 最近的码
  22. public String lineSn = "XT";
  23. public JLabel feedinglabel; // 请求扫码信号
  24. public JLabel feedingmeslabel; // MES扫码反馈
  25. public JLabel feedingtotransferlabel; // 去中转台信号
  26. public JTextField productSnFeeding; // 工件码显示
  27. public JButton status_menu; // 状态显示
  28. public JButton resetFeedingBtn; // 刷新按钮
  29. public JButton signalBtn; // 信号监控按钮
  30. public PlcSignalMonitor signalMonitor; // 信号监控组件
  31. public ImageIcon imageGreen;
  32. public ImageIcon imageGray;
  33. public String plcIp1 = "192.168.90.83"; // PLC3 西侧
  34. public String plcIp2 = "192.168.90.84"; // PLC4 东侧
  35. // 只连接一个PLC
  36. public S7PLC s7PLC = null;
  37. public Integer workWidth = 470;
  38. public Integer workHeight = 300;
  39. public String plcDb = "DB7005"; // 共用工位6的DB块
  40. public Integer tjFeedingFlag = 0; // 上料状态标识: 0=等待请求, 1=已获取工件码等待清空
  41. public Boolean toTransfer = false; // 去中转台信号(用于触发清空)
  42. public int scanRetryCount = 0; // 扫码重试计数器,最多重试1次(共2次扫码机会)
  43. public static final int MAX_SCAN_RETRY = 1; // 最大重试次数
  44. public Boolean plcHeart = false;
  45. // 用于检测状态变化的上一次值(减少日志输出)
  46. private java.util.Map<String, Boolean> lastReadValues = new java.util.HashMap<>();
  47. private java.util.Map<String, Boolean> lastWriteValues = new java.util.HashMap<>();
  48. public Integer isLocal = 0; // 0=正式 1=本地
  49. public WorkJPanelOP40Transfer(String position) {
  50. this.setLayout(null);
  51. this.setBackground(Color.WHITE);
  52. this.pos = position;
  53. // 根据位置选择连接的PLC,DB块都是DB7005(共用工位6的DB块)
  54. if (pos.equals("A")) {
  55. // 中转车1 连接 PLC3(西侧)
  56. s7PLC = new S7PLC(EPlcType.S1500, plcIp1);
  57. } else if (pos.equals("B")) {
  58. // 中转车2 连接 PLC4(东侧)
  59. s7PLC = new S7PLC(EPlcType.S1500, plcIp2);
  60. }
  61. JLabel oprnoLabel = new JLabel("中转车" + (pos.equals("A") ? "1" : "2"));
  62. oprnoLabel.setHorizontalAlignment(SwingConstants.CENTER);
  63. oprnoLabel.setForeground(Color.BLACK);
  64. oprnoLabel.setFont(new Font("微软雅黑", Font.PLAIN, 20));
  65. oprnoLabel.setBounds(0, 0, workWidth / 5, 40);
  66. this.add(oprnoLabel);
  67. status_menu = new JButton("等待请求");
  68. status_menu.setBounds(workWidth / 5, 0, workWidth * 4 / 5, 40);
  69. status_menu.setForeground(Color.GREEN);
  70. status_menu.setFont(new Font("微软雅黑", Font.PLAIN, 16));
  71. status_menu.setBackground(Color.BLACK);
  72. this.add(status_menu);
  73. JLabel oprnoLabelFeeding = new JLabel("上料");
  74. oprnoLabelFeeding.setHorizontalAlignment(SwingConstants.CENTER);
  75. oprnoLabelFeeding.setForeground(Color.BLACK);
  76. oprnoLabelFeeding.setFont(new Font("微软雅黑", Font.PLAIN, 20));
  77. oprnoLabelFeeding.setBounds(0, 50, workWidth / 5, 40);
  78. this.add(oprnoLabelFeeding);
  79. productSnFeeding = new JTextField("");
  80. productSnFeeding.setHorizontalAlignment(SwingConstants.CENTER);
  81. productSnFeeding.setForeground(Color.BLACK);
  82. productSnFeeding.setFont(new Font("微软雅黑", Font.PLAIN, 16));
  83. productSnFeeding.setEditable(false);
  84. productSnFeeding.setBounds(workWidth / 5, 50, workWidth * 3 / 5, 40);
  85. productSnFeeding.addMouseListener(new MouseAdapter() {
  86. public void mouseClicked(MouseEvent e) {
  87. String sn = productSnFeeding.getText().trim();
  88. if (!sn.isEmpty()) {
  89. MesClient.createQRCode(productSnFeeding.getText(), oprno + pos);
  90. }
  91. }
  92. });
  93. this.add(productSnFeeding);
  94. resetFeedingBtn = new JButton("刷新");
  95. resetFeedingBtn.addActionListener(new ActionListener() {
  96. public void actionPerformed(ActionEvent e) {
  97. int choice = JOptionPane.showConfirmDialog(null, "确定刷新工件吗?", "操作确认", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
  98. if (choice == JOptionPane.YES_OPTION) {
  99. resetFeeding();
  100. }
  101. }
  102. });
  103. resetFeedingBtn.setFont(new Font("微软雅黑", Font.PLAIN, 22));
  104. resetFeedingBtn.setBounds(workWidth * 4 / 5 + 10, 50, workWidth / 5 - 10, 40);
  105. this.add(resetFeedingBtn);
  106. imageGreen = new ImageIcon(MesClient.class.getResource("/bg/green_dot.png"));
  107. imageGreen.setImage(imageGreen.getImage().getScaledInstance(20, 20, Image.SCALE_SMOOTH));
  108. imageGray = new ImageIcon(MesClient.class.getResource("/bg/grey_dot.png"));
  109. imageGray.setImage(imageGray.getImage().getScaledInstance(20, 20, Image.SCALE_SMOOTH));
  110. feedinglabel = new JLabel("请求扫码");
  111. feedinglabel.setHorizontalAlignment(SwingConstants.LEFT);
  112. feedinglabel.setIcon(imageGreen);
  113. feedinglabel.setForeground(Color.BLACK);
  114. feedinglabel.setBounds(0, 150, workWidth / 3, 30);
  115. this.add(feedinglabel);
  116. feedingmeslabel = new JLabel("MES扫码反馈");
  117. feedingmeslabel.setHorizontalAlignment(SwingConstants.LEFT);
  118. feedingmeslabel.setIcon(imageGreen);
  119. feedingmeslabel.setForeground(Color.BLACK);
  120. feedingmeslabel.setBounds(workWidth / 3, 150, workWidth / 3, 30);
  121. feedingmeslabel.addMouseListener(new MouseAdapter() {
  122. public void mouseClicked(MouseEvent e) {
  123. boolean ret = readAllowFeeding();
  124. int choice = JOptionPane.showConfirmDialog(null, "确定修改MES扫码反馈结果为" + (ret ? "false" : "true") + "吗?", "操作确认", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
  125. if (choice == JOptionPane.YES_OPTION) {
  126. writeAllowFeeding(!ret);
  127. }
  128. }
  129. });
  130. this.add(feedingmeslabel);
  131. feedingtotransferlabel = new JLabel("去中转台");
  132. feedingtotransferlabel.setHorizontalAlignment(SwingConstants.LEFT);
  133. feedingtotransferlabel.setIcon(imageGray);
  134. feedingtotransferlabel.setForeground(Color.BLACK);
  135. feedingtotransferlabel.setBounds(workWidth * 2 / 3, 150, workWidth / 3, 30);
  136. this.add(feedingtotransferlabel);
  137. // 信号监控按钮
  138. String stationName = "中转车" + (pos.equals("A") ? "1(西侧)" : "2(东侧)");
  139. signalMonitor = new PlcSignalMonitor(s7PLC, plcDb, stationName, MesClient.mesClientFrame);
  140. signalBtn = signalMonitor.createSignalButton();
  141. signalBtn.setBounds(4, 190, 90, 26);
  142. this.add(signalBtn);
  143. startOprnoTimer();
  144. heartData();
  145. resetScanA();
  146. }
  147. public void setMenuStatus(String msg, int error) {
  148. if (error == 0) {
  149. this.status_menu.setForeground(Color.GREEN);
  150. } else {
  151. this.status_menu.setForeground(Color.RED);
  152. }
  153. this.status_menu.setText(msg);
  154. }
  155. // 定时任务 -----------------------------------------------------------------------------------------------------
  156. public Timer cjTimerText2;
  157. public void heartData() {
  158. // 中转车不写心跳,由工位F/L (DB7005) 统一写入
  159. // 避免同一个地址被多个组件写入导致冲突
  160. //
  161. // 原代码(已禁用):
  162. // if (cjTimerText2 != null) {
  163. // cjTimerText2.cancel();
  164. // }
  165. // cjTimerText2 = new Timer();
  166. // cjTimerText2.schedule(new TimerTask() {
  167. // public void run() {
  168. // if (MesClient.isLogin == 1 && isLocal == 0) {
  169. // plcHeart = !plcHeart;
  170. // writeBoolean(plcDb + ".0.6", plcHeart);
  171. // }
  172. // }
  173. // }, 1000, 1000);
  174. }
  175. public Timer oprnoTimer;
  176. public void startOprnoTimer() {
  177. //心跳显示图标
  178. if (oprnoTimer != null) {
  179. oprnoTimer.cancel();
  180. }
  181. oprnoTimer = new Timer();
  182. oprnoTimer.schedule(new TimerTask() {
  183. public void run() {
  184. try {
  185. if (MesClient.isLogin == 1) {
  186. // 读取PLC信号并更新UI
  187. Boolean retFeeding = readFeeding();
  188. feedinglabel.setIcon(retFeeding ? imageGreen : imageGray);
  189. Boolean retFeedingAllow = readAllowFeeding();
  190. feedingmeslabel.setIcon(retFeedingAllow ? imageGreen : imageGray);
  191. toTransfer = readFeedingToTransfer();
  192. feedingtotransferlabel.setIcon(toTransfer ? imageGreen : imageGray);
  193. // 请求信号消失时,清除MES反馈
  194. if (!retFeeding && retFeedingAllow) {
  195. writeAllowFeeding(false);
  196. }
  197. // 状态机逻辑(简化版)
  198. // 扫码和saveSn由工位6(F/L)的barCodeForTransfer()方法处理
  199. // 中转车面板只负责显示和重置
  200. if (tjFeedingFlag == 1) {
  201. // 状态1:已获取工件码,等待toTransfer信号变为false后清空
  202. if (!toTransfer) {
  203. log.info("[" + oprno + pos + "]toTransfer变为false,清空工件码");
  204. resetFeeding();
  205. }
  206. }
  207. }
  208. } catch (Exception e) {
  209. log.error(e.getMessage());
  210. }
  211. }
  212. }, 100, 3000);
  213. }
  214. public void barCode() {
  215. if (!productSnFeeding.getText().isEmpty()) {
  216. return;
  217. }
  218. String sn = readQrcode();
  219. if (sn.equals("")) {
  220. if (productSnFeeding.getText().isEmpty()) {
  221. productSnFeeding.setText("空结果");
  222. this.setMenuStatus("获取工件码为空", -1);
  223. }
  224. } else {
  225. productSnFeeding.setText(sn);
  226. lastSn = sn;
  227. // 查询工件质量
  228. JSONObject retObj = DataUtil.checkQuality(sn, oprno + pos, MesClient.user_menu.getText());
  229. if (retObj.get("result") != null && retObj.get("result").toString().equalsIgnoreCase("true")) {
  230. tjFeedingFlag = 1;//可加工
  231. setMenuStatus("工件可以加工", 0);
  232. writeAllowFeeding(true);
  233. resetFeedingBtn.setEnabled(false);
  234. } else {
  235. writeAllowFeeding(false);
  236. resetFeedingBtn.setEnabled(true);
  237. if (retObj.get("result") == null) {
  238. setMenuStatus("请求失败,请重试", -1);
  239. } else {
  240. if (retObj.get("result").toString().equalsIgnoreCase("false")) {
  241. setMenuStatus(retObj.getString("message"), -1);
  242. }
  243. }
  244. }
  245. }
  246. }
  247. public void resetFeeding() {
  248. productSnFeeding.setText("");
  249. tjFeedingFlag = 0;
  250. scanRetryCount = 0; // 重置重试计数器
  251. setMenuStatus("等待请求", 0);
  252. resetFeedingBtn.setEnabled(true);
  253. writeAllowFeeding(false);
  254. }
  255. public void resetScanA() {
  256. tjFeedingFlag = 0;
  257. scanRetryCount = 0; // 重置重试计数器
  258. productSnFeeding.setText("");
  259. setMenuStatus("等待请求", 0);
  260. resetFeedingBtn.setEnabled(true);
  261. // 延迟写入PLC,避免初始化时连接超时
  262. if(MesClient.isLogin == 1) {
  263. writeAllowFeeding(false);
  264. }
  265. }
  266. // 与设备交互-----------------------------------------------------------------------------------------
  267. // 请求扫码信号 (DB7005.0.0)
  268. public boolean readFeeding() {
  269. if (isLocal == 1) {
  270. return false;
  271. } else {
  272. return readBoolean(plcDb + ".0.0");
  273. }
  274. }
  275. // 读取去中转台信号 (DB7005.1.0)
  276. public boolean readFeedingToTransfer() {
  277. if (isLocal == 1) {
  278. return false;
  279. } else {
  280. return readBoolean(plcDb + ".1.0");
  281. }
  282. }
  283. // 读取MES允许上料信号 (DB7005.0.4)
  284. public boolean readAllowFeeding() {
  285. if (isLocal == 1) {
  286. return false;
  287. } else {
  288. return readBoolean(plcDb + ".0.4");
  289. }
  290. }
  291. // 写入MES允许上料信号 (DB7005.0.4)
  292. public boolean writeAllowFeeding(Boolean b) {
  293. if (isLocal == 1) {
  294. return true;
  295. } else {
  296. return writeBoolean(plcDb + ".0.4", b);
  297. }
  298. }
  299. // 读取中转车号 (DB7005.2.0)
  300. public int readTransferNo() {
  301. if (isLocal == 1) {
  302. return 1;
  303. } else {
  304. return s7PLC.readInt16(plcDb + ".2.0");
  305. }
  306. }
  307. // 读取中转车层数 (DB7005.4.0)
  308. public int readTransferFloor() {
  309. if (isLocal == 1) {
  310. return 1;
  311. } else {
  312. return s7PLC.readInt16(plcDb + ".4.0");
  313. }
  314. }
  315. // 中转车不扫码,直接从数据库获取工件码
  316. public String readQrcode() {
  317. String sn = "";
  318. if (isLocal == 1) {
  319. return "";
  320. } else {
  321. try {
  322. // if(this.pos.equals("A")){
  323. // sn = MesClient.scanQrcode1();
  324. // }else{
  325. // sn = MesClient.scanQrcode2();
  326. // }
  327. if(pos.equals("A")){
  328. log.info("[{}]触发西侧摄像头扫码", oprno+pos);
  329. // sn = MesClient.scanQrcode1();
  330. sn = MesClient.scanSnA;
  331. }else{
  332. log.info("[{}]触发东侧摄像头扫码", oprno+pos);
  333. sn = MesClient.scanSnB;
  334. // sn = MesClient.scanQrcode2();
  335. }
  336. } catch (Exception e) {
  337. log.error("[" + oprno + pos + "]获取工件码异常: " + e.getMessage(), e);
  338. }
  339. }
  340. return sn;
  341. }
  342. // 读取布尔值(只在值变化时打印日志)
  343. public boolean readBoolean(String db) {
  344. boolean permit;
  345. try {
  346. // 检查PLC连接是否可用
  347. if(s7PLC == null) {
  348. return false;
  349. }
  350. permit = s7PLC.readBoolean(db);
  351. // 只在值变化时打印日志
  352. Boolean lastValue = lastReadValues.get(db);
  353. if (lastValue == null || lastValue != permit) {
  354. log.info("<" + this.oprno + this.pos + ">读取" + db + "-->成功,结果:{}", permit ? "true" : "false");
  355. lastReadValues.put(db, permit);
  356. }
  357. } catch (Exception e) {
  358. // 连接超时时不打印完整堆栈
  359. if(e.getMessage() != null && e.getMessage().contains("connect timed out")) {
  360. // 连接超时是常见情况,降低日志级别
  361. } else {
  362. log.error("<" + this.oprno + this.pos + ">读取" + db + "-->失败: {}", e.getMessage());
  363. }
  364. return false;
  365. }
  366. return permit;
  367. }
  368. // 写入布尔值(只在值变化时打印日志)
  369. public boolean writeBoolean(String db, boolean permit) {
  370. try {
  371. // 检查PLC连接是否可用
  372. if(s7PLC == null) {
  373. log.warn("<" + this.oprno + this.pos + ">PLC未初始化,跳过写入" + db);
  374. return false;
  375. }
  376. // 只在值变化时打印日志
  377. Boolean lastValue = lastWriteValues.get(db);
  378. if (lastValue == null || lastValue != permit) {
  379. s7PLC.writeBoolean(db, permit);
  380. log.info("<" + this.oprno + this.pos + ">写入" + db + "-->成功,写入内容:{}", permit ? "true" : "false");
  381. lastWriteValues.put(db, permit);
  382. } else {
  383. // 值未变化,仍然执行写入但不打印日志
  384. s7PLC.writeBoolean(db, permit);
  385. }
  386. } catch (Exception e) {
  387. // 连接超时时不打印完整堆栈
  388. if(e.getMessage() != null && e.getMessage().contains("connect timed out")) {
  389. log.warn("<" + this.oprno + this.pos + ">写入" + db + "-->连接超时(PLC未连接)");
  390. } else {
  391. log.error("<" + this.oprno + this.pos + ">写入" + db + "-->失败,写入内容:{}", permit ? "true" : "false");
  392. log.error(e.getMessage());
  393. }
  394. return false;
  395. }
  396. return true;
  397. }
  398. }