package com.mes.plc; import com.github.xingshuangs.iot.protocol.melsec.model.McDeviceAddress; import com.github.xingshuangs.iot.protocol.melsec.model.McDeviceContent; import com.github.xingshuangs.iot.protocol.melsec.service.McNetwork; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Collections; /** * 三菱 MC 3E 协议封装:读写位软元件、读 D 区字符串 * 地址写法:M205、D5000 */ public class McPlcClient { private static final Logger log = LoggerFactory.getLogger(McPlcClient.class); private final String ip; private final int port; private McNetwork mc; private volatile boolean connected = false; public McPlcClient(String ip, int port) { this.ip = ip; this.port = port; } public synchronized void connect() { if (connected) { return; } try { mc = new McNetwork(ip, port); mc.setPersistence(true); mc.setEnableReconnect(false); mc.setConnectTimeout(3000); mc.setReceiveTimeout(3000); mc.connect(); connected = true; log.info("PLC 连接成功 " + ip + ":" + port); } catch (Exception e) { closeQuietly(); connected = false; mc = null; log.info("PLC 连接失败 " + ip + ":" + port + " " + e.getMessage()); } } public synchronized void disconnect() { closeQuietly(); connected = false; mc = null; } public boolean isConnected() { return connected; } /** 读单个位,失败返回 false 并标记断开 */ public synchronized boolean readBit(String address) { Boolean v = tryReadBit(address); return v != null && v; } /** 读单个位:成功返回 true/false,通信失败返回 null(调用方可跳过本轮) */ public synchronized Boolean tryReadBit(String address) { try { checkMc(); McDeviceContent content = mc.readDeviceBatchInBit(McDeviceAddress.createBy(address, 1)); return mc.getBooleansBy(content.getData()).get(0); } catch (Exception e) { onError("读 " + address, e); return null; } } /** 写单个位 */ public synchronized boolean writeBit(String address, boolean value) { try { checkMc(); McDeviceAddress addr = McDeviceAddress.createBy(address, 1); byte[] data = mc.getBytesBy(Collections.singletonList(value)); mc.writeDeviceBatchInBit(new McDeviceContent(addr.getDeviceCode(), addr.getHeadDeviceNumber(), 1, data)); return true; } catch (Exception e) { onError("写 " + address, e); return false; } } /** * 读 D 区字符串(ASCII/GBK),charCount 为字符数 * 现场 PLC 存的是正序,直接按字节转字符串 */ public synchronized String readString(String address, int charCount) { try { checkMc(); int words = (charCount + 1) / 2; McDeviceContent content = mc.readDeviceBatchInWord(McDeviceAddress.createBy(address, words)); byte[] raw = content.getData(); String s = new String(raw, "GBK"); int end = s.indexOf('\0'); if (end >= 0) { s = s.substring(0, end); } return s.trim(); } catch (Exception e) { onError("读 " + address, e); return ""; } } private void checkMc() { if (!connected || mc == null) { throw new IllegalStateException("PLC 未连接"); } } private void onError(String action, Exception e) { log.info("PLC " + action + " 失败: " + e.getMessage()); // 通信异常后关闭旧连接,由外层重连 closeQuietly(); connected = false; mc = null; } private void closeQuietly() { try { if (mc != null) { mc.close(); } } catch (Exception ignored) { } } }