作者 钟来

plc终端功能

正在显示 68 个修改的文件 包含 3977 行增加59 行删除
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project xmlns="http://maven.apache.org/POM/4.0.0"
  3 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5 + <modelVersion>4.0.0</modelVersion>
  6 + <parent>
  7 + <groupId>com.zhonglai.luhui</groupId>
  8 + <artifactId>lh-jar</artifactId>
  9 + <version>1.0-SNAPSHOT</version>
  10 + </parent>
  11 +
  12 + <artifactId>lh-device-mqtt-terminal-jar</artifactId>
  13 +
  14 + <properties>
  15 + <maven.compiler.source>8</maven.compiler.source>
  16 + <maven.compiler.target>8</maven.compiler.target>
  17 + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  18 + </properties>
  19 + <dependencies>
  20 + <dependency>
  21 + <groupId>org.slf4j</groupId>
  22 + <artifactId>slf4j-api</artifactId>
  23 + </dependency>
  24 +
  25 + <dependency>
  26 + <groupId>ch.qos.logback</groupId>
  27 + <artifactId>logback-classic</artifactId>
  28 + </dependency>
  29 + <dependency>
  30 + <groupId>com.alibaba</groupId>
  31 + <artifactId>fastjson</artifactId>
  32 + </dependency>
  33 +
  34 + <!-- mqtt -->
  35 + <dependency>
  36 + <groupId>org.eclipse.paho</groupId>
  37 + <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
  38 + </dependency>
  39 +
  40 + </dependencies>
  41 +</project>
  1 +package com.zhonglai.luhui.device.mqtt.terminal.jar;
  2 +
  3 +import com.zhonglai.luhui.device.mqtt.terminal.jar.dto.Topic;
  4 +import com.zhonglai.luhui.device.mqtt.terminal.jar.mqtt.MqttService;
  5 +
  6 +/**
  7 + * 数据解析工厂
  8 + */
  9 +public interface ParseDataFactory {
  10 + void parse( Topic topicDto,byte[] paylaod, MqttService mqttService);
  11 +}
  1 +package com.zhonglai.luhui.device.mqtt.terminal.jar.config;
  2 +
  3 +import java.io.FileReader;
  4 +import java.util.HashSet;
  5 +import java.util.Properties;
  6 +import java.util.Set;
  7 +
  8 +public class MqttConfig {
  9 + public static String mqttBroker;
  10 + public static String mqttUserName;
  11 + public static String mqttPassword;
  12 + public static String mqttClientId;
  13 +
  14 + public static Set<String> subTopic;
  15 +
  16 + public static void init(String path)
  17 + {
  18 + //加载properties配置文件
  19 + Properties properties = loadProperties(path);
  20 +
  21 + mqttBroker = properties.getProperty("mqtt.broker");
  22 + mqttUserName = properties.getProperty("mqtt.username");
  23 + mqttPassword = properties.getProperty("mqtt.password");
  24 + mqttClientId = properties.getProperty("mqtt.clientId");
  25 + subTopic = new HashSet<>();
  26 +
  27 + String topicsStr = properties.getProperty("mqtt.subTopic");
  28 + if(null != topicsStr && !"".equals(topicsStr))
  29 + {
  30 + if (topicsStr != null && !topicsStr.trim().isEmpty()) {
  31 + String[] topics = topicsStr.split(",");
  32 + for (String topic : topics) {
  33 + subTopic.add(topic.trim());
  34 + }
  35 + }
  36 + }
  37 + }
  38 +
  39 + private static Properties loadProperties(String path)
  40 + {
  41 + Properties properties = new Properties();
  42 + try {
  43 + if(null != path && !"".equals(path))
  44 + {
  45 + properties.load(new FileReader(path+"/mqtt.properties"));
  46 + }else{
  47 + properties.load(MqttConfig.class.getClassLoader().getResourceAsStream("configs/mqtt.properties"));
  48 + }
  49 +
  50 + } catch (Exception e) {
  51 + throw new RuntimeException("加载mqtt.properties失败,未找到配置文件或内容为空");
  52 + }
  53 + return properties;
  54 + }
  55 +}
  1 +package com.zhonglai.luhui.device.mqtt.terminal.jar.dto;
  2 +
  3 +public class Topic {
  4 + private String topicType;
  5 + private String time;
  6 + public Topic()
  7 + {
  8 +
  9 + }
  10 + public Topic(String stopicStr)
  11 + {
  12 + String[] strs = stopicStr.split("/");
  13 + if (strs.length >= 2) {
  14 + topicType = strs[0];
  15 + time = strs[1];
  16 + } else {
  17 + topicType = stopicStr; // 或者给默认值
  18 + time = "";
  19 + }
  20 + }
  21 +
  22 + public String getTopicType() {
  23 + return topicType;
  24 + }
  25 +
  26 + public void setTopicType(String topicType) {
  27 + this.topicType = topicType;
  28 + }
  29 +
  30 + public String getTime() {
  31 + return time;
  32 + }
  33 +
  34 + public void setTime(String time) {
  35 + this.time = time;
  36 + }
  37 +}
  1 +package com.zhonglai.luhui.device.mqtt.terminal.jar.mqtt;
  2 +
  3 +import com.alibaba.fastjson.JSONObject;
  4 +import com.zhonglai.luhui.device.mqtt.terminal.jar.ParseDataFactory;
  5 +import com.zhonglai.luhui.device.mqtt.terminal.jar.dto.Topic;
  6 +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
  7 +import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
  8 +import org.eclipse.paho.client.mqttv3.MqttMessage;
  9 +import org.slf4j.Logger;
  10 +import org.slf4j.LoggerFactory;
  11 +
  12 +import java.util.Set;
  13 +
  14 +/**
  15 + * mqtt回调
  16 + */
  17 +public class MqttCallback implements MqttCallbackExtended {
  18 + private final Logger log = LoggerFactory.getLogger(this.getClass());
  19 +
  20 + private final MqttService mqttclient;
  21 + private final ParseDataFactory parseDataFactory;
  22 +
  23 + public MqttCallback(MqttService mqttService, ParseDataFactory parseDataFactory) {
  24 + this.mqttclient = mqttService;
  25 + this.parseDataFactory = parseDataFactory;
  26 + }
  27 +
  28 + @Override
  29 + public void connectComplete(boolean b, String s) {
  30 + log.info("连接成功");
  31 + Set<String> topics = mqttclient.subscribe();
  32 + log.info("-----------订阅成功:{}--------------------",topics);
  33 + }
  34 +
  35 + @Override
  36 + public void connectionLost(Throwable cause) {
  37 + log.error("连接丢失重新链接",cause);
  38 + while (!mqttclient.connect())
  39 + {
  40 + try {
  41 + Thread.sleep(60000);
  42 + } catch (InterruptedException e) {
  43 + e.printStackTrace();
  44 + }
  45 + }
  46 + }
  47 +
  48 + @Override
  49 + public void messageArrived(String topic, MqttMessage message) throws Exception {
  50 + log.info("mqtt发来消息:topic={} payload={}", topic, message.toString());
  51 + Topic topicDto = new Topic(topic);
  52 + parseDataFactory.parse(topicDto, message.getPayload(),mqttclient);
  53 + }
  54 +
  55 + @Override
  56 + public void deliveryComplete(IMqttDeliveryToken token) {
  57 + log.info("消息发送完成");
  58 + }
  59 +}
  1 +package com.zhonglai.luhui.device.mqtt.terminal.jar.mqtt;
  2 +
  3 +import com.zhonglai.luhui.device.mqtt.terminal.jar.ParseDataFactory;
  4 +import com.zhonglai.luhui.device.mqtt.terminal.jar.config.MqttConfig;
  5 +import org.eclipse.paho.client.mqttv3.MqttClient;
  6 +import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
  7 +import org.eclipse.paho.client.mqttv3.MqttException;
  8 +import org.eclipse.paho.client.mqttv3.MqttMessage;
  9 +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
  10 +import org.slf4j.Logger;
  11 +import org.slf4j.LoggerFactory;
  12 +
  13 +import javax.annotation.PreDestroy;
  14 +import java.util.Set;
  15 +
  16 +public class MqttService {
  17 + private final Logger log = LoggerFactory.getLogger(this.getClass());
  18 +
  19 + private MqttClient mqttclient;
  20 + private MqttConnectOptions options;
  21 +
  22 + // 使用 volatile 确保多线程内存可见性和禁止指令重排
  23 + private static volatile MqttService instance;
  24 +
  25 + private ParseDataFactory parseDataFactory;
  26 +
  27 + private MqttService() {
  28 + }
  29 +
  30 + public static MqttService getInstance() {
  31 + if (instance == null) {
  32 + synchronized (MqttService.class) {
  33 + if (instance == null) {
  34 + instance = new MqttService();
  35 + }
  36 + }
  37 + }
  38 + return instance;
  39 + }
  40 +
  41 + private void init() throws MqttException {
  42 + if(null == mqttclient)
  43 + {
  44 + mqttclient = new MqttClient(MqttConfig.mqttBroker, MqttConfig.mqttClientId, new MemoryPersistence());
  45 + }
  46 + options = new MqttConnectOptions();
  47 + options.setCleanSession(true);
  48 + options.setConnectionTimeout(15);
  49 + options.setKeepAliveInterval(60); // 添加心跳设置,单位为秒
  50 + //设置断开后重新连接
  51 + options.setAutomaticReconnect(true);
  52 + mqttclient.setCallback(new MqttCallback(this,parseDataFactory));
  53 + }
  54 +
  55 + public boolean connect() {
  56 + try {
  57 + options.setUserName(MqttConfig.mqttUserName);
  58 + options.setPassword(MqttConfig.mqttPassword.toCharArray());
  59 + mqttclient.connect(options);
  60 + return true;
  61 + } catch (MqttException e) {
  62 + log.error("-----------mqtt连接服务器失败--------------------",e);
  63 + }
  64 + return false;
  65 + }
  66 +
  67 + public void start(ParseDataFactory factory) throws MqttException{
  68 + this.parseDataFactory = factory;
  69 + log.info("-----------开始启动mqtt监听服务--------------------");
  70 + init();
  71 + connect();
  72 + log.info("-----------mqtt连接服务器成功--------------------");
  73 +
  74 + }
  75 +
  76 + @PreDestroy
  77 + public void stop() throws MqttException {
  78 + if(null != mqttclient)
  79 + {
  80 + log.info("退出mqtt服务");
  81 + mqttclient.unsubscribe(MqttConfig.subTopic.toArray(new String[MqttConfig.subTopic.size()]));
  82 + mqttclient.disconnect();
  83 + mqttclient.close();
  84 + }
  85 + instance = null;
  86 + }
  87 + public void publish(String topic, String messageStr) throws MqttException {
  88 + MqttMessage message = new MqttMessage();
  89 + message.setPayload(messageStr.getBytes());
  90 + mqttclient.publish(topic,message);
  91 + }
  92 +
  93 + public Set<String> subscribe()
  94 + {
  95 + try {
  96 + mqttclient.subscribe(MqttConfig.subTopic.toArray(new String[MqttConfig.subTopic.size()]));
  97 + } catch (MqttException e) {
  98 + e.printStackTrace();
  99 + }
  100 + return MqttConfig.subTopic;
  101 + }
  102 +
  103 + public boolean isConnect()
  104 + {
  105 + return mqttclient != null && mqttclient.isConnected();
  106 + }
  107 +}
@@ -7,6 +7,10 @@ import com.ruoyi.common.utils.StringUtils; @@ -7,6 +7,10 @@ import com.ruoyi.common.utils.StringUtils;
7 import com.zhonglai.luhui.device.analysis.comm.dto.thingsmodels.specs.*; 7 import com.zhonglai.luhui.device.analysis.comm.dto.thingsmodels.specs.*;
8 import com.zhonglai.luhui.device.domain.IotThingsModel; 8 import com.zhonglai.luhui.device.domain.IotThingsModel;
9 import lombok.Data; 9 import lombok.Data;
  10 +import org.slf4j.Logger;
  11 +import org.slf4j.LoggerFactory;
  12 +
  13 +import java.util.function.BiConsumer;
10 14
11 /** 15 /**
12 * 物模型工厂的公用方法 16 * 物模型工厂的公用方法
@@ -15,6 +19,8 @@ import lombok.Data; @@ -15,6 +19,8 @@ import lombok.Data;
15 @Data 19 @Data
16 public abstract class ThingsModelItemBase<T> implements ThingsModelBase<T> 20 public abstract class ThingsModelItemBase<T> implements ThingsModelBase<T>
17 { 21 {
  22 + private static final Logger log = LoggerFactory.getLogger(ThingsModelItemBase.class);
  23 +
18 /** 24 /**
19 * 下位机端数据转换物模型 25 * 下位机端数据转换物模型
20 * @param thingsModelDataTypeEnum 26 * @param thingsModelDataTypeEnum
@@ -24,14 +30,7 @@ public abstract class ThingsModelItemBase<T> implements ThingsModelBase<T> @@ -24,14 +30,7 @@ public abstract class ThingsModelItemBase<T> implements ThingsModelBase<T>
24 */ 30 */
25 public static ThingsModelItemBase newhingsModel(ThingsModelDataTypeEnum thingsModelDataTypeEnum,IotThingsModel thingsModel,JsonElement jsonElement) 31 public static ThingsModelItemBase newhingsModel(ThingsModelDataTypeEnum thingsModelDataTypeEnum,IotThingsModel thingsModel,JsonElement jsonElement)
26 { 32 {
27 - if (!jsonElement.isJsonNull())  
28 - {  
29 - ThingsModelItemBase thingsModelItemBase = createThingsModelItemBase(thingsModelDataTypeEnum,thingsModel,jsonElement);  
30 - thingsModelItemBase.conversionThingsModel(thingsModel);  
31 - thingsModelItemBase.setSaveView(jsonElement.getAsString());  
32 - return thingsModelItemBase;  
33 - }  
34 - return null; 33 + return safeNewhingsModel(thingsModelDataTypeEnum, thingsModel, jsonElement, ThingsModelItemBase::setSaveView);
35 } 34 }
36 35
37 /** 36 /**
@@ -43,12 +42,31 @@ public abstract class ThingsModelItemBase<T> implements ThingsModelBase<T> @@ -43,12 +42,31 @@ public abstract class ThingsModelItemBase<T> implements ThingsModelBase<T>
43 */ 42 */
44 public static ThingsModelItemBase newhingsModelReverse(ThingsModelDataTypeEnum thingsModelDataTypeEnum,IotThingsModel thingsModel,JsonElement jsonElement) 43 public static ThingsModelItemBase newhingsModelReverse(ThingsModelDataTypeEnum thingsModelDataTypeEnum,IotThingsModel thingsModel,JsonElement jsonElement)
45 { 44 {
46 - if (!jsonElement.isJsonNull())  
47 - {  
48 - ThingsModelItemBase thingsModelItemBase = createThingsModelItemBase(thingsModelDataTypeEnum,thingsModel,jsonElement);  
49 - thingsModelItemBase.conversionThingsModel(thingsModel);  
50 - thingsModelItemBase.reverse(jsonElement.getAsString());  
51 - return thingsModelItemBase; 45 + return safeNewhingsModel(thingsModelDataTypeEnum, thingsModel, jsonElement, ThingsModelItemBase::reverse);
  46 + }
  47 +
  48 + private static ThingsModelItemBase safeNewhingsModel(
  49 + ThingsModelDataTypeEnum type,
  50 + IotThingsModel model,
  51 + JsonElement element,
  52 + BiConsumer<ThingsModelItemBase, String> consumer) {
  53 +
  54 + if (element != null && !element.isJsonNull()) {
  55 + String rawValue = element.getAsString();
  56 + ThingsModelItemBase item = createThingsModelItemBase(type, model, element);
  57 + item.conversionThingsModel(model);
  58 +
  59 + if (rawValue != null && !rawValue.trim().isEmpty()) {
  60 + try {
  61 + consumer.accept(item, rawValue);
  62 + } catch (Exception e) {
  63 + log.error("字段 [{}] 处理失败,value={}", model.getModel_name(), rawValue, e);
  64 + consumer.accept(item, "0"); // 默认值
  65 + }
  66 + } else {
  67 + consumer.accept(item, "0");
  68 + }
  69 + return item;
52 } 70 }
53 return null; 71 return null;
54 } 72 }
@@ -4,6 +4,7 @@ public enum CommandType { @@ -4,6 +4,7 @@ public enum CommandType {
4 read, 4 read,
5 write, 5 write,
6 notice, 6 notice,
  7 + host,
7 cleanDeviceHost, 8 cleanDeviceHost,
8 cleanDeviceInfo, 9 cleanDeviceInfo,
9 upIotThingsModel, 10 upIotThingsModel,
@@ -20,6 +20,7 @@ @@ -20,6 +20,7 @@
20 <module>lh-jar-order-service</module> 20 <module>lh-jar-order-service</module>
21 <module>lh-jar-plugins-init</module> 21 <module>lh-jar-plugins-init</module>
22 <module>lh-jar-ssh-proxy</module> 22 <module>lh-jar-ssh-proxy</module>
  23 + <module>lh-device-mqtt-terminal-jar</module>
23 </modules> 24 </modules>
24 25
25 <properties> 26 <properties>
@@ -8,6 +8,7 @@ import com.ruoyi.common.core.domain.Message; @@ -8,6 +8,7 @@ import com.ruoyi.common.core.domain.Message;
8 import com.ruoyi.common.enums.BusinessType; 8 import com.ruoyi.common.enums.BusinessType;
9 import com.ruoyi.common.utils.GsonConstructor; 9 import com.ruoyi.common.utils.GsonConstructor;
10 import com.zhonglai.luhui.action.BaseController; 10 import com.zhonglai.luhui.action.BaseController;
  11 +import com.zhonglai.luhui.api.service.RocketMqSendService;
11 import com.zhonglai.luhui.device.domain.IotDevice; 12 import com.zhonglai.luhui.device.domain.IotDevice;
12 import com.zhonglai.luhui.device.dto.CommandType; 13 import com.zhonglai.luhui.device.dto.CommandType;
13 import com.zhonglai.luhui.device.dto.DeviceCommand; 14 import com.zhonglai.luhui.device.dto.DeviceCommand;
@@ -32,11 +33,9 @@ import java.io.IOException; @@ -32,11 +33,9 @@ import java.io.IOException;
32 @RestController 33 @RestController
33 @RequestMapping("/iot/controlDevice") 34 @RequestMapping("/iot/controlDevice")
34 public class ControlDeviceConreoller extends BaseController { 35 public class ControlDeviceConreoller extends BaseController {
35 - @Autowired  
36 - private RocketMqService rocketMqService;  
37 36
38 @Autowired 37 @Autowired
39 - private IIotDeviceService iotDeviceService; 38 + private RocketMqSendService rocketMqSendService;
40 @ApiOperation(value = "写指令",notes = "body参数描述:\r\n" + 39 @ApiOperation(value = "写指令",notes = "body参数描述:\r\n" +
41 "{\n" + 40 "{\n" +
42 " \"0\":{\n" + 41 " \"0\":{\n" +
@@ -71,7 +70,7 @@ public class ControlDeviceConreoller extends BaseController { @@ -71,7 +70,7 @@ public class ControlDeviceConreoller extends BaseController {
71 deviceCommand.setCommandType(CommandType.write); 70 deviceCommand.setCommandType(CommandType.write);
72 deviceCommand.setData(GsonConstructor.get().fromJson(body,JsonObject.class)); 71 deviceCommand.setData(GsonConstructor.get().fromJson(body,JsonObject.class));
73 72
74 - return deviceControl(deviceCommand); 73 + return rocketMqSendService.deviceControl(deviceCommand);
75 } 74 }
76 75
77 @ApiOperation(value = "读指令",notes = "body参数描述:\r\n" + 76 @ApiOperation(value = "读指令",notes = "body参数描述:\r\n" +
@@ -108,7 +107,7 @@ public class ControlDeviceConreoller extends BaseController { @@ -108,7 +107,7 @@ public class ControlDeviceConreoller extends BaseController {
108 deviceCommand.setDeviceId(deviceId); 107 deviceCommand.setDeviceId(deviceId);
109 deviceCommand.setCommandType(CommandType.write); 108 deviceCommand.setCommandType(CommandType.write);
110 deviceCommand.setData(GsonConstructor.get().fromJson(body,JsonObject.class)); 109 deviceCommand.setData(GsonConstructor.get().fromJson(body,JsonObject.class));
111 - return deviceControl(deviceCommand); 110 + return rocketMqSendService.deviceControl(deviceCommand);
112 } 111 }
113 112
114 @ApiOperation("更新缓存模型") 113 @ApiOperation("更新缓存模型")
@@ -121,7 +120,7 @@ public class ControlDeviceConreoller extends BaseController { @@ -121,7 +120,7 @@ public class ControlDeviceConreoller extends BaseController {
121 JsonObject jsonObject = new JsonObject(); 120 JsonObject jsonObject = new JsonObject();
122 jsonObject.addProperty("product_id",product_id); 121 jsonObject.addProperty("product_id",product_id);
123 deviceCommand.setData(jsonObject); 122 deviceCommand.setData(jsonObject);
124 - return sysControl(deviceCommand); 123 + return rocketMqSendService.sysControl(deviceCommand);
125 } 124 }
126 125
127 @ApiOperation("更新缓存翻译模型") 126 @ApiOperation("更新缓存翻译模型")
@@ -134,7 +133,7 @@ public class ControlDeviceConreoller extends BaseController { @@ -134,7 +133,7 @@ public class ControlDeviceConreoller extends BaseController {
134 JsonObject jsonObject = new JsonObject(); 133 JsonObject jsonObject = new JsonObject();
135 jsonObject.addProperty("product_id",product_id); 134 jsonObject.addProperty("product_id",product_id);
136 deviceCommand.setData(jsonObject); 135 deviceCommand.setData(jsonObject);
137 - return sysControl(deviceCommand); 136 + return rocketMqSendService.sysControl(deviceCommand);
138 } 137 }
139 138
140 @ApiOperation("清除网关缓存") 139 @ApiOperation("清除网关缓存")
@@ -145,7 +144,7 @@ public class ControlDeviceConreoller extends BaseController { @@ -145,7 +144,7 @@ public class ControlDeviceConreoller extends BaseController {
145 DeviceCommand deviceCommand = new DeviceCommand(); 144 DeviceCommand deviceCommand = new DeviceCommand();
146 deviceCommand.setDeviceId(deviceId); 145 deviceCommand.setDeviceId(deviceId);
147 deviceCommand.setCommandType(CommandType.cleanDeviceHost); 146 deviceCommand.setCommandType(CommandType.cleanDeviceHost);
148 - return sysControl(deviceCommand); 147 + return rocketMqSendService.sysControl(deviceCommand);
149 } 148 }
150 149
151 @ApiOperation("更新终端缓存") 150 @ApiOperation("更新终端缓存")
@@ -162,7 +161,7 @@ public class ControlDeviceConreoller extends BaseController { @@ -162,7 +161,7 @@ public class ControlDeviceConreoller extends BaseController {
162 JsonObject jsonObject = new JsonObject(); 161 JsonObject jsonObject = new JsonObject();
163 jsonObject.addProperty("sensor_number",sensor_number); 162 jsonObject.addProperty("sensor_number",sensor_number);
164 deviceCommand.setData(jsonObject); 163 deviceCommand.setData(jsonObject);
165 - return sysControl(deviceCommand); 164 + return rocketMqSendService.sysControl(deviceCommand);
166 } 165 }
167 166
168 @ApiOperation("添加订阅") 167 @ApiOperation("添加订阅")
@@ -179,7 +178,7 @@ public class ControlDeviceConreoller extends BaseController { @@ -179,7 +178,7 @@ public class ControlDeviceConreoller extends BaseController {
179 jsonObject.addProperty("product_ids",product_ids); 178 jsonObject.addProperty("product_ids",product_ids);
180 jsonObject.addProperty("ip",ip); 179 jsonObject.addProperty("ip",ip);
181 deviceCommand.setData(jsonObject); 180 deviceCommand.setData(jsonObject);
182 - return sysControl(deviceCommand); 181 + return rocketMqSendService.sysControl(deviceCommand);
183 } 182 }
184 183
185 @ApiOperation(value = "通知",notes = "body参数描述:\r\n" + 184 @ApiOperation(value = "通知",notes = "body参数描述:\r\n" +
@@ -202,32 +201,8 @@ public class ControlDeviceConreoller extends BaseController { @@ -202,32 +201,8 @@ public class ControlDeviceConreoller extends BaseController {
202 deviceCommand.setDeviceId(deviceId); 201 deviceCommand.setDeviceId(deviceId);
203 deviceCommand.setCommandType(CommandType.notice); 202 deviceCommand.setCommandType(CommandType.notice);
204 deviceCommand.setData(GsonConstructor.get().fromJson(body,JsonObject.class)); 203 deviceCommand.setData(GsonConstructor.get().fromJson(body,JsonObject.class));
205 - return deviceControl(deviceCommand); 204 + return rocketMqSendService.deviceControl(deviceCommand);
206 } 205 }
207 206
208 - private AjaxResult deviceControl(DeviceCommand deviceCommand)  
209 - {  
210 - IotDevice iotDevice = iotDeviceService.selectIotDeviceByClient_id(deviceCommand.getDeviceId());  
211 - if(null == iotDevice || iotDevice.getStatus()!=3)  
212 - {  
213 - return AjaxResult.error("设备不存在或者不在线");  
214 - }  
215 207
216 - Message message = rocketMqService.send("deviceCommandListen",GsonConstructor.get().toJson(deviceCommand).getBytes(),iotDevice.getOperation_token());  
217 - if(message.getCode()==1)  
218 - {  
219 - return new AjaxResult(HttpStatus.SUCCESS, message.getMessage(), message.getData());  
220 - }  
221 - return new AjaxResult(HttpStatus.ERROR, message.getMessage(), message.getData());  
222 - }  
223 -  
224 - private AjaxResult sysControl(DeviceCommand deviceCommand)  
225 - {  
226 - Message message = rocketMqService.send("deviceCommandListen",GsonConstructor.get().toJson(deviceCommand).getBytes(),"SysCommand");  
227 - if(message.getCode()==1)  
228 - {  
229 - return new AjaxResult(HttpStatus.SUCCESS, message.getMessage(), message.getData());  
230 - }  
231 - return new AjaxResult(HttpStatus.ERROR, message.getMessage(), message.getData());  
232 - }  
233 } 208 }
  1 +package com.zhonglai.luhui.api.controller.iot;
  2 +
  3 +import com.alibaba.fastjson.JSONObject;
  4 +import com.google.gson.JsonObject;
  5 +import com.ruoyi.common.core.domain.AjaxResult;
  6 +import com.ruoyi.common.utils.GsonConstructor;
  7 +import com.zhonglai.luhui.action.BaseController;
  8 +import com.zhonglai.luhui.api.controller.iot.dto.HostCommand;
  9 +import com.zhonglai.luhui.api.controller.iot.dto.HostCommandFunction;
  10 +import com.zhonglai.luhui.api.controller.iot.dto.camera.StartStream;
  11 +import com.zhonglai.luhui.api.controller.iot.dto.camera.StartStreamBySerial;
  12 +import com.zhonglai.luhui.api.controller.iot.dto.camera.StopStream;
  13 +import com.zhonglai.luhui.api.service.RocketMqSendService;
  14 +import com.zhonglai.luhui.device.dto.CommandType;
  15 +import com.zhonglai.luhui.device.dto.DeviceCommand;
  16 +import io.swagger.annotations.Api;
  17 +import io.swagger.annotations.ApiImplicitParam;
  18 +import io.swagger.annotations.ApiImplicitParams;
  19 +import io.swagger.annotations.ApiOperation;
  20 +import org.springframework.beans.factory.annotation.Autowired;
  21 +import org.springframework.web.bind.annotation.*;
  22 +
  23 +import java.io.IOException;
  24 +
  25 +@Api(tags = "工控机控制器")
  26 +@RestController
  27 +@RequestMapping("/iot/controlDevice")
  28 +public class ControlGkjController extends BaseController {
  29 +
  30 + @Autowired
  31 + private RocketMqSendService rocketMqSendService;
  32 +
  33 + @ApiOperation(value = "ls")
  34 + @ApiImplicitParams({
  35 + @ApiImplicitParam(name = "deviceId", value = "设备ID", required = true, dataType = "String", paramType = "path"),
  36 + })
  37 + @GetMapping("/ls/{deviceId}")
  38 + public AjaxResult write(@PathVariable String deviceId) throws IOException {
  39 + HostCommand hostCommand = new HostCommand();
  40 + hostCommand.setFunction("ls");
  41 + DeviceCommand deviceCommand = new DeviceCommand();
  42 + deviceCommand.setDeviceId(deviceId);
  43 + deviceCommand.setCommandType(CommandType.host);
  44 + deviceCommand.setData(GsonConstructor.get().fromJson(GsonConstructor.get().toJson(hostCommand), JsonObject.class));
  45 + return rocketMqSendService.deviceControl(deviceCommand);
  46 + }
  47 +
  48 + @ApiOperation(value = "cd")
  49 + @ApiImplicitParams({
  50 + @ApiImplicitParam(name = "deviceId", value = "设备ID", required = true, dataType = "String", paramType = "path"),
  51 + @ApiImplicitParam(name = "path", value = "路径", required = true, dataType = "String", paramType = "path"),
  52 + })
  53 + @GetMapping("/cd/{deviceId}")
  54 + public AjaxResult cd(@PathVariable String deviceId,String path) {
  55 + HostCommand hostCommand = new HostCommand();
  56 + hostCommand.setFunction("cd");
  57 + JSONObject data = new JSONObject();
  58 + data.put("path", path);
  59 + hostCommand.setData(data);
  60 + DeviceCommand deviceCommand = new DeviceCommand();
  61 + deviceCommand.setDeviceId(deviceId);
  62 + deviceCommand.setCommandType(CommandType.host);
  63 + deviceCommand.setData(GsonConstructor.get().fromJson(GsonConstructor.get().toJson(hostCommand), JsonObject.class));
  64 + return rocketMqSendService.deviceControl(deviceCommand);
  65 + }
  66 +
  67 + @ApiOperation(value = "mkdir")
  68 + @ApiImplicitParams({
  69 + @ApiImplicitParam(name = "deviceId", value = "设备ID", required = true, dataType = "String", paramType = "path"),
  70 + @ApiImplicitParam(name = "name", value = "名称", required = true, dataType = "String", paramType = "path"),
  71 + })
  72 + @GetMapping("/mkdir/{deviceId}")
  73 + public AjaxResult mkdir(@PathVariable String deviceId,String name) {
  74 + HostCommand hostCommand = new HostCommand();
  75 + hostCommand.setFunction("mkdir");
  76 + JSONObject data = new JSONObject();
  77 + data.put("name", name);
  78 + hostCommand.setData(data);
  79 + DeviceCommand deviceCommand = new DeviceCommand();
  80 + deviceCommand.setDeviceId(deviceId);
  81 + deviceCommand.setCommandType(CommandType.host);
  82 + deviceCommand.setData(GsonConstructor.get().fromJson(GsonConstructor.get().toJson(hostCommand), JsonObject.class));
  83 + return rocketMqSendService.deviceControl(deviceCommand);
  84 + }
  85 +
  86 + @ApiOperation(value = "rm")
  87 + @ApiImplicitParams({
  88 + @ApiImplicitParam(name = "deviceId", value = "设备ID", required = true, dataType = "String", paramType = "path"),
  89 + @ApiImplicitParam(name = "name", value = "名称", required = true, dataType = "String", paramType = "path"),
  90 + })
  91 + @GetMapping("/rm/{deviceId}")
  92 + public AjaxResult rm(@PathVariable String deviceId,String name) {
  93 + HostCommand hostCommand = new HostCommand();
  94 + hostCommand.setFunction("rm");
  95 + JSONObject data = new JSONObject();
  96 + data.put("name", name);
  97 + hostCommand.setData(data);
  98 + DeviceCommand deviceCommand = new DeviceCommand();
  99 + deviceCommand.setDeviceId(deviceId);
  100 + deviceCommand.setCommandType(CommandType.host);
  101 + deviceCommand.setData(GsonConstructor.get().fromJson(GsonConstructor.get().toJson(hostCommand), JsonObject.class));
  102 + return rocketMqSendService.deviceControl(deviceCommand);
  103 + }
  104 +
  105 + @ApiOperation(value = "copy")
  106 + @ApiImplicitParams({
  107 + @ApiImplicitParam(name = "deviceId", value = "设备ID", required = true, dataType = "String", paramType = "path"),
  108 + @ApiImplicitParam(name = "source", value = "源", required = true, dataType = "String", paramType = "path"),
  109 + @ApiImplicitParam(name = "target", value = "目标", required = true, dataType = "String", paramType = "path"),
  110 + })
  111 + @GetMapping("/copy/{deviceId}")
  112 + public AjaxResult copy(@PathVariable String deviceId,String source,String target) {
  113 + HostCommand hostCommand = new HostCommand();
  114 + hostCommand.setFunction("copy");
  115 + JSONObject data = new JSONObject();
  116 + data.put("source", source);
  117 + data.put("target", target);
  118 + hostCommand.setData(data);
  119 + DeviceCommand deviceCommand = new DeviceCommand();
  120 + deviceCommand.setDeviceId(deviceId);
  121 + deviceCommand.setCommandType(CommandType.host);
  122 + deviceCommand.setData(GsonConstructor.get().fromJson(GsonConstructor.get().toJson(hostCommand), JsonObject.class));
  123 + return rocketMqSendService.deviceControl(deviceCommand);
  124 + }
  125 +
  126 + @ApiOperation(value = "mk")
  127 + @ApiImplicitParams({
  128 + @ApiImplicitParam(name = "deviceId", value = "设备ID", required = true, dataType = "String", paramType = "path"),
  129 + @ApiImplicitParam(name = "name", value = "名称", required = true, dataType = "String", paramType = "path"),
  130 + })
  131 + @GetMapping("/mk/{deviceId}")
  132 + public AjaxResult mk(@PathVariable String deviceId,String name) {
  133 + HostCommand hostCommand = new HostCommand();
  134 + hostCommand.setFunction("mk");
  135 + JSONObject data = new JSONObject();
  136 + data.put("name", name);
  137 + hostCommand.setData(data);
  138 + DeviceCommand deviceCommand = new DeviceCommand();
  139 + deviceCommand.setDeviceId(deviceId);
  140 + deviceCommand.setCommandType(CommandType.host);
  141 + deviceCommand.setData(GsonConstructor.get().fromJson(GsonConstructor.get().toJson(hostCommand), JsonObject.class));
  142 + return rocketMqSendService.deviceControl(deviceCommand);
  143 + }
  144 +
  145 + @ApiOperation(value = "download")
  146 + @ApiImplicitParams({
  147 + @ApiImplicitParam(name = "deviceId", value = "设备ID", required = true, dataType = "String", paramType = "path"),
  148 + @ApiImplicitParam(name = "url", value = "url", required = true, dataType = "String", paramType = "path"),
  149 + @ApiImplicitParam(name = "name", value = "名称", required = true, dataType = "String", paramType = "path"),
  150 + })
  151 + @GetMapping("/download/{deviceId}")
  152 + public AjaxResult download(@PathVariable String deviceId,String url,String name) {
  153 + HostCommand hostCommand = new HostCommand();
  154 + hostCommand.setFunction("download");
  155 + JSONObject data = new JSONObject();
  156 + data.put("name", name);
  157 + data.put("url", url);
  158 + hostCommand.setData(data);
  159 + DeviceCommand deviceCommand = new DeviceCommand();
  160 + deviceCommand.setDeviceId(deviceId);
  161 + deviceCommand.setCommandType(CommandType.host);
  162 + deviceCommand.setData(GsonConstructor.get().fromJson(GsonConstructor.get().toJson(hostCommand), JsonObject.class));
  163 + return rocketMqSendService.deviceControl(deviceCommand);
  164 + }
  165 +
  166 + @ApiOperation(value = "upload")
  167 + @ApiImplicitParams({
  168 + @ApiImplicitParam(name = "deviceId", value = "设备ID", required = true, dataType = "String", paramType = "path"),
  169 + @ApiImplicitParam(name = "url", value = "url", required = true, dataType = "String", paramType = "path"),
  170 + @ApiImplicitParam(name = "name", value = "名称", required = true, dataType = "String", paramType = "path"),
  171 + })
  172 + @GetMapping("/upload/{deviceId}")
  173 + public AjaxResult upload(@PathVariable String deviceId,String url,String name) {
  174 + HostCommand hostCommand = new HostCommand();
  175 + hostCommand.setFunction("upload");
  176 + JSONObject data = new JSONObject();
  177 + data.put("name", name);
  178 + data.put("url", url);
  179 + hostCommand.setData(data);
  180 + DeviceCommand deviceCommand = new DeviceCommand();
  181 + deviceCommand.setDeviceId(deviceId);
  182 + deviceCommand.setCommandType(CommandType.host);
  183 + deviceCommand.setData(GsonConstructor.get().fromJson(GsonConstructor.get().toJson(hostCommand), JsonObject.class));
  184 + return rocketMqSendService.deviceControl(deviceCommand);
  185 + }
  186 +
  187 + @ApiOperation(value = "摄像头拉流")
  188 + @ApiImplicitParams({
  189 + @ApiImplicitParam(name = "deviceId", value = "设备ID", required = true, dataType = "String", paramType = "path"),
  190 + })
  191 + @GetMapping("/cameraStartStream/{deviceId}")
  192 + public AjaxResult camera(@PathVariable String deviceId, StartStream startStream) {
  193 + HostCommand hostCommand = new HostCommand();
  194 + hostCommand.setFunction("camera");
  195 + JSONObject data = new JSONObject();
  196 + data.put("commd", "startStream");
  197 + data.put("param", GsonConstructor.get().toJson(startStream));
  198 + hostCommand.setData(data);
  199 + DeviceCommand deviceCommand = new DeviceCommand();
  200 + deviceCommand.setDeviceId(deviceId);
  201 + deviceCommand.setCommandType(CommandType.host);
  202 + deviceCommand.setData(GsonConstructor.get().fromJson(GsonConstructor.get().toJson(hostCommand), JsonObject.class));
  203 + return rocketMqSendService.deviceControl(deviceCommand);
  204 + }
  205 +
  206 + @ApiOperation(value = "摄像头根据序列号拉流")
  207 + @ApiImplicitParams({
  208 + @ApiImplicitParam(name = "deviceId", value = "设备ID", required = true, dataType = "String", paramType = "path"),
  209 + })
  210 + @GetMapping("/cameraStartStreamBySerial/{deviceId}")
  211 + public AjaxResult cameraStartStreamBySerial(@PathVariable String deviceId, StartStreamBySerial startStreamBySerial) {
  212 + HostCommand hostCommand = new HostCommand();
  213 + hostCommand.setFunction("camera");
  214 + JSONObject data = new JSONObject();
  215 + data.put("commd", "startStreamBySerial");
  216 + data.put("param", GsonConstructor.get().toJson(startStreamBySerial));
  217 + hostCommand.setData(data);
  218 + DeviceCommand deviceCommand = new DeviceCommand();
  219 + deviceCommand.setDeviceId(deviceId);
  220 + deviceCommand.setCommandType(CommandType.host);
  221 + deviceCommand.setData(GsonConstructor.get().fromJson(GsonConstructor.get().toJson(hostCommand), JsonObject.class));
  222 + return rocketMqSendService.deviceControl(deviceCommand);
  223 + }
  224 +
  225 + @ApiOperation(value = "摄像头关流")
  226 + @ApiImplicitParams({
  227 + @ApiImplicitParam(name = "deviceId", value = "设备ID", required = true, dataType = "String", paramType = "path"),
  228 + })
  229 + @GetMapping("/cameraStopStream/{deviceId}")
  230 + public AjaxResult cameraStopStream(@PathVariable String deviceId, StopStream stopStream) {
  231 + HostCommand hostCommand = new HostCommand();
  232 + hostCommand.setFunction("camera");
  233 + JSONObject data = new JSONObject();
  234 + data.put("commd", "stopStream");
  235 + data.put("param", GsonConstructor.get().toJson(stopStream));
  236 + hostCommand.setData(data);
  237 + DeviceCommand deviceCommand = new DeviceCommand();
  238 + deviceCommand.setDeviceId(deviceId);
  239 + deviceCommand.setCommandType(CommandType.host);
  240 + deviceCommand.setData(GsonConstructor.get().fromJson(GsonConstructor.get().toJson(hostCommand), JsonObject.class));
  241 + return rocketMqSendService.deviceControl(deviceCommand);
  242 + }
  243 +}
  1 +package com.zhonglai.luhui.api.controller.iot.dto;
  2 +
  3 +import com.alibaba.fastjson.JSONObject;
  4 +
  5 +public class HostCommand {
  6 + private String function;
  7 + private JSONObject data;
  8 +
  9 + public String getFunction() {
  10 + return function;
  11 + }
  12 +
  13 + public void setFunction(String function) {
  14 + this.function = function;
  15 + }
  16 +
  17 + public JSONObject getData() {
  18 + return data;
  19 + }
  20 +
  21 + public void setData(JSONObject data) {
  22 + this.data = data;
  23 + }
  24 +}
  1 +package com.zhonglai.luhui.api.controller.iot.dto;
  2 +
  3 +public enum HostCommandFunction {
  4 + startStream,
  5 + stopStream,
  6 + startStreamBySerial,
  7 + getLocalIpAddress
  8 +}
  1 +package com.zhonglai.luhui.api.controller.iot.dto.camera;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +
  6 +@ApiModel("摄像头启动指令")
  7 +public class StartStream {
  8 + @ApiModelProperty("流id,推荐为摄像头的序列号")
  9 + private String stream;
  10 + @ApiModelProperty("摄像头的rtsp地址")
  11 + private String rtspUrl;
  12 + @ApiModelProperty("无人观看自动关闭流")
  13 + private String auto_close;
  14 +
  15 + public String getStream() {
  16 + return stream;
  17 + }
  18 +
  19 + public void setStream(String stream) {
  20 + this.stream = stream;
  21 + }
  22 +
  23 + public String getRtspUrl() {
  24 + return rtspUrl;
  25 + }
  26 +
  27 + public void setRtspUrl(String rtspUrl) {
  28 + this.rtspUrl = rtspUrl;
  29 + }
  30 +
  31 + public String getAuto_close() {
  32 + return auto_close;
  33 + }
  34 +
  35 + public void setAuto_close(String auto_close) {
  36 + this.auto_close = auto_close;
  37 + }
  38 +}
  1 +package com.zhonglai.luhui.api.controller.iot.dto.camera;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +
  6 +@ApiModel("摄像头根据序列号启动指令")
  7 +public class StartStreamBySerial {
  8 + @ApiModelProperty("摄像头序列号")
  9 + private String deviceSerial;
  10 + @ApiModelProperty("无人观看自动关闭流")
  11 + private String auto_close;
  12 +
  13 + public String getDeviceSerial() {
  14 + return deviceSerial;
  15 + }
  16 +
  17 + public void setDeviceSerial(String deviceSerial) {
  18 + this.deviceSerial = deviceSerial;
  19 + }
  20 +
  21 + public String getAuto_close() {
  22 + return auto_close;
  23 + }
  24 +
  25 + public void setAuto_close(String auto_close) {
  26 + this.auto_close = auto_close;
  27 + }
  28 +}
  1 +package com.zhonglai.luhui.api.controller.iot.dto.camera;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +
  6 +@ApiModel("摄像头关闭指令")
  7 +public class StopStream {
  8 + @ApiModelProperty("流id,推荐为摄像头的序列号")
  9 + private String stream;
  10 +
  11 + public String getStream() {
  12 + return stream;
  13 + }
  14 +
  15 + public void setStream(String stream) {
  16 + this.stream = stream;
  17 + }
  18 +}
  1 +package com.zhonglai.luhui.api.service;
  2 +
  3 +import com.ruoyi.common.constant.HttpStatus;
  4 +import com.ruoyi.common.core.domain.AjaxResult;
  5 +import com.ruoyi.common.core.domain.Message;
  6 +import com.ruoyi.common.utils.GsonConstructor;
  7 +import com.zhonglai.luhui.device.domain.IotDevice;
  8 +import com.zhonglai.luhui.device.dto.DeviceCommand;
  9 +import com.zhonglai.luhui.device.service.IIotDeviceService;
  10 +import com.zhonglai.luhui.rocketmq.service.RocketMqService;
  11 +import org.springframework.beans.factory.annotation.Autowired;
  12 +import org.springframework.stereotype.Service;
  13 +
  14 +@Service
  15 +public class RocketMqSendService {
  16 +
  17 + @Autowired
  18 + private RocketMqService rocketMqService;
  19 +
  20 + @Autowired
  21 + private IIotDeviceService iotDeviceService;
  22 +
  23 + public AjaxResult deviceControl(DeviceCommand deviceCommand)
  24 + {
  25 + IotDevice iotDevice = iotDeviceService.selectIotDeviceByClient_id(deviceCommand.getDeviceId());
  26 + if(null == iotDevice || iotDevice.getStatus()!=3)
  27 + {
  28 + return AjaxResult.error("设备不存在或者不在线");
  29 + }
  30 +
  31 + Message message = rocketMqService.send("deviceCommandListen", GsonConstructor.get().toJson(deviceCommand).getBytes(),iotDevice.getOperation_token());
  32 + if(message.getCode()==1)
  33 + {
  34 + return new AjaxResult(HttpStatus.SUCCESS, message.getMessage(), message.getData());
  35 + }
  36 + return new AjaxResult(HttpStatus.ERROR, message.getMessage(), message.getData());
  37 + }
  38 +
  39 + public AjaxResult sysControl(DeviceCommand deviceCommand)
  40 + {
  41 + Message message = rocketMqService.send("deviceCommandListen",GsonConstructor.get().toJson(deviceCommand).getBytes(),"SysCommand");
  42 + if(message.getCode()==1)
  43 + {
  44 + return new AjaxResult(HttpStatus.SUCCESS, message.getMessage(), message.getData());
  45 + }
  46 + return new AjaxResult(HttpStatus.ERROR, message.getMessage(), message.getData());
  47 + }
  48 +}
  1 +## 地址类型与范围
  2 +| 类型 | 地址范围 (1-based) | 数据类型 | 说明 |
  3 +| ------------------------ | -------------- | --------------- | ---------------------- |
  4 +| Coil (线圈输出) | 00001 – 09999 | BIT | 可读可写的单个位,通常用于开关量输出 |
  5 +| Discrete Input (离散输入) | 10001 – 19999 | BIT | 只读的单个位,通常用于开关量输入 |
  6 +| Input Register (输入寄存器) | 30001 – 39999 | 16-bit 或 32-bit | 只读寄存器,通常用于传感器输入或模拟量 |
  7 +| Holding Register (保持寄存器) | 40001 – 49999 | 16-bit 或 32-bit | 可读可写寄存器,通常用于控制命令或模拟量输出 |
  8 +
  9 +## 常用功能码(Function Code)
  10 +| 功能码 | 功能描述 | 适用地址类型 |
  11 +| --------- | ------------------------ | ------------------------------ |
  12 +| 01 | Read Coils | Coil (00001–09999) |
  13 +| 02 | Read Discrete Inputs | Discrete Input (10001–19999) |
  14 +| 03 | Read Holding Registers | Holding Register (40001–49999) |
  15 +| 04 | Read Input Registers | Input Register (30001–39999) |
  16 +| 05 | Write Single Coil | Coil (00001–09999) |
  17 +| 06 | Write Single Register | Holding Register (40001–49999) |
  18 +| 15 (0x0F) | Write Multiple Coils | Coil (00001–09999) |
  19 +| 16 (0x10) | Write Multiple Registers | Holding Register (40001–49999) |
  20 +
  21 +## 扩展说明
  22 + 1、BIT 类型地址
  23 + Coil、Discrete Input 都是 1 位 (0/1)
  24 + Input Register 和 Holding Register 内部也可按 bit 位访问(x.y 格式,例如 40001.0 表示寄存器 40001 的第 0 位)
  25 +
  26 + 2、寄存器数据类型映射
  27 + | Modbus 寄存器 | BaseLocator 数据类型示例 | 说明 |
  28 + | ----------------------- | ------------------------------------------------- | ------------------------ |
  29 + | Holding Register 16-bit | TWO\_BYTE\_INT\_SIGNED / TWO\_BYTE\_INT\_UNSIGNED | 标准 16 位整数 |
  30 + | Input Register 16-bit | 同上 | 只读 |
  31 + | Holding/Input 32-bit | FOUR\_BYTE\_INT\_SIGNED / FOUR\_BYTE\_FLOAT | 两个连续寄存器组成一个 32-bit 整数或浮点 |
  32 + | Holding/Input 64-bit | EIGHT\_BYTE\_INT\_SIGNED / EIGHT\_BYTE\_FLOAT | 四个连续寄存器组成 64-bit |
  33 + | Holding/Input bit | holdingRegisterBit / inputRegisterBit | 对寄存器内部的某个位操作 |
  34 +
  35 +
  36 + 3、偏移量计算(0-based)
  37 + | 地址类型 | 1-based 地址 | 偏移量公式 |
  38 + | ---------------- | ----------- | ------------------------ |
  39 + | Coil | 00001–09999 | offset = address - 1 |
  40 + | Discrete Input | 10001–19999 | offset = address - 10001 |
  41 + | Input Register | 30001–39999 | offset = address - 30001 |
  42 + | Holding Register | 40001–49999 | offset = address - 40001 |
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project xmlns="http://maven.apache.org/POM/4.0.0"
  3 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5 + <modelVersion>4.0.0</modelVersion>
  6 + <parent>
  7 + <groupId>com.zhonglai.luhui</groupId>
  8 + <artifactId>lh-modules</artifactId>
  9 + <version>1.0-SNAPSHOT</version>
  10 + </parent>
  11 +
  12 + <artifactId>lh-device-modbus-terminal</artifactId>
  13 +
  14 + <properties>
  15 + <maven.compiler.source>8</maven.compiler.source>
  16 + <maven.compiler.target>8</maven.compiler.target>
  17 + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  18 + </properties>
  19 +
  20 + <dependencies>
  21 + <dependency>
  22 + <groupId>com.infiniteautomation</groupId>
  23 + <artifactId>modbus4j</artifactId>
  24 + </dependency>
  25 +
  26 + <dependency>
  27 + <groupId>com.fasterxml.jackson.core</groupId>
  28 + <artifactId>jackson-databind</artifactId>
  29 + </dependency>
  30 +
  31 + <dependency>
  32 + <groupId>com.fazecast</groupId>
  33 + <artifactId>jSerialComm</artifactId>
  34 + </dependency>
  35 +
  36 + <!-- 阿里JSON解析器 -->
  37 + <dependency>
  38 + <groupId>com.alibaba</groupId>
  39 + <artifactId>fastjson</artifactId>
  40 + </dependency>
  41 +
  42 + <!-- mqtt终端插件 -->
  43 + <dependency>
  44 + <groupId>com.zhonglai.luhui</groupId>
  45 + <artifactId>lh-device-mqtt-terminal-jar</artifactId>
  46 + </dependency>
  47 + <dependency>
  48 + <groupId>cn.hutool</groupId>
  49 + <artifactId>hutool-all</artifactId>
  50 + </dependency>
  51 +
  52 + <dependency>
  53 + <groupId>org.ini4j</groupId>
  54 + <artifactId>ini4j</artifactId>
  55 + </dependency>
  56 + </dependencies>
  57 +
  58 + <build>
  59 + <finalName>lh-device-modbus-terminal</finalName>
  60 + <plugins>
  61 + <plugin>
  62 + <groupId>org.apache.maven.plugins</groupId>
  63 + <artifactId>maven-jar-plugin</artifactId>
  64 + <version>2.4</version>
  65 + <configuration>
  66 + <archive>
  67 + <!--
  68 + 生成的jar中,不要包含pom.xml和pom.properties这两个文件
  69 + -->
  70 + <addMavenDescriptor>false</addMavenDescriptor>
  71 + <manifest>
  72 + <!--
  73 + 是否要把第三方jar放到manifest的classpath中
  74 + -->
  75 + <addClasspath>true</addClasspath>
  76 +
  77 + <!--
  78 + 生成的manifest中classpath的前缀,因为要把第三方jar放到lib目录下,所以classpath的前缀是lib/
  79 + -->
  80 + <classpathPrefix>lib/</classpathPrefix>
  81 + <mainClass>com.zhonglai.luhui.device.modbus.terminal.Main</mainClass>
  82 + </manifest>
  83 + </archive>
  84 + </configuration>
  85 + </plugin>
  86 +
  87 + <!-- The configuration of maven-assembly-plugin -->
  88 + <plugin>
  89 + <groupId>org.apache.maven.plugins</groupId>
  90 + <artifactId>maven-assembly-plugin</artifactId>
  91 + <version>2.4</version>
  92 + <configuration>
  93 + <descriptors>
  94 + <descriptor>${project.parent.parent.basedir}/configs/package.xml</descriptor>
  95 + </descriptors>
  96 + </configuration>
  97 + <executions>
  98 + <execution>
  99 + <id>make-assembly</id>
  100 + <phase>package</phase>
  101 + <goals>
  102 + <goal>single</goal>
  103 + </goals>
  104 + </execution>
  105 + </executions>
  106 + </plugin>
  107 + </plugins>
  108 + </build>
  109 +</project>
  1 +package com.zhonglai.luhui.device.modbus.terminal;
  2 +
  3 +
  4 +import com.zhonglai.luhui.device.modbus.terminal.config.CameraConfig;
  5 +import com.zhonglai.luhui.device.modbus.terminal.config.InitPlcConfig;
  6 +import com.zhonglai.luhui.device.modbus.terminal.data.ParseDataService;
  7 +import com.zhonglai.luhui.device.modbus.terminal.modbus.Modbus4jWrite;
  8 +import com.zhonglai.luhui.device.modbus.terminal.modbus.ModbusMasterMessage;
  9 +import com.zhonglai.luhui.device.modbus.terminal.task.CollectPlcDataTask;
  10 +import com.zhonglai.luhui.device.modbus.terminal.task.ScheduledThreadPool;
  11 +import com.zhonglai.luhui.device.mqtt.terminal.jar.config.MqttConfig;
  12 +import com.zhonglai.luhui.device.mqtt.terminal.jar.mqtt.MqttService;
  13 +import org.apache.commons.logging.Log;
  14 +import org.apache.commons.logging.LogFactory;
  15 +
  16 +public class Main {
  17 + private static Log log = LogFactory.getLog(Modbus4jWrite.class);
  18 + public static void main(String[] args) throws Exception {
  19 +
  20 + String configPath = null;
  21 + if (args.length!=0)
  22 + {
  23 + configPath = args[0];
  24 + }
  25 +
  26 + //启动mqtt服务
  27 + MqttConfig.init(configPath); // 先加载配置
  28 + MqttService mqttService = MqttService.getInstance();
  29 + mqttService.start(new ParseDataService());
  30 +
  31 + //加载modbus配置
  32 + String jsonPath = Main.class.getClassLoader().getResource("configs/plcs.json").getPath();;
  33 + if (null != configPath)
  34 + {
  35 + jsonPath = configPath+"/plcs.json";
  36 + }
  37 + InitPlcConfig.initPlcConfigFromFile(jsonPath);
  38 +
  39 + String camerapath = Main.class.getClassLoader().getResource("configs/camera.properties").getPath();;
  40 + if (null != configPath)
  41 + {
  42 + camerapath = configPath+"/camera.properties";
  43 + }
  44 + CameraConfig.init(camerapath);
  45 +
  46 + CollectPlcDataTask collectPlcDataTask = new CollectPlcDataTask();
  47 + collectPlcDataTask.collect(mqttService);
  48 +
  49 + // 添加 JVM 关闭钩子,保证优雅退出
  50 + Runtime.getRuntime().addShutdownHook(new Thread(() -> {
  51 + log.info("🛑 应用关闭中,正在释放资源...");
  52 + try {
  53 + MqttService.getInstance().stop();
  54 + ModbusMasterMessage.closeAll();
  55 + ScheduledThreadPool.stopScheduler();
  56 + log.info("✅ 资源释放完成");
  57 + } catch (Exception e) {
  58 + e.printStackTrace();
  59 + }
  60 + }));
  61 + }
  62 +
  63 +}
  64 +
  1 +package com.zhonglai.luhui.device.modbus.terminal.camera;
  2 +
  3 +import cn.hutool.core.net.NetUtil;
  4 +import cn.hutool.http.HttpUtil;
  5 +import com.alibaba.fastjson.JSON;
  6 +import com.alibaba.fastjson.JSONObject;
  7 +import com.zhonglai.luhui.device.modbus.terminal.config.CameraConfig;
  8 +import org.slf4j.Logger;
  9 +import org.slf4j.LoggerFactory;
  10 +
  11 +import java.net.DatagramSocket;
  12 +import java.net.InetAddress;
  13 +
  14 +public class WebRtcService {
  15 + private static Logger logger = LoggerFactory.getLogger(WebRtcService.class);
  16 +
  17 + public static StringBuffer getZlmApi()
  18 + {
  19 + return new StringBuffer().append("http://").append(CameraConfig.webrtc_host).append("/index/api");
  20 + }
  21 +
  22 + public static String getPlayUrl(String ip,String app,String stream)
  23 + {
  24 + return "https://"+ip+"/index/api/webrtc?app="+app+"&stream="+stream+"&type=play";
  25 + }
  26 +
  27 + public static boolean isMediaOnline(String stream,String app,String secret) {
  28 + String url = getZlmApi() + "/isMediaOnline?secret="+secret+"&schema=rtsp&vhost=__defaultVhost__&app="+app+"&stream="+stream;
  29 + String str = HttpUtil.get(url);
  30 + System.out.println(str);
  31 + return JSON.parseObject(str).containsKey("online") && JSON.parseObject(str).getBoolean("online");
  32 + }
  33 +
  34 +}
  1 +package com.zhonglai.luhui.device.modbus.terminal.camera.opf;
  2 +
  3 +import com.alibaba.fastjson.JSONObject;
  4 +
  5 +public interface CameraCommdFunction {
  6 + public JSONObject execute();
  7 +}
  1 +package com.zhonglai.luhui.device.modbus.terminal.camera.opf;
  2 +
  3 +import com.alibaba.fastjson.JSONObject;
  4 +
  5 +/**
  6 + * 摄像头操作指令
  7 + */
  8 +public class CameraOperationInstructions {
  9 + private String commd;
  10 + private CameraCommdFunction param;
  11 +
  12 + public String getCommd() {
  13 + return commd;
  14 + }
  15 +
  16 + public void setCommd(String commd) {
  17 + this.commd = commd;
  18 + }
  19 +
  20 + public CameraCommdFunction getParam() {
  21 + return param;
  22 + }
  23 +
  24 + public void setParam(CameraCommdFunction param) {
  25 + this.param = param;
  26 + }
  27 +
  28 + public static CameraOperationInstructions createCameraOperationInstructions(JSONObject parameter)
  29 + {
  30 + CameraOperationInstructions cameraOperationInstructions = new CameraOperationInstructions();
  31 + cameraOperationInstructions.setCommd(parameter.getString("commd"));
  32 + String jsonstr = parameter.getString("param");
  33 + switch (cameraOperationInstructions.getCommd())
  34 + {
  35 + case "startStream":
  36 + cameraOperationInstructions.setParam(JSONObject.parseObject(jsonstr, StartStream.class));
  37 + break;
  38 + case "stopStream":
  39 + cameraOperationInstructions.setParam(JSONObject.parseObject(jsonstr, StopStream.class));
  40 + break;
  41 + case "startStreamBySerial":
  42 + cameraOperationInstructions.setParam(JSONObject.parseObject(jsonstr, StartStreamBySerial.class));
  43 + break;
  44 + case "getLocalIpAddress":
  45 + break;
  46 + }
  47 + return cameraOperationInstructions;
  48 + }
  49 +
  50 + public JSONObject execute()
  51 + {
  52 + return param.execute();
  53 + }
  54 +}
  1 +package com.zhonglai.luhui.device.modbus.terminal.camera.opf;
  2 +
  3 +import cn.hutool.http.HttpUtil;
  4 +import com.alibaba.fastjson.JSON;
  5 +import com.alibaba.fastjson.JSONObject;
  6 +import com.zhonglai.luhui.device.modbus.terminal.camera.WebRtcService;
  7 +import com.zhonglai.luhui.device.modbus.terminal.config.CameraConfig;
  8 +import org.slf4j.Logger;
  9 +import org.slf4j.LoggerFactory;
  10 +
  11 +public class StartStream implements CameraCommdFunction {
  12 + private static Logger logger = LoggerFactory.getLogger(StartStream.class);
  13 + private String stream;
  14 + private String rtspUrl;
  15 + private String auto_close;
  16 +
  17 + public String getStream() {
  18 + return stream;
  19 + }
  20 +
  21 + public void setStream(String stream) {
  22 + this.stream = stream;
  23 + }
  24 +
  25 + public String getRtspUrl() {
  26 + return rtspUrl;
  27 + }
  28 +
  29 + public void setRtspUrl(String rtspUrl) {
  30 + this.rtspUrl = rtspUrl;
  31 + }
  32 +
  33 + public String getAuto_close() {
  34 + return auto_close;
  35 + }
  36 +
  37 + public void setAuto_close(String auto_close) {
  38 + this.auto_close = auto_close;
  39 + }
  40 +
  41 + @Override
  42 + public JSONObject execute() {
  43 + String[] rtspurls = rtspUrl.split(",");
  44 + if(rtspurls.length!=0)
  45 + {
  46 + String playurls = "";
  47 + String localip = CameraConfig.localIp;
  48 + for(String rtspurl : rtspurls) {
  49 + String newStream = stream;
  50 + String playurl = WebRtcService.getPlayUrl(localip, CameraConfig.webrtc_app, newStream);
  51 + boolean oline = WebRtcService.isMediaOnline(newStream, CameraConfig.webrtc_app, CameraConfig.webrtcSecret);
  52 + if (!oline)
  53 + {
  54 + StringBuffer stringBuffer = WebRtcService.getZlmApi();
  55 + stringBuffer.append("/addStreamProxy?");
  56 + stringBuffer.append("secret=");
  57 + stringBuffer.append( CameraConfig.webrtcSecret);
  58 + stringBuffer.append("&vhost=__defaultVhost__");
  59 + stringBuffer.append("&app=");
  60 + stringBuffer.append( CameraConfig.webrtc_app);
  61 + stringBuffer.append("&stream=");
  62 + stringBuffer.append(newStream);
  63 + stringBuffer.append("&url=");
  64 + stringBuffer.append(rtspurl);
  65 + stringBuffer.append("&enable_auto_close=1");
  66 + stringBuffer.append("&retry_count=-1&rtp_type=0&timeout_sec=10&enable_hls=false&enable_hls_fmp4=false&enable_mp4=false&enable_rtsp=true&enable_rtmp=false&enable_ts=false&enable_fmp4=true&hls_demand=false&rtsp_demand=false&rtmp_demand=false&ts_demand=false&fmp4_demand=false&enable_audio=true&add_mute_audio=true&mp4_max_second=10&mp4_as_player=false&auto_close="+auto_close);
  67 + logger.info("添加流的接口请求:"+stringBuffer.toString());
  68 + String str = HttpUtil.get(stringBuffer.toString());
  69 + JSONObject jsonObject = JSON.parseObject(str);
  70 + if(jsonObject.containsKey("code") && jsonObject.getInteger("code")==0)
  71 + {
  72 + jsonObject.put("code",1);
  73 + if(!"".equals(playurls))
  74 + {
  75 + playurls += ",";
  76 + }
  77 + playurls+=playurl;
  78 + }else {
  79 + jsonObject.put("code",0);
  80 + System.out.println(str);
  81 + return jsonObject;
  82 + }
  83 + }else{
  84 + if(!"".equals(playurls))
  85 + {
  86 + playurls += ",";
  87 + }
  88 + playurls+=playurl;
  89 + }
  90 +
  91 + }
  92 + JSONObject jsonObject = new JSONObject();
  93 + jsonObject.put("code",1);
  94 + JSONObject data = new JSONObject();
  95 + data.put("playurl",playurls);
  96 + data.put("key", CameraConfig.webrtcSecret);
  97 + jsonObject.put("data",data);
  98 + return jsonObject;
  99 + }
  100 + JSONObject jsonObject = new JSONObject();
  101 + jsonObject.put("code",0);
  102 + jsonObject.put("msg","rtspUrl参数错误");
  103 + return jsonObject;
  104 + }
  105 +}
  1 +package com.zhonglai.luhui.device.modbus.terminal.camera.opf;
  2 +
  3 +import cn.hutool.http.HttpUtil;
  4 +import com.alibaba.fastjson.JSON;
  5 +import com.alibaba.fastjson.JSONObject;
  6 +import com.zhonglai.luhui.device.modbus.terminal.config.CameraConfig;
  7 +import org.slf4j.Logger;
  8 +import org.slf4j.LoggerFactory;
  9 +
  10 +public class StartStreamBySerial implements CameraCommdFunction {
  11 + private static Logger logger = LoggerFactory.getLogger(StartStream.class);
  12 + private String deviceSerial;
  13 + private String auto_close;
  14 +
  15 + public String getDeviceSerial() {
  16 + return deviceSerial;
  17 + }
  18 +
  19 + public void setDeviceSerial(String deviceSerial) {
  20 + this.deviceSerial = deviceSerial;
  21 + }
  22 +
  23 + public String getAuto_close() {
  24 + return auto_close;
  25 + }
  26 +
  27 + public void setAuto_close(String auto_close) {
  28 + this.auto_close = auto_close;
  29 + }
  30 +
  31 + @Override
  32 + public JSONObject execute() {
  33 + String str = HttpUtil.get(CameraConfig.yuerleApiUrl+"?deviceSerial="+deviceSerial);
  34 + JSONObject jsonObject = JSON.parseObject(str);
  35 + if(jsonObject.containsKey("code") && jsonObject.getInteger("code")==1 && null != jsonObject.get("data"))
  36 + {
  37 + String enable_rtsp = jsonObject.getString("data");
  38 + logger.info("令牌{}获取到设备{}的rtsp地址{}",CameraConfig.webrtcSecret,deviceSerial,enable_rtsp);
  39 + StartStream startStream = new StartStream();
  40 + startStream.setRtspUrl(enable_rtsp);
  41 + startStream.setAuto_close(auto_close);
  42 + startStream.setStream(deviceSerial);
  43 + return startStream.execute();
  44 + }
  45 + return jsonObject;
  46 + }
  47 +}
  1 +package com.zhonglai.luhui.device.modbus.terminal.camera.opf;
  2 +
  3 +import cn.hutool.http.HttpUtil;
  4 +import com.alibaba.fastjson.JSONObject;
  5 +import com.zhonglai.luhui.device.modbus.terminal.camera.WebRtcService;
  6 +import com.zhonglai.luhui.device.modbus.terminal.config.CameraConfig;
  7 +
  8 +public class StopStream implements CameraCommdFunction {
  9 + private String stream;
  10 +
  11 + public String getStream() {
  12 + return stream;
  13 + }
  14 +
  15 + public void setStream(String stream) {
  16 + this.stream = stream;
  17 + }
  18 +
  19 + @Override
  20 + public JSONObject execute() {
  21 + String url = WebRtcService.getZlmApi() + "/close_stream?schema=rtsp&vhost=__defaultVhost__&app=" + CameraConfig.webrtc_app + "&stream=" + stream+"&force=1&secret="+ CameraConfig.webrtcSecret;
  22 + String str = HttpUtil.get(url);
  23 + System.out.println(str);
  24 + return JSONObject.parseObject(str);
  25 + }
  26 +}
  1 +package com.zhonglai.luhui.device.modbus.terminal.config;
  2 +
  3 +import cn.hutool.core.io.FileUtil;
  4 +import cn.hutool.core.net.NetUtil;
  5 +import com.zhonglai.luhui.device.modbus.terminal.camera.WebRtcService;
  6 +import com.zhonglai.luhui.device.mqtt.terminal.jar.config.MqttConfig;
  7 +import org.ini4j.Ini;
  8 +import org.slf4j.Logger;
  9 +import org.slf4j.LoggerFactory;
  10 +
  11 +import java.io.*;
  12 +import java.net.DatagramSocket;
  13 +import java.net.InetAddress;
  14 +import java.util.Properties;
  15 +
  16 +public class CameraConfig {
  17 + private static Logger logger = LoggerFactory.getLogger(CameraConfig.class);
  18 + public static String yuerleApiUrl;
  19 + public static String webrtc_host;
  20 +
  21 + public static String webrtcSecret;
  22 +
  23 + public static String webrtc_app;
  24 +
  25 + public static String localIp;
  26 +
  27 + public static void init(String configPath )
  28 + {
  29 + Properties properties = loadProperties(configPath);
  30 +
  31 + yuerleApiUrl = properties.getProperty("yuerleApiUrl");
  32 + webrtc_app = properties.getProperty("webrtc_app");
  33 + webrtc_host = properties.getProperty("webrtc_host");
  34 + webrtcSecret = null==properties.getProperty("webrtcSecret")?getWebrtcSecret():properties.getProperty("webrtcSecret");
  35 + String ip = getLocalIp();
  36 + localIp = null == ip?getLocalIpAddress():ip;
  37 + }
  38 +
  39 + public static String getLocalIp()
  40 + {
  41 + File file = new File("/app/zlmediakit_config/ip.txt");
  42 + if (file.exists())
  43 + {
  44 + String str = FileUtil.readString(file, "utf-8");
  45 + String result = str.trim().replaceAll("[\\r\\n]", "");
  46 + return result;
  47 + }
  48 + String localIpEnv = System.getenv("localIp");
  49 + if (localIpEnv != null) {
  50 + try {
  51 + String realIp = InetAddress.getByName(localIpEnv).getHostAddress();
  52 + return realIp;
  53 + } catch (Exception e) {
  54 + logger.error("宿主机 IP 获取失败",e);
  55 + }
  56 + }
  57 + return null;
  58 + }
  59 +
  60 + public static String getZLMSecret() {
  61 + File file = new File("/app/zlmediakit_config/config.ini");
  62 +
  63 + if (file.exists())
  64 + {
  65 + try {
  66 + // 读取 config.ini 文件
  67 + Ini ini = new Ini(file);
  68 + // 获取 [api] 段里的 secret 属性
  69 + String secret = ini.get("api", "secret");
  70 + logger.info("zlmediakit配置文件读取成功:"+secret);
  71 + return secret;
  72 + } catch (IOException e) {
  73 + logger.error("zlmediakit配置文件读取失败",e);
  74 + }
  75 + }
  76 +
  77 + String[] command = {
  78 + "docker", "exec", "zlmediakit",
  79 + "grep", "^secret=", "/opt/media/conf/config.ini"
  80 + };
  81 +
  82 + logger.info("正在通过指令{}获取操作令牌...",command);
  83 + ProcessBuilder processBuilder = new ProcessBuilder(command);
  84 + processBuilder.redirectErrorStream(true); // 合并错误流和标准输出流
  85 +
  86 + try {
  87 + Process process = processBuilder.start();
  88 +
  89 + try (BufferedReader reader = new BufferedReader(
  90 + new InputStreamReader(process.getInputStream()))) {
  91 + String line;
  92 + while ((line = reader.readLine()) != null) {
  93 + logger.info("执行指令返回的数据:{}",line);
  94 + if (line.startsWith("secret=")) {
  95 + return line.substring("secret=".length()).trim();
  96 + }
  97 + }
  98 + }
  99 +
  100 + int exitCode = process.waitFor();
  101 + if (exitCode != 0) {
  102 + logger.info("命令执行失败,退出码:"+exitCode);
  103 + }
  104 + } catch (Exception e) {
  105 + logger.info("拿操作令牌异常,去配置里面拿:",e);
  106 + return "keXTvTDSHAxFDpBA0MDAHhxWeVXLQmUq";
  107 + }
  108 +
  109 +
  110 + return null; // 未找到 secret 或执行失败
  111 + }
  112 +
  113 + /**
  114 + * 获取webrtc的密钥
  115 + * @return
  116 + */
  117 + public static String getWebrtcSecret() {
  118 + if(null == webrtcSecret)
  119 + {
  120 + refreshWebrtcSecret();
  121 + }
  122 + return webrtcSecret;
  123 + }
  124 +
  125 + /**
  126 + * 刷新webrtc的密钥
  127 + */
  128 + public static void refreshWebrtcSecret()
  129 + {
  130 + webrtcSecret = getZLMSecret();
  131 + }
  132 +
  133 + private static Properties loadProperties(String path)
  134 + {
  135 + Properties properties = new Properties();
  136 + try {
  137 + if(null != path && !"".equals(path))
  138 + {
  139 + properties.load(new FileReader(path));
  140 + }else{
  141 + properties.load(MqttConfig.class.getClassLoader().getResourceAsStream("configs/camera.properties"));
  142 + }
  143 +
  144 + } catch (Exception e) {
  145 + throw new RuntimeException("加载camera.properties失败,未找到配置文件或内容为空");
  146 + }
  147 + return properties;
  148 + }
  149 +
  150 + /**
  151 + * 获取本机IP
  152 + * @return
  153 + */
  154 + public static String getLocalIpAddress() {
  155 + if(null != localIp)
  156 + {
  157 + return localIp;
  158 + }
  159 + try {
  160 + // 构造一个连接外部地址的 socket(这里用 Google 的公共 DNS IP)
  161 + try (DatagramSocket socket = new DatagramSocket()) {
  162 + socket.connect(InetAddress.getByName("8.8.8.8"), 10002);
  163 + InetAddress localAddress = socket.getLocalAddress();
  164 + String ip = localAddress.getHostAddress();
  165 + if("0.0.0.0".equals(ip))
  166 + {
  167 + return null;
  168 + }
  169 + return localIp=ip;
  170 + }
  171 + } catch (Exception e) {
  172 + e.printStackTrace();
  173 + return null;
  174 + }
  175 + }
  176 +}
  1 +package com.zhonglai.luhui.device.modbus.terminal.config;
  2 +
  3 +import com.fasterxml.jackson.core.type.TypeReference;
  4 +import com.fasterxml.jackson.databind.ObjectMapper;
  5 +import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.CachPlcConfig;
  6 +import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.PlcPoint;
  7 +import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.PlcSystem;
  8 +
  9 +import java.io.File;
  10 +import java.io.IOException;
  11 +import java.util.HashMap;
  12 +import java.util.List;
  13 +import java.util.Map;
  14 +
  15 +/**
  16 + * plc配置
  17 + */
  18 +public class InitPlcConfig {
  19 + private static Map<Integer, CachPlcConfig> plcsConfigMap = new HashMap<>();
  20 +
  21 + /**
  22 + * 从文件初始化plc点位配置
  23 + * @param jsonPath
  24 + * @throws IOException
  25 + */
  26 + public static void initPlcConfigFromFile(String jsonPath) throws IOException {
  27 + ObjectMapper mapper = new ObjectMapper();
  28 + Map<String, List<PlcSystem>> plcsMap = mapper.readValue(new File(jsonPath),
  29 + new TypeReference<Map<String, List<PlcSystem>>>() {});
  30 + List<PlcSystem> plcSystems = plcsMap.get("plcs");
  31 + for (PlcSystem plc : plcSystems)
  32 + {
  33 + CachPlcConfig cachPlcConfig = plcsConfigMap.get(plc.getId());
  34 + if (cachPlcConfig == null)
  35 + {
  36 + cachPlcConfig = new CachPlcConfig();
  37 + cachPlcConfig.setId(plc.getId());
  38 + cachPlcConfig.setSystemName(plc.getSystemName());
  39 + cachPlcConfig.setProtocolType(plc.getProtocolType());
  40 + cachPlcConfig.setConnectConfig(plc.getConnectConfig());
  41 + cachPlcConfig.setPlcMap(new HashMap());
  42 +
  43 + plcsConfigMap.put(plc.getId(), cachPlcConfig);
  44 + }
  45 + Map<String, PlcPoint> plcMap = cachPlcConfig.getPlcMap();
  46 + if(plcMap.size()==0 && plc.getPoints().size()>0)
  47 + {
  48 + Map<String, PlcPoint> finalPlcMap = plcMap;
  49 + plc.getPoints().forEach(p -> finalPlcMap.put(p.system, p));
  50 + }
  51 + }
  52 + }
  53 +
  54 + /**
  55 + * 获取指定plc的点位配置
  56 + * @param id 系统编号
  57 + * @return
  58 + */
  59 + public static CachPlcConfig getPlcSystems(Integer id)
  60 + {
  61 + return plcsConfigMap.get(id);
  62 + }
  63 +
  64 + /**
  65 + * 获取指定plc的指定点位配置
  66 + * @param system 指定点位系统名
  67 + * @param id 系统编号
  68 + * @return
  69 + */
  70 + public static PlcPoint getPlcSystem(Integer id,String system)
  71 + {
  72 + if (plcsConfigMap.containsKey(id) && plcsConfigMap.get(id).getPlcMap().containsKey(system)) return plcsConfigMap.get(id).getPlcMap().get(system);
  73 + return null;
  74 + }
  75 +
  76 + public static Map<Integer, CachPlcConfig> getPlcsConfigMap()
  77 + {
  78 + return plcsConfigMap;
  79 + }
  80 +}
  1 +package com.zhonglai.luhui.device.modbus.terminal.data;
  2 +
  3 +import com.zhonglai.luhui.device.mqtt.terminal.jar.ParseDataFactory;
  4 +import com.zhonglai.luhui.device.mqtt.terminal.jar.dto.Topic;
  5 +import com.zhonglai.luhui.device.mqtt.terminal.jar.mqtt.MqttService;
  6 +
  7 +/**
  8 + * 解析数据服务
  9 + */
  10 +public class ParseDataService implements ParseDataFactory {
  11 + @Override
  12 + public void parse(Topic topicDto, byte[] payload, MqttService mqttService) {
  13 + TopicFactoryAdapter topicFactory = TopicFactoryAdapter.createTopicFactory(topicDto, payload);
  14 + if(null != topicFactory)
  15 + {
  16 + topicFactory.parse(mqttService);
  17 + }
  18 + }
  19 +}
  1 +package com.zhonglai.luhui.device.modbus.terminal.data;
  2 +
  3 +import com.zhonglai.luhui.device.mqtt.terminal.jar.dto.Topic;
  4 +import com.zhonglai.luhui.device.mqtt.terminal.jar.mqtt.MqttService;
  5 +
  6 +public interface TopicFactory {
  7 + void parse(MqttService mqttService);
  8 +}
  1 +package com.zhonglai.luhui.device.modbus.terminal.data;
  2 +
  3 +import com.zhonglai.luhui.device.modbus.terminal.data.topic.HostTopic;
  4 +import com.zhonglai.luhui.device.modbus.terminal.data.topic.PutTopic;
  5 +import com.zhonglai.luhui.device.modbus.terminal.data.topic.ReadTopic;
  6 +import com.zhonglai.luhui.device.mqtt.terminal.jar.dto.Topic;
  7 +import org.slf4j.Logger;
  8 +import org.slf4j.LoggerFactory;
  9 +
  10 +public abstract class TopicFactoryAdapter implements TopicFactory{
  11 +
  12 + protected static final Logger logger = LoggerFactory.getLogger(TopicFactoryAdapter.class);
  13 +
  14 + protected Topic topicDto;
  15 + protected byte[] payload;
  16 +
  17 + public TopicFactoryAdapter(Topic topicDto, byte[] payload)
  18 + {
  19 + this.topicDto = topicDto;
  20 + this.payload = payload;
  21 + }
  22 + public static TopicFactoryAdapter createTopicFactory(Topic topicDto, byte[] payload)
  23 + {
  24 + switch (topicDto.getTopicType())
  25 + {
  26 + case "PUT": // 写入 , payload: {"1":{"name1":xxx,"name2":xxx}}
  27 + return new PutTopic(topicDto, payload);
  28 + case "READ": // 读取 , payload: {"1":{"name1","name2","name3"}}
  29 + return new ReadTopic(topicDto, payload);
  30 + case "HOST": // 主机操作 , payload: {""}
  31 + return new HostTopic(topicDto, payload);
  32 + default:
  33 + return null;
  34 + }
  35 + }
  36 +}
  1 +package com.zhonglai.luhui.device.modbus.terminal.data.topic;
  2 +
  3 +import com.zhonglai.luhui.device.modbus.terminal.camera.opf.CameraOperationInstructions;
  4 +import com.zhonglai.luhui.device.mqtt.terminal.jar.dto.Topic;
  5 +import com.zhonglai.luhui.device.mqtt.terminal.jar.mqtt.MqttService;
  6 +import com.zhonglai.luhui.device.modbus.terminal.data.TopicFactoryAdapter;
  7 +import com.alibaba.fastjson.JSONObject;
  8 +import org.eclipse.paho.client.mqttv3.MqttException;
  9 +
  10 +import java.io.*;
  11 +import java.net.HttpURLConnection;
  12 +import java.net.URL;
  13 +import java.nio.file.*;
  14 +import java.nio.file.attribute.FileTime;
  15 +import java.text.SimpleDateFormat;
  16 +import java.util.ArrayList;
  17 +import java.util.Date;
  18 +import java.util.List;
  19 +import java.util.concurrent.atomic.AtomicReference;
  20 +
  21 +public class HostTopic extends TopicFactoryAdapter {
  22 + private static final AtomicReference<Path> currentDir =
  23 + new AtomicReference<>(Paths.get(System.getProperty("user.dir")));
  24 +
  25 + public HostTopic(Topic topicDto, byte[] payload) {
  26 + super(topicDto, payload);
  27 + }
  28 +
  29 + @Override
  30 + public void parse(MqttService mqttService) {
  31 + String str = new String(payload);
  32 + JSONObject jsonObject = JSONObject.parseObject(str);
  33 + String function = jsonObject.getString("function");
  34 + JSONObject data = jsonObject.getJSONObject("data");
  35 +
  36 + JSONObject result = new JSONObject();
  37 + try {
  38 + switch (function) {
  39 + case "ls":
  40 + result.put("result", listFiles());
  41 + break;
  42 + case "cd":
  43 + result.put("result", changeDirectory(data.getString("path")));
  44 + break;
  45 + case "mkdir":
  46 + result.put("result", makeDir(data.getString("name")));
  47 + break;
  48 + case "rm":
  49 + result.put("result", removeFile(data.getString("name")));
  50 + break;
  51 + case "copy":
  52 + result.put("result", copyFile(data.getString("source"), data.getString("target")));
  53 + break;
  54 + case "mk":
  55 + result.put("result", makeFile(data.getString("name")));
  56 + break;
  57 + case "download":
  58 + result.put("result", downloadFile(data.getString("url"), data.getString("name")));
  59 + break;
  60 + case "upload":
  61 + result.put("result", uploadFile(data.getString("name"), data.getString("url")));
  62 + break;
  63 + case "camera":
  64 + result.put("result", camera(data));
  65 + break;
  66 +
  67 + default:
  68 + result.put("error", "Unknown function: " + function);
  69 + }
  70 + } catch (Exception e) {
  71 + result.put("error", e.getMessage());
  72 + }
  73 +
  74 + // 回传执行结果
  75 + try {
  76 + mqttService.publish("HOST_REQ/"+topicDto.getTime(), result.toJSONString());
  77 + } catch (MqttException e) {
  78 + logger.info("返回mqtt指令失败");
  79 + }
  80 + }
  81 +
  82 + private JSONObject listFiles() throws IOException {
  83 + JSONObject result = new JSONObject();
  84 + Path dir = currentDir.get();
  85 + if (!Files.isDirectory(dir)) {
  86 + result.put("error", "Not a directory: " + dir.toString());
  87 + return result;
  88 + }
  89 +
  90 + // 时间格式化器
  91 + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  92 +
  93 + // 文件数组
  94 + List<JSONObject> files = new ArrayList<>();
  95 + Files.list(dir).forEach(path -> {
  96 + JSONObject fileObj = new JSONObject();
  97 + fileObj.put("name", path.getFileName().toString());
  98 + try {
  99 + FileTime fileTime = Files.getLastModifiedTime(path);
  100 + fileObj.put("lastModified", sdf.format(new Date(fileTime.toMillis())));
  101 + } catch (IOException e) {
  102 + fileObj.put("lastModified", "unknown");
  103 + }
  104 + fileObj.put("isDirectory", Files.isDirectory(path));
  105 +
  106 + try {
  107 + if (Files.isDirectory(path)) {
  108 + fileObj.put("size", "-");
  109 + } else {
  110 + fileObj.put("size", Files.size(path));
  111 + }
  112 + } catch (IOException e) {
  113 + fileObj.put("size", "unknown");
  114 + }
  115 +
  116 + files.add(fileObj);
  117 + });
  118 +
  119 + result.put("path", dir.toAbsolutePath().toString());
  120 + result.put("files", files);
  121 + return result;
  122 + }
  123 +
  124 +
  125 + private String changeDirectory(String path) {
  126 + Path newPath = currentDir.get().resolve(path).normalize();
  127 + if (Files.isDirectory(newPath)) {
  128 + currentDir.set(newPath);
  129 + return newPath.toAbsolutePath().toString(); // 返回绝对路径
  130 + }
  131 + return "";
  132 + }
  133 +
  134 + private String makeDir(String name) throws IOException {
  135 + Path newPath = currentDir.get().resolve(name);
  136 + Files.createDirectories(newPath);
  137 + return newPath.toString();
  138 + }
  139 +
  140 + private String removeFile(String name) throws IOException {
  141 + Path target = currentDir.get().resolve(name);
  142 + Files.deleteIfExists(target);
  143 + return target.toString();
  144 + }
  145 +
  146 + private String copyFile(String source, String target) throws IOException {
  147 + Path src = currentDir.get().resolve(source);
  148 + Path tgt = currentDir.get().resolve(target);
  149 + Files.copy(src, tgt, StandardCopyOption.REPLACE_EXISTING);
  150 + return "Copied from " + src.toString() + " to " + tgt.toString();
  151 + }
  152 +
  153 + private String makeFile(String name) throws IOException {
  154 + Path file = currentDir.get().resolve(name);
  155 + Files.createFile(file);
  156 + return "File created: " + file.toString();
  157 + }
  158 +
  159 + private String downloadFile(String urlStr, String fileName) throws IOException {
  160 + Path target = currentDir.get().resolve(fileName);
  161 +
  162 + URL url = new URL(urlStr);
  163 + HttpURLConnection conn = (HttpURLConnection) url.openConnection();
  164 + conn.setConnectTimeout(10000);
  165 + conn.setReadTimeout(10000);
  166 +
  167 + try (InputStream in = conn.getInputStream();
  168 + OutputStream out = new FileOutputStream(target.toFile())) {
  169 + byte[] buffer = new byte[8192];
  170 + int len;
  171 + while ((len = in.read(buffer)) != -1) {
  172 + out.write(buffer, 0, len);
  173 + }
  174 + }
  175 + return "Downloaded file to " + target.toString();
  176 + }
  177 +
  178 + private String uploadFile(String fileName, String urlStr) throws IOException {
  179 + Path file = currentDir.get().resolve(fileName);
  180 + if (!Files.exists(file)) {
  181 + return "File not found: " + file.toString();
  182 + }
  183 +
  184 + String boundary = "----WebKitFormBoundary" + System.currentTimeMillis();
  185 + String LINE_FEED = "\r\n";
  186 +
  187 + URL url = new URL(urlStr);
  188 + HttpURLConnection conn = (HttpURLConnection) url.openConnection();
  189 + conn.setDoOutput(true);
  190 + conn.setRequestMethod("POST");
  191 + conn.setConnectTimeout(10000);
  192 + conn.setReadTimeout(10000);
  193 + conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
  194 +
  195 + try (OutputStream out = conn.getOutputStream();
  196 + PrintWriter writer = new PrintWriter(new OutputStreamWriter(out, "UTF-8"), true);
  197 + FileInputStream inputStream = new FileInputStream(file.toFile())) {
  198 +
  199 + // --- 表单文件头
  200 + writer.append("--").append(boundary).append(LINE_FEED);
  201 + writer.append("Content-Disposition: form-data; name=\"file\"; filename=\"")
  202 + .append(file.getFileName().toString()).append("\"").append(LINE_FEED);
  203 + writer.append("Content-Type: application/octet-stream").append(LINE_FEED);
  204 + writer.append(LINE_FEED);
  205 + writer.flush();
  206 +
  207 + // --- 文件内容
  208 + byte[] buffer = new byte[8192];
  209 + int len;
  210 + while ((len = inputStream.read(buffer)) != -1) {
  211 + out.write(buffer, 0, len);
  212 + }
  213 + out.flush();
  214 +
  215 + // --- 文件结束
  216 + writer.append(LINE_FEED).flush();
  217 + writer.append("--").append(boundary).append("--").append(LINE_FEED);
  218 + writer.flush();
  219 + }
  220 +
  221 + int responseCode = conn.getResponseCode();
  222 + String responseMsg = "";
  223 + InputStream respStream = null;
  224 + try {
  225 + respStream = conn.getInputStream();
  226 + } catch (IOException e) {
  227 + respStream = conn.getErrorStream(); // 出错时取 errorStream
  228 + }
  229 + if (respStream != null) {
  230 + StringBuilder sb = new StringBuilder();
  231 + BufferedReader reader = new BufferedReader(new InputStreamReader(respStream, "UTF-8"));
  232 + String line;
  233 + while ((line = reader.readLine()) != null) {
  234 + sb.append(line).append("\n");
  235 + }
  236 + responseMsg = sb.toString().trim();
  237 + reader.close();
  238 + }
  239 +
  240 + return "Uploaded " + file.toString() + " to " + urlStr +
  241 + " (HTTP " + responseCode + ") response: " + responseMsg;
  242 + }
  243 +
  244 + private String camera(JSONObject parameter) throws IOException {
  245 + CameraOperationInstructions cameraOperationInstructions = CameraOperationInstructions.createCameraOperationInstructions( parameter);
  246 + if (null != cameraOperationInstructions)
  247 + {
  248 + return cameraOperationInstructions.execute().toJSONString();
  249 + }
  250 + return "camera ok" ;
  251 + }
  252 +}
  1 +package com.zhonglai.luhui.device.modbus.terminal.data.topic;
  2 +
  3 +import com.alibaba.fastjson.JSONObject;
  4 +import com.zhonglai.luhui.device.modbus.terminal.config.InitPlcConfig;
  5 +import com.zhonglai.luhui.device.modbus.terminal.data.TopicFactoryAdapter;
  6 +import com.zhonglai.luhui.device.modbus.terminal.modbus.Modbus4jWrite;
  7 +import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.PlcPoint;
  8 +import com.zhonglai.luhui.device.mqtt.terminal.jar.dto.Topic;
  9 +import com.zhonglai.luhui.device.mqtt.terminal.jar.mqtt.MqttService;
  10 +import org.eclipse.paho.client.mqttv3.MqttException;
  11 +
  12 +import java.util.ArrayList;
  13 +import java.util.List;
  14 +
  15 +/**
  16 + * 设备写数据
  17 + * payload: {"1":{"name1":xxx,"name2":xxx}}
  18 + */
  19 +public class PutTopic extends TopicFactoryAdapter {
  20 + public PutTopic(Topic topicDto, byte[] payload) {
  21 + super(topicDto, payload);
  22 + }
  23 +
  24 + @Override
  25 + public void parse(MqttService mqttService) {
  26 + String str = new String( payload);
  27 + JSONObject jsonObject = JSONObject.parseObject(str);
  28 + for (String key : jsonObject.keySet())
  29 + {
  30 + JSONObject plcCommand = jsonObject.getJSONObject(key);
  31 + Integer id = Integer.parseInt(key);
  32 + List<PlcPoint> plcPoints = getPlcPoints(id, plcCommand);
  33 + try {
  34 + new Modbus4jWrite(id).batchWrite(plcPoints,true);
  35 + } catch (Exception e) {
  36 + logger.error("写plc异常",e);
  37 + }
  38 + }
  39 + try {
  40 + JSONObject rJsonObject = new JSONObject();
  41 + rJsonObject.put("result",1);
  42 + mqttService.publish("PUT_REQ/"+topicDto.getTime(),rJsonObject.toJSONString());
  43 + } catch (MqttException e) {
  44 + logger.error("饭hi结果异常",e);
  45 + }
  46 + }
  47 +
  48 + private List<PlcPoint> getPlcPoints(Integer id,JSONObject plcCommand)
  49 + {
  50 + List<PlcPoint> plcPoints = new ArrayList<>();
  51 + for (String pointName : plcCommand.keySet())
  52 + {
  53 + PlcPoint plcPoint = InitPlcConfig.getPlcSystem(id, pointName);
  54 + if ( null != plcPoint)
  55 + {
  56 + plcPoint.setValue(plcCommand.getString(pointName));
  57 + plcPoints.add(plcPoint);
  58 + }
  59 + }
  60 + return plcPoints;
  61 +
  62 + }
  63 +
  64 +}
  1 +package com.zhonglai.luhui.device.modbus.terminal.data.topic;
  2 +
  3 +import com.alibaba.fastjson.JSONObject;
  4 +import com.zhonglai.luhui.device.modbus.terminal.config.InitPlcConfig;
  5 +import com.zhonglai.luhui.device.modbus.terminal.data.TopicFactoryAdapter;
  6 +import com.zhonglai.luhui.device.modbus.terminal.modbus.Modbus4jRead;
  7 +import com.zhonglai.luhui.device.modbus.terminal.modbus.Modbus4jWrite;
  8 +import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.PlcPoint;
  9 +import com.zhonglai.luhui.device.mqtt.terminal.jar.dto.Topic;
  10 +import com.zhonglai.luhui.device.mqtt.terminal.jar.mqtt.MqttService;
  11 +import org.eclipse.paho.client.mqttv3.MqttException;
  12 +
  13 +import java.util.ArrayList;
  14 +import java.util.List;
  15 +import java.util.Map;
  16 +
  17 +/**
  18 + * 设备读数据
  19 + * payload: {"1":"name1","name2","name3"}
  20 + */
  21 +public class ReadTopic extends TopicFactoryAdapter {
  22 + public ReadTopic(Topic topicDto, byte[] payload) {
  23 + super(topicDto, payload);
  24 + }
  25 +
  26 + @Override
  27 + public void parse(MqttService mqttService) {
  28 + JSONObject rJsonObject = new JSONObject();
  29 + String str = new String( payload);
  30 + JSONObject jsonObject = JSONObject.parseObject(str);
  31 + for (String key : jsonObject.keySet())
  32 + {
  33 + String plcCommand = jsonObject.getString(key);
  34 + Integer id = Integer.parseInt(key);
  35 + List<PlcPoint> plcPoints = getPlcPoints(id, plcCommand);
  36 + try {
  37 + Map<String, Object> map = new Modbus4jRead(id).batchRead(plcPoints,true);
  38 + rJsonObject.put(key, map);
  39 + } catch (Exception e) {
  40 + logger.error("读plc异常",e);
  41 + return;
  42 + }
  43 + }
  44 + try {
  45 + mqttService.publish("READ_REQ/"+topicDto.getTime(),rJsonObject.toJSONString());
  46 + } catch (MqttException e) {
  47 + logger.error("返回结果异常",e);
  48 + }
  49 + }
  50 +
  51 + private List<PlcPoint> getPlcPoints(Integer id,String plcCommand)
  52 + {
  53 + List<PlcPoint> plcPoints = new ArrayList<>();
  54 + for (String pointName : plcCommand.split(","))
  55 + {
  56 + PlcPoint plcPoint = InitPlcConfig.getPlcSystem(id, pointName);
  57 + if ( null != plcPoint)
  58 + {
  59 + plcPoints.add(plcPoint);
  60 + }
  61 + }
  62 + return plcPoints;
  63 +
  64 + }
  65 +}
  1 +package com.zhonglai.luhui.device.modbus.terminal.modbus;
  2 +
  3 +import com.serotonin.modbus4j.BatchRead;
  4 +import com.serotonin.modbus4j.BatchResults;
  5 +import com.serotonin.modbus4j.ModbusMaster;
  6 +import com.serotonin.modbus4j.code.DataType;
  7 +import com.serotonin.modbus4j.exception.ErrorResponseException;
  8 +import com.serotonin.modbus4j.exception.ModbusInitException;
  9 +import com.serotonin.modbus4j.exception.ModbusTransportException;
  10 +import com.serotonin.modbus4j.locator.BaseLocator;
  11 +import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.PlcPoint;
  12 +
  13 +import java.io.File;
  14 +import java.util.HashMap;
  15 +import java.util.List;
  16 +import java.util.Map;
  17 +
  18 +/**
  19 + * modbus通讯工具类,采用modbus4j实现
  20 + *
  21 + * @author lxq
  22 + * @dependencies modbus4j-3.0.3.jar
  23 + * @website https://github.com/infiniteautomation/modbus4j
  24 + */
  25 +public class Modbus4jRead {
  26 +
  27 + private ModbusMaster master;
  28 +
  29 + public Modbus4jRead(Integer id) throws Exception {
  30 + this.master = ModbusMasterMessage.createMaster(id);
  31 + }
  32 +
  33 + /**
  34 + * 批量读取使用方法
  35 + *
  36 + * @throws ModbusTransportException
  37 + * @throws ErrorResponseException
  38 + * @throws ModbusInitException
  39 + */
  40 + /**
  41 + * 批量读取点位
  42 + */
  43 + public Map<String, Object> batchRead(List<PlcPoint> plcPoints, boolean zeroBasedAddress) throws ModbusTransportException, ErrorResponseException {
  44 + BatchRead<String> batch = new BatchRead<>();
  45 +
  46 + for (PlcPoint plcPoint : plcPoints) {
  47 + String key = plcPoint.getSystem();
  48 +
  49 + // 解析 Modbus 地址,生成 BaseLocator
  50 + BaseLocator<?> locator = ModbusAddressParser.parseLocator(
  51 + plcPoint.getAddress(),
  52 + plcPoint.getDataType(),
  53 + plcPoint.getSlaveId(),
  54 + zeroBasedAddress,
  55 + plcPoint.getOrder()
  56 + );
  57 +
  58 + batch.addLocator(key, locator);
  59 + }
  60 +
  61 + batch.setContiguousRequests(false);
  62 +
  63 + // 发送批量请求
  64 + BatchResults<String> results = master.send(batch);
  65 +
  66 + // 转换成 JSON 对象
  67 + Map<String, Object> jsonMap = new HashMap<>();
  68 + for (PlcPoint plcPoint : plcPoints) {
  69 +
  70 + // 输出所有结果
  71 + String key = plcPoint.getSystem();
  72 + Object value = results.getValue(key);
  73 + switch (plcPoint.getDataType())
  74 + {
  75 + case FLOAT32:
  76 + if (value instanceof Integer) {
  77 + int raw = (int) value;
  78 + switch (plcPoint.getOrder()) {
  79 + case "ABCD":
  80 + value = ModbusAddressParser.intToFloatABCD(raw);
  81 + break;
  82 + case "BADC":
  83 + value = ModbusAddressParser.intToFloatBADC(raw);
  84 + break;
  85 + case "CDAB":
  86 + value = ModbusAddressParser.intToFloatCDAB(raw);
  87 + break;
  88 + case "DCBA":
  89 + value = ModbusAddressParser.intToFloatDCBA(raw);
  90 + break;
  91 + }
  92 + }
  93 + break;
  94 + case INT32:
  95 + if (value instanceof Integer) {
  96 + int raw = (int) value;
  97 + switch (plcPoint.getOrder()) {
  98 + case "ABCD":
  99 + value = ModbusAddressParser.int32ABCD(raw);
  100 + break;
  101 + case "BADC":
  102 + value = ModbusAddressParser.int32BADC(raw);
  103 + break;
  104 + case "CDAB":
  105 + value = ModbusAddressParser.int32CDAB(raw);
  106 + break;
  107 + case "DCBA":
  108 + value = ModbusAddressParser.int32DCBA(raw);
  109 + break;
  110 + }
  111 + }
  112 + break;
  113 + }
  114 + jsonMap.put(key, value);
  115 + }
  116 + return jsonMap;
  117 + }
  118 +
  119 +}
  1 +package com.zhonglai.luhui.device.modbus.terminal.modbus;
  2 +
  3 +import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.PlcPoint;
  4 +import org.apache.commons.logging.Log;
  5 +import org.apache.commons.logging.LogFactory;
  6 +
  7 +import com.serotonin.modbus4j.ModbusFactory;
  8 +import com.serotonin.modbus4j.ModbusMaster;
  9 +import com.serotonin.modbus4j.code.DataType;
  10 +import com.serotonin.modbus4j.exception.ErrorResponseException;
  11 +import com.serotonin.modbus4j.exception.ModbusInitException;
  12 +import com.serotonin.modbus4j.exception.ModbusTransportException;
  13 +import com.serotonin.modbus4j.ip.IpParameters;
  14 +import com.serotonin.modbus4j.locator.BaseLocator;
  15 +import com.serotonin.modbus4j.msg.ModbusResponse;
  16 +import com.serotonin.modbus4j.msg.WriteCoilRequest;
  17 +import com.serotonin.modbus4j.msg.WriteCoilResponse;
  18 +import com.serotonin.modbus4j.msg.WriteCoilsRequest;
  19 +import com.serotonin.modbus4j.msg.WriteCoilsResponse;
  20 +import com.serotonin.modbus4j.msg.WriteRegisterRequest;
  21 +import com.serotonin.modbus4j.msg.WriteRegisterResponse;
  22 +import com.serotonin.modbus4j.msg.WriteRegistersRequest;
  23 +
  24 +import java.util.List;
  25 +
  26 +/**
  27 + * modbus4j写入数据
  28 + *
  29 + * @author xq
  30 + *
  31 + */
  32 +public class Modbus4jWrite {
  33 + static Log log = LogFactory.getLog(Modbus4jWrite.class);
  34 + private ModbusMaster master;
  35 +
  36 + public Modbus4jWrite(Integer id) throws Exception {
  37 + this.master = ModbusMasterMessage.createMaster(id);
  38 + }
  39 +
  40 +
  41 + /**
  42 + * 单点写
  43 + */
  44 + public void writePoint(PlcPoint point, boolean zeroBasedAddress) throws ModbusTransportException, ErrorResponseException {
  45 + BaseLocator<?> locator = ModbusAddressParser.parseLocator(
  46 + point.getAddress(),
  47 + point.getDataType(),
  48 + point.getSlaveId(),
  49 + zeroBasedAddress,
  50 + point.getOrder()
  51 + );
  52 + Object value = point.getValue();
  53 +
  54 + // 如果 value 是 String,则根据 dataType 转换
  55 + if (value instanceof String) {
  56 + String strVal = (String) value;
  57 + switch (point.getDataType()) {
  58 + case BIT:
  59 + value = Boolean.parseBoolean(strVal);
  60 + break;
  61 + case INT16:
  62 + value = Short.parseShort(strVal);
  63 + break;
  64 + case UINT16:
  65 + value = Integer.parseInt(strVal); // Modbus4j 内部支持 unsigned
  66 + break;
  67 + case INT32:
  68 + int raw = Integer.parseInt(strVal);
  69 + switch (point.getOrder()) {
  70 + case "ABCD":
  71 + value = ModbusAddressParser.int32ToABCD(raw);
  72 + break;
  73 + case "BADC":
  74 + value = ModbusAddressParser.int32ToBADC(raw);
  75 + break;
  76 + case "CDAB":
  77 + value = ModbusAddressParser.int32ToCDAB(raw);
  78 + break;
  79 + case "DCBA":
  80 + value = ModbusAddressParser.int32ToDCBA(raw);
  81 + break;
  82 + default:
  83 + value = raw;
  84 + break;
  85 + }
  86 + break;
  87 + case INT64:
  88 + value = Long.parseLong(strVal);
  89 + break;
  90 + case FLOAT32:
  91 + float fvalue = Float.parseFloat(strVal);
  92 + switch (point.getOrder()) {
  93 + case "ABCD":
  94 + value = ModbusAddressParser.floatToIntABCD(fvalue);
  95 + break;
  96 + case "BADC":
  97 + value = ModbusAddressParser.floatToIntBADC(fvalue);
  98 + break;
  99 + case "CDAB":
  100 + value = ModbusAddressParser.floatToIntCDAB(fvalue);
  101 + break;
  102 + case "DCBA":
  103 + value = ModbusAddressParser.floatToIntDCBA(fvalue);
  104 + break;
  105 + default:
  106 + value = fvalue;
  107 + break;
  108 + }
  109 + break;
  110 + case DOUBLE64:
  111 + value = Double.parseDouble(strVal);
  112 + break;
  113 + default:
  114 + throw new IllegalArgumentException("不支持的数据类型: " + point.getDataType());
  115 + }
  116 + }
  117 +
  118 + // 设置值
  119 + master.setValue(locator, value);
  120 + log.info("写入成功: " + point.getName() + " = " + point.getValue());
  121 + }
  122 +
  123 + /**
  124 + * 批量写(兼容 3.1.0,没有 BatchWrite)
  125 + */
  126 + public void batchWrite(List<PlcPoint> points, boolean zeroBasedAddress) throws ModbusTransportException, ErrorResponseException {
  127 +
  128 + for (PlcPoint plcPoint:points) {
  129 + writePoint(plcPoint, zeroBasedAddress);
  130 + }
  131 + }
  132 +
  133 + public boolean isConnected() {
  134 + return master.isConnected();
  135 + }
  136 + public void close() {
  137 + master.destroy();
  138 + }
  139 +}
  1 +package com.zhonglai.luhui.device.modbus.terminal.modbus;
  2 +
  3 +import com.serotonin.modbus4j.locator.BaseLocator;
  4 +import com.serotonin.modbus4j.code.DataType;
  5 +import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.PlcDataType;
  6 +
  7 +import java.nio.ByteBuffer;
  8 +import java.nio.ByteOrder;
  9 +
  10 +public class ModbusAddressParser {
  11 +
  12 + /**
  13 + * 解析 Modbus 地址,生成 BaseLocator
  14 + * @param rawAddress 原始地址 (40001, 40001.01, 40003-40004)
  15 + * @param type 数据类型 (BIT, INT16, UINT16, INT32, INT64, FLOAT32, DOUBLE64)
  16 + * @param slaveId 从站ID
  17 + * @return BaseLocator 对象
  18 + */
  19 + public static BaseLocator<?> parseLocator(String rawAddress, PlcDataType type, int slaveId, boolean zeroBasedAddress,String order) {
  20 + int offset = parseOffset(rawAddress,zeroBasedAddress);
  21 + int bitIndex = parseBitIndex(rawAddress);
  22 +
  23 + switch (type) {
  24 + case BIT:
  25 + if (rawAddress.startsWith("0")) {
  26 + // Coil (线圈输出)
  27 + return BaseLocator.coilStatus(slaveId, offset);
  28 + } else if (rawAddress.startsWith("1")) {
  29 + // Discrete Input (离散量输入)
  30 + return BaseLocator.inputStatus(slaveId, offset);
  31 + } else if (rawAddress.startsWith("3") && bitIndex >= 0) {
  32 + // Input Register 内部 bit 位
  33 + return BaseLocator.inputRegisterBit(slaveId, offset, bitIndex);
  34 + } else if (rawAddress.startsWith("4") && bitIndex >= 0) {
  35 + // Holding Register 内部 bit 位
  36 + return BaseLocator.holdingRegisterBit(slaveId, offset, bitIndex);
  37 + } else {
  38 + throw new IllegalArgumentException("地址不支持 BIT 类型: " + rawAddress);
  39 + }
  40 +
  41 + case INT16:
  42 + if (rawAddress.startsWith("3")) {
  43 + return BaseLocator.inputRegister(slaveId, offset, DataType.TWO_BYTE_INT_SIGNED);
  44 + } else if (rawAddress.startsWith("4")) {
  45 + return BaseLocator.holdingRegister(slaveId, offset, DataType.TWO_BYTE_INT_SIGNED);
  46 + }
  47 + break;
  48 +
  49 + case UINT16:
  50 + if (rawAddress.startsWith("3")) {
  51 + return BaseLocator.inputRegister(slaveId, offset, DataType.TWO_BYTE_INT_UNSIGNED);
  52 + } else if (rawAddress.startsWith("4")) {
  53 + return BaseLocator.holdingRegister(slaveId, offset, DataType.TWO_BYTE_INT_UNSIGNED);
  54 + }
  55 + break;
  56 +
  57 + case INT32:
  58 + if (rawAddress.startsWith("3")) {
  59 + return BaseLocator.inputRegister(slaveId, offset, DataType.FOUR_BYTE_INT_SIGNED);
  60 + } else if (rawAddress.startsWith("4")) {
  61 + return BaseLocator.holdingRegister(slaveId, offset, DataType.FOUR_BYTE_INT_SIGNED);
  62 + }
  63 + break;
  64 +
  65 + case INT64:
  66 + if (rawAddress.startsWith("3")) {
  67 + return BaseLocator.inputRegister(slaveId, offset, DataType.EIGHT_BYTE_INT_SIGNED);
  68 + } else if (rawAddress.startsWith("4")) {
  69 + return BaseLocator.holdingRegister(slaveId, offset, DataType.EIGHT_BYTE_INT_SIGNED);
  70 + }
  71 + break;
  72 +
  73 + case FLOAT32:
  74 + if (order == null) {
  75 + order = "ABCD"; // 默认大端
  76 + }
  77 + int floatDataType = DataType.FOUR_BYTE_FLOAT;
  78 + switch (order) {
  79 + case "ABCD": // 大端 IEEE754
  80 + floatDataType = DataType.FOUR_BYTE_FLOAT;
  81 + break;
  82 + case "CDAB": // 寄存器交换
  83 + floatDataType = DataType.FOUR_BYTE_FLOAT_SWAPPED;
  84 + break;
  85 + case "BADC": // 字节交换
  86 + floatDataType = DataType.FOUR_BYTE_FLOAT_SWAPPED_INVERTED;
  87 + break;
  88 + case "DCBA": // 全反转
  89 + floatDataType = DataType.FOUR_BYTE_INT_SIGNED_SWAPPED_SWAPPED;
  90 + // ⚠ modbus4j 没有直接 DCBA 的 float,可以用 int SWAP-SWAP 再手动转 float
  91 + break;
  92 + }
  93 + if (rawAddress.startsWith("3")) {
  94 + return BaseLocator.inputRegister(slaveId, offset, floatDataType);
  95 + } else if (rawAddress.startsWith("4")) {
  96 + return BaseLocator.holdingRegister(slaveId, offset, floatDataType);
  97 + }
  98 + break;
  99 +
  100 + case DOUBLE64:
  101 + if (rawAddress.startsWith("3")) {
  102 + return BaseLocator.inputRegister(slaveId, offset, DataType.EIGHT_BYTE_FLOAT);
  103 + } else if (rawAddress.startsWith("4")) {
  104 + return BaseLocator.holdingRegister(slaveId, offset, DataType.EIGHT_BYTE_FLOAT);
  105 + }
  106 + break;
  107 +
  108 + default:
  109 + throw new IllegalArgumentException("不支持的数据类型: " + type);
  110 + }
  111 +
  112 + throw new IllegalArgumentException("地址和数据类型不匹配: " + rawAddress + " -> " + type);
  113 + }
  114 +
  115 + /** 解析寄存器偏移量 */
  116 + public static int parseOffset(String rawAddress, boolean zeroBasedAddress) {
  117 + if (rawAddress == null || rawAddress.isEmpty()) {
  118 + throw new IllegalArgumentException("地址不能为空");
  119 + }
  120 +
  121 + // 处理区间地址:40003-40004
  122 + if (rawAddress.contains("-")) {
  123 + String[] parts = rawAddress.split("-");
  124 + rawAddress = parts[0]; // 取起始地址
  125 + }
  126 +
  127 + // 去掉小数点后部分:40001.01 -> 40001
  128 + String basePart = rawAddress.split("\\.")[0];
  129 + int address = Integer.parseInt(basePart);
  130 +
  131 + int offset;
  132 + if (address >= 1 && address <= 9999) { // Coil (0xxxx)
  133 + offset = address - 1;
  134 + } else if (address >= 10001 && address <= 19999) { // Discrete Input (1xxxx)
  135 + offset = address - 10001;
  136 + } else if (address >= 30001 && address <= 39999) { // Input Register (3xxxx)
  137 + offset = address - 30001;
  138 + } else if (address >= 40001 && address <= 49999) { // Holding Register (4xxxx)
  139 + offset = address - 40001;
  140 + } else {
  141 + throw new IllegalArgumentException("无效的Modbus地址: " + rawAddress);
  142 + }
  143 +
  144 + // 根据 zeroBasedAddress 参数调整偏移
  145 + if (!zeroBasedAddress) {
  146 + offset += 1;
  147 + }
  148 +
  149 + return offset;
  150 + }
  151 +
  152 +
  153 + /** 解析 bit 索引 */
  154 + public static int parseBitIndex(String rawAddress) {
  155 + if (rawAddress.contains(".")) {
  156 + String[] parts = rawAddress.split("\\.");
  157 + return Integer.parseInt(parts[1]) - 1; // 转成从0开始
  158 + }
  159 + return -1; // 没有 bit 位
  160 + }
  161 +
  162 + public static float intToFloatDCBA(int value) {
  163 + byte[] bytes = new byte[4];
  164 + bytes[0] = (byte) (value & 0xFF);
  165 + bytes[1] = (byte) ((value >> 8) & 0xFF);
  166 + bytes[2] = (byte) ((value >> 16) & 0xFF);
  167 + bytes[3] = (byte) ((value >> 24) & 0xFF);
  168 +
  169 + // 反转成 DCBA
  170 + byte tmp;
  171 + tmp = bytes[0]; bytes[0] = bytes[3]; bytes[3] = tmp;
  172 + tmp = bytes[1]; bytes[1] = bytes[2]; bytes[2] = tmp;
  173 +
  174 + return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getFloat();
  175 + }
  176 +
  177 + public static int floatToIntDCBA(float f) {
  178 + byte[] bytes = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putFloat(f).array();
  179 +
  180 + // 反转成 DCBA
  181 + byte tmp;
  182 + tmp = bytes[0]; bytes[0] = bytes[3]; bytes[3] = tmp;
  183 + tmp = bytes[1]; bytes[1] = bytes[2]; bytes[2] = tmp;
  184 +
  185 + return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt();
  186 + }
  187 +
  188 +
  189 + public static float intToFloatABCD(int value) {
  190 + byte[] bytes = intToBytes(value);
  191 + return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getFloat();
  192 + }
  193 +
  194 + public static float intToFloatBADC(int value) {
  195 + byte[] bytes = intToBytes(value);
  196 + swap(bytes, 0, 1);
  197 + swap(bytes, 2, 3);
  198 + return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getFloat();
  199 + }
  200 +
  201 + public static float intToFloatCDAB(int value) {
  202 + byte[] bytes = intToBytes(value);
  203 + swap(bytes, 0, 2);
  204 + swap(bytes, 1, 3);
  205 + return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getFloat();
  206 + }
  207 +
  208 + // ========== 32位整型的不同字节序 ==========
  209 + public static int int32ABCD(int value) {
  210 + return value; // 默认就是ABCD
  211 + }
  212 +
  213 + public static int int32BADC(int value) {
  214 + byte[] bytes = intToBytes(value);
  215 + swap(bytes, 0, 1);
  216 + swap(bytes, 2, 3);
  217 + return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt();
  218 + }
  219 +
  220 + public static int int32CDAB(int value) {
  221 + byte[] bytes = intToBytes(value);
  222 + swap(bytes, 0, 2);
  223 + swap(bytes, 1, 3);
  224 + return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt();
  225 + }
  226 +
  227 + public static int int32DCBA(int value) {
  228 + byte[] bytes = intToBytes(value);
  229 + reverse(bytes);
  230 + return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt();
  231 + }
  232 +
  233 + // ========== 通用工具 ==========
  234 + private static byte[] intToBytes(int value) {
  235 + return new byte[] {
  236 + (byte) ((value >> 24) & 0xFF),
  237 + (byte) ((value >> 16) & 0xFF),
  238 + (byte) ((value >> 8) & 0xFF),
  239 + (byte) (value & 0xFF)
  240 + };
  241 + }
  242 +
  243 + private static void swap(byte[] arr, int i, int j) {
  244 + byte tmp = arr[i];
  245 + arr[i] = arr[j];
  246 + arr[j] = tmp;
  247 + }
  248 +
  249 + private static void reverse(byte[] arr) {
  250 + for (int i = 0, j = arr.length - 1; i < j; i++, j--) {
  251 + swap(arr, i, j);
  252 + }
  253 + }
  254 +
  255 +
  256 + // ========== Float 写入 ==========
  257 + public static int floatToIntABCD(float f) {
  258 + byte[] bytes = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putFloat(f).array();
  259 + return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt();
  260 + }
  261 +
  262 + public static int floatToIntBADC(float f) {
  263 + byte[] bytes = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putFloat(f).array();
  264 + swap(bytes, 0, 1);
  265 + swap(bytes, 2, 3);
  266 + return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt();
  267 + }
  268 +
  269 + public static int floatToIntCDAB(float f) {
  270 + byte[] bytes = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putFloat(f).array();
  271 + swap(bytes, 0, 2);
  272 + swap(bytes, 1, 3);
  273 + return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt();
  274 + }
  275 +
  276 +
  277 + // ========== Int32 写入 ==========
  278 + public static int int32ToABCD(int value) {
  279 + return value; // 默认
  280 + }
  281 +
  282 + public static int int32ToBADC(int value) {
  283 + byte[] bytes = intToBytes(value);
  284 + swap(bytes, 0, 1);
  285 + swap(bytes, 2, 3);
  286 + return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt();
  287 + }
  288 +
  289 + public static int int32ToCDAB(int value) {
  290 + byte[] bytes = intToBytes(value);
  291 + swap(bytes, 0, 2);
  292 + swap(bytes, 1, 3);
  293 + return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt();
  294 + }
  295 +
  296 + public static int int32ToDCBA(int value) {
  297 + byte[] bytes = intToBytes(value);
  298 + reverse(bytes);
  299 + return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt();
  300 + }
  301 +
  302 +}
  1 +package com.zhonglai.luhui.device.modbus.terminal.modbus;
  2 +
  3 +import com.alibaba.fastjson.JSONObject;
  4 +import com.serotonin.modbus4j.ModbusFactory;
  5 +import com.serotonin.modbus4j.ModbusMaster;
  6 +import com.serotonin.modbus4j.exception.ModbusInitException;
  7 +import com.serotonin.modbus4j.exception.ModbusTransportException;
  8 +import com.serotonin.modbus4j.ip.IpParameters;
  9 +import com.serotonin.modbus4j.msg.*;
  10 +import com.zhonglai.luhui.device.modbus.terminal.config.InitPlcConfig;
  11 +import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.CachPlcConfig;
  12 +import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.JSerialCommWrapper;
  13 +
  14 +import java.io.File;
  15 +import java.util.concurrent.ConcurrentHashMap;
  16 +
  17 +public class ModbusMasterMessage {
  18 + /**
  19 + * 工厂
  20 + */
  21 + static ModbusFactory modbusFactory;
  22 + static {
  23 + if (modbusFactory == null) {
  24 + modbusFactory = new ModbusFactory();
  25 + }
  26 + }
  27 +
  28 + // 存储每个系统的 ModbusMaster
  29 + private static final ConcurrentHashMap<Integer, ModbusMaster> masterCache = new ConcurrentHashMap<>();
  30 + // 每个系统 ID 对应的锁对象
  31 + private static final ConcurrentHashMap<Integer, Object> lockMap = new ConcurrentHashMap<>();
  32 +
  33 +
  34 +
  35 + /**
  36 + * 创建或获取 ModbusMaster (线程安全)
  37 + */
  38 + public static ModbusMaster createMaster(Integer id) throws Exception {
  39 + ModbusMaster master = masterCache.get(id);
  40 + if (master != null && master.isConnected()) {
  41 + return master;
  42 + }
  43 +
  44 + // 获取该 ID 的专用锁
  45 + Object lock = lockMap.computeIfAbsent(id, k -> new Object());
  46 +
  47 + synchronized (lock) {
  48 + // 双重检查,避免重复创建
  49 + master = masterCache.get(id);
  50 + if (master != null && master.isConnected()) {
  51 + return master;
  52 + }
  53 +
  54 + // 如果旧的存在但断开了,销毁
  55 + if (master != null) {
  56 + master.destroy();
  57 + }
  58 +
  59 + // 创建新 master
  60 + ModbusMaster newMaster = selectMaster(id);
  61 +// newMaster.setIoLog(new MyIOLog(new File("logs/modbus-" + id + ".log")));
  62 + newMaster.init();
  63 +
  64 + masterCache.put(id, newMaster);
  65 + return newMaster;
  66 + }
  67 + }
  68 +
  69 +
  70 + private static ModbusMaster selectMaster(Integer id) throws Exception {
  71 +
  72 + CachPlcConfig cachPlcConfig = InitPlcConfig.getPlcSystems(id);
  73 + if (null == cachPlcConfig)
  74 + {
  75 + throw new Exception("系统: " + id+" 未配置");
  76 + }
  77 + JSONObject connectConfig = cachPlcConfig.getConnectConfig();
  78 +
  79 + switch (cachPlcConfig.getProtocolType())
  80 + {
  81 + case RTU:
  82 + return modbusFactory.createRtuMaster(JSONObject.parseObject(connectConfig.toJSONString(),JSerialCommWrapper.class));
  83 + case TCP:
  84 + return modbusFactory.createTcpMaster(JSONObject.parseObject(connectConfig.toJSONString(),IpParameters.class), true); //这里传的是 false → 表示短连接模式,每次用完连接就会关闭。
  85 + case ASCII:
  86 + return modbusFactory.createAsciiMaster(JSONObject.parseObject(connectConfig.toJSONString(),JSerialCommWrapper.class));
  87 + case UDP:
  88 + return modbusFactory.createUdpMaster(JSONObject.parseObject(connectConfig.toJSONString(),IpParameters.class));
  89 + default:
  90 + throw new Exception("系统: " + id+" 不支持的协议");
  91 + }
  92 + }
  93 +
  94 + /**
  95 + * 关闭指定系统的 Master
  96 + */
  97 + public static void closeMaster(Integer id) {
  98 + Object lock = lockMap.computeIfAbsent(id, k -> new Object());
  99 + synchronized (lock) {
  100 + ModbusMaster master = masterCache.remove(id);
  101 + if (master != null) {
  102 + master.destroy();
  103 + }
  104 + lockMap.remove(id);
  105 + }
  106 + }
  107 +
  108 + /**
  109 + * 关闭所有 Master
  110 + */
  111 + public static void closeAll() {
  112 + masterCache.values().forEach(ModbusMaster::destroy);
  113 + masterCache.clear();
  114 + lockMap.clear();
  115 + }
  116 +}
  1 +package com.zhonglai.luhui.device.modbus.terminal.modbus;
  2 +
  3 +import com.serotonin.modbus4j.sero.log.BaseIOLog;
  4 +
  5 +import java.io.File;
  6 +
  7 +public class MyIOLog extends BaseIOLog {
  8 +
  9 +
  10 + /**
  11 + * <p>Constructor for BaseIOLog.</p>
  12 + *
  13 + * @param logFile a {@link File} object.
  14 + */
  15 + public MyIOLog(File logFile) {
  16 + super(logFile);
  17 + }
  18 +
  19 + @Override
  20 + protected void sizeCheck() {
  21 + // 简单示例:不限制大小
  22 + // 可以实现文件超过一定大小时备份、清空等逻辑
  23 + }
  24 +
  25 + @Override
  26 + public void log(String message) {
  27 + super.log(message);
  28 + // 同时在控制台打印
  29 + System.out.println(message);
  30 + }
  31 +}
  1 +package com.zhonglai.luhui.device.modbus.terminal.modbus.dto;
  2 +
  3 +import com.alibaba.fastjson.JSONObject;
  4 +import com.fasterxml.jackson.databind.util.JSONPObject;
  5 +
  6 +import java.util.Map;
  7 +
  8 +public class CachPlcConfig {
  9 + private Integer id;
  10 + private String systemName;
  11 + private ProtocolType protocolType;
  12 + private JSONObject connectConfig;
  13 + private Map<String, PlcPoint> plcMap;
  14 +
  15 + public String getSystemName() {
  16 + return systemName;
  17 + }
  18 +
  19 + public void setSystemName(String systemName) {
  20 + this.systemName = systemName;
  21 + }
  22 +
  23 + public Map<String, PlcPoint> getPlcMap() {
  24 + return plcMap;
  25 + }
  26 +
  27 + public void setPlcMap(Map<String, PlcPoint> plcMap) {
  28 + this.plcMap = plcMap;
  29 + }
  30 +
  31 + public JSONObject getConnectConfig() {
  32 + return connectConfig;
  33 + }
  34 +
  35 + public void setConnectConfig(JSONObject connectConfig) {
  36 + this.connectConfig = connectConfig;
  37 + }
  38 +
  39 + public ProtocolType getProtocolType() {
  40 + return protocolType;
  41 + }
  42 +
  43 + public void setProtocolType(ProtocolType protocolType) {
  44 + this.protocolType = protocolType;
  45 + }
  46 +
  47 + public Integer getId() {
  48 + return id;
  49 + }
  50 +
  51 + public void setId(Integer id) {
  52 + this.id = id;
  53 + }
  54 +}
  1 +package com.zhonglai.luhui.device.modbus.terminal.modbus.dto;
  2 +
  3 +import com.serotonin.modbus4j.serial.SerialPortWrapper;
  4 +import com.fazecast.jSerialComm.SerialPort; // 需要 jSerialComm 库
  5 +
  6 +import java.io.InputStream;
  7 +import java.io.OutputStream;
  8 +
  9 +public class JSerialCommWrapper implements SerialPortWrapper {
  10 + private final String commPortId; // 串口号,例如 "COM3" 或 "/dev/ttyUSB0"
  11 + private final int baudRate; // 波特率
  12 + private final int dataBits;
  13 + private final int stopBits;
  14 + private final int parity; // 0 = NONE, 1 = ODD, 2 = EVEN
  15 +
  16 + private SerialPort serialPort;
  17 +
  18 + public JSerialCommWrapper(String commPortId, int baudRate, int dataBits, int stopBits, int parity) {
  19 + this.commPortId = commPortId;
  20 + this.baudRate = baudRate;
  21 + this.dataBits = dataBits;
  22 + this.stopBits = stopBits;
  23 + this.parity = parity;
  24 + }
  25 +
  26 + @Override
  27 + public void close() throws Exception {
  28 + if (serialPort != null) {
  29 + serialPort.closePort();
  30 + }
  31 + }
  32 +
  33 + @Override
  34 + public void open() throws Exception {
  35 + serialPort = SerialPort.getCommPort(commPortId);
  36 + serialPort.setComPortParameters(baudRate, dataBits, stopBits, parity);
  37 + serialPort.openPort();
  38 + }
  39 +
  40 + @Override
  41 + public InputStream getInputStream() {
  42 + return serialPort.getInputStream();
  43 + }
  44 +
  45 + @Override
  46 + public OutputStream getOutputStream() {
  47 + return serialPort.getOutputStream();
  48 + }
  49 +
  50 + @Override
  51 + public int getBaudRate() {
  52 + return baudRate;
  53 + }
  54 +
  55 + @Override
  56 + public int getDataBits() {
  57 + return dataBits;
  58 + }
  59 +
  60 + @Override
  61 + public int getStopBits() {
  62 + return stopBits;
  63 + }
  64 +
  65 + @Override
  66 + public int getParity() {
  67 + return parity;
  68 + }
  69 +
  70 +}
  1 +package com.zhonglai.luhui.device.modbus.terminal.modbus.dto;
  2 +
  3 +import com.fasterxml.jackson.annotation.JsonCreator;
  4 +
  5 +/**
  6 + * PLC读写权限
  7 + */
  8 +public enum PlcAccess {
  9 + R,
  10 + W,
  11 + RW;
  12 +
  13 +
  14 + @JsonCreator
  15 + public static PlcAccess fromString(String value) {
  16 + if (value == null) return null;
  17 + switch (value.toUpperCase()) {
  18 + case "R":
  19 + return R;
  20 + case "W":
  21 + return W;
  22 + case "RW":
  23 + return RW;
  24 + default:
  25 + throw new IllegalArgumentException("未知的枚举值: " + value);
  26 + }
  27 + }
  28 +}
  1 +package com.zhonglai.luhui.device.modbus.terminal.modbus.dto;
  2 +
  3 +import com.fasterxml.jackson.annotation.JsonCreator;
  4 +
  5 +public enum PlcDataType {
  6 + BIT, // 线圈/开关量
  7 + INPUT_BIT, // 输入离散量
  8 + INT16,
  9 + UINT16,
  10 + INT32,
  11 + INT64,
  12 + FLOAT32,
  13 + DOUBLE64;
  14 +
  15 + //忽略大小写
  16 + @JsonCreator
  17 + public static PlcDataType fromString(String value) {
  18 + if (value == null) return null;
  19 + switch (value.toUpperCase()) {
  20 + case "BIT":
  21 + return BIT;
  22 + case "INT":
  23 + case "INT16":
  24 + return INT16;
  25 + case "UINT":
  26 + case "UINT16":
  27 + return UINT16;
  28 + case "LONG": // 兼容 long
  29 + return INT32;
  30 + case "INT32":
  31 + return INT32;
  32 + case "INT64":
  33 + return INT64;
  34 + case "FLOAT":
  35 + case "FLOAT32":
  36 + return FLOAT32;
  37 + case "DOUBLE":
  38 + case "DOUBLE64":
  39 + return DOUBLE64;
  40 + case "INPUT_BIT":
  41 + case "INPUT":
  42 + return INPUT_BIT;
  43 + default:
  44 + throw new IllegalArgumentException("未知的枚举值: " + value);
  45 + }
  46 + }
  47 +}
  48 +
  1 +package com.zhonglai.luhui.device.modbus.terminal.modbus.dto;
  2 +
  3 +
  4 +public class PlcPoint {
  5 + public String name; // 点位名
  6 + public String system; // 点位系统名
  7 + public String address; // Modbus 地址,如 40001, 40001.01, 40003-40004
  8 + public PlcDataType dataType; // bit, int16, uint16, int32, float32, double64
  9 + public Integer slaveId = 1;
  10 + public Object value;
  11 + private String order = "DCBA"; // 新增,默认值
  12 + private PlcAccess access;
  13 +
  14 + public PlcAccess getAccess() {
  15 + return access;
  16 + }
  17 +
  18 + public void setAccess(PlcAccess access) {
  19 + this.access = access;
  20 + }
  21 +
  22 + public String getOrder() {
  23 + return order;
  24 + }
  25 +
  26 + public void setOrder(String order) {
  27 + this.order = order;
  28 + }
  29 +
  30 + public Object getValue() {
  31 + return value;
  32 + }
  33 +
  34 + public void setValue(Object value) {
  35 + this.value = value;
  36 + }
  37 +
  38 + public String getName() {
  39 + return name;
  40 + }
  41 +
  42 + public void setName(String name) {
  43 + this.name = name;
  44 + }
  45 +
  46 + public String getSystem() {
  47 + return system;
  48 + }
  49 +
  50 + public void setSystem(String system) {
  51 + this.system = system;
  52 + }
  53 +
  54 + public String getAddress() {
  55 + return address;
  56 + }
  57 +
  58 + public void setAddress(String address) {
  59 + this.address = address;
  60 + }
  61 +
  62 + public PlcDataType getDataType() {
  63 + return dataType;
  64 + }
  65 +
  66 + public void setDataType(PlcDataType dataType) {
  67 + this.dataType = dataType;
  68 + }
  69 +
  70 + public Integer getSlaveId() {
  71 + return slaveId;
  72 + }
  73 +
  74 + public void setSlaveId(Integer slaveId) {
  75 + this.slaveId = slaveId;
  76 + }
  77 +
  78 +}
  1 +package com.zhonglai.luhui.device.modbus.terminal.modbus.dto;
  2 +import com.alibaba.fastjson.JSONObject;
  3 +
  4 +import java.util.List;
  5 +
  6 +public class PlcSystem {
  7 + private Integer id;
  8 + private String systemName;
  9 + private ProtocolType protocolType;
  10 + private JSONObject connectConfig;
  11 + private List<PlcPoint> points;
  12 +
  13 + public String getSystemName() {
  14 + return systemName;
  15 + }
  16 +
  17 + public void setSystemName(String systemName) {
  18 + this.systemName = systemName;
  19 + }
  20 +
  21 + public ProtocolType getProtocolType() {
  22 + return protocolType;
  23 + }
  24 +
  25 + public void setProtocolType(ProtocolType protocolType) {
  26 + this.protocolType = protocolType;
  27 + }
  28 +
  29 + public JSONObject getConnectConfig() {
  30 + return connectConfig;
  31 + }
  32 +
  33 + public void setConnectConfig(JSONObject connectConfig) {
  34 + this.connectConfig = connectConfig;
  35 + }
  36 +
  37 + public List<PlcPoint> getPoints() {
  38 + return points;
  39 + }
  40 +
  41 + public void setPoints(List<PlcPoint> points) {
  42 + this.points = points;
  43 + }
  44 +
  45 + public Integer getId() {
  46 + return id;
  47 + }
  48 +
  49 + public void setId(Integer id) {
  50 + this.id = id;
  51 + }
  52 +}
  1 +package com.zhonglai.luhui.device.modbus.terminal.modbus.dto;
  2 +
  3 +/**
  4 + * 协议类型
  5 + */
  6 +public enum ProtocolType {
  7 + RTU,
  8 + UDP,
  9 + ASCII,
  10 + TCP,
  11 +}
  1 +package com.zhonglai.luhui.device.modbus.terminal.task;
  2 +
  3 +import com.alibaba.fastjson.JSONObject;
  4 +import com.serotonin.modbus4j.exception.ErrorResponseException;
  5 +import com.serotonin.modbus4j.exception.ModbusTransportException;
  6 +import com.zhonglai.luhui.device.modbus.terminal.config.InitPlcConfig;
  7 +import com.zhonglai.luhui.device.modbus.terminal.modbus.Modbus4jRead;
  8 +import com.zhonglai.luhui.device.modbus.terminal.modbus.ModbusMasterMessage;
  9 +import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.CachPlcConfig;
  10 +import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.PlcPoint;
  11 +import com.zhonglai.luhui.device.mqtt.terminal.jar.mqtt.MqttService;
  12 +import org.eclipse.paho.client.mqttv3.MqttException;
  13 +import org.slf4j.Logger;
  14 +import org.slf4j.LoggerFactory;
  15 +
  16 +import java.util.ArrayList;
  17 +import java.util.List;
  18 +import java.util.Map;
  19 +import java.util.concurrent.TimeUnit;
  20 +
  21 +/**
  22 + * 定时采集上报plc数据
  23 + */
  24 +public class CollectPlcDataTask {
  25 + protected static final Logger logger = LoggerFactory.getLogger(CollectPlcDataTask.class);
  26 +
  27 + private static final int maxDataLenth = 30;
  28 + public void collect(MqttService mqttService) {
  29 + ScheduledThreadPool.scheduler.scheduleAtFixedRate(() -> {
  30 + try {
  31 + pubMqttData(mqttService);
  32 + }catch (Exception e)
  33 + {
  34 + logger.info("plc通讯异常:{}",e.getMessage());
  35 + }
  36 + },0, 60, TimeUnit.SECONDS);
  37 + }
  38 +
  39 + private void pubMqttData(MqttService mqttService)
  40 + {
  41 + //查看可以访问的plc
  42 + Map<Integer, CachPlcConfig> plcConfigMap = InitPlcConfig.getPlcsConfigMap();
  43 + for (Integer plcId : plcConfigMap.keySet())
  44 + {
  45 + CachPlcConfig cachPlcConfig = plcConfigMap.get(plcId);
  46 + try {
  47 + pubMqttData(mqttService, plcId, cachPlcConfig);
  48 + } catch (Exception e) {
  49 + logger.info("plc {} 通讯异常:{}",plcId,e.getMessage());
  50 + }
  51 + }
  52 + }
  53 +
  54 + private void pubMqttData(MqttService mqttService, Integer plcId,CachPlcConfig cachPlcConfig) throws Exception
  55 + {
  56 + Map<String, PlcPoint> map = cachPlcConfig.getPlcMap();
  57 +
  58 + List<PlcPoint> plcPoints = new ArrayList<>();
  59 +
  60 + int dataLenth = 1 ;
  61 + for (String system : map.keySet())
  62 + {
  63 + PlcPoint plcPoint = map.get(system);
  64 + plcPoints.add(plcPoint);
  65 + dataLenth++;
  66 + if (dataLenth == maxDataLenth)
  67 + {
  68 + if(!subMqttData(mqttService, plcId, plcPoints))
  69 + {
  70 + plcPoints.clear();
  71 + break;
  72 + }
  73 + System.out.println("plc "+plcId+" 发送数据 "+plcPoints.size()+" 条,等待1s接着发送");
  74 + dataLenth = 1;
  75 + plcPoints.clear();
  76 + plcPoints = new ArrayList<>();
  77 + try {
  78 + Thread.sleep(1000l);
  79 + } catch (InterruptedException e) {
  80 + throw new RuntimeException(e);
  81 + }
  82 + }
  83 + }
  84 +
  85 + if (plcPoints.size() > 0)
  86 + {
  87 + subMqttData(mqttService, plcId, plcPoints);
  88 + System.out.println("plc "+plcId+" 发送数据 "+plcPoints.size()+" 条");
  89 + }
  90 + }
  91 +
  92 + private boolean subMqttData(MqttService mqttService, Integer plcId, List<PlcPoint> plcPoints)
  93 + {
  94 + //通知
  95 + try {
  96 + Map<String, Object> datMap = new Modbus4jRead(plcId).batchRead(plcPoints,true);
  97 + JSONObject mqttSendData = new JSONObject();
  98 + mqttSendData.put(plcId+"", datMap);
  99 + String rdata = JSONObject.toJSONString(mqttSendData);
  100 + mqttService.publish("ADD_POST",rdata);
  101 + } catch (Exception e) {
  102 + if (e instanceof ModbusTransportException )
  103 + {
  104 + logger.error("plc通讯超时:"+plcId);
  105 + ModbusMasterMessage.closeMaster(plcId); // 销毁旧连接
  106 + return false;
  107 + }else
  108 + if(e instanceof MqttException)
  109 + {
  110 + logger.error("mqtt连接异常",e);
  111 + }else
  112 + if (e instanceof ErrorResponseException)
  113 + {
  114 + logger.error("plc返回指令异常",e);
  115 + ModbusMasterMessage.closeMaster(plcId); // 销毁旧连接
  116 + return false;
  117 + }else{
  118 + throw new RuntimeException(e);
  119 + }
  120 + }
  121 + return true;
  122 + }
  123 +}
  1 +package com.zhonglai.luhui.device.modbus.terminal.task;
  2 +
  3 +import java.util.concurrent.Executors;
  4 +import java.util.concurrent.ScheduledExecutorService;
  5 +
  6 +public class ScheduledThreadPool {
  7 + public static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
  8 +
  9 + public static void stopScheduler() {
  10 + if (null != scheduler)
  11 + {
  12 + scheduler.shutdown();
  13 + }
  14 + }
  15 +}
  1 +yuerleApiUrl=https://ly.api.yu2le.com/bigScreen/getYsLocalIp
  2 +webrtc_app=yuerle
  3 +webrtc_host=zlmediakit
  1 +mqtt.broker=tcp://iot.yu2le.com:1883
  2 +mqtt.username=lh_tc_kt
  3 +mqtt.password=lh_tc_kt
  4 +mqtt.clientId=LHZDY974113706494905
  5 +mqtt.subTopic=PUT/+,READ/+,HOST/+
  1 +{
  2 + "plcs": [
  3 + {
  4 + "id": 6,
  5 + "systemName": "测试",
  6 + "protocolType": "TCP",
  7 + "connectConfig": { "host": "192.168.1.82", "port": 2000},
  8 + "points": [
  9 + {"name": "水泵1故障", "system": "sb1gz", "address": "40001.01", "dataType": "bit","access":"r"},
  10 + {"name": "水泵2故障", "system": "sb2gz", "address": "40001.02", "dataType": "bit","access":"r"},
  11 + {"name": "氧锥泵1故障", "system": "yzb1gz", "address": "40001.03", "dataType": "bit","access":"r"},
  12 + {"name": "氧锥泵2故障", "system": "yzb2gz", "address": "40001.04", "dataType": "bit","access":"r"},
  13 + {"name": "氧锥泵3故障", "system": "yzb3gz", "address": "40001.05", "dataType": "bit","access":"r"},
  14 + {"name": "氧锥泵4故障", "system": "yzb4gz", "address": "40001.06", "dataType": "bit","access":"r"},
  15 + {"name": "排污泵故障", "system": "pwb_gz", "address": "40001.07", "dataType": "bit","access":"r"},
  16 + {"name": "排污阀1开不到位", "system": "pwf1kbdw", "address": "40001.09", "dataType": "bit","access":"r"},
  17 + {"name": "溶氧上限报警设定值", "system": "ry_sxsz", "address": "40053-40054", "dataType": "float32","order": "CDAB","access":"rw"},
  18 + {"name": "溶氧下限报警设定值", "system": "ry_xxsz", "address": "40055-40056", "dataType": "float32","order": "CDAB","access":"rw"}
  19 + ]
  20 + },
  21 + {
  22 + "id": 1,
  23 + "systemName": "成鱼系统1",
  24 + "protocolType": "TCP",
  25 + "connectConfig": { "host": "192.168.1.19", "port": 2000},
  26 + "points": [
  27 + {"name": "自动", "system": "zd", "address": "10001", "dataType": "bit"},
  28 + {"name": "远程", "system": "yc", "address": "10002", "dataType": "bit"},
  29 + {"name": "补水泵启动", "system": "bsbqd", "address": "10003", "dataType": "bit"},
  30 + {"name": "水泵1运行", "system": "sb1yx", "address": "10004", "dataType": "bit"},
  31 + {"name": "水泵2运行", "system": "sb2yx", "address": "10005", "dataType": "bit"},
  32 + {"name": "氧锥泵1运行", "system": "yzb1yx", "address": "10006", "dataType": "bit"},
  33 + {"name": "氧锥泵2运行", "system": "yzb2yx", "address": "10007", "dataType": "bit"},
  34 + {"name": "氧锥泵3运行", "system": "yzb3yx", "address": "10008", "dataType": "bit"},
  35 + {"name": "氧锥泵4运行", "system": "yzb4yx", "address": "10009", "dataType": "bit"},
  36 + {"name": "排污泵运行", "system": "pwb", "address": "10010", "dataType": "bit"},
  37 + {"name": "微滤机电源合闸", "system": "wljdyhz", "address": "10011", "dataType": "bit"},
  38 + {"name": "紫外灯电源合闸", "system": "zwd", "address": "10012", "dataType": "bit"},
  39 + {"name": "微滤池液位高", "system": "wlcyw_g", "address": "10013", "dataType": "bit"},
  40 + {"name": "微滤池液位低", "system": "wlcyw_d", "address": "10014", "dataType": "bit"},
  41 + {"name": "蝶阀1开到位", "system": "df1kdw", "address": "10015", "dataType": "bit"},
  42 + {"name": "蝶阀1关到位", "system": "df1gdw", "address": "10016", "dataType": "bit"},
  43 + {"name": "蝶阀2开到位", "system": "df2kdw", "address": "10017", "dataType": "bit"},
  44 + {"name": "蝶阀2关到位", "system": "df2gdw", "address": "10018", "dataType": "bit"},
  45 + {"name": "蝶阀3开到位", "system": "df3kdw", "address": "10019", "dataType": "bit"},
  46 + {"name": "蝶阀3关到位", "system": "df3gdw", "address": "10020", "dataType": "bit"},
  47 + {"name": "蝶阀4开到位", "system": "df4kdw", "address": "10021", "dataType": "bit"},
  48 + {"name": "蝶阀4关到位", "system": "df4gdw", "address": "10022", "dataType": "bit"},
  49 + {"name": "蝶阀5开到位", "system": "df5kdw", "address": "10023", "dataType": "bit"},
  50 + {"name": "蝶阀5关到位", "system": "df5gdw", "address": "10024", "dataType": "bit"},
  51 + {"name": "蝶阀6开到位", "system": "df6kdw", "address": "10025", "dataType": "bit"},
  52 + {"name": "蝶阀6关到位", "system": "df6gdw", "address": "10026", "dataType": "bit"},
  53 + {"name": "蝶阀7开到位", "system": "df7kdw", "address": "10027", "dataType": "bit"},
  54 + {"name": "蝶阀7关到位", "system": "df7gdw", "address": "10028", "dataType": "bit"},
  55 + {"name": "蝶阀8开到位", "system": "df8kdw", "address": "10029", "dataType": "bit"},
  56 + {"name": "蝶阀8关到位", "system": "df8gdw", "address": "10030", "dataType": "bit"},
  57 + {"name": "循环水泵运行", "system": "xhsbyx", "address": "10031", "dataType": "bit"},
  58 + {"name": "系统报警", "system": "xtbj", "address": "00001", "dataType": "bit"},
  59 + {"name": "水泵1故障", "system": "sb1gz", "address": "40001.01", "dataType": "bit"},
  60 + {"name": "水泵2故障", "system": "sb2gz", "address": "40001.02", "dataType": "bit"},
  61 + {"name": "氧锥泵1故障", "system": "yzb1gz", "address": "40001.03", "dataType": "bit"},
  62 + {"name": "氧锥泵2故障", "system": "yzb2gz", "address": "40001.04", "dataType": "bit"},
  63 + {"name": "氧锥泵3故障", "system": "yzb3gz", "address": "40001.05", "dataType": "bit"},
  64 + {"name": "氧锥泵4故障", "system": "yzb4gz", "address": "40001.06", "dataType": "bit"},
  65 + {"name": "排污泵故障", "system": "pwb_gz", "address": "40001.07", "dataType": "bit"},
  66 + {"name": "排污阀1开不到位", "system": "pwf1kbdw", "address": "40001.09", "dataType": "bit"},
  67 + {"name": "排污阀1关不到位", "system": "pwf1gbdw", "address": "40001.10", "dataType": "bit"},
  68 + {"name": "排污阀2开不到位", "system": "pwf2kbdw", "address": "40001.11", "dataType": "bit"},
  69 + {"name": "排污阀2关不到位", "system": "pwf2gbdw", "address": "40001.12", "dataType": "bit"},
  70 + {"name": "排污阀3开不到位", "system": "pwf3kbdw", "address": "40001.13", "dataType": "bit"},
  71 + {"name": "排污阀3关不到位", "system": "pwf3gbdw", "address": "40001.14", "dataType": "bit"},
  72 + {"name": "排污阀4开不到位", "system": "pwf4kbdw", "address": "40001.15", "dataType": "bit"},
  73 + {"name": "排污阀4关不到位", "system": "pwf4gbdw", "address": "40001.16", "dataType": "bit"},
  74 + {"name": "排污阀5开不到位", "system": "pwf5kbdw", "address": "40002.01", "dataType": "bit"},
  75 + {"name": "排污阀5关不到位", "system": "pwf5gbdw", "address": "40002.02", "dataType": "bit"},
  76 + {"name": "排污阀6开不到位", "system": "pwf6kbdw", "address": "40002.03", "dataType": "bit"},
  77 + {"name": "排污阀6关不到位", "system": "pwf6gbdw", "address": "40002.04", "dataType": "bit"},
  78 + {"name": "排污阀7开不到位", "system": "pwf7kbdw", "address": "40002.05", "dataType": "bit"},
  79 + {"name": "排污阀7关不到位", "system": "pwf7gbdw", "address": "40002.06", "dataType": "bit"},
  80 + {"name": "排污阀8开不到位", "system": "pwf8kbdw", "address": "40002.07", "dataType": "bit"},
  81 + {"name": "排污阀8关不到位", "system": "pwf8gbdw", "address": "40002.08", "dataType": "bit"},
  82 + {"name": "补水高液位超时", "system": "bsgywdcs", "address": "40002.11", "dataType": "bit"},
  83 + {"name": "微滤池高液位超时", "system": "wlcgywdcs", "address": "40002.12", "dataType": "bit"},
  84 + {"name": "微滤机电源跳闸", "system": "wljdytz", "address": "40002.13", "dataType": "bit"},
  85 + {"name": "紫外杀菌灯跳闸", "system": "zwsjd_tz", "address": "40002.14", "dataType": "bit"},
  86 + {"name": "溶氧超限报警", "system": "rycxbj", "address": "40002.15", "dataType": "bit"},
  87 + {"name": "微滤池低液位长时间不消失报警", "system": "wlcdywbcsbj", "address": "40002.16", "dataType": "bit"},
  88 + {"name": "溶氧值", "system": "ry", "address": "40003-40004", "dataType": "float32"},
  89 + {"name": "温度值", "system": "wd", "address": "40005-40006", "dataType": "float32"},
  90 + {"name": "电能值", "system": "dn", "address": "40007-40008", "dataType": "float32"},
  91 + {"name": "当前氧锥泵运行台数", "system": "dqyzb", "address": "40009", "dataType": "int16"},
  92 + {"name": "氧锥泵1运行时间", "system": "yzb1_sj", "address": "40011-40012", "dataType": "int32"},
  93 + {"name": "氧锥泵2运行时间", "system": "yzb2_sj", "address": "40013-40014", "dataType": "int32"},
  94 + {"name": "氧锥泵3运行时间", "system": "yzb3_sj", "address": "40015-40016", "dataType": "int32"},
  95 + {"name": "氧锥泵4运行时间", "system": "yzb4_sj", "address": "40017-40018", "dataType": "int32"},
  96 + {"name": "生化池水温", "system": "shcsw", "address": "40019-40020", "dataType": "float32"},
  97 + {"name": "循环水泵故障", "system": "xhsb_gz", "address": "40021.01", "dataType": "bit"},
  98 + {"name": "生化池水温低限报警", "system": "shcsw_dx_bj", "address": "40021.02", "dataType": "bit"},
  99 + {"name": "生化池水温高限报警", "system": "shcsw_gx_bj", "address": "40021.03", "dataType": "bit"},
  100 + {"name": "排污阀1开OR关", "system": "pwf1_or", "address": "40051.01", "dataType": "bit"},
  101 + {"name": "排污阀2开OR关", "system": "pwf2_or", "address": "40051.02", "dataType": "bit"},
  102 + {"name": "排污阀3开OR关", "system": "pwf3_or", "address": "40051.03", "dataType": "bit"},
  103 + {"name": "排污阀4开OR关", "system": "pwf4_or", "address": "40051.04", "dataType": "bit"},
  104 + {"name": "排污阀5开OR关", "system": "pwf5_or", "address": "40051.05", "dataType": "bit"},
  105 + {"name": "排污阀6开OR关", "system": "pwf6_or", "address": "40051.06", "dataType": "bit"},
  106 + {"name": "排污阀7开OR关", "system": "pwf7_or", "address": "40051.07", "dataType": "bit"},
  107 + {"name": "排污阀8开OR关", "system": "pwf8_or", "address": "40051.08", "dataType": "bit"},
  108 + {"name": "水泵1启动", "system": "sb1_qd", "address": "40051.09", "dataType": "bit"},
  109 + {"name": "水泵2启动", "system": "sb2_qd", "address": "40051.10", "dataType": "bit"},
  110 + {"name": "氧锥泵1启动", "system": "yzb1_qd", "address": "40051.11", "dataType": "bit"},
  111 + {"name": "氧锥泵2启动", "system": "yzb2_qd", "address": "40051.12", "dataType": "bit"},
  112 + {"name": "氧锥泵3启动", "system": "yzb3_qd", "address": "40051.13", "dataType": "bit"},
  113 + {"name": "氧锥泵4启动", "system": "yzb4_qd", "address": "40051.14", "dataType": "bit"},
  114 + {"name": "排污泵启动", "system": "pwb_qd", "address": "40051.15", "dataType": "bit"},
  115 + {"name": "水泵1停止", "system": "sb1_tz", "address": "40052.01", "dataType": "bit"},
  116 + {"name": "水泵2停止", "system": "sb2_tz", "address": "40052.02", "dataType": "bit"},
  117 + {"name": "氧锥泵1停止", "system": "yzb1_tz", "address": "40052.03", "dataType": "bit"},
  118 + {"name": "氧锥泵2停止", "system": "yzb2_tz", "address": "40052.04", "dataType": "bit"},
  119 + {"name": "氧锥泵3停止", "system": "yzb3_tz", "address": "40052.05", "dataType": "bit"},
  120 + {"name": "氧锥泵4停止", "system": "yzb4_tz", "address": "40052.06", "dataType": "bit"},
  121 + {"name": "排污泵停止", "system": "pwb_tz", "address": "40052.07", "dataType": "bit"},
  122 + {"name": "清报警", "system": "qbj", "address": "40052.09", "dataType": "bit"},
  123 + {"name": "累计时间清零", "system": "lj_sjql", "address": "40052.10", "dataType": "bit"},
  124 + {"name": "溶氧上限报警设定值", "system": "ry_sxsz", "address": "40053-40054", "dataType": "float32"},
  125 + {"name": "溶氧下限报警设定值", "system": "ry_xxsz", "address": "40055-40056", "dataType": "float32"}
  126 + ]
  127 + },
  128 + {
  129 + "id": 2,
  130 + "systemName": "成鱼系统2",
  131 + "protocolType": "TCP",
  132 + "connectConfig": { "host": "192.168.2.2", "port": 2001},
  133 + "points": [
  134 + {"name": "自动", "system": "zd", "address": "10001", "dataType": "bit"},
  135 + {"name": "远程", "system": "yc", "address": "10002", "dataType": "bit"},
  136 + {"name": "补水泵启动", "system": "bsbqd", "address": "10003", "dataType": "bit"},
  137 + {"name": "水泵1运行", "system": "sb1yx", "address": "10004", "dataType": "bit"},
  138 + {"name": "水泵2运行", "system": "sb2yx", "address": "10005", "dataType": "bit"},
  139 + {"name": "氧锥泵1运行", "system": "yzb1yx", "address": "10006", "dataType": "bit"},
  140 + {"name": "氧锥泵2运行", "system": "yzb2yx", "address": "10007", "dataType": "bit"},
  141 + {"name": "氧锥泵3运行", "system": "yzb3yx", "address": "10008", "dataType": "bit"},
  142 + {"name": "氧锥泵4运行", "system": "yzb4yx", "address": "10009", "dataType": "bit"},
  143 + {"name": "排污泵运行", "system": "pwb", "address": "10010", "dataType": "bit"},
  144 + {"name": "微滤机电源合闸", "system": "wljdyhz", "address": "10011", "dataType": "bit"},
  145 + {"name": "紫外灯电源合闸", "system": "zwd", "address": "10012", "dataType": "bit"},
  146 + {"name": "微滤池液位高", "system": "wlcyw_g", "address": "10013", "dataType": "bit"},
  147 + {"name": "微滤池液位低", "system": "wlcyw_d", "address": "10014", "dataType": "bit"},
  148 + {"name": "蝶阀1开到位", "system": "df1kdw", "address": "10015", "dataType": "bit"},
  149 + {"name": "蝶阀1关到位", "system": "df1gdw", "address": "10016", "dataType": "bit"},
  150 + {"name": "蝶阀2开到位", "system": "df2kdw", "address": "10017", "dataType": "bit"},
  151 + {"name": "蝶阀2关到位", "system": "df2gdw", "address": "10018", "dataType": "bit"},
  152 + {"name": "蝶阀3开到位", "system": "df3kdw", "address": "10019", "dataType": "bit"},
  153 + {"name": "蝶阀3关到位", "system": "df3gdw", "address": "10020", "dataType": "bit"},
  154 + {"name": "蝶阀4开到位", "system": "df4kdw", "address": "10021", "dataType": "bit"},
  155 + {"name": "蝶阀4关到位", "system": "df4gdw", "address": "10022", "dataType": "bit"},
  156 + {"name": "蝶阀5开到位", "system": "df5kdw", "address": "10023", "dataType": "bit"},
  157 + {"name": "蝶阀5关到位", "system": "df5gdw", "address": "10024", "dataType": "bit"},
  158 + {"name": "蝶阀6开到位", "system": "df6kdw", "address": "10025", "dataType": "bit"},
  159 + {"name": "蝶阀6关到位", "system": "df6gdw", "address": "10026", "dataType": "bit"},
  160 + {"name": "蝶阀7开到位", "system": "df7kdw", "address": "10027", "dataType": "bit"},
  161 + {"name": "蝶阀7关到位", "system": "df7gdw", "address": "10028", "dataType": "bit"},
  162 + {"name": "蝶阀8开到位", "system": "df8kdw", "address": "10029", "dataType": "bit"},
  163 + {"name": "蝶阀8关到位", "system": "df8gdw", "address": "10030", "dataType": "bit"},
  164 + {"name": "循环水泵运行", "system": "xhsbyx", "address": "10031", "dataType": "bit"},
  165 + {"name": "系统报警", "system": "xtbj", "address": "00001", "dataType": "bit"},
  166 + {"name": "水泵1故障", "system": "sb1gz", "address": "40001.01", "dataType": "bit"},
  167 + {"name": "水泵2故障", "system": "sb2gz", "address": "40001.02", "dataType": "bit"},
  168 + {"name": "氧锥泵1故障", "system": "yzb1gz", "address": "40001.03", "dataType": "bit"},
  169 + {"name": "氧锥泵2故障", "system": "yzb2gz", "address": "40001.04", "dataType": "bit"},
  170 + {"name": "氧锥泵3故障", "system": "yzb3gz", "address": "40001.05", "dataType": "bit"},
  171 + {"name": "氧锥泵4故障", "system": "yzb4gz", "address": "40001.06", "dataType": "bit"},
  172 + {"name": "排污泵故障", "system": "pwb_gz", "address": "40001.07", "dataType": "bit"},
  173 + {"name": "排污阀1开不到位", "system": "pwf1kbdw", "address": "40001.09", "dataType": "bit"},
  174 + {"name": "排污阀1关不到位", "system": "pwf1gbdw", "address": "40001.10", "dataType": "bit"},
  175 + {"name": "排污阀2开不到位", "system": "pwf2kbdw", "address": "40001.11", "dataType": "bit"},
  176 + {"name": "排污阀2关不到位", "system": "pwf2gbdw", "address": "40001.12", "dataType": "bit"},
  177 + {"name": "排污阀3开不到位", "system": "pwf3kbdw", "address": "40001.13", "dataType": "bit"},
  178 + {"name": "排污阀3关不到位", "system": "pwf3gbdw", "address": "40001.14", "dataType": "bit"},
  179 + {"name": "排污阀4开不到位", "system": "pwf4kbdw", "address": "40001.15", "dataType": "bit"},
  180 + {"name": "排污阀4关不到位", "system": "pwf4gbdw", "address": "40001.16", "dataType": "bit"},
  181 + {"name": "排污阀5开不到位", "system": "pwf5kbdw", "address": "40002.01", "dataType": "bit"},
  182 + {"name": "排污阀5关不到位", "system": "pwf5gbdw", "address": "40002.02", "dataType": "bit"},
  183 + {"name": "排污阀6开不到位", "system": "pwf6kbdw", "address": "40002.03", "dataType": "bit"},
  184 + {"name": "排污阀6关不到位", "system": "pwf6gbdw", "address": "40002.04", "dataType": "bit"},
  185 + {"name": "排污阀7开不到位", "system": "pwf7kbdw", "address": "40002.05", "dataType": "bit"},
  186 + {"name": "排污阀7关不到位", "system": "pwf7gbdw", "address": "40002.06", "dataType": "bit"},
  187 + {"name": "排污阀8开不到位", "system": "pwf8kbdw", "address": "40002.07", "dataType": "bit"},
  188 + {"name": "排污阀8关不到位", "system": "pwf8gbdw", "address": "40002.08", "dataType": "bit"},
  189 + {"name": "补水高液位超时", "system": "bsgywdcs", "address": "40002.11", "dataType": "bit"},
  190 + {"name": "微滤池高液位超时", "system": "wlcgywdcs", "address": "40002.12", "dataType": "bit"},
  191 + {"name": "微滤机电源跳闸", "system": "wljdytz", "address": "40002.13", "dataType": "bit"},
  192 + {"name": "紫外杀菌灯跳闸", "system": "zwsjd_tz", "address": "40002.14", "dataType": "bit"},
  193 + {"name": "溶氧超限报警", "system": "rycxbj", "address": "40002.15", "dataType": "bit"},
  194 + {"name": "微滤池低液位长时间不消失报警", "system": "wlcdywbcsbj", "address": "40002.16", "dataType": "bit"},
  195 + {"name": "溶氧值", "system": "ry", "address": "40003-40004", "dataType": "float32"},
  196 + {"name": "温度值", "system": "wd", "address": "40005-40006", "dataType": "float32"},
  197 + {"name": "电能值", "system": "dn", "address": "40007-40008", "dataType": "float32"},
  198 + {"name": "当前氧锥泵运行台数", "system": "dqyzb", "address": "40009", "dataType": "int16"},
  199 + {"name": "氧锥泵1运行时间", "system": "yzb1_sj", "address": "40011-40012", "dataType": "int32"},
  200 + {"name": "氧锥泵2运行时间", "system": "yzb2_sj", "address": "40013-40014", "dataType": "int32"},
  201 + {"name": "氧锥泵3运行时间", "system": "yzb3_sj", "address": "40015-40016", "dataType": "int32"},
  202 + {"name": "氧锥泵4运行时间", "system": "yzb4_sj", "address": "40017-40018", "dataType": "int32"},
  203 + {"name": "生化池水温", "system": "shcsw", "address": "40019-40020", "dataType": "float32"},
  204 + {"name": "循环水泵故障", "system": "xhsb_gz", "address": "40021.01", "dataType": "bit"},
  205 + {"name": "生化池水温低限报警", "system": "shcsw_dx_bj", "address": "40021.02", "dataType": "bit"},
  206 + {"name": "生化池水温高限报警", "system": "shcsw_gx_bj", "address": "40021.03", "dataType": "bit"},
  207 + {"name": "排污阀1开OR关", "system": "pwf1_or", "address": "40051.01", "dataType": "bit"},
  208 + {"name": "排污阀2开OR关", "system": "pwf2_or", "address": "40051.02", "dataType": "bit"},
  209 + {"name": "排污阀3开OR关", "system": "pwf3_or", "address": "40051.03", "dataType": "bit"},
  210 + {"name": "排污阀4开OR关", "system": "pwf4_or", "address": "40051.04", "dataType": "bit"},
  211 + {"name": "排污阀5开OR关", "system": "pwf5_or", "address": "40051.05", "dataType": "bit"},
  212 + {"name": "排污阀6开OR关", "system": "pwf6_or", "address": "40051.06", "dataType": "bit"},
  213 + {"name": "排污阀7开OR关", "system": "pwf7_or", "address": "40051.07", "dataType": "bit"},
  214 + {"name": "排污阀8开OR关", "system": "pwf8_or", "address": "40051.08", "dataType": "bit"},
  215 + {"name": "水泵1启动", "system": "sb1_qd", "address": "40051.09", "dataType": "bit"},
  216 + {"name": "水泵2启动", "system": "sb2_qd", "address": "40051.10", "dataType": "bit"},
  217 + {"name": "氧锥泵1启动", "system": "yzb1_qd", "address": "40051.11", "dataType": "bit"},
  218 + {"name": "氧锥泵2启动", "system": "yzb2_qd", "address": "40051.12", "dataType": "bit"},
  219 + {"name": "氧锥泵3启动", "system": "yzb3_qd", "address": "40051.13", "dataType": "bit"},
  220 + {"name": "氧锥泵4启动", "system": "yzb4_qd", "address": "40051.14", "dataType": "bit"},
  221 + {"name": "排污泵启动", "system": "pwb_qd", "address": "40051.15", "dataType": "bit"},
  222 + {"name": "水泵1停止", "system": "sb1_tz", "address": "40052.01", "dataType": "bit"},
  223 + {"name": "水泵2停止", "system": "sb2_tz", "address": "40052.02", "dataType": "bit"},
  224 + {"name": "氧锥泵1停止", "system": "yzb1_tz", "address": "40052.03", "dataType": "bit"},
  225 + {"name": "氧锥泵2停止", "system": "yzb2_tz", "address": "40052.04", "dataType": "bit"},
  226 + {"name": "氧锥泵3停止", "system": "yzb3_tz", "address": "40052.05", "dataType": "bit"},
  227 + {"name": "氧锥泵4停止", "system": "yzb4_tz", "address": "40052.06", "dataType": "bit"},
  228 + {"name": "排污泵停止", "system": "pwb_tz", "address": "40052.07", "dataType": "bit"},
  229 + {"name": "清报警", "system": "qbj", "address": "40052.09", "dataType": "bit"},
  230 + {"name": "累计时间清零", "system": "lj_sjql", "address": "40052.10", "dataType": "bit"},
  231 + {"name": "溶氧上限报警设定值", "system": "ry_sxsz", "address": "40053-40054", "dataType": "float32"},
  232 + {"name": "溶氧下限报警设定值", "system": "ry_xxsz", "address": "40055-40056", "dataType": "float32"}
  233 + ]
  234 + },
  235 + {
  236 + "id": 3,
  237 + "systemName": "源水处理区",
  238 + "protocolType": "TCP",
  239 + "connectConfig": { "host": "192.168.2.5", "port": 2004},
  240 + "points": [
  241 + {"name": "自动", "system": "zd", "address": "10001", "dataType": "bit"},
  242 + {"name": "远程", "system": "yc", "address": "10002", "dataType": "bit"},
  243 + {"name": "水源泵1启动", "system": "syp1", "address": "10003", "dataType": "bit"},
  244 + {"name": "水源泵2启动", "system": "syp2", "address": "10004", "dataType": "bit"},
  245 + {"name": "水源泵3启动", "system": "syp3", "address": "10005", "dataType": "bit"},
  246 + {"name": "风机1启动", "system": "fj1", "address": "10006", "dataType": "bit"},
  247 + {"name": "风机2启动", "system": "fj2", "address": "10007", "dataType": "bit"},
  248 + {"name": "紫外灯电源合闸", "system": "zw", "address": "10008", "dataType": "bit"},
  249 + {"name": "生化池高液位", "system": "shg", "address": "10009", "dataType": "bit"},
  250 + {"name": "生化池低液位", "system": "shd", "address": "10010", "dataType": "bit"},
  251 + {"name": "水源泵1故障", "system": "syp1g", "address": "40001.01", "dataType": "bit"},
  252 + {"name": "水源泵2故障", "system": "syp2g", "address": "40001.02", "dataType": "bit"},
  253 + {"name": "水源泵3故障", "system": "syp3g", "address": "40001.03", "dataType": "bit"},
  254 + {"name": "风机1故障", "system": "fj1g", "address": "40001.04", "dataType": "bit"},
  255 + {"name": "风机2故障", "system": "fj2g", "address": "40001.05", "dataType": "bit"},
  256 + {"name": "紫外杀菌等跳闸故障", "system": "zwg", "address": "40001.06", "dataType": "bit"},
  257 + {"name": "电能值", "system": "dn", "address": "40007-40008", "dataType": "float"},
  258 + {"name": "当前水源泵启动台数", "system": "dqsy", "address": "40009", "dataType": "int"},
  259 + {"name": "当前风机运行台数", "system": "dqfj", "address": "40010", "dataType": "int"},
  260 + {"name": "水源泵1启动时间", "system": "syp1sj", "address": "40011-40012", "dataType": "long"},
  261 + {"name": "水源泵2启动时间", "system": "syp2sj", "address": "40013-40014", "dataType": "long"},
  262 + {"name": "水源泵3启动时间", "system": "syp3sj", "address": "40015-40016", "dataType": "long"},
  263 + {"name": "风机1启动时间", "system": "fj1sj", "address": "40017-40018", "dataType": "long"},
  264 + {"name": "风机2启动时间", "system": "fj2sj", "address": "40019-40020", "dataType": "long"},
  265 + {"name": "补水阀1开OR关", "system": "bsf1", "address": "40051.01", "dataType": "bit"},
  266 + {"name": "补水阀2开OR关", "system": "bsf2", "address": "40051.02", "dataType": "bit"},
  267 + {"name": "水源泵1启动", "system": "syp1s", "address": "40051.01", "dataType": "bit"},
  268 + {"name": "水源泵2启动", "system": "syp2s", "address": "40051.02", "dataType": "bit"},
  269 + {"name": "水源泵3启动", "system": "syp3s", "address": "40051.03", "dataType": "bit"},
  270 + {"name": "风机1启动", "system": "fj1s", "address": "40051.04", "dataType": "bit"},
  271 + {"name": "风机2启动", "system": "fj2s", "address": "40051.05", "dataType": "bit"},
  272 + {"name": "水源泵1停止", "system": "syp1t", "address": "40051.09", "dataType": "bit"},
  273 + {"name": "水源泵2停止", "system": "syp2t", "address": "40051.10", "dataType": "bit"},
  274 + {"name": "水源泵3停止", "system": "syp3t", "address": "40051.11", "dataType": "bit"},
  275 + {"name": "风机1停止", "system": "fj1t", "address": "40051.12", "dataType": "bit"},
  276 + {"name": "风机2停止", "system": "fj2t", "address": "40051.13", "dataType": "bit"},
  277 + {"name": "清报警", "system": "qbj", "address": "40052.01", "dataType": "bit"},
  278 + {"name": "累计时间清零", "system": "ljsj", "address": "40052.02", "dataType": "bit"}
  279 + ]
  280 + },
  281 + {
  282 + "id": 4,
  283 + "systemName": "育苗系统",
  284 + "protocolType": "TCP",
  285 + "connectConfig": { "host": "192.168.2.4", "port": 2002},
  286 + "points": [
  287 + {"name": "手动/自动", "system": "sdz", "address": "10001", "dataType": "bit"},
  288 + {"name": "本地/远程", "system": "bdyy", "address": "10002", "dataType": "bit"},
  289 + {"name": "水泵1运行", "system": "sb1", "address": "10004", "dataType": "bit"},
  290 + {"name": "水泵2运行", "system": "sb2", "address": "10005", "dataType": "bit"},
  291 + {"name": "风机1运行", "system": "fj1", "address": "10006", "dataType": "bit"},
  292 + {"name": "风机2运行", "system": "fj2", "address": "10007", "dataType": "bit"},
  293 + {"name": "热源泵1电源合闸", "system": "ryb1", "address": "10008", "dataType": "bit"},
  294 + {"name": "热源泵2电源合闸", "system": "ryb2", "address": "10009", "dataType": "bit"},
  295 + {"name": "微滤机电源合闸", "system": "wlj", "address": "10010", "dataType": "bit"},
  296 + {"name": "紫外灯电源合闸", "system": "zwd", "address": "10011", "dataType": "bit"},
  297 + {"name": "补水池高液位", "system": "bsc", "address": "10012", "dataType": "bit"},
  298 + {"name": "微滤池高液位", "system": "wlq", "address": "10013", "dataType": "bit"},
  299 + {"name": "溶氧超限报警", "system": "rycj", "address": "10015", "dataType": "bit"},
  300 + {"name": "微滤池低液位", "system": "wld", "address": "10015", "dataType": "bit"},
  301 + {"name": "微滤池低液位长时间不消失报警", "system": "wldc", "address": "10016", "dataType": "bit"},
  302 + {"name": "系统报警", "system": "xtbj", "address": "00001", "dataType": "bit"},
  303 +
  304 + {"name": "溶氧值", "system": "ryz", "address": "40003-40004", "dataType": "float32"},
  305 + {"name": "温度值", "system": "wdz", "address": "40005-40006", "dataType": "float32"},
  306 + {"name": "电能值", "system": "dnz", "address": "40007-40008", "dataType": "float32"},
  307 + {"name": "当前风机运行台数", "system": "dqfj", "address": "40009", "dataType": "int32"},
  308 + {"name": "风机1运行时间", "system": "fj1sj", "address": "40011-40012", "dataType": "int64"},
  309 + {"name": "风机2运行时间", "system": "fj2sj", "address": "40013-40014", "dataType": "int64"},
  310 +
  311 + {"name": "水泵1启动", "system": "sb1start", "address": "40051.01", "dataType": "bit"},
  312 + {"name": "水泵2启动", "system": "sb2start", "address": "40051.02", "dataType": "bit"},
  313 + {"name": "风机1启动", "system": "fj1start", "address": "40051.03", "dataType": "bit"},
  314 + {"name": "风机2启动", "system": "fj2start", "address": "40051.04", "dataType": "bit"},
  315 + {"name": "补水泵3启动", "system": "bsp3start", "address": "40051.05", "dataType": "bit"},
  316 + {"name": "水泵1停止", "system": "sb1stop", "address": "40051.09", "dataType": "bit"},
  317 + {"name": "水泵2停止", "system": "sb2stop", "address": "40051.10", "dataType": "bit"},
  318 + {"name": "风机1停止", "system": "fj1stop", "address": "40051.11", "dataType": "bit"},
  319 + {"name": "风机2停止", "system": "fj2stop", "address": "40051.12", "dataType": "bit"},
  320 + {"name": "补水泵3停止", "system": "bsp3stop", "address": "40051.13", "dataType": "bit"},
  321 +
  322 + {"name": "清报警", "system": "qbj", "address": "40052.01", "dataType": "bit"},
  323 + {"name": "累计时间清零", "system": "ljsjql", "address": "40052.02", "dataType": "bit"},
  324 +
  325 + {"name": "溶氧上限报警设定值", "system": "rysjup", "address": "40053-40054", "dataType": "float32"},
  326 + {"name": "溶氧下限报警设定值", "system": "rysjdown", "address": "40055-40056", "dataType": "float32"}
  327 + ]
  328 + },
  329 + {
  330 + "id": 5,
  331 + "systemName": "设备房系统",
  332 + "protocolType": "TCP",
  333 + "connectConfig": { "host": "192.168.2.3", "port": 2003},
  334 + "points": [
  335 + {"name": "自动", "system": "zd", "address": "10001", "dataType": "bit"},
  336 + {"name": "远程", "system": "yc", "address": "10002", "dataType": "bit"},
  337 + {"name": "风机1运行", "system": "fj1", "address": "10003", "dataType": "bit"},
  338 + {"name": "风机2运行", "system": "fj2", "address": "10004", "dataType": "bit"},
  339 + {"name": "风机3运行", "system": "fj3", "address": "10005", "dataType": "bit"},
  340 + {"name": "风机4运行", "system": "fj4", "address": "10006", "dataType": "bit"},
  341 + {"name": "补水泵1运行", "system": "bsb1", "address": "10007", "dataType": "bit"},
  342 + {"name": "补水泵2运行", "system": "bsb2", "address": "10008", "dataType": "bit"},
  343 + {"name": "热泵1电源合闸", "system": "rb1", "address": "10010", "dataType": "bit"},
  344 + {"name": "热泵2电源合闸", "system": "rb2", "address": "10011", "dataType": "bit"},
  345 + {"name": "空压机电源合闸", "system": "kyj", "address": "10012", "dataType": "bit"},
  346 + {"name": "补水阀1开到位", "system": "bsf1", "address": "10013", "dataType": "bit"},
  347 + {"name": "补水阀2开到位", "system": "bsf2", "address": "10014", "dataType": "bit"},
  348 + {"name": "补水阀1关到位", "system": "bsf1g", "address": "10016", "dataType": "bit"},
  349 + {"name": "补水阀2关到位", "system": "bsf2g", "address": "10017", "dataType": "bit"},
  350 + {"name": "补水1高液位", "system": "bsg1", "address": "10019", "dataType": "bit"},
  351 + {"name": "补水2高液位", "system": "bsg2", "address": "10020", "dataType": "bit"},
  352 + {"name": "系统报警", "system": "bj", "address": "00001", "dataType": "bit"},
  353 +
  354 + {"name": "风机1故障", "system": "fj1g", "address": "40001.01", "dataType": "bit"},
  355 + {"name": "风机2故障", "system": "fj2g", "address": "40001.02", "dataType": "bit"},
  356 + {"name": "风机3故障", "system": "fj3g", "address": "40001.03", "dataType": "bit"},
  357 + {"name": "风机4故障", "system": "fj4g", "address": "40001.04", "dataType": "bit"},
  358 + {"name": "补水泵1故障", "system": "bsb1g", "address": "40001.05", "dataType": "bit"},
  359 + {"name": "补水泵2故障", "system": "bsb2g", "address": "40001.06", "dataType": "bit"},
  360 + {"name": "热泵1跳闸故障", "system": "rb1g", "address": "40001.08", "dataType": "bit"},
  361 + {"name": "热泵2跳闸故障", "system": "rb2g", "address": "40001.09", "dataType": "bit"},
  362 + {"name": "补水阀1开不到位", "system": "bsf1b", "address": "40001.10", "dataType": "bit"},
  363 + {"name": "补水阀1关不到位", "system": "bsf1bg", "address": "40001.11", "dataType": "bit"},
  364 + {"name": "补水阀2开不到位", "system": "bsf2b", "address": "40001.12", "dataType": "bit"},
  365 + {"name": "补水阀2关不到位", "system": "bsf2bg", "address": "40001.13", "dataType": "bit"},
  366 + {"name": "空压机跳闸故障", "system": "kyjg", "address": "40001.16", "dataType": "bit"},
  367 +
  368 + {"name": "电能值", "system": "dnz", "address": "40007-40008", "dataType": "float"},
  369 + {"name": "当前风机运行台数", "system": "dqfj", "address": "40009", "dataType": "int"},
  370 + {"name": "风机1运行时间", "system": "fj1t", "address": "40011-40012", "dataType": "long"},
  371 + {"name": "风机2运行时间", "system": "fj2t", "address": "40013-40014", "dataType": "long"},
  372 + {"name": "风机3运行时间", "system": "fj3t", "address": "40015-40016", "dataType": "long"},
  373 + {"name": "风机4运行时间", "system": "fj4t", "address": "40017-40018", "dataType": "long"},
  374 +
  375 + {"name": "补水阀1开OR关", "system": "bsf1c", "address": "40051.01", "dataType": "bit"},
  376 + {"name": "补水阀2开OR关", "system": "bsf2c", "address": "40051.02", "dataType": "bit"},
  377 + {"name": "风机1启动", "system": "fj1s", "address": "40051.09", "dataType": "bit"},
  378 + {"name": "风机2启动", "system": "fj2s", "address": "40051.10", "dataType": "bit"},
  379 + {"name": "风机3启动", "system": "fj3s", "address": "40051.11", "dataType": "bit"},
  380 + {"name": "风机4启动", "system": "fj4s", "address": "40051.12", "dataType": "bit"},
  381 + {"name": "补水泵1启动", "system": "bsb1s", "address": "40051.13", "dataType": "bit"},
  382 + {"name": "补水泵2启动", "system": "bsb2s", "address": "40051.14", "dataType": "bit"},
  383 + {"name": "风机1停止", "system": "fj1p", "address": "40052.01", "dataType": "bit"},
  384 + {"name": "风机2停止", "system": "fj2p", "address": "40052.02", "dataType": "bit"},
  385 + {"name": "风机3停止", "system": "fj3p", "address": "40052.03", "dataType": "bit"},
  386 + {"name": "风机4停止", "system": "fj4p", "address": "40052.04", "dataType": "bit"},
  387 + {"name": "补水泵1停止", "system": "bsb1p", "address": "40052.05", "dataType": "bit"},
  388 + {"name": "补水泵2停止", "system": "bsb2p", "address": "40052.06", "dataType": "bit"},
  389 + {"name": "清报警", "system": "qbj", "address": "40052.09", "dataType": "bit"},
  390 + {"name": "累计时间清零", "system": "ljtq", "address": "40052.10", "dataType": "bit"}
  391 + ]
  392 + }
  393 + ]
  394 +}
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +
  3 +<assembly>
  4 + <id>bin</id>
  5 + <!-- 最终打包成一个用于发布的zip文件 -->
  6 + <formats>
  7 + <format>zip</format>
  8 + </formats>
  9 +
  10 + <!-- Adds dependencies to zip package under lib directory -->
  11 + <dependencySets>
  12 + <dependencySet>
  13 + <!--
  14 + 不使用项目的artifact,第三方jar不要解压,打包进zip文件的lib目录
  15 + -->
  16 + <useProjectArtifact>false</useProjectArtifact>
  17 + <outputDirectory>lib</outputDirectory>
  18 + <unpack>false</unpack>
  19 + </dependencySet>
  20 + </dependencySets>
  21 +
  22 + <fileSets>
  23 + <!-- 把项目相关的说明文件,打包进zip文件的根目录 -->
  24 + <fileSet>
  25 + <directory>${project.basedir}</directory>
  26 + <outputDirectory>/</outputDirectory>
  27 + <includes>
  28 + <include>README*</include>
  29 + <include>LICENSE*</include>
  30 + <include>NOTICE*</include>
  31 + </includes>
  32 + </fileSet>
  33 +
  34 + <!-- 把项目的配置文件,打包进zip文件的config目录 -->
  35 + <fileSet>
  36 + <directory>${project.basedir}\src\main\resources\configs</directory>
  37 + <outputDirectory>../configs</outputDirectory>
  38 + <includes>
  39 + <include>*.properties</include>
  40 + </includes>
  41 + </fileSet>
  42 +
  43 + <!-- 把项目的配置文件,提出来 -->
  44 + <fileSet>
  45 + <directory>${project.basedir}\src\main\resources</directory>
  46 + <outputDirectory>/</outputDirectory>
  47 + <includes>
  48 + <include>*.properties</include>
  49 + <include>*.yml</include>
  50 + </includes>
  51 + </fileSet>
  52 +
  53 + <!-- 把项目的脚本文件目录( src/main/scripts )中的启动脚本文件,打包进zip文件的跟目录 -->
  54 + <fileSet>
  55 + <directory>${project.basedir}\bin</directory>
  56 + <outputDirectory></outputDirectory>
  57 + <includes>
  58 + <include>start.*</include>
  59 + <include>stop.*</include>
  60 + </includes>
  61 + </fileSet>
  62 +
  63 + <!-- 把项目自己编译出来的jar文件,打包进zip文件的根目录 -->
  64 + <fileSet>
  65 + <directory>${project.build.directory}</directory>
  66 + <outputDirectory></outputDirectory>
  67 + <includes>
  68 + <include>*.jar</include>
  69 + </includes>
  70 + </fileSet>
  71 + </fileSets>
  72 +</assembly>
  1 +import com.alibaba.fastjson.JSONObject;
  2 +import com.zhonglai.luhui.device.modbus.terminal.config.InitPlcConfig;
  3 +import com.zhonglai.luhui.device.modbus.terminal.modbus.Modbus4jRead;
  4 +import com.zhonglai.luhui.device.modbus.terminal.modbus.Modbus4jWrite;
  5 +import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.PlcPoint;
  6 +
  7 +import java.util.ArrayList;
  8 +import java.util.Arrays;
  9 +import java.util.List;
  10 +import java.util.Map;
  11 +
  12 +public class TestModbus {
  13 + public static void main(String[] args) throws Exception {
  14 + args = new String[]{"E:\\work\\idea\\Luhui\\lh-modules\\lh-device-modbus-terminal\\src\\main\\resources\\configs\\plcs.json","false","测试", "ry_sxsz=3.16","ry_xxsz=2.1"};
  15 + if (args.length < 2) {
  16 + System.out.println("用法: java -jar modbus-app.jar <plcs.json路径> <点位名1> <点位名2> ...");
  17 + return;
  18 + }
  19 +// testRead(args);
  20 +
  21 + testWrite(args);
  22 + }
  23 +
  24 + private static void testRead(String[] args) throws Exception {
  25 + String jsonPath = args[0];
  26 + boolean zeroBasedAddress = new Boolean(args[1]);
  27 + Integer id = Integer.parseInt(args[2]);
  28 + List<String> pointNames = Arrays.asList(Arrays.copyOfRange(args, 3, args.length));
  29 +
  30 + InitPlcConfig.initPlcConfigFromFile(jsonPath);
  31 +
  32 + List<PlcPoint> plcPoints = new ArrayList<>();
  33 + for (String pointName :pointNames)
  34 + {
  35 + PlcPoint plcPoint = InitPlcConfig.getPlcSystem(id, pointName);
  36 + if ( null != plcPoint)
  37 + {
  38 + plcPoints.add(plcPoint);
  39 + }
  40 +
  41 + }
  42 + Map<String, Object> map = new Modbus4jRead(id).batchRead(plcPoints,zeroBasedAddress);
  43 + System.out.println(JSONObject.toJSONString(map));
  44 + }
  45 + private static void testWrite(String[] args) throws Exception
  46 + {
  47 + String jsonPath = args[0];
  48 + boolean zeroBasedAddress = new Boolean(args[1]);
  49 + Integer id = Integer.parseInt(args[2]);
  50 + List<String> pointNames = Arrays.asList(Arrays.copyOfRange(args, 3, args.length));
  51 +
  52 + InitPlcConfig.initPlcConfigFromFile(jsonPath);
  53 +
  54 + List<PlcPoint> plcPoints = new ArrayList<>();
  55 + for (String pointName :pointNames)
  56 + {
  57 + String[] pointNameArr = pointName.split("=");
  58 + PlcPoint plcPoint = InitPlcConfig.getPlcSystem(id, pointNameArr[0]);
  59 + if ( null != plcPoint)
  60 + {
  61 + plcPoint.setValue(pointNameArr[1]);
  62 + plcPoints.add(plcPoint);
  63 + }
  64 +
  65 + }
  66 + new Modbus4jWrite(id).batchWrite(plcPoints,zeroBasedAddress);
  67 + }
  68 +}
@@ -62,6 +62,27 @@ public class DeviceCommandListenServiceImpl implements DeviceCommandServiceFacto @@ -62,6 +62,27 @@ public class DeviceCommandListenServiceImpl implements DeviceCommandServiceFacto
62 return noticeMessageDomain; 62 return noticeMessageDomain;
63 } 63 }
64 64
  65 + @Override
  66 + public NoticeMessageDto host(String deviceId, JsonObject jsonObject) {
  67 + ParserDeviceHostDto parserDeviceHostDto = DeviceCach.getDeviceHost(deviceId);
  68 +
  69 + Topic topic = new Topic();
  70 + topic.setTopicType("HOST");
  71 + topic.setClientid(deviceId);
  72 + topic.setRoleid(parserDeviceHostDto.getIotProduct().getRole_id()+"");
  73 + topic.setUsername(parserDeviceHostDto.getIotProduct().getMqtt_username());
  74 + topic.setPayloadtype("Json");
  75 + topic.setMessageid(com.ruoyi.common.utils.DateUtils.getNowTimeMilly()+"");
  76 +
  77 + NoticeMessageDto noticeMessageDto = new NoticeMessageDto();
  78 + noticeMessageDto.setTopic(topic);
  79 + noticeMessageDto.setTopicconfig(topicconfig);
  80 +
  81 + noticeMessageDto.setCommd(jsonObject.toString().getBytes());
  82 +
  83 + return noticeMessageDto;
  84 + }
  85 +
65 86
66 private Topic getWriteTopic(String deviceId, IotProduct iotProduct) 87 private Topic getWriteTopic(String deviceId, IotProduct iotProduct)
67 { 88 {
@@ -49,6 +49,27 @@ public class DeviceCommandListenServiceImpl implements DeviceCommandServiceFacto @@ -49,6 +49,27 @@ public class DeviceCommandListenServiceImpl implements DeviceCommandServiceFacto
49 return null; 49 return null;
50 } 50 }
51 51
  52 + @Override
  53 + public NoticeMessageDto host(String deviceId, JsonObject jsonObject) {
  54 + ParserDeviceHostDto parserDeviceHostDto = DeviceCach.getDeviceHost(deviceId);
  55 +
  56 + Topic topic = new Topic();
  57 + topic.setTopicType("HOST");
  58 + topic.setClientid(deviceId);
  59 + topic.setRoleid(parserDeviceHostDto.getIotProduct().getRole_id()+"");
  60 + topic.setUsername(parserDeviceHostDto.getIotProduct().getMqtt_username());
  61 + topic.setPayloadtype("Json");
  62 + topic.setMessageid(DateUtils.getNowTimeMilly()+"");
  63 +
  64 + NoticeMessageDto noticeMessageDto = new NoticeMessageDto();
  65 + noticeMessageDto.setTopic(topic);
  66 + noticeMessageDto.setTopicconfig(topicModel);
  67 +
  68 + noticeMessageDto.setCommd(jsonObject.toString().getBytes());
  69 +
  70 + return noticeMessageDto;
  71 + }
  72 +
52 private Topic getWriteTopic(String deviceId, IotProduct iotProduct) 73 private Topic getWriteTopic(String deviceId, IotProduct iotProduct)
53 { 74 {
54 Topic topic = new Topic(); 75 Topic topic = new Topic();
@@ -55,6 +55,27 @@ public class DeviceCommandListenServiceImpl implements DeviceCommandServiceFacto @@ -55,6 +55,27 @@ public class DeviceCommandListenServiceImpl implements DeviceCommandServiceFacto
55 return null; 55 return null;
56 } 56 }
57 57
  58 + @Override
  59 + public NoticeMessageDto host(String deviceId, JsonObject jsonObject) {
  60 + ParserDeviceHostDto parserDeviceHostDto = DeviceCach.getDeviceHost(deviceId);
  61 +
  62 + Topic topic = new Topic();
  63 + topic.setTopicType("HOST");
  64 + topic.setClientid(deviceId);
  65 + topic.setRoleid(parserDeviceHostDto.getIotProduct().getRole_id()+"");
  66 + topic.setUsername(parserDeviceHostDto.getIotProduct().getMqtt_username());
  67 + topic.setPayloadtype("Json");
  68 + topic.setMessageid(DateUtils.getNowTimeMilly()+"");
  69 +
  70 + NoticeMessageDto noticeMessageDto = new NoticeMessageDto();
  71 + noticeMessageDto.setTopic(topic);
  72 + noticeMessageDto.setTopicconfig(topicModel);
  73 +
  74 + noticeMessageDto.setCommd(jsonObject.toString().getBytes());
  75 +
  76 + return noticeMessageDto;
  77 + }
  78 +
58 private Topic getWriteTopic(String deviceId, IotProduct iotProduct) 79 private Topic getWriteTopic(String deviceId, IotProduct iotProduct)
59 { 80 {
60 Topic topic = new Topic(); 81 Topic topic = new Topic();
@@ -22,5 +22,9 @@ @@ -22,5 +22,9 @@
22 <groupId>com.zhonglai.luhui</groupId> 22 <groupId>com.zhonglai.luhui</groupId>
23 <artifactId>lh-device-protocol-factory</artifactId> 23 <artifactId>lh-device-protocol-factory</artifactId>
24 </dependency> 24 </dependency>
  25 + <dependency>
  26 + <groupId>com.infiniteautomation</groupId>
  27 + <artifactId>modbus4j</artifactId>
  28 + </dependency>
25 </dependencies> 29 </dependencies>
26 </project> 30 </project>
  1 +package com.zhonglai.luhui.device.protocol.modbus.modbus;
  2 +import com.serotonin.modbus4j.ModbusFactory;
  3 +import com.serotonin.modbus4j.ModbusMaster;
  4 +import com.serotonin.modbus4j.exception.ModbusInitException;
  5 +import com.serotonin.modbus4j.ip.IpParameters;
  6 +import com.fasterxml.jackson.databind.ObjectMapper;
  7 +import com.serotonin.modbus4j.msg.*;
  8 +
  9 +import java.io.File;
  10 +import java.util.*;
  11 +
  12 +/**
  13 + * 通用点位解析类
  14 + */
  15 +public class ModbusPointReader {
  16 +
  17 + private ModbusMaster master;
  18 + private List<PointConfig> points;
  19 +
  20 + public ModbusPointReader(String ip, int port, String configPath) throws Exception {
  21 + // 初始化 Modbus Master
  22 + IpParameters params = new IpParameters();
  23 + params.setHost(ip);
  24 + params.setPort(port);
  25 +
  26 + ModbusFactory factory = new ModbusFactory();
  27 + master = factory.createTcpMaster(params, false);
  28 + master.init();
  29 +
  30 + // 加载配置文件(JSON)
  31 + ObjectMapper mapper = new ObjectMapper();
  32 + points = Arrays.asList(mapper.readValue(new File(configPath), PointConfig[].class));
  33 + }
  34 +
  35 + public void readAll() throws Exception {
  36 + for (PointConfig p : points) {
  37 + Object value = readPoint(p);
  38 + System.out.printf("设备: %s | 点位: %s | 值: %s%n",
  39 + p.device, p.name, value);
  40 + }
  41 + }
  42 +
  43 + private Object readPoint(PointConfig p) throws Exception {
  44 + int slaveId = 1; // 您可以根据需要修改,这里使用固定的1
  45 +
  46 + switch (p.type) {
  47 + case "coil":
  48 + ReadCoilsRequest coilsRequest = new ReadCoilsRequest(slaveId, p.address, p.length);
  49 + ReadCoilsResponse coilsResponse = (ReadCoilsResponse) master.send(coilsRequest);
  50 + return coilsResponse.getBooleanData()[0];
  51 +
  52 + case "discreteInput":
  53 + ReadDiscreteInputsRequest discreteRequest = new ReadDiscreteInputsRequest(slaveId, p.address, p.length);
  54 + ReadDiscreteInputsResponse discreteResponse = (ReadDiscreteInputsResponse) master.send(discreteRequest);
  55 + return discreteResponse.getBooleanData()[0];
  56 +
  57 + case "holdingRegister":
  58 + ReadHoldingRegistersRequest holdingRequest = new ReadHoldingRegistersRequest(slaveId, p.address, p.length);
  59 + ReadHoldingRegistersResponse holdingResponse = (ReadHoldingRegistersResponse) master.send(holdingRequest);
  60 + return parseRegister(holdingResponse.getShortData(), p);
  61 +
  62 + case "inputRegister":
  63 + ReadInputRegistersRequest inputRequest = new ReadInputRegistersRequest(slaveId, p.address, p.length);
  64 + ReadInputRegistersResponse inputResponse = (ReadInputRegistersResponse) master.send(inputRequest);
  65 + return parseRegister(inputResponse.getShortData(), p);
  66 +
  67 + default:
  68 + throw new IllegalArgumentException("未知点位类型: " + p.type);
  69 + }
  70 + }
  71 +
  72 + private Object parseRegister(short[] regs, PointConfig p) {
  73 + switch (p.datatype) {
  74 + case "bool":
  75 + return ((regs[0] >> p.bit) & 1) == 1;
  76 + case "int16":
  77 + return regs[0];
  78 + case "int32":
  79 + return (regs[0] << 16) | (regs[1] & 0xFFFF);
  80 + case "float":
  81 + int raw = (regs[0] << 16) | (regs[1] & 0xFFFF);
  82 + return Float.intBitsToFloat(raw);
  83 + case "double":
  84 + long raw64 = ((long) regs[0] << 48) | ((long) regs[1] << 32) | ((long) regs[2] << 16) | (regs[3] & 0xFFFF);
  85 + return Double.longBitsToDouble(raw64);
  86 + default:
  87 + return regs[0];
  88 + }
  89 + }
  90 +
  91 + public void close() {
  92 + if (master != null) master.destroy();
  93 + }
  94 +
  95 + // 使用示例
  96 + public static void main(String[] args) throws Exception {
  97 + ModbusPointReader reader = new ModbusPointReader("192.168.2.11", 2000, "points.json");
  98 + reader.readAll();
  99 + reader.close();
  100 + }
  101 +}
  102 +
  1 +package com.zhonglai.luhui.device.protocol.modbus.modbus;
  2 +
  3 +/**
  4 + *点位配置类
  5 + */
  6 +public class PointConfig {
  7 + public String device;
  8 + public String name;
  9 + public String type;
  10 + public int address;
  11 + public int length;
  12 + public String datatype;
  13 + public int bit = 0;
  14 +}
@@ -3,6 +3,7 @@ package com.zhonglai.luhui.device.protocol.plc004.control; @@ -3,6 +3,7 @@ package com.zhonglai.luhui.device.protocol.plc004.control;
3 import com.google.gson.JsonElement; 3 import com.google.gson.JsonElement;
4 import com.google.gson.JsonObject; 4 import com.google.gson.JsonObject;
5 import com.google.gson.reflect.TypeToken; 5 import com.google.gson.reflect.TypeToken;
  6 +import com.ruoyi.common.utils.DateUtils;
6 import com.ruoyi.common.utils.GsonConstructor; 7 import com.ruoyi.common.utils.GsonConstructor;
7 import com.zhonglai.luhui.device.analysis.comm.factory.Topic; 8 import com.zhonglai.luhui.device.analysis.comm.factory.Topic;
8 import com.zhonglai.luhui.device.domain.IotProduct; 9 import com.zhonglai.luhui.device.domain.IotProduct;
@@ -50,6 +51,27 @@ public class DeviceCommandListenServiceImpl implements DeviceCommandServiceFacto @@ -50,6 +51,27 @@ public class DeviceCommandListenServiceImpl implements DeviceCommandServiceFacto
50 return null; 51 return null;
51 } 52 }
52 53
  54 + @Override
  55 + public NoticeMessageDto host(String deviceId, JsonObject jsonObject) {
  56 + ParserDeviceHostDto parserDeviceHostDto = DeviceCach.getDeviceHost(deviceId);
  57 +
  58 + Topic topic = new Topic();
  59 + topic.setTopicType("HOST");
  60 + topic.setClientid(deviceId);
  61 + topic.setRoleid(parserDeviceHostDto.getIotProduct().getRole_id()+"");
  62 + topic.setUsername(parserDeviceHostDto.getIotProduct().getMqtt_username());
  63 + topic.setPayloadtype("Json");
  64 + topic.setMessageid(DateUtils.getNowTimeMilly()+"");
  65 +
  66 + NoticeMessageDto noticeMessageDto = new NoticeMessageDto();
  67 + noticeMessageDto.setTopic(topic);
  68 + noticeMessageDto.setTopicconfig(topicModel);
  69 +
  70 + noticeMessageDto.setCommd(jsonObject.toString().getBytes());
  71 +
  72 + return noticeMessageDto;
  73 + }
  74 +
53 /** 75 /**
54 * 将嵌套的JsonObject扁平化。 76 * 将嵌套的JsonObject扁平化。
55 * 77 *
@@ -52,6 +52,27 @@ public class DeviceCommandListenServiceImpl implements DeviceCommandServiceFacto @@ -52,6 +52,27 @@ public class DeviceCommandListenServiceImpl implements DeviceCommandServiceFacto
52 return null; 52 return null;
53 } 53 }
54 54
  55 + @Override
  56 + public NoticeMessageDto host(String deviceId, JsonObject jsonObject) {
  57 + ParserDeviceHostDto parserDeviceHostDto = DeviceCach.getDeviceHost(deviceId);
  58 +
  59 + Topic topic = new Topic();
  60 + topic.setTopicType("HOST");
  61 + topic.setClientid(deviceId);
  62 + topic.setRoleid(parserDeviceHostDto.getIotProduct().getRole_id()+"");
  63 + topic.setUsername(parserDeviceHostDto.getIotProduct().getMqtt_username());
  64 + topic.setPayloadtype("Json");
  65 + topic.setMessageid(com.ruoyi.common.utils.DateUtils.getNowTimeMilly()+"");
  66 +
  67 + NoticeMessageDto noticeMessageDto = new NoticeMessageDto();
  68 + noticeMessageDto.setTopic(topic);
  69 + noticeMessageDto.setTopicconfig(topicconfig);
  70 +
  71 + noticeMessageDto.setCommd(jsonObject.toString().getBytes());
  72 +
  73 + return noticeMessageDto;
  74 + }
  75 +
55 76
56 private Topic getWriteTopic(String deviceId, IotProduct iotProduct) 77 private Topic getWriteTopic(String deviceId, IotProduct iotProduct)
57 { 78 {
@@ -146,6 +146,20 @@ public class DeviceCommandListenService implements RocketMQReplyListener<Message @@ -146,6 +146,20 @@ public class DeviceCommandListenService implements RocketMQReplyListener<Message
146 }else { 146 }else {
147 return new Message(MessageCode.DEFAULT_FAIL_CODE,"指令发送失败"); 147 return new Message(MessageCode.DEFAULT_FAIL_CODE,"指令发送失败");
148 } 148 }
  149 + case host:
  150 +
  151 + noticeMessageDomain = deviceCommandServiceFactory.host(deviceCommand.getDeviceId(), jsonObject);
  152 +
  153 + if(null == noticeMessageDomain)
  154 + {
  155 + return new Message(MessageCode.DEFAULT_FAIL_CODE,"该设备不支持主机操作功能");
  156 + }
  157 + if(clienNoticeServiceFactory.sendMessage(noticeMessageDomain))
  158 + {
  159 + return new Message(MessageCode.DEFAULT_SUCCESS_CODE,"指令发送成功");
  160 + }else {
  161 + return new Message(MessageCode.DEFAULT_FAIL_CODE,"指令发送失败");
  162 + }
149 default: 163 default:
150 return new Message(MessageCode.DEFAULT_FAIL_CODE,"指令类型不存在,请联系管理员"); 164 return new Message(MessageCode.DEFAULT_FAIL_CODE,"指令类型不存在,请联系管理员");
151 } 165 }
@@ -11,4 +11,6 @@ public interface DeviceCommandServiceFactory { @@ -11,4 +11,6 @@ public interface DeviceCommandServiceFactory {
11 NoticeMessageDto write(String deviceId, JsonObject jsonObject); 11 NoticeMessageDto write(String deviceId, JsonObject jsonObject);
12 12
13 NoticeMessageDto notice(String deviceId, JsonObject jsonObject); 13 NoticeMessageDto notice(String deviceId, JsonObject jsonObject);
  14 +
  15 + NoticeMessageDto host(String deviceId, JsonObject jsonObject);
14 } 16 }
@@ -41,5 +41,10 @@ public enum CommandType { @@ -41,5 +41,10 @@ public enum CommandType {
41 /** 41 /**
42 * 删除订阅 42 * 删除订阅
43 */ 43 */
44 - delSubscribe 44 + delSubscribe,
  45 +
  46 + /**
  47 + * 主机操作
  48 + */
  49 + host
45 } 50 }
@@ -11,9 +11,12 @@ import com.zhonglai.luhui.device.analysis.comm.dto.thingsmodels.ThingsModelItemB @@ -11,9 +11,12 @@ import com.zhonglai.luhui.device.analysis.comm.dto.thingsmodels.ThingsModelItemB
11 import com.zhonglai.luhui.device.analysis.comm.factory.Topic; 11 import com.zhonglai.luhui.device.analysis.comm.factory.Topic;
12 import com.zhonglai.luhui.device.analysis.comm.util.DateUtils; 12 import com.zhonglai.luhui.device.analysis.comm.util.DateUtils;
13 import com.zhonglai.luhui.device.domain.IotThingsModel; 13 import com.zhonglai.luhui.device.domain.IotThingsModel;
  14 +import com.zhonglai.luhui.device.protocol.factory.control.DeviceCommandListenService;
14 import com.zhonglai.luhui.device.protocol.factory.dto.*; 15 import com.zhonglai.luhui.device.protocol.factory.dto.*;
15 import com.zhonglai.luhui.device.protocol.factory.service.IotThingsModelService; 16 import com.zhonglai.luhui.device.protocol.factory.service.IotThingsModelService;
16 import org.apache.commons.lang3.EnumUtils; 17 import org.apache.commons.lang3.EnumUtils;
  18 +import org.slf4j.Logger;
  19 +import org.slf4j.LoggerFactory;
17 20
18 import java.util.ArrayList; 21 import java.util.ArrayList;
19 import java.util.List; 22 import java.util.List;
@@ -129,7 +132,7 @@ public class DefaultProtocolPurificationFactoryImpl implements ProtocolPurificat @@ -129,7 +132,7 @@ public class DefaultProtocolPurificationFactoryImpl implements ProtocolPurificat
129 if(null != jsonElement && !jsonElement.isJsonNull() ) 132 if(null != jsonElement && !jsonElement.isJsonNull() )
130 { 133 {
131 ThingsModelItemBase thingsModelItemBase = getThingsModelItemBase(thingsModel,jsonElement); 134 ThingsModelItemBase thingsModelItemBase = getThingsModelItemBase(thingsModel,jsonElement);
132 - if(!thingsModelItemBase.checkValue()) 135 + if(null == thingsModelItemBase || !thingsModelItemBase.checkValue())
133 { 136 {
134 continue; 137 continue;
135 } 138 }
@@ -170,16 +173,31 @@ public class DefaultProtocolPurificationFactoryImpl implements ProtocolPurificat @@ -170,16 +173,31 @@ public class DefaultProtocolPurificationFactoryImpl implements ProtocolPurificat
170 return null; 173 return null;
171 } 174 }
172 175
173 - private ThingsModelItemBase getThingsModelItemBase(IotThingsModel thingsModel,JsonElement jsonElement)  
174 - {  
175 - String data_type = thingsModel.getData_type().toUpperCase();  
176 - if(!EnumUtils.isValidEnum(ThingsModelDataTypeEnum.class,data_type))  
177 - {  
178 - data_type = ThingsModelDataTypeEnum.STRING.name(); 176 + private ThingsModelItemBase getThingsModelItemBase(IotThingsModel thingsModel, JsonElement jsonElement) {
  177 + String dataType = thingsModel.getData_type();
  178 + ThingsModelDataTypeEnum thingsModelDataTypeEnum;
  179 +
  180 + try {
  181 + if (dataType == null) {
  182 + thingsModelDataTypeEnum = ThingsModelDataTypeEnum.STRING;
  183 + } else {
  184 + // 统一转大写再校验
  185 + String upperType = dataType.toUpperCase();
  186 + if (EnumUtils.isValidEnum(ThingsModelDataTypeEnum.class, upperType)) {
  187 + thingsModelDataTypeEnum = Enum.valueOf(ThingsModelDataTypeEnum.class, upperType);
  188 + } else {
  189 + thingsModelDataTypeEnum = ThingsModelDataTypeEnum.STRING;
  190 + }
179 } 191 }
180 - return ThingsModelItemBase.newhingsModel(Enum.valueOf(ThingsModelDataTypeEnum.class,data_type),thingsModel, jsonElement); 192 + } catch (IllegalArgumentException | NullPointerException e) {
  193 + // 容错处理,回退到 STRING
  194 + thingsModelDataTypeEnum = ThingsModelDataTypeEnum.STRING;
181 } 195 }
182 196
  197 + return ThingsModelItemBase.newhingsModel(thingsModelDataTypeEnum, thingsModel, jsonElement);
  198 + }
  199 +
  200 +
183 private DeviceSensorData getDeviceSensorData(Integer time,String sensorNumber,Topic topic,IotThingsModel thingsModel,ThingsModelItemBase thingsModelItemBase) 201 private DeviceSensorData getDeviceSensorData(Integer time,String sensorNumber,Topic topic,IotThingsModel thingsModel,ThingsModelItemBase thingsModelItemBase)
184 { 202 {
185 DeviceSensorData sensorData = new DeviceSensorData(); 203 DeviceSensorData sensorData = new DeviceSensorData();
@@ -38,6 +38,7 @@ @@ -38,6 +38,7 @@
38 <module>lh-ssh-service-lesten</module> 38 <module>lh-ssh-service-lesten</module>
39 <module>lh-deviceInfo-sync</module> 39 <module>lh-deviceInfo-sync</module>
40 <module>lh-camera</module> 40 <module>lh-camera</module>
  41 + <module>lh-device-modbus-terminal</module>
41 </modules> 42 </modules>
42 43
43 <properties> 44 <properties>
@@ -390,6 +390,12 @@ @@ -390,6 +390,12 @@
390 <artifactId>lh-jar-ssh-proxy</artifactId> 390 <artifactId>lh-jar-ssh-proxy</artifactId>
391 <version>${ruoyi.version}</version> 391 <version>${ruoyi.version}</version>
392 </dependency> 392 </dependency>
  393 + <!-- mqtt终端插件 -->
  394 + <dependency>
  395 + <groupId>com.zhonglai.luhui</groupId>
  396 + <artifactId>lh-device-mqtt-terminal-jar</artifactId>
  397 + <version>${ruoyi.version}</version>
  398 + </dependency>
393 <!-- 支持data --> 399 <!-- 支持data -->
394 <dependency> 400 <dependency>
395 <groupId>org.projectlombok</groupId> 401 <groupId>org.projectlombok</groupId>
@@ -627,6 +633,24 @@ @@ -627,6 +633,24 @@
627 <artifactId>tess4j</artifactId> 633 <artifactId>tess4j</artifactId>
628 <version>5.4.0</version> 634 <version>5.4.0</version>
629 </dependency> 635 </dependency>
  636 +
  637 + <!-- https://mvnrepository.com/artifact/com.infiniteautomation/modbus4j -->
  638 + <dependency>
  639 + <groupId>com.infiniteautomation</groupId>
  640 + <artifactId>modbus4j</artifactId>
  641 + <version>3.1.0</version>
  642 + </dependency>
  643 + <dependency>
  644 + <groupId>com.fazecast</groupId>
  645 + <artifactId>jSerialComm</artifactId>
  646 + <version>2.11.2</version>
  647 + </dependency>
  648 +
  649 + <dependency>
  650 + <groupId>org.ini4j</groupId>
  651 + <artifactId>ini4j</artifactId>
  652 + <version>0.5.4</version>
  653 + </dependency>
630 </dependencies> 654 </dependencies>
631 655
632 656
1 -# 项目相关配置 jhlt: # 名称 name: zhonglai # 版本 version: 3.8.2 # 版权年份 copyrightYear: 2024 # 实例演示开关 demoEnabled: true # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath) profile: uploadPath # 开发环境配置 server: # 服务器的HTTP端口,默认为8080 port: 8086 servlet: # 应用的访问路径 context-path: / tomcat: # tomcat的URI编码 uri-encoding: UTF-8 # 连接数满后的排队数,默认为100 accept-count: 1000 threads: # tomcat最大线程数,默认为200 max: 800 # Tomcat启动初始化的线程数,默认值10 min-spare: 100 # 日志配置 logging: level: com.ruoyi: debug org.springframework: warn # Spring配置 spring: # 资源信息 messages: # 国际化资源文件路径 basename: i18n/messages profiles: active: druid # 文件上传 servlet: multipart: # 单个文件大小 max-file-size: 10MB # 设置总上传的文件大小 max-request-size: 20MB # 服务模块 devtools: restart: # 热部署开关 enabled: true # web: # resources: # static-locations: classpath:/static/, classpath:/templates/ # token配置 token: # 令牌自定义标识 header: Authorization # 令牌有效期(默认30分钟) expireTime: 31536000 # MyBatis配置 mybatis: # 搜索指定包别名 typeAliasesPackage: com.ruoyi.**.domain # 配置mapper的扫描,找到所有的mapper.xml映射文件 mapperLocations: classpath*:mapper/**/*Mapper.xml # 加载全局的配置文件 configLocation: classpath:mybatis/mybatis-config.xml # PageHelper分页插件 pagehelper: helperDialect: mysql supportMethodsArguments: true params: count=countSql # Swagger配置 swagger: # 是否开启swagger enabled: true # 请求前缀 pathMapping: /api sys: ## // 对于登录login 注册register 验证码captchaImage 允许匿名访问 antMatchers: - /doc.html - /webjars/** - /swagger-ui.html - /v2/api-docs/** - /swagger/api-docs - /token/code - /swagger-resources/** - /login/** - /profile/** chatgpt: token: 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 # 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 timeout: 5000 apiHost: https://api.openai.com/ proxy: isProxy: true host: 127.0.0.1 port: 7890  
  1 +# 项目相关配置 jhlt: # 名称 name: zhonglai # 版本 version: 3.8.2 # 版权年份 copyrightYear: 2024 # 实例演示开关 demoEnabled: true # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath) profile: uploadPath # 开发环境配置 server: # 服务器的HTTP端口,默认为8080 port: 8086 servlet: # 应用的访问路径 context-path: / tomcat: # tomcat的URI编码 uri-encoding: UTF-8 # 连接数满后的排队数,默认为100 accept-count: 1000 threads: # tomcat最大线程数,默认为200 max: 800 # Tomcat启动初始化的线程数,默认值10 min-spare: 100 # 日志配置 logging: level: com.ruoyi: debug org.springframework: warn # Spring配置 spring: # 资源信息 messages: # 国际化资源文件路径 basename: i18n/messages profiles: active: druid # 文件上传 servlet: multipart: # 单个文件大小 max-file-size: 10MB # 设置总上传的文件大小 max-request-size: 20MB # 服务模块 devtools: restart: # 热部署开关 enabled: true # web: # resources: # static-locations: classpath:/static/, classpath:/templates/ # token配置 token: # 令牌自定义标识 header: Authorization # 令牌有效期(默认30分钟) expireTime: 31536000 # MyBatis配置 mybatis: # 搜索指定包别名 typeAliasesPackage: com.ruoyi.**.domain # 配置mapper的扫描,找到所有的mapper.xml映射文件 mapperLocations: classpath*:mapper/**/*Mapper.xml # 加载全局的配置文件 configLocation: classpath:mybatis/mybatis-config.xml # PageHelper分页插件 pagehelper: helperDialect: mysql supportMethodsArguments: true params: count=countSql # Swagger配置 swagger: # 是否开启swagger enabled: true # 请求前缀 pathMapping: /api sys: # 对于登录login 注册register 验证码captchaImage 允许匿名访问 antMatchers: - /doc.html - /webjars/** - /swagger-ui.html - /v2/api-docs/** - /swagger/api-docs - /token/code - /swagger-resources/** - /login/** - /profile/** chatgpt: token: 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 # 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 timeout: 5000 apiHost: https://api.openai.com/ proxy: isProxy: true host: 127.0.0.1 port: 7890