package com.mes.util; import com.github.xingshuangs.iot.protocol.s7.enums.EPlcType; import com.github.xingshuangs.iot.protocol.s7.service.S7PLC; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.nio.charset.StandardCharsets; /** * 西门子 S7-200 SMART PLC 通信服务。 *
* 使用 iot-communication 库的 S200_SMART 协议(支持 V 区原生地址,如 V2800.3、VB3000)。 * s7connector 不支持 S7-200 SMART,请勿混用。 */ public class PlcService { public static final Logger log = LoggerFactory.getLogger(PlcService.class); private static S7PLC s7PLC; private static String plcIp = "192.168.2.1"; private static int rack = 0; private static int slot = 0; private static volatile boolean connected = false; public static void setPlcIp(String ip) { if (ip != null && !ip.trim().isEmpty()) { plcIp = ip.trim(); } } public static void setConnectionParams(int rackNo, int slotNo) { rack = rackNo; slot = slotNo; } public static String getPlcIp() { return plcIp; } public static boolean isConnected() { return connected; } public static synchronized boolean connect() { disconnect(); try { s7PLC = new S7PLC(EPlcType.S200_SMART, plcIp, 102, rack, slot); s7PLC.setPersistence(true); s7PLC.setConnectTimeout(3000); s7PLC.setReceiveTimeout(3000); s7PLC.connect(); s7PLC.readBoolean(addr(QmPlcPoint.STATUS_BYTE, QmPlcPoint.DATA_READY_BIT)); connected = true; log.info("S7-200 SMART PLC连接成功: {} rack={} slot={}", plcIp, rack, slot); return true; } catch (Exception e) { connected = false; closeQuietly(); log.info("S7-200 SMART PLC连接失败: {} - {}", plcIp, e.getMessage()); return false; } } public static synchronized void disconnect() { connected = false; closeQuietly(); s7PLC = null; } private static void closeQuietly() { if (s7PLC != null) { try { s7PLC.close(); } catch (Exception ignored) { } } } public static synchronized boolean ensureConnected() { if (connected && s7PLC != null) { try { if (s7PLC.checkConnected()) { s7PLC.readBoolean(addr(QmPlcPoint.STATUS_BYTE, QmPlcPoint.DATA_READY_BIT)); return true; } } catch (Exception e) { connected = false; } } return connect(); } public static byte[] readStatusByte() { ensureConnectedOrThrow(); return s7PLC.readByte("V" + QmPlcPoint.STATUS_BYTE, 1); } public static byte[] readMesDataBlock() { ensureConnectedOrThrow(); return s7PLC.readByte("V" + QmPlcPoint.DATA_OFFSET, QmPlcPoint.DATA_LENGTH); } public static boolean isDataReady(byte[] statusByte) { return QmPlcPoint.readBool(statusByte, 0, QmPlcPoint.DATA_READY_BIT); } public static boolean readDataReady() { ensureConnectedOrThrow(); return s7PLC.readBoolean(addr(QmPlcPoint.STATUS_BYTE, QmPlcPoint.DATA_READY_BIT)); } public static boolean setCollectComplete(boolean complete) { return writeVBit(QmPlcPoint.STATUS_BYTE, QmPlcPoint.COLLECT_COMPLETE_BIT, complete); } /** * 上位机采集完成反馈:V2800.3 写1并保持1~3秒后复位为0。 */ public static boolean pulseCollectComplete() { if (!setCollectComplete(true)) { return false; } try { Thread.sleep(QmPlcPoint.COLLECT_COMPLETE_PULSE_MS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); log.info("V2800.3脉冲被中断"); return false; } return setCollectComplete(false); } public static boolean isQrMatchComplete(byte[] statusByte) { return QmPlcPoint.readBool(statusByte, 0, QmPlcPoint.QR_MATCH_BIT); } public static boolean setMesConnected(boolean connectedFlag) { return writeVBit(QmPlcPoint.STATUS_BYTE, QmPlcPoint.MES_CONNECTED_BIT, connectedFlag); } public static String readMesData() { byte[] data = readMesDataBlock(); if (data == null) { log.warn("读取VB3000失败: 返回null"); return ""; } log.info("读取VB3000完成, byteLength={}", data.length); return QmPlcPoint.readPlcString(data); } /** * 质量校验通过后,将扫码内容写入 VB3500(纯 ASCII 原始字符串,无长度字节前缀)。 */ public static boolean writeScanCode(String barcode) { if (!ensureConnected()) { return false; } if (barcode == null || barcode.trim().isEmpty()) { log.info("二维码为空,无法写入VB3500"); return false; } try { String code = barcode.trim(); byte[] codeBytes = code.getBytes(StandardCharsets.US_ASCII); byte[] buffer = new byte[QmPlcPoint.SCAN_CODE_LENGTH]; int copyLen = Math.min(codeBytes.length, buffer.length); if (codeBytes.length > buffer.length) { log.info("二维码长度超过VB3500容量({}字节),已截断", QmPlcPoint.SCAN_CODE_LENGTH); } System.arraycopy(codeBytes, 0, buffer, 0, copyLen); s7PLC.writeByte("V" + QmPlcPoint.SCAN_CODE_OFFSET, buffer); log.info("S7-200 SMART 写入 VB3500 二维码(ASCII): {}", code); return true; } catch (Exception e) { connected = false; log.info("写入VB3500失败: {}", e.getMessage()); return false; } } /** 复位 VB3500 扫码区 */ public static boolean clearScanCode() { if (!ensureConnected()) { return false; } try { byte[] empty = new byte[QmPlcPoint.SCAN_CODE_LENGTH]; s7PLC.writeByte("V" + QmPlcPoint.SCAN_CODE_OFFSET, empty); log.info("S7-200 SMART 已清空 VB3500"); return true; } catch (Exception e) { connected = false; log.info("清空VB3500失败: {}", e.getMessage()); return false; } } private static boolean writeVBit(int byteOffset, int bitOffset, boolean value) { if (!ensureConnected()) { return false; } try { String address = addr(byteOffset, bitOffset); s7PLC.writeBoolean(address, value); log.info("S7-200 SMART 写入 {} = {}", address, value); return true; } catch (Exception e) { connected = false; log.info("S7-200 SMART PLC写入失败: {}", e.getMessage()); return false; } } private static String addr(int byteOffset, int bitOffset) { return "V" + byteOffset + "." + bitOffset; } private static void ensureConnectedOrThrow() { if (!ensureConnected()) { throw new IllegalStateException("PLC未连接"); } } }