PlcProxyServer.java 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. package com.mes.util;
  2. import com.alibaba.fastjson2.JSONObject;
  3. import com.github.xingshuangs.iot.protocol.melsec.enums.EMcFrameType;
  4. import com.github.xingshuangs.iot.protocol.melsec.enums.EMcSeries;
  5. import com.github.xingshuangs.iot.protocol.melsec.service.McPLC;
  6. import com.sun.net.httpserver.HttpExchange;
  7. import com.sun.net.httpserver.HttpHandler;
  8. import com.sun.net.httpserver.HttpServer;
  9. import org.slf4j.Logger;
  10. import org.slf4j.LoggerFactory;
  11. import java.io.OutputStream;
  12. import java.net.InetSocketAddress;
  13. import java.net.URI;
  14. import java.net.URLDecoder;
  15. import java.util.HashMap;
  16. import java.util.List;
  17. import java.util.Locale;
  18. import java.util.Map;
  19. import java.util.Properties;
  20. import java.util.concurrent.Executors;
  21. public class PlcProxyServer {
  22. private static final Logger log = LoggerFactory.getLogger(PlcProxyServer.class);
  23. private static final int DEFAULT_PORT = 18080;
  24. private static final int DEFAULT_CONNECT_TIMEOUT_MS = 2000;
  25. private static final int DEFAULT_RECEIVE_TIMEOUT_MS = 3000;
  26. private static final Map<String, Object> PLC_LOCKS = new HashMap<>();
  27. private static final Map<String, McPLC> PLC_CACHE = new HashMap<>();
  28. public static void main(String[] args) throws Exception {
  29. Properties props = ConfigUtils.loadPropertiesUnchecked();
  30. PlcTarget.setProperties(props);
  31. int port = parseInt(props.getProperty("plc.proxy.server.port"), DEFAULT_PORT);
  32. HttpServer server = HttpServer.create(new InetSocketAddress(port), 0);
  33. server.createContext("/health", new HealthHandler());
  34. server.createContext("/readBoolean", new ReadBooleanHandler());
  35. server.createContext("/readBooleans", new ReadBooleansHandler());
  36. server.createContext("/readInt16", new ReadInt16Handler());
  37. server.createContext("/writeBoolean", new WriteBooleanHandler());
  38. server.setExecutor(Executors.newCachedThreadPool());
  39. server.start();
  40. log.info("PLC proxy server started, port={}", port);
  41. System.out.println("PLC proxy server started, port=" + port);
  42. }
  43. private static class HealthHandler implements HttpHandler {
  44. @Override
  45. public void handle(HttpExchange exchange) {
  46. JSONObject json = ok();
  47. json.put("status", "UP");
  48. writeJson(exchange, 200, json);
  49. }
  50. }
  51. private static class ReadBooleanHandler implements HttpHandler {
  52. @Override
  53. public void handle(HttpExchange exchange) {
  54. handlePlc(exchange, new PlcAction() {
  55. @Override
  56. public Object execute(McPLC plc, Map<String, String> params) {
  57. return plc.readBoolean(required(params, "address"));
  58. }
  59. });
  60. }
  61. }
  62. private static class ReadBooleansHandler implements HttpHandler {
  63. @Override
  64. public void handle(HttpExchange exchange) {
  65. handlePlc(exchange, new PlcAction() {
  66. @Override
  67. public Object execute(McPLC plc, Map<String, String> params) {
  68. int count = parseInt(params.get("count"), 1);
  69. return plc.readBoolean(required(params, "address"), count);
  70. }
  71. });
  72. }
  73. }
  74. private static class ReadInt16Handler implements HttpHandler {
  75. @Override
  76. public void handle(HttpExchange exchange) {
  77. handlePlc(exchange, new PlcAction() {
  78. @Override
  79. public Object execute(McPLC plc, Map<String, String> params) {
  80. return plc.readInt16(required(params, "address"));
  81. }
  82. });
  83. }
  84. }
  85. private static class WriteBooleanHandler implements HttpHandler {
  86. @Override
  87. public void handle(HttpExchange exchange) {
  88. handlePlc(exchange, new PlcAction() {
  89. @Override
  90. public Object execute(McPLC plc, Map<String, String> params) {
  91. boolean value = Boolean.parseBoolean(required(params, "value"));
  92. plc.writeBoolean(required(params, "address"), value);
  93. return Boolean.valueOf(value);
  94. }
  95. });
  96. }
  97. }
  98. private static void handlePlc(HttpExchange exchange, PlcAction action) {
  99. Map<String, String> params = parseQuery(exchange.getRequestURI());
  100. try {
  101. PlcTarget target = PlcTarget.from(params);
  102. Object result;
  103. synchronized (getPlcLock(target.key)) {
  104. McPLC plc = getCachedPlc(target);
  105. try {
  106. result = action.execute(plc, params);
  107. } catch (RuntimeException e) {
  108. discardCachedPlc(target.key, plc);
  109. throw e;
  110. }
  111. }
  112. JSONObject json = ok();
  113. json.put("value", result);
  114. writeJson(exchange, 200, json);
  115. } catch (Exception e) {
  116. log.warn("PLC proxy request failed, uri={}", exchange.getRequestURI(), e);
  117. JSONObject json = new JSONObject();
  118. json.put("success", false);
  119. json.put("message", e.getMessage());
  120. writeJson(exchange, 500, json);
  121. }
  122. }
  123. private static McPLC getCachedPlc(PlcTarget target) {
  124. McPLC plc = PLC_CACHE.get(target.key);
  125. if (plc == null) {
  126. plc = createPlc(target);
  127. PLC_CACHE.put(target.key, plc);
  128. log.info("PLC proxy connected, plc={}, series={}, frame={}", target.key, target.series, target.frame);
  129. }
  130. return plc;
  131. }
  132. private static McPLC createPlc(PlcTarget target) {
  133. EMcSeries series = parseSeries(target.series);
  134. EMcFrameType frameType = parseFrameType(target.frame, series.getFrameType());
  135. McPLC plc = new McPLC(series, frameType, target.ip, target.port);
  136. plc.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT_MS);
  137. plc.setReceiveTimeout(DEFAULT_RECEIVE_TIMEOUT_MS);
  138. return plc;
  139. }
  140. private static void discardCachedPlc(String key, McPLC plc) {
  141. McPLC cached = PLC_CACHE.get(key);
  142. if (cached == plc) {
  143. PLC_CACHE.remove(key);
  144. }
  145. if (plc != null) {
  146. plc.close();
  147. }
  148. }
  149. private static Object getPlcLock(String key) {
  150. synchronized (PLC_LOCKS) {
  151. Object lock = PLC_LOCKS.get(key);
  152. if (lock == null) {
  153. lock = new Object();
  154. PLC_LOCKS.put(key, lock);
  155. }
  156. return lock;
  157. }
  158. }
  159. private static JSONObject ok() {
  160. JSONObject json = new JSONObject();
  161. json.put("success", true);
  162. return json;
  163. }
  164. private static void writeJson(HttpExchange exchange, int status, JSONObject json) {
  165. byte[] body = json.toJSONString().getBytes(java.nio.charset.StandardCharsets.UTF_8);
  166. try {
  167. exchange.getResponseHeaders().set("Content-Type", "application/json;charset=UTF-8");
  168. exchange.sendResponseHeaders(status, body.length);
  169. try (OutputStream os = exchange.getResponseBody()) {
  170. os.write(body);
  171. }
  172. } catch (Exception e) {
  173. log.warn("write proxy response failed", e);
  174. } finally {
  175. exchange.close();
  176. }
  177. }
  178. private static Map<String, String> parseQuery(URI uri) {
  179. Map<String, String> params = new HashMap<>();
  180. String query = uri.getRawQuery();
  181. if (query == null || query.isEmpty()) {
  182. return params;
  183. }
  184. String[] pairs = query.split("&");
  185. for (String pair : pairs) {
  186. int index = pair.indexOf('=');
  187. String key = index >= 0 ? pair.substring(0, index) : pair;
  188. String value = index >= 0 ? pair.substring(index + 1) : "";
  189. params.put(decode(key), decode(value));
  190. }
  191. return params;
  192. }
  193. private static String required(Map<String, String> params, String key) {
  194. String value = params.get(key);
  195. if (value == null || value.trim().isEmpty()) {
  196. throw new IllegalArgumentException("missing parameter: " + key);
  197. }
  198. return value;
  199. }
  200. private static String decode(String value) {
  201. try {
  202. return URLDecoder.decode(value, "UTF-8");
  203. } catch (Exception e) {
  204. return value;
  205. }
  206. }
  207. private static EMcSeries parseSeries(String series) {
  208. if ("Q_L".equalsIgnoreCase(series)) {
  209. return EMcSeries.Q_L;
  210. }
  211. if ("IQ_R".equalsIgnoreCase(series)) {
  212. return EMcSeries.IQ_R;
  213. }
  214. return EMcSeries.QnA;
  215. }
  216. private static EMcFrameType parseFrameType(String frameType, EMcFrameType defaultValue) {
  217. if (frameType == null || frameType.trim().isEmpty()) {
  218. return defaultValue;
  219. }
  220. String normalized = frameType.trim().toUpperCase(Locale.ROOT);
  221. if (!normalized.startsWith("FRAME_")) {
  222. normalized = "FRAME_" + normalized;
  223. }
  224. try {
  225. return EMcFrameType.valueOf(normalized);
  226. } catch (Exception e) {
  227. return defaultValue;
  228. }
  229. }
  230. private static int parseInt(String value, int defaultValue) {
  231. try {
  232. return Integer.parseInt(value);
  233. } catch (Exception e) {
  234. return defaultValue;
  235. }
  236. }
  237. private interface PlcAction {
  238. Object execute(McPLC plc, Map<String, String> params);
  239. }
  240. private static class PlcTarget {
  241. private static Properties properties = new Properties();
  242. private final String key;
  243. private final String ip;
  244. private final int port;
  245. private final String series;
  246. private final String frame;
  247. private PlcTarget(String ip, int port, String series, String frame) {
  248. this.ip = ip;
  249. this.port = port;
  250. this.series = firstNotEmpty(series, findConfiguredValue(ip, port, "plc.series"));
  251. this.frame = firstNotEmpty(frame, findConfiguredValue(ip, port, "plc.frame"));
  252. this.key = ip + ":" + port;
  253. }
  254. private static void setProperties(Properties props) {
  255. properties = props == null ? new Properties() : props;
  256. }
  257. private static PlcTarget from(Map<String, String> params) {
  258. String plc = required(params, "plc");
  259. int index = plc.lastIndexOf(':');
  260. if (index <= 0 || index == plc.length() - 1) {
  261. throw new IllegalArgumentException("invalid plc parameter: " + plc);
  262. }
  263. String ip = plc.substring(0, index);
  264. int port = parseInt(plc.substring(index + 1), 0);
  265. if (port <= 0) {
  266. throw new IllegalArgumentException("invalid plc port: " + plc);
  267. }
  268. return new PlcTarget(ip, port, params.get("series"), params.get("frame"));
  269. }
  270. private static String findConfiguredValue(String ip, int port, String suffix) {
  271. for (Object keyObj : properties.keySet()) {
  272. String key = String.valueOf(keyObj);
  273. if (!key.endsWith(".plc.ip")) {
  274. continue;
  275. }
  276. String prefix = key.substring(0, key.length() - ".plc.ip".length());
  277. String configuredIp = properties.getProperty(prefix + ".plc.ip");
  278. int configuredPort = parseInt(properties.getProperty(prefix + ".plc.port"), 0);
  279. if (ip.equals(configuredIp) && port == configuredPort) {
  280. return properties.getProperty(prefix + "." + suffix);
  281. }
  282. }
  283. return "";
  284. }
  285. private static String firstNotEmpty(String first, String second) {
  286. if (first != null && !first.trim().isEmpty()) {
  287. return first;
  288. }
  289. return second == null ? "" : second;
  290. }
  291. }
  292. }