package com.mes.util; import com.alibaba.fastjson2.JSONObject; import com.github.xingshuangs.iot.protocol.melsec.enums.EMcFrameType; import com.github.xingshuangs.iot.protocol.melsec.enums.EMcSeries; import com.github.xingshuangs.iot.protocol.melsec.service.McPLC; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.URI; import java.net.URLDecoder; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.concurrent.Executors; public class PlcProxyServer { private static final Logger log = LoggerFactory.getLogger(PlcProxyServer.class); private static final int DEFAULT_PORT = 18080; private static final int DEFAULT_CONNECT_TIMEOUT_MS = 2000; private static final int DEFAULT_RECEIVE_TIMEOUT_MS = 3000; private static final Map PLC_LOCKS = new HashMap<>(); private static final Map PLC_CACHE = new HashMap<>(); public static void main(String[] args) throws Exception { Properties props = ConfigUtils.loadPropertiesUnchecked(); PlcTarget.setProperties(props); int port = parseInt(props.getProperty("plc.proxy.server.port"), DEFAULT_PORT); HttpServer server = HttpServer.create(new InetSocketAddress(port), 0); server.createContext("/health", new HealthHandler()); server.createContext("/readBoolean", new ReadBooleanHandler()); server.createContext("/readBooleans", new ReadBooleansHandler()); server.createContext("/readInt16", new ReadInt16Handler()); server.createContext("/writeBoolean", new WriteBooleanHandler()); server.setExecutor(Executors.newCachedThreadPool()); server.start(); log.info("PLC proxy server started, port={}", port); System.out.println("PLC proxy server started, port=" + port); } private static class HealthHandler implements HttpHandler { @Override public void handle(HttpExchange exchange) { JSONObject json = ok(); json.put("status", "UP"); writeJson(exchange, 200, json); } } private static class ReadBooleanHandler implements HttpHandler { @Override public void handle(HttpExchange exchange) { handlePlc(exchange, new PlcAction() { @Override public Object execute(McPLC plc, Map params) { return plc.readBoolean(required(params, "address")); } }); } } private static class ReadBooleansHandler implements HttpHandler { @Override public void handle(HttpExchange exchange) { handlePlc(exchange, new PlcAction() { @Override public Object execute(McPLC plc, Map params) { int count = parseInt(params.get("count"), 1); return plc.readBoolean(required(params, "address"), count); } }); } } private static class ReadInt16Handler implements HttpHandler { @Override public void handle(HttpExchange exchange) { handlePlc(exchange, new PlcAction() { @Override public Object execute(McPLC plc, Map params) { return plc.readInt16(required(params, "address")); } }); } } private static class WriteBooleanHandler implements HttpHandler { @Override public void handle(HttpExchange exchange) { handlePlc(exchange, new PlcAction() { @Override public Object execute(McPLC plc, Map params) { boolean value = Boolean.parseBoolean(required(params, "value")); plc.writeBoolean(required(params, "address"), value); return Boolean.valueOf(value); } }); } } private static void handlePlc(HttpExchange exchange, PlcAction action) { Map params = parseQuery(exchange.getRequestURI()); try { PlcTarget target = PlcTarget.from(params); Object result; synchronized (getPlcLock(target.key)) { McPLC plc = getCachedPlc(target); try { result = action.execute(plc, params); } catch (RuntimeException e) { discardCachedPlc(target.key, plc); throw e; } } JSONObject json = ok(); json.put("value", result); writeJson(exchange, 200, json); } catch (Exception e) { log.warn("PLC proxy request failed, uri={}", exchange.getRequestURI(), e); JSONObject json = new JSONObject(); json.put("success", false); json.put("message", e.getMessage()); writeJson(exchange, 500, json); } } private static McPLC getCachedPlc(PlcTarget target) { McPLC plc = PLC_CACHE.get(target.key); if (plc == null) { plc = createPlc(target); PLC_CACHE.put(target.key, plc); log.info("PLC proxy connected, plc={}, series={}, frame={}", target.key, target.series, target.frame); } return plc; } private static McPLC createPlc(PlcTarget target) { EMcSeries series = parseSeries(target.series); EMcFrameType frameType = parseFrameType(target.frame, series.getFrameType()); McPLC plc = new McPLC(series, frameType, target.ip, target.port); plc.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT_MS); plc.setReceiveTimeout(DEFAULT_RECEIVE_TIMEOUT_MS); return plc; } private static void discardCachedPlc(String key, McPLC plc) { McPLC cached = PLC_CACHE.get(key); if (cached == plc) { PLC_CACHE.remove(key); } if (plc != null) { plc.close(); } } private static Object getPlcLock(String key) { synchronized (PLC_LOCKS) { Object lock = PLC_LOCKS.get(key); if (lock == null) { lock = new Object(); PLC_LOCKS.put(key, lock); } return lock; } } private static JSONObject ok() { JSONObject json = new JSONObject(); json.put("success", true); return json; } private static void writeJson(HttpExchange exchange, int status, JSONObject json) { byte[] body = json.toJSONString().getBytes(java.nio.charset.StandardCharsets.UTF_8); try { exchange.getResponseHeaders().set("Content-Type", "application/json;charset=UTF-8"); exchange.sendResponseHeaders(status, body.length); try (OutputStream os = exchange.getResponseBody()) { os.write(body); } } catch (Exception e) { log.warn("write proxy response failed", e); } finally { exchange.close(); } } private static Map parseQuery(URI uri) { Map params = new HashMap<>(); String query = uri.getRawQuery(); if (query == null || query.isEmpty()) { return params; } String[] pairs = query.split("&"); for (String pair : pairs) { int index = pair.indexOf('='); String key = index >= 0 ? pair.substring(0, index) : pair; String value = index >= 0 ? pair.substring(index + 1) : ""; params.put(decode(key), decode(value)); } return params; } private static String required(Map params, String key) { String value = params.get(key); if (value == null || value.trim().isEmpty()) { throw new IllegalArgumentException("missing parameter: " + key); } return value; } private static String decode(String value) { try { return URLDecoder.decode(value, "UTF-8"); } catch (Exception e) { return value; } } private static EMcSeries parseSeries(String series) { if ("Q_L".equalsIgnoreCase(series)) { return EMcSeries.Q_L; } if ("IQ_R".equalsIgnoreCase(series)) { return EMcSeries.IQ_R; } return EMcSeries.QnA; } private static EMcFrameType parseFrameType(String frameType, EMcFrameType defaultValue) { if (frameType == null || frameType.trim().isEmpty()) { return defaultValue; } String normalized = frameType.trim().toUpperCase(Locale.ROOT); if (!normalized.startsWith("FRAME_")) { normalized = "FRAME_" + normalized; } try { return EMcFrameType.valueOf(normalized); } catch (Exception e) { return defaultValue; } } private static int parseInt(String value, int defaultValue) { try { return Integer.parseInt(value); } catch (Exception e) { return defaultValue; } } private interface PlcAction { Object execute(McPLC plc, Map params); } private static class PlcTarget { private static Properties properties = new Properties(); private final String key; private final String ip; private final int port; private final String series; private final String frame; private PlcTarget(String ip, int port, String series, String frame) { this.ip = ip; this.port = port; this.series = firstNotEmpty(series, findConfiguredValue(ip, port, "plc.series")); this.frame = firstNotEmpty(frame, findConfiguredValue(ip, port, "plc.frame")); this.key = ip + ":" + port; } private static void setProperties(Properties props) { properties = props == null ? new Properties() : props; } private static PlcTarget from(Map params) { String plc = required(params, "plc"); int index = plc.lastIndexOf(':'); if (index <= 0 || index == plc.length() - 1) { throw new IllegalArgumentException("invalid plc parameter: " + plc); } String ip = plc.substring(0, index); int port = parseInt(plc.substring(index + 1), 0); if (port <= 0) { throw new IllegalArgumentException("invalid plc port: " + plc); } return new PlcTarget(ip, port, params.get("series"), params.get("frame")); } private static String findConfiguredValue(String ip, int port, String suffix) { for (Object keyObj : properties.keySet()) { String key = String.valueOf(keyObj); if (!key.endsWith(".plc.ip")) { continue; } String prefix = key.substring(0, key.length() - ".plc.ip".length()); String configuredIp = properties.getProperty(prefix + ".plc.ip"); int configuredPort = parseInt(properties.getProperty(prefix + ".plc.port"), 0); if (ip.equals(configuredIp) && port == configuredPort) { return properties.getProperty(prefix + "." + suffix); } } return ""; } private static String firstNotEmpty(String first, String second) { if (first != null && !first.trim().isEmpty()) { return first; } return second == null ? "" : second; } } }