xiuwei il y a 11 mois
commit
fbabbbd461
46 fichiers modifiés avec 3418 ajouts et 0 suppressions
  1. 28 0
      .vscode/launch.json
  2. 116 0
      pom.xml
  3. 33 0
      src/main/java/StartPro2File.java
  4. 41 0
      src/main/java/com/syjy/DataExchangeException.java
  5. 124 0
      src/main/java/com/syjy/DataStorageTask.java
  6. 76 0
      src/main/java/com/syjy/FileUtils.java
  7. 36 0
      src/main/java/com/syjy/GetStaticFileUtils.java
  8. 114 0
      src/main/java/com/syjy/MyReadConfigFile.java
  9. 132 0
      src/main/java/com/syjy/PointDataCalculator.java
  10. 116 0
      src/main/java/com/syjy/ReadConfigFile.java
  11. 35 0
      src/main/java/com/syjy/TunnelBuilder.java
  12. 91 0
      src/main/java/com/syjy/TunnelLoggerFactory.java
  13. 25 0
      src/main/java/com/syjy/WindData.java
  14. 92 0
      src/main/java/com/syjy/WriteDataCSVLoggerFactory.java
  15. 250 0
      src/main/java/com/syjy/container/ProtocolDataContainer.java
  16. 197 0
      src/main/java/com/syjy/container/ProtocolTunnelContainer.java
  17. 192 0
      src/main/java/com/syjy/container/RecurringTaskContainer.java
  18. 44 0
      src/main/java/com/syjy/http/HttpRequestHandler.java
  19. 39 0
      src/main/java/com/syjy/http/HttpServer.java
  20. 20 0
      src/main/java/com/syjy/http/HttpServerInitializer.java
  21. 48 0
      src/main/java/com/syjy/http/RequestController.java
  22. 83 0
      src/main/java/com/syjy/http/RequestControllerManager.java
  23. 42 0
      src/main/java/com/syjy/http/RequestHandler.java
  24. 16 0
      src/main/java/com/syjy/http/RequestHandlerFunction.java
  25. 51 0
      src/main/java/com/syjy/tunnelinfo/BaseTunnelInfo.java
  26. 59 0
      src/main/java/com/syjy/tunnelinfo/DataPoint.java
  27. 136 0
      src/main/java/com/syjy/tunnelinfo/TunnelStatus.java
  28. 80 0
      src/main/java/com/syjy/tunnelinfo/TunnelType.java
  29. 24 0
      src/main/java/com/syjy/tunnelinfo/gathertunnelinfo/CalculatorTunnelInfo.java
  30. 75 0
      src/main/java/com/syjy/tunnelinfo/gathertunnelinfo/GatherModbusRtuTunnelInfo.java
  31. 89 0
      src/main/java/com/syjy/tunnelinfo/gathertunnelinfo/GatherModbusTcpTunnelInfo.java
  32. 134 0
      src/main/java/com/syjy/tunnelworker/BaseProtocolTunnel.java
  33. 22 0
      src/main/java/com/syjy/tunnelworker/gathers/DataGatherInterface.java
  34. 200 0
      src/main/java/com/syjy/tunnelworker/gathers/iml/AbstractModbusDataGather.java
  35. 101 0
      src/main/java/com/syjy/tunnelworker/gathers/iml/ModbusRtuDataGather.java
  36. 118 0
      src/main/java/com/syjy/tunnelworker/gathers/iml/ModbusTcpDataGather.java
  37. 11 0
      src/main/java/com/syjy/tunnelworker/package-info.java
  38. 77 0
      src/main/java/com/syjy/tunnelworker/workassist/SingleThreadPoolExecutorUtil.java
  39. 36 0
      src/main/java/com/syjy/tunnelworker/workassist/TunnelBuilder.java
  40. BIN
      src/main/resources/com/syjy/favicon.ico
  41. 163 0
      src/main/resources/com/syjy/master.html
  42. 40 0
      src/main/resources/logback.xml
  43. BIN
      src/main/resources/pointconfig-example.xls
  44. BIN
      src/main/resources/pointconfig.xls
  45. BIN
      src/main/resources/pointconfig111.xls
  46. 12 0
      src/test/java/Test.java

+ 28 - 0
.vscode/launch.json

@@ -0,0 +1,28 @@
+{
+    // 使用 IntelliSense 了解相关属性。 
+    // 悬停以查看现有属性的描述。
+    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "type": "java",
+            "name": "Current File",
+            "request": "launch",
+            "mainClass": "${file}"
+        },
+        {
+            "type": "java",
+            "name": "StartPro2File",
+            "request": "launch",
+            "mainClass": "StartPro2File",
+            "projectName": "windCollect"
+        },
+        {
+            "type": "java",
+            "name": "Test",
+            "request": "launch",
+            "mainClass": "Test",
+            "projectName": "windCollect"
+        }
+    ]
+}

+ 116 - 0
pom.xml

@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>com.syjy</groupId>
+    <artifactId>windCollect</artifactId>
+    <version>1.9</version>
+    <description>
+    </description>
+    <repositories>
+        <repository>
+            <id>syjy-repos</id>
+            <name>syjy Repository</name>
+            <url>http://49.4.68.219:8888/nexus/content/groups/public/</url>
+            <releases>
+                <enabled>true</enabled>
+            </releases>
+            <snapshots>
+                <enabled>true</enabled>
+            </snapshots>
+        </repository>
+    </repositories>
+
+    <properties>
+        <java.version>1.8</java.version>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+    </properties>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-shade-plugin</artifactId>
+                <version>2.4.3</version>
+                <executions>
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>shade</goal>
+                        </goals>
+                        <configuration>
+                            <createDependencyReducedPom>false</createDependencyReducedPom>
+                            <transformers>
+                                <transformer
+                                        implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+                                    <mainClass>StartPro2File</mainClass>
+                                </transformer>
+                            </transformers>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+    <dependencies>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-poi</artifactId>
+            <version>4.6.11</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.2.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml</artifactId>
+            <version>3.17</version>
+        </dependency>
+        <dependency>
+            <groupId>wei.yigulu</groupId>
+            <artifactId>protocol-all</artifactId>
+            <version>2.3.16</version>
+        </dependency>
+        <dependency>
+            <groupId>com.googlecode.aviator</groupId>
+            <artifactId>aviator</artifactId>
+            <version>5.0.0</version>
+        </dependency>
+        <!-- 处理excel和上面功能是一样的-->
+        <dependency>
+            <groupId>net.sourceforge.jexcelapi</groupId>
+            <artifactId>jxl</artifactId>
+            <version>2.6.10</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+            <version>2.6</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+            <version>1.11</version>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>1.16.20</version>
+        </dependency>
+
+
+    </dependencies>
+</project>

+ 33 - 0
src/main/java/StartPro2File.java

@@ -0,0 +1,33 @@
+import java.util.List;
+
+import com.syjy.FileUtils;
+import com.syjy.MyReadConfigFile;
+import com.syjy.TunnelBuilder;
+import com.syjy.container.ProtocolDataContainer;
+import com.syjy.container.RecurringTaskContainer;
+import com.syjy.http.HttpServer;
+import com.syjy.tunnelinfo.BaseTunnelInfo;
+
+/**
+ * @author: xiuwei
+ * @version:
+ */
+public class StartPro2File {
+
+    public static void main(String[] args) throws Exception {
+
+        List<BaseTunnelInfo> tunnels = MyReadConfigFile.readConfigFile();
+        for (BaseTunnelInfo tunnelInfo : tunnels) {
+            System.out.println(tunnelInfo.getClass().getSimpleName());
+            TunnelBuilder.buildTunnel(tunnelInfo).startTunnel();
+        }
+        RecurringTaskContainer.getInstance().addRecurringTask(10, "打印实时数据入文件", () -> {
+            FileUtils.write2File(FileUtils.getResourcesPath(), "realTimeData.log", ProtocolDataContainer.getInstance().toStringFormatted());
+            return null;
+        });
+
+        HttpServer httpServer = new HttpServer(8999);
+        httpServer.start();
+
+    }
+}

+ 41 - 0
src/main/java/com/syjy/DataExchangeException.java

@@ -0,0 +1,41 @@
+package com.syjy;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * dataexchange 模块中的异常
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class DataExchangeException extends Exception {
+
+
+	/**
+	 * 构造方法
+	 *
+	 * @param code 异常码
+	 * @param msg  异常信息
+	 */
+	public DataExchangeException(Integer code, String msg) {
+		super(msg);
+		this.code = code;
+	}
+
+	/**
+	 * 异常码
+	 */
+	Integer code;
+
+
+
+	/**
+	 * 异常的具体信息
+	 */
+	String localMsg;
+
+
+}

+ 124 - 0
src/main/java/com/syjy/DataStorageTask.java

@@ -0,0 +1,124 @@
+package com.syjy;
+
+import cn.hutool.core.lang.Console;
+import com.syjy.container.ProtocolDataContainer;
+import com.syjy.tunnelinfo.BaseTunnelInfo;
+import com.syjy.tunnelinfo.DataPoint;
+import lombok.Data;
+import org.slf4j.Logger;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+
+/**
+ * 入库存表任务
+ */
+public class DataStorageTask {
+
+
+
+    private DataStorageTask() {
+    }
+
+    /**
+     * 获取单例实例
+     *
+     * @return the instance
+     */
+    public static final DataStorageTask getInstance() {
+        return DataStorageTask.LazyHolder.INSTANCE;
+    }
+
+    private static class LazyHolder {
+        private static final DataStorageTask INSTANCE = new DataStorageTask();
+    }
+
+    /**
+     * 线程池
+     */
+    ScheduledExecutorService executor = Executors.newScheduledThreadPool(4);
+
+
+    private  static  final ProtocolDataContainer instance = ProtocolDataContainer.getInstance();
+
+    /**
+     * 通道池
+     */
+    List<WindDataRecorder> recorders = new ArrayList<>();
+
+    public void addRecorder(BaseTunnelInfo tunnelInfo) {
+        Map<String,List<DataPoint>> eqPoints=new HashMap<>();
+        String eqName="";
+        for(DataPoint d:tunnelInfo.getDataPoints()){
+            if(!d.getDescribe().contains(":")){
+                throw new RuntimeException("点位描述必须为 “设备名:点释义”");
+            }
+            eqName= d.getDescribe().substring(0,d.getDescribe().indexOf(":"));
+            if(eqPoints.containsKey(eqName)){
+                eqPoints.get(eqName).add(d);
+            }else{
+                List<DataPoint> ds=new ArrayList<>();
+                ds.add(d);
+                eqPoints.put(eqName,ds);
+            }
+        }
+        for(Map.Entry<String,List<DataPoint>> e :eqPoints.entrySet()){
+            WindDataRecorder windDataRecorder = new WindDataRecorder(e.getValue(),e.getKey());
+            recorders.add(windDataRecorder);
+            executor.scheduleAtFixedRate(() -> {
+                write2File(windDataRecorder);
+            }, 0, 1, TimeUnit.SECONDS);
+        }
+
+    }
+
+
+    private void write2File(WindDataRecorder windDataRecorder) {
+        try {
+            windDataRecorder.getWriter().info(instance.getNumber(windDataRecorder.getWsPoint()).toString()+","+instance.getNumber(windDataRecorder.getWdPoint()).toString());
+        }catch (Exception e){
+            Console.error(e);
+        }
+    }
+
+
+    @Data
+    static class WindDataRecorder {
+
+        Integer wsPoint;
+
+        Integer wdPoint;
+
+        Logger writer;
+
+
+
+        /**
+         * 构造方法
+         * @param
+         */
+        public WindDataRecorder(List<DataPoint> dataPoints,String eqName) {
+            for(DataPoint d:dataPoints){
+                if(d.getDescribe().contains("风速")){
+                    this.wsPoint=d.getId();
+                    instance.putDescribe(d.getId(),eqName+":风速");
+                }else if(d.getDescribe().contains("风向")){
+                    this.wdPoint=d.getId();
+                    instance.putDescribe(d.getId(),eqName+":风向");
+                }
+            }
+           this.writer= WriteDataCSVLoggerFactory.getLogger(eqName);
+            writer.info("风速,风向");
+        }
+
+
+    }
+
+
+}

+ 76 - 0
src/main/java/com/syjy/FileUtils.java

@@ -0,0 +1,76 @@
+package com.syjy;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.UnsupportedEncodingException;
+
+/**
+ * 文件的操作
+ *
+ * @author: xiuwei
+ * @version:
+ */
+@Slf4j
+public class FileUtils {
+
+
+	public static String getResourcesPath() {
+		String path = FileUtils.class.getProtectionDomain().getCodeSource().getLocation().getPath();
+		try {
+			path = java.net.URLDecoder.decode(path, "UTF-8");
+		} catch (UnsupportedEncodingException e) {
+			e.printStackTrace();
+		}
+		if(path.contains(".jar!/")){
+			path=path.substring(0,path.indexOf(".jar!/")+3);
+		}
+		File file = new File(path);
+		if (file.isDirectory()) {
+			return file.getAbsolutePath();
+		} else {
+			return file.getParent().replace("file:\\","").replace("file:","");
+		}
+	}
+
+
+	public static String getResourcesPath(Class clazz) {
+		String path = clazz.getProtectionDomain().getCodeSource().getLocation().getPath();
+		try {
+			path = java.net.URLDecoder.decode(path, "UTF-8");
+		} catch (UnsupportedEncodingException e) {
+			e.printStackTrace();
+		}
+		if(path.contains(".jar!/")){
+			path=path.substring(0,path.indexOf(".jar!/")+3);
+		}
+		File file = new File(path);
+		if (file.isDirectory()) {
+			return file.getAbsolutePath();
+		} else {
+			return file.getParent().replace("file:\\","").replace("file:","");
+		}
+	}
+
+
+	public static void write2File(String path, String fileName, String context) throws IOException {
+		File directory = new File(path);
+		if (!directory.exists()) {
+			directory.mkdirs();
+		}
+		File file = new File(path + "/" + fileName);
+		if (!file.exists()) {
+			try {
+				file.createNewFile();
+			} catch (IOException e) {
+				e.printStackTrace();
+			}
+		}
+		PrintStream stream = new PrintStream(file);
+		stream.print(context);
+		stream.flush();
+		stream.close();
+	}
+}

+ 36 - 0
src/main/java/com/syjy/GetStaticFileUtils.java

@@ -0,0 +1,36 @@
+package com.syjy;
+
+import io.netty.util.internal.StringUtil;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+public class GetStaticFileUtils {
+    public static String getFileContext(String fileName) {
+        StringBuilder sb = new StringBuilder();
+        InputStream resourceAsStream = GetStaticFileUtils.class.getResourceAsStream(fileName);
+        if (resourceAsStream == null) {
+            return sb.toString();
+        }
+        try {
+            BufferedReader br = new BufferedReader(
+                    new InputStreamReader(resourceAsStream, "UTF-8"));
+            for (String line = br.readLine(); line != null; line = br.readLine()) {
+                if (!StringUtil.isNullOrEmpty(line)) {
+                    sb.append(line);
+                    sb.append("\r\n");
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return sb.toString();
+    }
+
+    public static InputStream getFileStream(String fileName) {
+        return GetStaticFileUtils.class.getResourceAsStream(fileName);
+    }
+
+
+}

+ 114 - 0
src/main/java/com/syjy/MyReadConfigFile.java

@@ -0,0 +1,114 @@
+package com.syjy;
+
+import com.syjy.tunnelinfo.BaseTunnelInfo;
+import com.syjy.tunnelinfo.DataPoint;
+import com.syjy.tunnelinfo.gathertunnelinfo.*;
+import jxl.Cell;
+import jxl.Sheet;
+import jxl.Workbook;
+import jxl.read.biff.BiffException;
+import org.apache.commons.lang3.StringUtils;
+import wei.yigulu.modbus.domain.datatype.ModbusDataTypeEnum;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 读取配置文件
+ *
+ * @author: xiuwei
+ * @version:
+ */
+public class MyReadConfigFile {
+
+    public static List<BaseTunnelInfo> readConfigFile() throws IOException, BiffException {
+        List<BaseTunnelInfo> tunnelInfos = new ArrayList<>();
+        File configFile = new File(FileUtils.getResourcesPath(MyReadConfigFile.class) + "/pointconfig.xls");
+        InputStream is = Files.newInputStream(configFile.toPath());
+        Workbook wb = Workbook.getWorkbook(is);
+        Sheet[] sheets = wb.getSheets();
+        String cellValue;
+        BaseTunnelInfo tunnelInfo;
+        List<DataPoint> dataPoints;
+        DataPoint dataPoint;
+        ModbusDataTypeEnum dataType;
+        int sheetI = 1;
+        for (Sheet sheet : sheets) {
+            try {
+                cellValue = getCellContext(sheet.getCell(1, 0));
+                switch (cellValue) {
+                    case "ModbusTCPM":
+                        tunnelInfo = new GatherModbusTcpTunnelInfo();
+                        break;
+                    case "ModbusRTUM":
+                        tunnelInfo = new GatherModbusRtuTunnelInfo();
+                        break;
+                    case "Calculator":
+                        tunnelInfo = new CalculatorTunnelInfo();
+                        break;
+                    default:
+                        throw new DataExchangeException(4002, "通道类型异常");
+                }
+                tunnelInfo.setId(sheetI + "");
+                tunnelInfo.setTunnelName(sheet.getName() + sheetI);
+                tunnelInfo.setByRow(sheet.getRow(2));
+                tunnelInfo.setRefreshInterval(Integer.parseInt(getCellContext(sheet.getCell(3, 0))));
+                dataPoints = new ArrayList<>();
+                for (int i = 4; i < sheet.getRows(); i++) {
+                    dataPoint = new DataPoint();
+                    try {
+                        dataPoint.setId(Integer.parseInt(getCellContext(sheet.getCell(0, i))));
+                    } catch (DataExchangeException e) {
+                        continue;
+                    }
+                    if ("Calculator".equals(getCellContext(sheet.getCell(1, 0)))) {
+                        dataPoint.setFormula(getCellContext(sheet.getCell(1, i)));
+                    } else {
+                        dataPoint.setProtocolPoint(Integer.parseInt(getCellContext(sheet.getCell(1, i))));
+                    }
+                    cellValue = getCellContext(sheet.getCell(2, i));
+                    if ("遥信".equals(cellValue)) {
+                        dataType = ModbusDataTypeEnum.A16;
+                    } else if ("遥测".equals(cellValue)) {
+                        dataType = ModbusDataTypeEnum.ABCD;
+                    } else {
+                        dataType = ModbusDataTypeEnum.valueOf(cellValue);
+                    }
+                    dataPoint.setDataType(dataType);
+                    dataPoint.setMag(Double.parseDouble(getCellContext(sheet.getCell(3, i))));
+                    dataPoint.setDescribe(sheet.getCell(4, i).getContents());
+                    if (sheet.getRow(i).length > 5 && sheet.getCell(5, i) != null && !"".equals(sheet.getCell(5, i).getContents())) {
+                        dataPoint.setSlaveId(Integer.parseInt(getCellContext(sheet.getCell(5, i))));
+                    }
+                    dataPoints.add(dataPoint);
+                }
+                tunnelInfo.setDataPoints(dataPoints);
+                DataStorageTask.getInstance().addRecorder(tunnelInfo);
+                tunnelInfos.add(tunnelInfo);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+            sheetI++;
+        }
+        return tunnelInfos;
+    }
+
+    public static String getCellContext(Cell cell) throws DataExchangeException {
+        if (cell == null) {
+            throw new DataExchangeException(4001, "配置文件缺少配置内容," + cell.getRow() + "行" + cell.getColumn() + "列");
+        }
+        String val = cell.getContents();
+        if (StringUtils.isNoneEmpty(val)) {
+            return val;
+        } else {
+            throw new DataExchangeException(4001, "配置文件缺少配置内容," + cell.getRow() + "行" + cell.getColumn() + "列");
+        }
+    }
+
+
+}

+ 132 - 0
src/main/java/com/syjy/PointDataCalculator.java

@@ -0,0 +1,132 @@
+package com.syjy;
+
+import com.googlecode.aviator.AviatorEvaluator;
+import com.googlecode.aviator.Expression;
+import com.googlecode.aviator.Options;
+import com.googlecode.aviator.exception.ExpressionSyntaxErrorException;
+import com.syjy.container.ProtocolDataContainer;
+import com.syjy.tunnelinfo.DataPoint;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import wei.yigulu.modbus.domain.datatype.ModbusDataTypeEnum;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * 点位数据计算器
+ * 公式中使用   P_点位_P  来划分点位
+ *
+ * @author: xiuwei
+ * @version:
+ */
+@Slf4j
+public class PointDataCalculator  {
+
+  private static final Pattern expr = Pattern.compile("(?<=P_).*?(?=_P)");
+
+  static {
+    AviatorEvaluator.setOption(Options.ALWAYS_PARSE_FLOATING_POINT_NUMBER_INTO_DECIMAL, true);
+  }
+
+
+  /**
+   * 数据类型标识  0 :遥信  ; 1:遥测
+   */
+  @Getter
+  private int dataTypeFlag;
+
+  /**
+   * 计算表达式
+   */
+  Expression compiledExp;
+  /**
+   * 计算所需参数
+   */
+  private Map<String, Integer> calculatingParameters;
+  /**
+   * 计算所需公式
+   */
+  private String formula;
+
+
+  @Getter
+  private final  Integer id;
+
+  private ProtocolDataContainer protocolDataContainer = ProtocolDataContainer.getInstance();
+
+  public PointDataCalculator(DataPoint dataPoint) {
+    this.id=dataPoint.getId();
+    if(StringUtils.isNoneEmpty(dataPoint.getFormula())){
+      this.formula=dataPoint.getFormula();
+    }
+    if(dataPoint.getDataType()==ModbusDataTypeEnum.A16){
+      dataTypeFlag =0;
+    }else{
+      dataTypeFlag =1;
+    }
+  }
+
+
+  /**
+   * 计算
+   * 对公式进行计算
+   *
+   * @return V 计算完成的数据
+   * @throws DataExchangeException 数据交换异常
+   */
+  public Object calculate() throws DataExchangeException {
+    Map<String, Object> values = new HashMap<>();
+    for (Map.Entry<String, Integer> e : getCalculatingParameters().entrySet()) {
+      values.put(e.getKey(), protocolDataContainer.getData(e.getValue()));
+    }
+    try {
+      return getOrInitCompiledExp().execute(values);
+    } catch (ArithmeticException e) {
+      log.error("除数为0,计算公式为:{},请检查数据池内是否有该点位是否有数据", this.formula);
+      throw new DataExchangeException(10020,"除数为0,计算公式为:{},请检查数据池内是否有该点位是否有数据");
+    } catch (Exception e) {
+      throw new DataExchangeException(10021, "计算器计算数据时发生异常,公式为:" + this.formula);
+    }
+  }
+
+  /**
+   * 对公式进行初始化  如果公式解析失败将会抛出异常
+   *
+   * @return Expression  解析完成的表达式
+   * @throws DataExchangeException 表达式解析失败时的异常
+   */
+  private Expression getOrInitCompiledExp() throws DataExchangeException {
+    if (this.compiledExp == null) {
+      try {
+        this.compiledExp = AviatorEvaluator.compile(formula);
+      } catch (ExpressionSyntaxErrorException e) {
+        log.error("公式解析失败,公式不可用", e);
+        throw new DataExchangeException(50001, "公式解析失败,公式不可用");
+      }
+    }
+    return this.compiledExp;
+  }
+
+
+  /**
+   * 对计算所需的参数进行整理
+   *
+   * @return P_(点位)_P  替换成真实的数据   字段--数值
+   */
+  public Map<String, Integer> getCalculatingParameters() {
+    if (this.calculatingParameters == null) {
+      this.calculatingParameters = new HashMap<>();
+      Matcher matcher = expr.matcher(this.formula);
+      while (matcher.find()) {
+        String s = matcher.group();
+        this.calculatingParameters.put("P_" + s + "_P", Integer.parseInt(s));
+      }
+    }
+    return this.calculatingParameters;
+  }
+
+}

+ 116 - 0
src/main/java/com/syjy/ReadConfigFile.java

@@ -0,0 +1,116 @@
+package com.syjy;
+
+import com.syjy.tunnelinfo.BaseTunnelInfo;
+import com.syjy.tunnelinfo.DataPoint;
+import com.syjy.tunnelinfo.gathertunnelinfo.CalculatorTunnelInfo;
+import com.syjy.tunnelinfo.gathertunnelinfo.GatherModbusRtuTunnelInfo;
+import com.syjy.tunnelinfo.gathertunnelinfo.GatherModbusTcpTunnelInfo;
+import jxl.Cell;
+import jxl.Sheet;
+import jxl.Workbook;
+import jxl.read.biff.BiffException;
+import org.apache.commons.lang3.StringUtils;
+import wei.yigulu.modbus.domain.datatype.ModbusDataTypeEnum;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 读取配置表格
+ *
+ * @author: xiuwei
+ * @version:
+ */
+public class ReadConfigFile {
+
+
+	public static List<BaseTunnelInfo> readConfigFile() throws IOException, BiffException {
+		List<BaseTunnelInfo> tunnelInfos = new ArrayList<>();
+		File configFile = new File(FileUtils.getResourcesPath() + "/pointconfig.xls");
+		InputStream is = new FileInputStream(configFile);
+		Workbook wb = Workbook.getWorkbook(is);
+		Sheet[] sheets = wb.getSheets();
+		String cellValue;
+		BaseTunnelInfo tunnelInfo;
+		List<DataPoint> dataPoints;
+		DataPoint dataPoint;
+		ModbusDataTypeEnum dataType;
+		int sheetI = 1;
+		for (Sheet sheet : sheets) {
+			try {
+				cellValue = getCellContext(sheet.getCell(1, 0));
+				switch (cellValue) {
+					case "ModbusTCPM":
+						tunnelInfo = new GatherModbusTcpTunnelInfo();
+						break;
+					case "ModbusRTUM":
+						tunnelInfo = new GatherModbusRtuTunnelInfo();
+						break;
+
+					case "Calculator" :
+						tunnelInfo=new CalculatorTunnelInfo();break;
+					default:
+						throw new DataExchangeException(4002, "通道类型异常");
+				}
+				tunnelInfo.setId(sheetI + "");
+				tunnelInfo.setTunnelName(sheet.getName() + sheetI);
+				tunnelInfo.setByRow(sheet.getRow(2));
+				tunnelInfo.setRefreshInterval(Integer.parseInt(getCellContext(sheet.getCell(3, 0))));
+				dataPoints = new ArrayList<>();
+				for (int i = 4; i < sheet.getRows(); i++) {
+					dataPoint = new DataPoint();
+					try {
+						dataPoint.setId(Integer.parseInt(getCellContext(sheet.getCell(0, i))));
+					}catch (DataExchangeException e){
+						continue;
+					}
+					if("Calculator".equals(getCellContext(sheet.getCell(1, 0)))){
+						dataPoint.setFormula(getCellContext(sheet.getCell(1, i)));
+					}else {
+						dataPoint.setProtocolPoint(Integer.parseInt(getCellContext(sheet.getCell(1, i))));
+					}
+					cellValue = getCellContext(sheet.getCell(2, i));
+					if ("遥信".equals(cellValue)) {
+						dataType = ModbusDataTypeEnum.A16;
+					} else if ("遥测".equals(cellValue)) {
+						dataType = ModbusDataTypeEnum.ABCD;
+					} else {
+						dataType = ModbusDataTypeEnum.valueOf(cellValue);
+					}
+					dataPoint.setDataType(dataType);
+					dataPoint.setMag(Double.parseDouble(getCellContext(sheet.getCell(3, i))));
+					dataPoint.setDescribe(sheet.getCell(4, i).getContents());
+					if(sheet.getCell(5, i)!=null && !"".equals(sheet.getCell(5, i).getContents())){
+						dataPoint.setSlaveId(Integer.parseInt(getCellContext(sheet.getCell(5, i))));
+					}
+					dataPoints.add(dataPoint);
+				}
+				tunnelInfo.setDataPoints(dataPoints);
+				tunnelInfos.add(tunnelInfo);
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+			sheetI++;
+		}
+		return tunnelInfos;
+	}
+
+
+	public static String getCellContext(Cell cell) throws DataExchangeException {
+		if (cell == null) {
+			throw new DataExchangeException(4001, "配置文件缺少配置内容," + cell.getRow() + "行" + cell.getColumn() + "列");
+		}
+		String val = cell.getContents();
+		if (StringUtils.isNoneEmpty(val)) {
+			return val;
+		} else {
+			throw new DataExchangeException(4001, "配置文件缺少配置内容," + cell.getRow() + "行" + cell.getColumn() + "列");
+		}
+	}
+
+
+}

+ 35 - 0
src/main/java/com/syjy/TunnelBuilder.java

@@ -0,0 +1,35 @@
+package com.syjy;
+
+
+import com.syjy.tunnelinfo.BaseTunnelInfo;
+import com.syjy.tunnelinfo.gathertunnelinfo.GatherModbusRtuTunnelInfo;
+import com.syjy.tunnelinfo.gathertunnelinfo.GatherModbusTcpTunnelInfo;
+import com.syjy.tunnelworker.BaseProtocolTunnel;
+import com.syjy.tunnelworker.gathers.iml.ModbusRtuDataGather;
+import com.syjy.tunnelworker.gathers.iml.ModbusTcpDataGather;
+
+/**
+ * 用Tunnel Info 创建通达
+ *
+ * @author: xiuwei
+ * @version:
+ */
+public class TunnelBuilder {
+
+
+    public static BaseProtocolTunnel buildTunnel(BaseTunnelInfo tunnelInfo) throws DataExchangeException {
+        switch (tunnelInfo.getTunnelType()) {
+            case MODBUSRTUMASTER:
+                return new ModbusRtuDataGather((GatherModbusRtuTunnelInfo) tunnelInfo).buildTunnel();
+            case MODBUSTCPMASTER:
+                return new ModbusTcpDataGather((GatherModbusTcpTunnelInfo) tunnelInfo).buildTunnel();
+
+            default:
+                return null;
+        }
+    }
+
+
+}
+
+

+ 91 - 0
src/main/java/com/syjy/TunnelLoggerFactory.java

@@ -0,0 +1,91 @@
+package com.syjy;
+
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
+import ch.qos.logback.classic.filter.ThresholdFilter;
+import ch.qos.logback.core.ConsoleAppender;
+import ch.qos.logback.core.rolling.RollingFileAppender;
+import ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP;
+import ch.qos.logback.core.rolling.TimeBasedRollingPolicy;
+import ch.qos.logback.core.util.FileSize;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 通道logger的工厂  仅针对在slf4j 日志框架下 logback 实现
+ * 修改 日志框架或实现时均需修改该类
+ *
+ * @author: xiuwei
+ * @version:
+ */
+public class TunnelLoggerFactory {
+
+
+	public static Logger getLogger(String tunnelId, String tunnelName) {
+		//使用在slf4j的接口框架下获取 logback的 LoggerContext
+		LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
+		Logger logger = loggerContext.getLogger(tunnelId);
+
+		//构建编码规则
+		PatternLayoutEncoder encoder = new PatternLayoutEncoder();
+		encoder.setContext(loggerContext);
+		encoder.setPattern("%d{HH:mm:ss.SSS} [%thread] %-5level  %M-%L  - %msg%n");
+		encoder.start();
+
+
+		ConsoleAppender consoleAppender = new ConsoleAppender();
+		consoleAppender.setContext(loggerContext);
+		consoleAppender.setEncoder(encoder);
+		ThresholdFilter levelFilterConsole = new ThresholdFilter();
+		levelFilterConsole.setLevel("info");
+		levelFilterConsole.start();
+		consoleAppender.addFilter(levelFilterConsole);
+		consoleAppender.start();
+		logger.addAppender(consoleAppender);
+
+		//构建appender
+		RollingFileAppender appender = new RollingFileAppender();
+		appender.setContext(loggerContext);
+
+		//构建过滤器
+		ThresholdFilter levelFilter = new ThresholdFilter();
+		levelFilter.setLevel("trace");
+		levelFilter.start();
+		appender.addFilter(levelFilter);
+
+		//构建滚动规则
+		TimeBasedRollingPolicy rollingPolicyBase = new TimeBasedRollingPolicy<>();
+		rollingPolicyBase.setContext(loggerContext);
+		rollingPolicyBase.setParent(appender);
+		System.out.println("日志打印路径  "+FileUtils.getResourcesPath() + "/logs/" );
+		rollingPolicyBase.setFileNamePattern(FileUtils.getResourcesPath() + "/logs/" + tunnelName + ".%d{yyyy-MM-dd}.%i.log");
+		//设定滚动触发器
+		SizeAndTimeBasedFNATP sizeAndTimeBasedFNATP = new SizeAndTimeBasedFNATP();
+		sizeAndTimeBasedFNATP.setMaxFileSize(FileSize.valueOf("100MB"));
+		rollingPolicyBase.setTimeBasedFileNamingAndTriggeringPolicy(sizeAndTimeBasedFNATP);
+		//最大文件数
+		rollingPolicyBase.setMaxHistory(10);
+		rollingPolicyBase.setCleanHistoryOnStart(true);
+		rollingPolicyBase.setTotalSizeCap(FileSize.valueOf("1GB"));
+		rollingPolicyBase.start();
+
+		appender.setEncoder(encoder);
+		appender.setRollingPolicy(rollingPolicyBase);
+		appender.start();
+
+		logger.setAdditive(false);
+		logger.addAppender(appender);
+		return logger;
+	}
+
+	/**
+	 * 关闭该logger
+	 *
+	 * @param logger
+	 */
+	public static void stop(Logger logger) {
+		LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
+		loggerContext.removeObject(logger.getName());
+		logger.detachAndStopAllAppenders();
+	}
+}

+ 25 - 0
src/main/java/com/syjy/WindData.java

@@ -0,0 +1,25 @@
+package com.syjy;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 传感器测风数据
+ */
+@Data
+public class WindData {
+
+    /**
+     * 风速
+     */
+    BigDecimal ws;
+    /**
+     * 风向
+     */
+    BigDecimal wd;
+
+
+
+
+}

+ 92 - 0
src/main/java/com/syjy/WriteDataCSVLoggerFactory.java

@@ -0,0 +1,92 @@
+package com.syjy;
+
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
+import ch.qos.logback.classic.filter.ThresholdFilter;
+import ch.qos.logback.core.ConsoleAppender;
+import ch.qos.logback.core.rolling.RollingFileAppender;
+import ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP;
+import ch.qos.logback.core.rolling.TimeBasedRollingPolicy;
+import ch.qos.logback.core.util.FileSize;
+import com.syjy.FileUtils;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 通道logger的工厂  仅针对在slf4j 日志框架下 logback 实现
+ * 修改 日志框架或实现时均需修改该类
+ *
+ * @author: xiuwei
+ * @version:
+ */
+public class WriteDataCSVLoggerFactory {
+
+
+	public static Logger getLogger(String name) {
+		//使用在slf4j的接口框架下获取 logback的 LoggerContext
+		LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
+		Logger logger = loggerContext.getLogger(name);
+
+		//构建编码规则
+		PatternLayoutEncoder encoder = new PatternLayoutEncoder();
+		encoder.setContext(loggerContext);
+		encoder.setPattern("%d{HH:mm:ss},%msg%n");
+		encoder.start();
+
+
+		ConsoleAppender consoleAppender = new ConsoleAppender();
+		consoleAppender.setContext(loggerContext);
+		consoleAppender.setEncoder(encoder);
+		ThresholdFilter levelFilterConsole = new ThresholdFilter();
+		levelFilterConsole.setLevel("info");
+		levelFilterConsole.start();
+		consoleAppender.addFilter(levelFilterConsole);
+		consoleAppender.start();
+		logger.addAppender(consoleAppender);
+
+		//构建appender
+		RollingFileAppender appender = new RollingFileAppender();
+		appender.setContext(loggerContext);
+
+		//构建过滤器
+		ThresholdFilter levelFilter = new ThresholdFilter();
+		levelFilter.setLevel("trace");
+		levelFilter.start();
+		appender.addFilter(levelFilter);
+
+		//构建滚动规则
+		TimeBasedRollingPolicy rollingPolicyBase = new TimeBasedRollingPolicy<>();
+		rollingPolicyBase.setContext(loggerContext);
+		rollingPolicyBase.setParent(appender);
+		System.out.println("日志打印路径  "+ FileUtils.getResourcesPath() + "/logs/" );
+		rollingPolicyBase.setFileNamePattern(FileUtils.getResourcesPath() + "/logs/" + name + ".%d{yyyy-MM-dd}.%i.csv");
+		//设定滚动触发器
+		SizeAndTimeBasedFNATP sizeAndTimeBasedFNATP = new SizeAndTimeBasedFNATP();
+		sizeAndTimeBasedFNATP.setMaxFileSize(FileSize.valueOf("100MB"));
+		rollingPolicyBase.setTimeBasedFileNamingAndTriggeringPolicy(sizeAndTimeBasedFNATP);
+		//最大文件数
+		rollingPolicyBase.setMaxHistory(10);
+		rollingPolicyBase.setCleanHistoryOnStart(true);
+		rollingPolicyBase.setTotalSizeCap(FileSize.valueOf("1GB"));
+		rollingPolicyBase.start();
+
+		appender.setEncoder(encoder);
+		appender.setRollingPolicy(rollingPolicyBase);
+		appender.start();
+
+		logger.setAdditive(false);
+		logger.addAppender(appender);
+		return logger;
+	}
+
+	/**
+	 * 关闭该logger
+	 *
+	 * @param logger
+	 */
+	public static void stop(Logger logger) {
+		LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
+		loggerContext.removeObject(logger.getName());
+		logger.detachAndStopAllAppenders();
+	}
+}

+ 250 - 0
src/main/java/com/syjy/container/ProtocolDataContainer.java

@@ -0,0 +1,250 @@
+package com.syjy.container;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang.StringUtils;
+import org.joda.time.DateTime;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 协议采集到数据的容器
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+
+@Slf4j
+public class ProtocolDataContainer {
+
+
+	/**
+	 * 遥测数据
+	 */
+	private Map<Integer, BigDecimal> yc = new ConcurrentHashMap<>();
+	/**
+	 * 遥信数据
+	 */
+	private Map<Integer, Boolean> yx = new ConcurrentHashMap<>();
+	/**
+	 * 点位数据是否是有效值
+	 */
+	private Map<Integer, Boolean> isValid = new ConcurrentHashMap<>();
+
+
+	/**
+	 * 点位描述
+	 */
+	private final Map<Integer, String> describe = new ConcurrentHashMap<>();
+
+	public void putDescribe(Integer point, String des) {
+		this.describe.put(point, des);
+	}
+
+
+	private ProtocolDataContainer() {
+	}
+
+	/**
+	 * 获取单例实例
+	 *
+	 * @return the instance
+	 */
+	public static final ProtocolDataContainer getInstance() {
+		return LazyHolder.INSTANCE;
+	}
+
+	/**
+	 * 向池子内置数据
+	 *
+	 * @param point point
+	 * @param value value
+	 */
+	public void putNumber(Integer point, BigDecimal value) {
+		this.yc.put(point, value);
+	}
+
+
+	/**
+	 * 向池子内置数据
+	 *
+	 * @param point point
+	 * @param value value
+	 */
+	public void putBoolean(Integer point, Boolean value) {
+		this.yx.put(point, value);
+	}
+
+
+	/**
+	 * 获取到缓存池内的数值 如果遥测池内没有但是存在于遥信池 将返回 0or1
+	 *
+	 * @param point 点位
+	 * @return Number
+	 */
+	public BigDecimal getNumber(Integer point) {
+		if (isYcContains(point)) {
+			return this.yc.get(point);
+		} else if (isYxContains(point)) {
+			return this.yx.get(point) ? BigDecimal.valueOf(1D) : BigDecimal.valueOf(0D);
+		} else {
+			return BigDecimal.valueOf(-99D);
+		}
+	}
+
+	/**
+	 * 获取到返回池内的遥信数据  如果遥信池内没有 遥测池内有 那么大于0的数值 将会返回ture 否则为false
+	 *
+	 * @param point 点位
+	 * @return Boolean
+	 */
+	public Boolean getBoolean(Integer point) {
+		if (isYxContains(point)) {
+			return this.yx.get(point);
+		} else if (isYcContains(point)) {
+			return this.yc.get(point).doubleValue() > 0;
+		} else {
+			return false;
+		}
+	}
+
+	/**
+	 * 获取所有的数值
+	 *
+	 * @return {@link Map<Integer, BigDecimal>}
+	 */
+	public  Map<Integer,BigDecimal>  getAllNumbers(){
+		return this.yc;
+	}
+
+	/**
+	 * 获取所有的遥信
+	 *
+	 * @return {@link Map<Integer, Boolean>}
+	 */
+	public  Map<Integer,Boolean>  getAllBooleans(){
+		return this.yx;
+	}
+
+	/**
+	 * 获取点位数据  直接返回
+	 *
+	 * @param point 点位key
+	 * @return 返回值  可能是Boolean 或者是 BigDecimal
+	 */
+	public Object getData(Integer point) {
+		if (isYcContains(point)) {
+			return this.yc.get(point);
+		}
+		if (isYxContains(point)) {
+			return this.yx.get(point);
+		}
+		return BigDecimal.valueOf(0);
+	}
+
+	/**
+	 * 遥信数据池是否含有该点位
+	 *
+	 * @param point point
+	 * @return the boolean
+	 */
+	public Boolean isYxContains(Integer point) {
+		return this.yx.containsKey(point);
+	}
+
+	/**
+	 * 遥测数据池是否含有该点位值
+	 *
+	 * @param point point
+	 * @return the boolean
+	 */
+	public Boolean isYcContains(Integer point) {
+		return this.yc.containsKey(point);
+	}
+
+
+	@Override
+	public String toString() {
+		JSONObject object = new JSONObject();
+		object.put("yx", this.yx);
+		object.put("yc", this.yc);
+		return object.toJSONString();
+	}
+
+	/**
+	 * 格式化打印信息 以表格的形式打印通道数据
+	 *
+	 * @return
+	 */
+	public String toStringFormatted() {
+		StringBuffer stringBuffer = new StringBuffer();
+		String splitLine = "—————————————————————|—————————————————————|——————————————————————\n";
+		stringBuffer.append(DateTime.now().toString("yyyy-MM-dd HH:mm:ss:SSS") + "\n");
+		stringBuffer.append(splitLine);
+		stringBuffer.append("  key  |    value    |  key  |    value    |  key  |    value    |\n");
+		stringBuffer.append(splitLine);
+		int i = 0;
+		Iterator<Integer> it = new TreeSet(this.yx.keySet()).iterator();
+		Integer position;
+		while (it.hasNext()) {
+			position = it.next();
+			stringBuffer.append(StringUtils.center(position + "", 7));
+			stringBuffer.append("|");
+			stringBuffer.append(StringUtils.center(this.yx.get(position) + "", 13));
+			stringBuffer.append("|");
+			i++;
+			if (i % 3 == 0) {
+				stringBuffer.append("\n");
+				stringBuffer.append(splitLine);
+			}
+		}
+		it = new TreeSet(this.yc.keySet()).iterator();
+		while (it.hasNext()) {
+			position = it.next();
+			stringBuffer.append(StringUtils.center(position + "", 7));
+			stringBuffer.append("|");
+			stringBuffer.append(StringUtils.center(StringUtils.substring(this.yc.get(position) + "", 0, 11), 13));
+			stringBuffer.append("|");
+			i++;
+			if (i % 3 == 0) {
+				stringBuffer.append("\n");
+				stringBuffer.append(splitLine);
+			}
+		}
+		return stringBuffer.toString();
+	}
+
+	private static class LazyHolder {
+		private static final ProtocolDataContainer INSTANCE = new ProtocolDataContainer();
+	}
+
+
+	public String toJSONString() {
+		TreeMap<Integer, Object> map = new TreeMap();
+		map.putAll(this.yc);
+		map.putAll(this.yx);
+		Map<String,String> object=new LinkedHashMap<>();
+		for (Integer i : map.keySet()) {
+			if (map.get(i) instanceof BigDecimal) {
+				object.put(this.describe.get(i), ((BigDecimal) map.get(i)).setScale(2, BigDecimal.ROUND_HALF_UP).toString());
+			} else {
+				object.put(this.describe.get(i), map.get(i).toString());
+			}
+
+		}
+		return JSON.toJSONString(object);
+	}
+
+	public JSONObject getValues() {
+		JSONObject jsonObject = new JSONObject();
+		this.yc.forEach((k, v) -> jsonObject.put(k.toString(), v.setScale(2, RoundingMode.HALF_UP)));
+		this.yx.forEach((k, v) -> jsonObject.put(k.toString(), v));
+		return jsonObject;
+	}
+
+
+}

+ 197 - 0
src/main/java/com/syjy/container/ProtocolTunnelContainer.java

@@ -0,0 +1,197 @@
+package com.syjy.container;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.syjy.DataExchangeException;
+import com.syjy.tunnelinfo.BaseTunnelInfo;
+import com.syjy.tunnelinfo.TunnelStatus;
+import com.syjy.tunnelworker.BaseProtocolTunnel;
+import com.syjy.tunnelworker.gathers.DataGatherInterface;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang.StringUtils;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.*;
+
+/**
+ * 装载通道对象的容器
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@Slf4j
+public class ProtocolTunnelContainer {
+
+    /**
+     * 通道集合
+     */
+    private Map<String, BaseProtocolTunnel> tunnels = new ConcurrentHashMap<>();
+
+
+
+
+    ScheduledExecutorService executor = Executors.newScheduledThreadPool(4);
+
+    /**
+     * 单例构造
+     */
+    private ProtocolTunnelContainer() {
+       }
+
+    /**
+     * 单例
+     *
+     * @return 实例
+     */
+    public static final ProtocolTunnelContainer getInstance() {
+        return LazyHolder.INSTANCE;
+    }
+
+    /**
+     * 获取tunnelID下的 tunnel
+     *
+     * @param tunnelId 通道id
+     * @return BaseProtocolTunnel 通道
+     */
+    public BaseProtocolTunnel getTunnel(String tunnelId) {
+        if (this.tunnels.containsKey(tunnelId)) {
+            return this.tunnels.get(tunnelId);
+        }
+        return null;
+    }
+
+    /**
+     * 获取所有的 tunnel
+     *
+     * @return BaseProtocolTunnel 通道
+     */
+    public Map<String, BaseProtocolTunnel> getAllTunnel() {
+        return this.tunnels;
+    }
+
+
+    /**
+     * 向集合中置入tunnel
+     *
+     * @param tunnel 通道对象
+     * @return 返回置入的tunnel
+     */
+    public BaseProtocolTunnel addTunnel(BaseProtocolTunnel tunnel) {
+        this.tunnels.put(tunnel.getTunnelId(), tunnel);
+        return tunnel;
+    }
+
+    /**
+     * 删除隧道 同时删除定时更新数据任务
+     *
+     * @param tunnelId 隧道id
+     */
+    public void removeTunnel(String tunnelId) {
+        this.tunnels.remove(tunnelId);
+    }
+
+
+
+    /**
+     * 添加数据读取任务
+     *
+     * @param baseProtocolTunnel 通道对象
+     */
+    public void addUpdateDateTask(BaseProtocolTunnel baseProtocolTunnel) {
+        if (baseProtocolTunnel instanceof DataGatherInterface) {
+           executor.scheduleAtFixedRate(() -> {
+                if (TunnelStatus.ABLE_STATUS.contains(baseProtocolTunnel.getTunnelStatus())) {
+                    try {
+                        ((DataGatherInterface) baseProtocolTunnel).getDataFromProtocol();
+                    } catch (DataExchangeException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }, 0, 1, TimeUnit.SECONDS);
+        }
+    }
+
+    @Override
+    public String toString() {
+        Map<String, JSONObject> str = new HashMap<>();
+        this.tunnels.forEach((k, v) -> {
+            str.put(k, v.getJSONObj());
+        });
+        return JSON.toJSONString(str);
+    }
+
+    /**
+     * 格式化打印信息 以表格的形式打印通道状态
+     *
+     * @return
+     */
+    public String toStringFormatted() {
+        StringBuffer stringBuffer = new StringBuffer();
+        String splitLine = "|——————————————————————————————————|————————————————————|—————————————————————|———————————————————————|\n";
+        stringBuffer.append(splitLine);
+        stringBuffer.append("|              通道id              |       通道类型      |        通道状态      |         通道名        |\n");
+        stringBuffer.append(splitLine);
+        for (BaseProtocolTunnel t : this.tunnels.values()) {
+            stringBuffer.append("|");
+            stringBuffer.append(StringUtils.center(t.getTunnelInfo().getId(), 34));
+            stringBuffer.append("|");
+            stringBuffer.append(StringUtils.center(t.getTunnelInfo().getTunnelType().toString(), 20));
+            stringBuffer.append("|");
+            stringBuffer.append(StringUtils.center(t.getTunnelStatus().toString(), 21));
+            stringBuffer.append("|");
+            stringBuffer.append(StringUtils.center(t.getTunnelInfo().getTunnelName(), 20));
+            stringBuffer.append("|");
+            stringBuffer.append("\n");
+            stringBuffer.append(splitLine);
+        }
+        return stringBuffer.toString();
+    }
+
+    /**
+     * 静态内部类 为了创建安全的单例
+     */
+    private static class LazyHolder {
+        private static final ProtocolTunnelContainer INSTANCE = new ProtocolTunnelContainer();
+    }
+
+    /**
+     * 创建一个对通道池内通道状态的检查任务
+     */
+    class CheckTunnelStatusTask implements Callable {
+
+        @Override
+        public Object call() throws Exception {
+            TunnelStatus tunnelStatus;
+            log.info("开始对所有的通道状态进行巡检");
+            Iterator<String> it = tunnels.keySet().iterator();
+            String keyString;
+            BaseProtocolTunnel tunnel;
+            BaseTunnelInfo tunnelInfo;
+            while (it.hasNext()) {
+                keyString = it.next();
+                tunnel = tunnels.get(keyString);
+                tunnelInfo = tunnel.getTunnelInfo();
+                tunnelStatus = tunnel.getTunnelStatus();
+                if (!TunnelStatus.ABLE_STATUS.contains(tunnelStatus)) {
+                    log.warn("发现本该开启的通道:{} 处在未开启成功的状态,重启该通道", tunnelInfo.getTunnelName());
+                    try {
+                        tunnel.tunnelStop();
+                    } catch (DataExchangeException e) {
+                        log.error("通道{}关闭时发生异常", tunnelInfo.getTunnelName(), e);
+                    }
+                    tunnel.refreshTunnelInfo(tunnelInfo).buildTunnel();
+                    try {
+                        tunnel.startTunnel();
+                    } catch (DataExchangeException e) {
+                        log.error("通道{}开启时发生异常", tunnelInfo.getTunnelName(), e);
+                    }
+                }
+            }
+            return null;
+        }
+    }
+
+
+}

+ 192 - 0
src/main/java/com/syjy/container/RecurringTaskContainer.java

@@ -0,0 +1,192 @@
+package com.syjy.container;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import lombok.extern.slf4j.Slf4j;
+import org.joda.time.DateTime;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.*;
+
+/**
+ * 循环反复任务池
+ * 会有线程查看线程任务执行时间 不允许有执行时间超过两秒的线程
+ * 最小执行间隔时间为3秒 因为 检查任务线程的频率为2s
+ *
+ * @author: xiuwei
+ * @version:
+ */
+@Slf4j
+public class RecurringTaskContainer {
+
+  /**
+   * 核心数量为10 排序数量为100 默认排序规则  多余线程存活时间为60秒的 定长线程
+   * 用于执行定时任务
+   */
+  private static final ExecutorService calculatorThread = new ThreadPoolExecutor(15, 1000,
+    60L, TimeUnit.SECONDS, new SynchronousQueue<>(), new ThreadFactoryBuilder().build());
+
+
+  private RecurringTaskContainer() {
+  }
+
+
+  /**
+   * 核心运行数量为4的定时任务池 用于拉起定时任务
+   */
+  private static final ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(4, new ThreadFactoryBuilder().build());
+
+  /**
+   * 定时任务对象  以时间间隔进行区分
+   */
+  private Map<Integer, Map<Callable, String>> tasks = new ConcurrentHashMap();
+
+  /**
+   * 单例
+   *
+   * @return 实例
+   */
+  public static final RecurringTaskContainer getInstance() {
+    return LazyHolder.INSTANCE;
+  }
+
+  /**
+   * 向定时任务集合里添加定时任务
+   *
+   * @param interval 时间间隔
+   * @param task     任务
+   * @return 返回添加的任务
+   */
+  public <T> Callable addRecurringCallable(Integer interval, String describe, Callable<T> task) {
+    if (interval < 2) {
+      throw new RuntimeException("该工具不允许执行3s以下间隔的任务");
+    }
+    log.info("循环任务池添加任务-{}", describe);
+    if (getTasks().containsKey(interval)) {
+      getTasks().get(interval).put(task, describe);
+    } else {
+      Map<Callable, String> map = new ConcurrentHashMap();
+      map.put(task, describe);
+      getTasks().put(interval, map);
+      startScheduledTask(interval);
+    }
+    return task;
+  }
+
+  /**
+   * 向定时任务集合里添加定时任务
+   *
+   * @param interval 时间间隔
+   * @param task     任务
+   * @return 返回添加的任务
+   */
+  public <T> Callable addRecurringTask(Integer interval, String describe, Task4Recurring<T> task) {
+    if (interval < 2) {
+      throw new RuntimeException("该工具不允许执行3s以下间隔的任务");
+    }
+    Callable<T> callable = () -> {
+      T o;
+      try {
+        o = task.task();
+      } catch (Exception e) {
+        log.error("定时任务本身产生异常", e);
+        throw e;
+      }
+      return o;
+    };
+    return addRecurringCallable(interval, describe, callable);
+  }
+
+  /**
+   * 移除定时任务
+   *
+   * @param interval 时间间隔
+   * @param task     执行的任务线程
+   */
+  public void removeTask(Integer interval, Callable task) {
+    if (this.getTasks().containsKey(interval)) {
+      this.getTasks().get(interval).remove(task);
+    }
+  }
+
+  private synchronized Map<Integer, Map<Callable, String>> getTasks() {
+    return this.tasks;
+  }
+
+  /**
+   * 移除定时任务
+   *
+   * @param task 任务线程
+   */
+  public void removeTask(Callable task) {
+    for (Integer key : getTasks().keySet()) {
+      if (this.getTasks().get(key).containsKey(task)) {
+        log.info("循环任务池移除任务-{}", this.getTasks().get(key).get(task));
+        this.getTasks().get(key).remove(task);
+        break;
+      }
+    }
+  }
+
+  /**
+   * 开启定时任务线程 用于拉起具体的定时任务
+   *
+   * @param interval 时间间隔
+   */
+  public void startScheduledTask(Integer interval) {
+    scheduledThreadPool.scheduleAtFixedRate(() -> {
+      final Map<Future, String> futures = new HashMap<>();
+      final DateTime startTime=DateTime.now();
+      Map<Callable, String> map = getTasks().get(interval);
+      Future future = null;
+      for (Callable c : map.keySet()) {
+        try {
+          future = calculatorThread.submit(c);
+          futures.put(future, map.get(c));
+        } catch (Exception e) {
+          if (e instanceof RejectedExecutionException) {
+            log.error("提交任务过多超过排序队列长度,任务被拒绝");
+          } else {
+            log.error("执行循环任务时发生异常", e);
+          }
+        }
+      }
+      scheduledThreadPool.schedule(() -> {
+        log.debug("检查{}S循环任务执行情况,共提交{}个任务,任务提交时间{}",interval,futures.size(),startTime.toString("HH:mm:ss:SSS"));
+        Iterator<Future> iter = futures.keySet().iterator();
+        Future future1;
+        while (iter.hasNext()) {
+          try {
+            future1 = iter.next();
+            if (!future1.isDone()) {
+              log.info("发现未完成的超时任务{},任务提交时间为{},取消该任务", futures.get(future1),startTime.toString("HH:mm:ss:SSS"));
+              future1.cancel(false);
+            }
+          } catch (Exception e) {
+            log.error("检查任务完成情况时发生异常{}", interval, e);
+          }
+        }
+      }, interval - 1, TimeUnit.SECONDS);
+    }, 0, interval, TimeUnit.SECONDS);
+
+  }
+
+  /**
+   * 它的意义就是为了打印一下日志 就是为了包裹一层try catch
+   *
+   * @param <T>
+   */
+  public interface Task4Recurring<T> {
+    T task() throws Exception;
+  }
+
+  /**
+   * 为了单例的内部类
+   *
+   * @return 实例
+   */
+  private static class LazyHolder {
+    private static final RecurringTaskContainer INSTANCE = new RecurringTaskContainer();
+  }
+}

+ 44 - 0
src/main/java/com/syjy/http/HttpRequestHandler.java

@@ -0,0 +1,44 @@
+package com.syjy.http;
+
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.handler.codec.http.*;
+import lombok.extern.slf4j.Slf4j;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+
+@Slf4j
+public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
+
+    private static final List<String> NO_LOG_URL = Arrays.asList("/getAllPoints", "/", "/com/syjy/favicon.ico");
+
+    @Override
+    public void channelReadComplete(ChannelHandlerContext ctx) {
+        ctx.flush();
+    }
+
+    @Override
+    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest req) throws Exception {
+        //100 Continue
+        if (HttpUtil.is100ContinueExpected(req)) {
+            ctx.write(new DefaultFullHttpResponse(
+                    HttpVersion.HTTP_1_1,
+                    HttpResponseStatus.CONTINUE));
+        }
+        // 获取请求的uri
+        String uri = req.uri();
+        QueryStringDecoder decoder = new QueryStringDecoder(uri);
+        if (!NO_LOG_URL.contains(decoder.path())) {
+            log.info("请求路径:{},请求类型:{}", uri, req.method());
+        }
+        FullHttpResponse response = RequestControllerManager.getInstance().getRequestHandler(req.method().name(), decoder.path()).handle(req);
+        if (!NO_LOG_URL.contains(decoder.path())) {
+            log.info("响应内容:{}", response.content().toString(StandardCharsets.UTF_8));
+        }
+        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
+    }
+
+}

+ 39 - 0
src/main/java/com/syjy/http/HttpServer.java

@@ -0,0 +1,39 @@
+package com.syjy.http;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+
+import java.net.InetSocketAddress;
+
+/**
+ * netty server
+ * 2018/11/1.
+ */
+public class HttpServer {
+
+    int port;
+
+    public HttpServer(int port) {
+        this.port = port;
+    }
+
+    public void start() throws Exception {
+        ServerBootstrap bootstrap = new ServerBootstrap();
+        EventLoopGroup boss = new NioEventLoopGroup();
+        EventLoopGroup work = new NioEventLoopGroup();
+        bootstrap.group(boss, work)
+                .channel(NioServerSocketChannel.class)
+                .childHandler(new HttpServerInitializer());
+        ChannelFuture f = bootstrap.bind(new InetSocketAddress(port)).sync();
+        System.out.println(" server start up on port : " + port);
+        f.channel().closeFuture().sync();
+
+    }
+
+}
+
+
+

+ 20 - 0
src/main/java/com/syjy/http/HttpServerInitializer.java

@@ -0,0 +1,20 @@
+package com.syjy.http;
+
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.handler.codec.http.HttpObjectAggregator;
+import io.netty.handler.codec.http.HttpServerCodec;
+
+
+public class HttpServerInitializer extends ChannelInitializer<SocketChannel> {
+
+    @Override
+    protected void initChannel(SocketChannel channel) throws Exception {
+        ChannelPipeline pipeline = channel.pipeline();
+        pipeline.addLast(new HttpServerCodec());// http 编解码
+        pipeline.addLast("httpAggregator", new HttpObjectAggregator(512 * 1024)); // http 消息聚合器                                                                     512*1024为接收的最大contentlength
+        pipeline.addLast(new HttpRequestHandler());// 请求处理器
+
+    }
+}

+ 48 - 0
src/main/java/com/syjy/http/RequestController.java

@@ -0,0 +1,48 @@
+package com.syjy.http;
+
+import com.syjy.GetStaticFileUtils;
+import com.syjy.container.ProtocolDataContainer;
+
+/**
+ * 请求控制器
+ *
+ * @author xiuwei
+ * @date 2021/11/26
+ */
+public class RequestController {
+
+
+    private RequestController() {
+    }
+
+    public static final RequestController getInstance() {
+        return LazyHolder.INSTANCE;
+    }
+
+    @RequestHandlerFunction(requestType = RequestControllerManager.RequestMethodType.GET, requestUrl = "/")
+    public RequestHandler index() {
+        return (request) -> index().getTextResponse(GetStaticFileUtils.getFileContext("master.html"));
+    }
+
+    @RequestHandlerFunction(requestType = RequestControllerManager.RequestMethodType.GET, requestUrl = "/getAllPoints")
+    public RequestHandler getAllPoints() {
+        return (request) -> index().getTextResponse(ProtocolDataContainer.getInstance().toJSONString());
+    }
+
+
+    @RequestHandlerFunction(requestType = RequestControllerManager.RequestMethodType.GET, requestUrl = "/favicon.ico")
+    public RequestHandler icon() {
+        return (request) -> icon().getIcon(GetStaticFileUtils.getFileStream("favicon.ico"));
+    }
+
+    @RequestHandlerFunction(requestType = RequestControllerManager.RequestMethodType.GET, requestUrl = "/getValues")
+    public RequestHandler getValues() {
+        return (request) -> getValues().getTextResponse(ProtocolDataContainer.getInstance().getValues().toJSONString());
+    }
+
+    private static class LazyHolder {
+        private static final RequestController INSTANCE = new RequestController();
+    }
+
+
+}

+ 83 - 0
src/main/java/com/syjy/http/RequestControllerManager.java

@@ -0,0 +1,83 @@
+package com.syjy.http;
+
+import io.netty.handler.codec.http.FullHttpRequest;
+import io.netty.handler.codec.http.FullHttpResponse;
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author: xiuwei
+ * @version:
+ */
+@Slf4j
+public class RequestControllerManager {
+
+    private final Map<String, Map<String, RequestHandler>> requestHandlerMap = new HashMap<>();
+
+    private RequestControllerManager() {
+        init();
+    }
+
+    public static final RequestControllerManager getInstance() {
+        return LazyHolder.INSTANCE;
+    }
+
+    private void addRequestHandler(String requestType, String requestUrl, RequestHandler requestHandler) {
+        if (!this.requestHandlerMap.containsKey(requestType)) {
+            this.requestHandlerMap.put(requestType, new HashMap<>());
+        }
+        this.requestHandlerMap.get(requestType).put(requestUrl, requestHandler);
+    }
+
+    public RequestHandler getRequestHandler(String requestType, String requestUrl) {
+        if (this.requestHandlerMap.containsKey(requestType) && this.requestHandlerMap.get(requestType).containsKey(requestUrl)) {
+            return this.requestHandlerMap.get(requestType).get(requestUrl);
+        } else {
+            return new RequestHandler() {
+                @Override
+                public FullHttpResponse handle(FullHttpRequest request) {
+                    return getTextResponse("404");
+                }
+            };
+        }
+    }
+
+    private void init() {
+        Method[] functions = RequestController.class.getDeclaredMethods();
+        for (Method m : functions) {
+            RequestHandlerFunction t = m.getAnnotation(RequestHandlerFunction.class);
+            if (t != null && m.getReturnType().equals(RequestHandler.class) && m.getParameterCount() == 0) {
+                try {
+                    addRequestHandler(t.requestType().toString(), t.requestUrl(), (RequestHandler) m.invoke(RequestController.getInstance()));
+                } catch (IllegalAccessException e) {
+                    e.printStackTrace();
+                } catch (InvocationTargetException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    public enum RequestMethodType {
+        GET, POST;
+
+        public String toString() {
+            switch (this) {
+                case GET:
+                    return "GET";
+                case POST:
+                    return "POST";
+            }
+            return "GET";
+        }
+    }
+
+    private static class LazyHolder {
+        private static final RequestControllerManager INSTANCE = new RequestControllerManager();
+    }
+
+}

+ 42 - 0
src/main/java/com/syjy/http/RequestHandler.java

@@ -0,0 +1,42 @@
+package com.syjy.http;
+
+import io.netty.buffer.Unpooled;
+import io.netty.handler.codec.http.*;
+import io.netty.util.CharsetUtil;
+import sun.misc.IOUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+@FunctionalInterface
+public interface RequestHandler {
+    FullHttpResponse handle(FullHttpRequest request);
+
+    default FullHttpResponse getTextResponse(String pageMsg) {
+        // 创建http响应
+        FullHttpResponse response = new DefaultFullHttpResponse(
+                HttpVersion.HTTP_1_1,
+                HttpResponseStatus.OK,
+                Unpooled.copiedBuffer(pageMsg, CharsetUtil.UTF_8));
+        // 设置头信息
+        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8");
+        return response;
+    }
+
+
+    default FullHttpResponse getIcon(InputStream icon) {
+        // 创建http响应
+        FullHttpResponse response = null;
+        try {
+            response = new DefaultFullHttpResponse(
+                    HttpVersion.HTTP_1_1,
+                    HttpResponseStatus.OK,
+                    Unpooled.copiedBuffer(IOUtils.readFully(icon, icon.available(), false)));
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        // 设置头信息
+        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "image/x-icon");
+        return response;
+    }
+}

+ 16 - 0
src/main/java/com/syjy/http/RequestHandlerFunction.java

@@ -0,0 +1,16 @@
+package com.syjy.http;
+
+import java.lang.annotation.*;
+
+
+@Target(ElementType.METHOD)
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RequestHandlerFunction {
+
+
+    RequestControllerManager.RequestMethodType requestType() default RequestControllerManager.RequestMethodType.GET;
+
+    String requestUrl();
+
+}

+ 51 - 0
src/main/java/com/syjy/tunnelinfo/BaseTunnelInfo.java

@@ -0,0 +1,51 @@
+package com.syjy.tunnelinfo;
+
+import com.syjy.DataExchangeException;
+import jxl.Cell;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 通道信息的基类,用于与数据库交互传递信息的实体
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@Data
+public abstract class BaseTunnelInfo implements Serializable {
+
+	/**
+	 * 通道的id
+	 */
+	protected String id;
+	/**
+	 * 通道的名称
+	 */
+
+	protected String tunnelName;
+
+	/**
+	 * 通道的类型,冗余字段
+	 */
+
+	protected TunnelType tunnelType;
+
+	/**
+	 * 数据刷新时间间隔 单位s 秒
+	 */
+
+	protected Integer refreshInterval = 5;
+
+
+	/**
+	 * 通道中的点位
+	 */
+	List<DataPoint> dataPoints;
+
+
+	public abstract void setByRow(Cell[] cells) throws DataExchangeException;
+
+
+}

+ 59 - 0
src/main/java/com/syjy/tunnelinfo/DataPoint.java

@@ -0,0 +1,59 @@
+package com.syjy.tunnelinfo;
+
+import lombok.Data;
+import wei.yigulu.modbus.domain.datatype.ModbusDataTypeEnum;
+
+/**
+ * 点位
+ *
+ * @author: xiuwei
+ * @version:
+ */
+@Data
+public class DataPoint {
+
+
+	/**
+	 * 在池子中的点位  也作为点位的唯一键
+	 */
+	Integer id;
+
+
+	/**
+	 * 通道中的点位
+	 */
+	Integer protocolPoint;
+
+
+	/**
+	 *计算公式
+	 */
+	String formula;
+
+
+	/**
+	 * 点位的类型
+	 */
+	ModbusDataTypeEnum dataType;
+
+
+	/**
+	 * 点位的倍率
+	 */
+	Double mag;
+
+
+	/**
+	 * 点位的备注描述
+	 */
+	String describe;
+
+
+	/**
+	 * 仅针对modbus协议    设备地址
+	 */
+	Integer slaveId;
+
+
+
+}

+ 136 - 0
src/main/java/com/syjy/tunnelinfo/TunnelStatus.java

@@ -0,0 +1,136 @@
+package com.syjy.tunnelinfo;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 通道的状态
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@Getter
+@AllArgsConstructor
+public enum TunnelStatus {
+
+
+	/**
+	 * 通道状态枚举
+	 */
+	BUILT(0, "已创建"),
+	/**
+	 * 已连接
+	 */
+	CONNECTED(1, "已连接"),
+	/**
+	 * 被动丢失连接
+	 */
+	LOSECONN(2, "被动丢失连接"),
+	/**
+	 * 通道已主动关闭
+	 */
+	ACTIVECLOSE(3, "通道已主动关闭"),
+	/**
+	 * 正在尝试连接
+	 */
+	CONNECTING(4, "正在尝试连接"),
+
+
+	/**
+	 * listeningport 监听网口成功
+	 */
+	LISTENPORTSUCCESS(5, "监听端口成功"),
+
+	/**
+	 * listeningSerial 监听串口成功
+	 */
+	LISTENSERIALSUCCESS(6, "监听串口成功"),
+
+
+	/**
+	 * listeningport 监听网口失败
+	 */
+	LISTENPORTFAIL(7, "监听端口失败"),
+
+	/**
+	 * listeningSerial 监听串口失败
+	 */
+	LISTENSERIALFAIL(8, "监听串口失败"),
+
+	/**
+	 * listeningport 正在监听网口
+	 */
+	LISTENINGPORT(9, "正在监听端口"),
+
+	/**
+	 * listeningSerial 正在监听串口
+	 */
+	LISTENINGSERIAL(10, "正在监听串口"),
+
+	/**
+	 * listeningport 监听网口成功,并有客户端连接
+	 */
+	LISTENPORTSUCCESSANDCONN(13, "监听端口成功并有客户端连接"),
+
+	/**
+	 * listeningport 监听网口成功,并无客户端连接
+	 */
+	LISTENPORTSUCCESSANDNOCONN(14, "监听端口成功并无客户端连接"),
+
+	/**
+	 * listeningport 监听网口成功,并有客户端连接,且交互异常
+	 */
+	LISTENPORTSUCCESSANDCONNANDCOMMERROR(15, "监听端口成功并有客户端连接但交互异常"),
+
+	/**
+	 * listeningSerial 监听串口成功但交互异常
+	 */
+	LISTENSERIALSUCCESSANDCOMMERROR(16, "监听串口成功但交互异常"),
+
+
+	/**
+	 * 已连接,但是交互异常
+	 */
+	CONNECTEDANDCOMMERROR(17, "已连接但交互异常");
+
+
+	/**
+	 * 可用状态
+	 */
+	public static final List<TunnelStatus> ABLE_STATUS= Arrays.asList(
+			TunnelStatus.LISTENSERIALSUCCESS,//监听串口正常
+			TunnelStatus.LISTENPORTSUCCESSANDCONN, //监听端口成功,并有连接
+			TunnelStatus.LISTENPORTSUCCESSANDCONNANDCOMMERROR,//监听端口成功,并有连接且交互异常
+			TunnelStatus.LISTENSERIALSUCCESSANDCOMMERROR,//监听串口正常 且交互异常
+			TunnelStatus.CONNECTED,//客户端 成功连接端
+			TunnelStatus.CONNECTEDANDCOMMERROR//客户端 成功连接端 但交互异常
+	);
+
+
+	/**
+	 * 正确的状态
+	 */
+	public static final List<TunnelStatus> CORRECT_STATUS= Arrays.asList(
+			TunnelStatus.LISTENSERIALSUCCESS,//监听串口正常
+			TunnelStatus.LISTENPORTSUCCESSANDCONN, //监听端口成功,并有连接
+			TunnelStatus.CONNECTED//客户端 成功连接端
+	);
+
+
+	/**
+	 * 通道的类型代号
+	 */
+	private Integer code;
+
+
+	/**
+	 * 通道的类型描述
+	 */
+	private String cnDescribe;
+
+
+
+}

+ 80 - 0
src/main/java/com/syjy/tunnelinfo/TunnelType.java

@@ -0,0 +1,80 @@
+package com.syjy.tunnelinfo;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 通道的类型
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@Getter
+@AllArgsConstructor
+public enum TunnelType {
+
+
+	/**
+	 * 通道类型枚举
+	 */
+	MODBUSTCPMASTER("modbusTCP采集", "in"),
+	/**
+	 * modbusRTU采集
+	 */
+	MODBUSRTUMASTER("modbusRTU采集", "in"),
+
+	/**
+	 * modbusRTU采集
+	 */
+	MODBUSRTUMASTERWITHTCPSERVER("modbusRTU采集使用TCP通道", "in"),
+	/**
+	 * IEC104TCP采集
+	 */
+	IEC104TCPMASTER("IEC104TCP采集", "in"),
+	/**
+	 * CDTRTU采集
+	 */
+	CDTRTUMASTER("CDTRTU采集", "in"),
+	/**
+	 * modbusTCP转发
+	 */
+	MODBUSTCPSLAVE("modbusTCP转发", "out"),
+
+	/**
+	 * modbusRTU转发
+	 */
+	MODBUSRTUSLAVE("modbusRTU转发", "out"),
+	/**
+	 * IEC104TCP转发
+	 */
+	IEC104TCPSLAVE("IEC104TCP转发", "out"),
+	/**
+	 * CDTRTU转发
+	 */
+	CDTRTUSLAVE("CDTRTU转发", "out"),
+
+	/**
+	 *计算采集通道
+	 */
+	CALCULATOR("计算通道", "in"),
+
+
+	/**
+	 *生成隔离文件通道
+	 */
+	GENERATEFILE("生成隔离文件通道", "in");
+
+
+	/**
+	 * 通道的类型描述
+	 */
+	private String cnDescribe;
+
+
+	/**
+	 * 流向
+	 */
+	private String flow;
+
+
+}

+ 24 - 0
src/main/java/com/syjy/tunnelinfo/gathertunnelinfo/CalculatorTunnelInfo.java

@@ -0,0 +1,24 @@
+package com.syjy.tunnelinfo.gathertunnelinfo;
+
+import com.syjy.DataExchangeException;
+import com.syjy.tunnelinfo.BaseTunnelInfo;
+import com.syjy.tunnelinfo.TunnelType;
+import jxl.Cell;
+
+/**
+ * 点位计算器通道
+ *
+ * @author: xiuwei
+ * @version:
+ */
+public class CalculatorTunnelInfo extends BaseTunnelInfo {
+
+	public  CalculatorTunnelInfo(){
+		this.setTunnelType(TunnelType.CALCULATOR);
+	}
+
+	@Override
+	public void setByRow(Cell[] cells) throws DataExchangeException {
+
+	}
+}

+ 75 - 0
src/main/java/com/syjy/tunnelinfo/gathertunnelinfo/GatherModbusRtuTunnelInfo.java

@@ -0,0 +1,75 @@
+package com.syjy.tunnelinfo.gathertunnelinfo;
+
+import com.syjy.DataExchangeException;
+import com.syjy.ReadConfigFile;
+import com.syjy.tunnelinfo.BaseTunnelInfo;
+import com.syjy.tunnelinfo.TunnelType;
+import jxl.Cell;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import wei.yigulu.comm.CommChannelConfig;
+import wei.yigulu.purejavacomm.PureJavaCommChannelConfig;
+
+
+/**
+ * modbus协议rtu采集通道
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+
+public class GatherModbusRtuTunnelInfo extends BaseTunnelInfo {
+
+
+	/**
+	 * 串口名称
+	 */
+	private String serialName;
+	/**
+	 * 波特率
+	 */
+	private Integer baudRate;
+	/**
+	 * 功能码
+	 */
+	private Integer functionCode = 3;
+	/**
+	 * 设备地址
+	 */
+	private Integer slaveId = 1;
+
+
+	/**
+	 * 数据位
+	 */
+	private PureJavaCommChannelConfig.Databits dataBits = PureJavaCommChannelConfig.Databits.DATABITS_8;
+	/**
+	 * 停止位
+	 */
+	private PureJavaCommChannelConfig.Stopbits stopBits = PureJavaCommChannelConfig.Stopbits.STOPBITS_1;
+	/**
+	 * 校验位
+	 */
+	private PureJavaCommChannelConfig.Paritybit parity = PureJavaCommChannelConfig.Paritybit.NONE;
+
+	/**
+	 * 初始化时确定通道类型
+	 */
+	public GatherModbusRtuTunnelInfo() {
+		this.setTunnelType(TunnelType.MODBUSRTUMASTER);
+	}
+
+
+	@Override
+	public void setByRow(Cell[] cells) throws DataExchangeException {
+		this.setSerialName(ReadConfigFile.getCellContext(cells[0]));
+		this.setBaudRate(Integer.parseInt(ReadConfigFile.getCellContext(cells[1])));
+		this.setDataBits(PureJavaCommChannelConfig.Databits.valueOf(Integer.parseInt(ReadConfigFile.getCellContext(cells[2]))));
+		this.setParity(PureJavaCommChannelConfig.Paritybit.valueOf(ReadConfigFile.getCellContext(cells[3])));
+		this.setStopBits(PureJavaCommChannelConfig.Stopbits.valueOf(Integer.parseInt(ReadConfigFile.getCellContext(cells[4]))));
+		this.setSlaveId(Integer.parseInt(ReadConfigFile.getCellContext(cells[5])));
+		this.setFunctionCode(Integer.parseInt(ReadConfigFile.getCellContext(cells[6])));
+	}
+}

+ 89 - 0
src/main/java/com/syjy/tunnelinfo/gathertunnelinfo/GatherModbusTcpTunnelInfo.java

@@ -0,0 +1,89 @@
+package com.syjy.tunnelinfo.gathertunnelinfo;
+
+
+import com.syjy.DataExchangeException;
+import com.syjy.ReadConfigFile;
+import com.syjy.tunnelinfo.BaseTunnelInfo;
+import com.syjy.tunnelinfo.TunnelType;
+import jxl.Cell;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * modbus协议tcp采集通道
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+
+public class GatherModbusTcpTunnelInfo extends BaseTunnelInfo {
+
+	/**
+	 * 初始化时确定通道类型
+	 */
+	public GatherModbusTcpTunnelInfo() {
+		this.setTunnelType(TunnelType.MODBUSTCPMASTER);
+	}
+
+	/**
+	 * 远端ip
+	 */
+	private String remoteIp;
+
+	/**
+	 * 远端端口
+	 */
+	private Integer remotePort = 502;
+
+	/**
+	 * 远端备用ip
+	 */
+	private String remoteSpareIp;
+
+	/**
+	 * 远端备用端口
+	 */
+	private Integer remoteSparePort;
+
+	/**
+	 * 自身的ip
+	 */
+	private String selfIp;
+
+	/**
+	 * 自身的端口
+	 */
+	private Integer selfPort;
+
+	/**
+	 * 功能码
+	 */
+	private Integer functionCode = 3;
+
+
+	/**
+	 * 设备地址
+	 */
+	private Integer slaveId = 1;
+
+
+	@Override
+	public void setByRow(Cell[] cells) throws DataExchangeException {
+		this.setRemoteIp(ReadConfigFile.getCellContext(cells[0]));
+		this.setRemotePort(Integer.parseInt(ReadConfigFile.getCellContext(cells[1])));
+		try {
+			this.setRemoteSpareIp(ReadConfigFile.getCellContext(cells[2]));
+		}catch (DataExchangeException e){
+
+		}
+		try {
+			this.setRemoteSparePort(Integer.parseInt(ReadConfigFile.getCellContext(cells[3])));
+		}catch (DataExchangeException e){
+
+		}
+		this.setSlaveId(Integer.parseInt(ReadConfigFile.getCellContext(cells[4])));
+		this.setFunctionCode(Integer.parseInt(ReadConfigFile.getCellContext(cells[5])));
+	}
+}

+ 134 - 0
src/main/java/com/syjy/tunnelworker/BaseProtocolTunnel.java

@@ -0,0 +1,134 @@
+package com.syjy.tunnelworker;
+
+import ch.qos.logback.classic.Logger;
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.annotation.JSONField;
+import com.syjy.DataExchangeException;
+import com.syjy.TunnelLoggerFactory;
+import com.syjy.container.ProtocolTunnelContainer;
+import com.syjy.tunnelinfo.BaseTunnelInfo;
+import com.syjy.tunnelinfo.TunnelStatus;
+import com.syjy.tunnelinfo.TunnelType;
+import lombok.Getter;
+import lombok.Setter;
+import wei.yigulu.netty.BaseProtocolBuilder;
+
+import java.net.InetSocketAddress;
+import java.util.List;
+
+/**
+ * 通道信息的抽象类,用以实时储存、管理、改变、查询通道的状态
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@Getter
+@Setter
+public abstract class BaseProtocolTunnel<T extends BaseTunnelInfo,E extends BaseProtocolBuilder> {
+
+
+	protected Logger log;
+
+
+	protected E protocolBuilder;
+	/**
+	 * 该通道的信息及其负责采集的点位
+	 */
+	@JSONField(serialize = false)
+	protected T tunnelInfo;
+	/**
+	 * 通道的ID
+	 */
+	protected String tunnelId;
+	/**
+	 * 通道的类型
+	 */
+	protected TunnelType tunnelType;
+	/**
+	 * 连接接信息
+	 */
+	@JSONField(serialize = false)
+	protected List<InetSocketAddress> connectors;
+	/**
+	 * 通道容器
+	 */
+	protected ProtocolTunnelContainer protocolTunnelContainer = ProtocolTunnelContainer.getInstance();
+	/**
+	 * 通道的状态
+	 */
+	private TunnelStatus tunnelStatus;
+
+	/**
+	 * 顶层的构造方法
+	 *
+	 * @param tunnelInfo 通道信息
+	 */
+	public BaseProtocolTunnel(T tunnelInfo) {
+		this.tunnelInfo = tunnelInfo;
+		this.tunnelType = tunnelInfo.getTunnelType();
+		this.tunnelId = tunnelInfo.getId();
+		this.log = TunnelLoggerFactory.getLogger(tunnelId, tunnelInfo.getTunnelName());
+	}
+
+	/**
+	 * 开启通道通讯
+	 *
+	 * @return AbstractDataGather abstract data gather
+	 * @throws DataExchangeException 抛出自定义的异常
+	 */
+	public abstract BaseProtocolTunnel startTunnel() throws DataExchangeException;
+
+	/**
+	 * 创建不同协议对应的通道
+	 *
+	 * @return AbstractDataGather abstract data gather
+	 * @throws DataExchangeException 抛出自定义的异常
+	 */
+	public abstract BaseProtocolTunnel buildTunnel() throws DataExchangeException;
+
+
+	/**
+	 * 更新通道信息
+	 *
+	 * @param tunnelInfo 通道信息
+	 * @return 实体本身
+	 */
+	public BaseProtocolTunnel refreshTunnelInfo(T tunnelInfo) {
+		log.info("刷新{}的通道信息及点位", tunnelInfo.getTunnelName());
+		this.tunnelInfo = tunnelInfo;
+		this.setLog(TunnelLoggerFactory.getLogger(tunnelId, tunnelInfo.getTunnelName()));
+		return this;
+	}
+
+	/**
+	 * 解析采集数据点表
+	 *
+	 * @throws DataExchangeException 自定义异常
+	 */
+	public abstract void parseGatherDataPoint() throws DataExchangeException;
+
+
+	/**
+	 * 通道停止
+	 * 停止 关闭通道
+	 *
+	 * @return {@link BaseProtocolTunnel}
+	 * @throws DataExchangeException 数据交换异常
+	 */
+	public BaseProtocolTunnel tunnelStop() throws DataExchangeException {
+		setTunnelStatus(TunnelStatus.ACTIVECLOSE);
+		TunnelLoggerFactory.stop(log);
+		return this;
+	}
+
+
+	public JSONObject getJSONObj() {
+		JSONObject jsonObject = new JSONObject();
+		jsonObject.put("tunnelId", this.tunnelId);
+		jsonObject.put("tunnelName", this.tunnelInfo.getTunnelName());
+		jsonObject.put("tunnelType", this.tunnelType);
+		jsonObject.put("tunnelStatus", this.tunnelStatus);
+		return jsonObject;
+	}
+
+}

+ 22 - 0
src/main/java/com/syjy/tunnelworker/gathers/DataGatherInterface.java

@@ -0,0 +1,22 @@
+package com.syjy.tunnelworker.gathers;
+
+import com.syjy.DataExchangeException;
+
+/**
+ * 通过接口采集数据的抽象类
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+public interface DataGatherInterface {
+
+
+	/**
+	 * 该通道通过自身协议采集负责点位的数据并存入缓存池
+	 *
+	 * @throws DataExchangeException 自定义异常
+	 */
+	void getDataFromProtocol() throws DataExchangeException;
+
+
+}

+ 200 - 0
src/main/java/com/syjy/tunnelworker/gathers/iml/AbstractModbusDataGather.java

@@ -0,0 +1,200 @@
+package com.syjy.tunnelworker.gathers.iml;
+
+import com.alibaba.fastjson.JSON;
+import com.syjy.DataExchangeException;
+import com.syjy.container.ProtocolDataContainer;
+import com.syjy.tunnelinfo.BaseTunnelInfo;
+import com.syjy.tunnelinfo.DataPoint;
+import com.syjy.tunnelworker.BaseProtocolTunnel;
+import com.syjy.tunnelworker.gathers.DataGatherInterface;
+import lombok.Getter;
+import lombok.Setter;
+import wei.yigulu.modbus.domain.FunctionCode;
+import wei.yigulu.modbus.domain.Obj4RequestCoil;
+import wei.yigulu.modbus.domain.Obj4RequestRegister;
+import wei.yigulu.modbus.domain.datatype.BooleanModbusDataInRegister;
+import wei.yigulu.modbus.domain.datatype.IModbusDataType;
+import wei.yigulu.modbus.domain.datatype.ModbusDataTypeEnum;
+import wei.yigulu.modbus.domain.datatype.NumericModbusData;
+import wei.yigulu.modbus.exceptiom.ModbusException;
+import wei.yigulu.modbus.utils.ModbusRequestDataUtils;
+import wei.yigulu.netty.BaseProtocolBuilder;
+import wei.yigulu.netty.MasterInterface;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 抽象的modbus数据获取
+ *
+ * @author: xiuwei
+ * @version:
+ */
+@Setter
+@Getter
+public abstract class AbstractModbusDataGather<T extends BaseTunnelInfo, E extends BaseProtocolBuilder> extends BaseProtocolTunnel<T, E> implements DataGatherInterface {
+
+    int slaveId;
+
+    FunctionCode functionCode;
+    /**
+     * 通道点位  ---- 点位对象
+     */
+    Map<Integer, Map<Integer, DataPoint>> dataPointMap = new HashMap<>();
+    /**
+     * 0: 线圈   1:寄存器
+     */
+    private int functionFlag;
+    /**
+     * 向modbus slave端发送的请求
+     */
+    private Map<Integer, List<Obj4RequestRegister>> requestRegisterListMap = new HashMap<>();
+    /**
+     * 向modbus slave端发送线圈的请求
+     */
+    private Map<Integer, List<Obj4RequestCoil>> requestCoilListMap = new HashMap<>();
+
+    protected E protocolBuilder;
+
+    /**
+     * 连续异常的次数
+     */
+    protected int consecutiveExceptions;
+
+
+    /**
+     * 顶层的构造方法
+     *
+     * @param tunnelInfo 通道信息
+     */
+    public AbstractModbusDataGather(T tunnelInfo) {
+        super(tunnelInfo);
+    }
+
+    @Override
+    public void parseGatherDataPoint() throws DataExchangeException {
+        log.info("解析该通道下所管理的点位");
+        List<DataPoint> dataPoints = tunnelInfo.getDataPoints();
+        if (dataPoints == null || dataPoints.size() == 0) {
+            return;
+        }
+        dataPoints.removeIf((o) -> o.getProtocolPoint() == null);
+        if (getFunctionCode().getCode() == 1 || getFunctionCode().getCode() == 2) {
+            setFunctionFlag(0);
+        } else {
+            setFunctionFlag(1);
+        }
+
+        Map<Integer, Map<Integer, ModbusDataTypeEnum>> points = new HashMap<>();
+        Map<Integer, List<Integer>> coilPoints = new HashMap<>();
+        List<Integer> coilList;
+        Map<Integer, ModbusDataTypeEnum> registerMap;
+        setDataPointMap(new HashMap<>());
+        if (getFunctionFlag() == 0) {
+            for (DataPoint d : dataPoints) {
+                add2DataPointMap(d);
+                if (d.getSlaveId() == null) {
+                    d.setSlaveId(getSlaveId());
+                }
+                if (coilPoints.containsKey(d.getSlaveId())) {
+                    coilPoints.get(d.getSlaveId()).add(d.getProtocolPoint());
+                } else {
+                    coilList = new ArrayList<>();
+                    coilList.add((d.getProtocolPoint()));
+                    coilPoints.put(d.getSlaveId(), coilList);
+                }
+            }
+            try {
+                for (Map.Entry<Integer, List<Integer>> e : coilPoints.entrySet()) {
+                    getRequestCoilListMap().put(e.getKey(), ModbusRequestDataUtils.splitModbusRequest(e.getValue(), e.getKey(), getFunctionCode()));
+                }
+            } catch (ModbusException e) {
+                throw new DataExchangeException(e.getCode(), e.getMsg());
+            }
+        } else {
+            for (DataPoint d : dataPoints) {
+                add2DataPointMap(d);
+                if (d.getSlaveId() == null) {
+                    d.setSlaveId(getSlaveId());
+                }
+                if (points.containsKey(d.getSlaveId())) {
+                    points.get(d.getSlaveId()).put(d.getProtocolPoint(), d.getDataType());
+                } else {
+                    registerMap = new HashMap<>();
+                    registerMap.put(d.getProtocolPoint(), d.getDataType());
+                    points.put(d.getSlaveId(), registerMap);
+                }
+            }
+            try {
+                for (Map.Entry<Integer, Map<Integer, ModbusDataTypeEnum>> e : points.entrySet()) {
+                    getRequestRegisterListMap().put(e.getKey(), ModbusRequestDataUtils.splitModbusRequest(e.getValue(), e.getKey(), getFunctionCode()));
+                }
+            } catch (ModbusException e) {
+                throw new DataExchangeException(e.getCode(), e.getMsg());
+            }
+            log.info("解析该通道下所管理的点位完成");
+        }
+    }
+
+
+    /**
+     * 向点表和数据池id的映射map添加值
+     *
+     * @param d d
+     */
+    public void add2DataPointMap(DataPoint d) {
+        if (d.getSlaveId() == null) {
+            d.setSlaveId(getSlaveId());
+        }
+        if (dataPointMap.containsKey(d.getSlaveId())) {
+            dataPointMap.get(d.getSlaveId()).put(d.getProtocolPoint(), d);
+        } else {
+            Map<Integer, DataPoint> map = new HashMap<>();
+            map.put(d.getProtocolPoint(), d);
+            dataPointMap.put(d.getSlaveId(), map);
+        }
+    }
+
+
+
+    @Override
+    public void getDataFromProtocol() throws DataExchangeException {
+        log.info("获取到从通道中获取的点位");
+        try {
+            if (getFunctionFlag() == 1 && getRequestRegisterListMap() != null) {
+                Map<Integer, IModbusDataType> map = null;
+                for (Map.Entry<Integer, List<Obj4RequestRegister>> e : getRequestRegisterListMap().entrySet()) {
+                    map = ModbusRequestDataUtils.getRegisterData((MasterInterface) protocolBuilder, e.getValue());
+                    log.info(JSON.toJSONString(map));
+                    for (Integer i : map.keySet()) {
+                        if (map.get(i) instanceof NumericModbusData) {
+                            ProtocolDataContainer.getInstance().putNumber(getDataPointMap().get(e.getKey()).get(i).getId(), ((NumericModbusData) map.get(i)).getValue().multiply(BigDecimal.valueOf(getDataPointMap().get(e.getKey()).get(i).getMag())));
+                        } else {
+                            ProtocolDataContainer.getInstance().putBoolean(getDataPointMap().get(e.getKey()).get(i).getId(), ((BooleanModbusDataInRegister) map.get(i)).getValue(0));
+                        }
+                    }
+                }
+            } else if (getFunctionFlag() == 0 && getRequestRegisterListMap() != null) {
+                Map<Integer, Boolean> map = null;
+                for (Map.Entry<Integer, List<Obj4RequestCoil>> e : getRequestCoilListMap().entrySet()) {
+                    map = ModbusRequestDataUtils.getCoilData((MasterInterface) protocolBuilder, e.getValue());
+                    log.info(JSON.toJSONString(map));
+                    for (Integer i : map.keySet()) {
+                        ProtocolDataContainer.getInstance().putBoolean(getDataPointMap().get(e.getKey()).get(i).getId(), map.get(i));
+                    }
+                }
+            }
+
+        } catch (ModbusException e) {
+            throw new DataExchangeException(e.getCode(), e.getMsg());
+        } catch (Exception e) {
+
+        }
+
+    }
+
+
+}

+ 101 - 0
src/main/java/com/syjy/tunnelworker/gathers/iml/ModbusRtuDataGather.java

@@ -0,0 +1,101 @@
+package com.syjy.tunnelworker.gathers.iml;
+
+import com.syjy.DataExchangeException;
+import com.syjy.tunnelinfo.TunnelStatus;
+import com.syjy.tunnelinfo.gathertunnelinfo.GatherModbusRtuTunnelInfo;
+import com.syjy.tunnelworker.BaseProtocolTunnel;
+import com.syjy.tunnelworker.workassist.SingleThreadPoolExecutorUtil;
+import wei.yigulu.modbus.domain.FunctionCode;
+import wei.yigulu.modbus.netty.ModbusRtuMasterBuilder;
+import wei.yigulu.purejavacomm.PureJavaCommChannelOption;
+
+/**
+ * modbus RTU 采集通道
+ *
+ * @author: xiuwei
+ * @version: 3.0
+ */
+public class ModbusRtuDataGather extends AbstractModbusDataGather<GatherModbusRtuTunnelInfo, ModbusRtuMasterBuilder> {
+
+
+    /**
+     * 构造方法
+     *
+     * @param gatherModbusRtuTunnelInfo 通道信息
+     */
+    public ModbusRtuDataGather(final GatherModbusRtuTunnelInfo gatherModbusRtuTunnelInfo) {
+        super(gatherModbusRtuTunnelInfo);
+    }
+
+    @Override
+    public ModbusRtuDataGather buildTunnel() throws DataExchangeException {
+        protocolBuilder = new ModbusRtuMasterBuilder(this.tunnelInfo.getSerialName());
+        protocolBuilder.setReadTimeOut(100);
+        protocolBuilder.getOrCreateBootstrap().option(PureJavaCommChannelOption.DTR, true);
+        protocolBuilder.getOrCreateBootstrap().option(PureJavaCommChannelOption.RTS, true);
+        protocolBuilder.setBaudRate(tunnelInfo.getBaudRate());
+        protocolBuilder.setDataBits(tunnelInfo.getDataBits());
+        protocolBuilder.setStopBits(tunnelInfo.getStopBits());
+        protocolBuilder.setParity(tunnelInfo.getParity());
+        protocolBuilder.setLog(this.log);
+        setSlaveId(this.tunnelInfo.getSlaveId());
+        setFunctionCode(FunctionCode.valueOf(this.tunnelInfo.getFunctionCode()));
+        setTunnelStatus(TunnelStatus.BUILT);
+        protocolTunnelContainer.addTunnel(this);
+        parseGatherDataPoint();
+        log.info("成功创建ModbusRTUMaster通道对象:{}", this.tunnelInfo.getTunnelName());
+        return this;
+    }
+
+
+    @Override
+    public ModbusRtuDataGather startTunnel() throws DataExchangeException {
+        log.info("modbus RTU master {} 通道开始连接", getTunnelInfo().getTunnelName());
+        protocolTunnelContainer.addUpdateDateTask(this);
+        try {
+            SingleThreadPoolExecutorUtil.executeBySingleThreadExecutor(() -> {
+                try {
+                    this.protocolBuilder.create();
+                    log.error("Modbus Rtu Master  创建通道失败");
+                    setTunnelStatus(TunnelStatus.LISTENSERIALFAIL);
+                } catch (Exception e) {
+                    log.error("Modbus Rtu Master  创建通道失败", e);
+                    setTunnelStatus(TunnelStatus.LISTENSERIALFAIL);
+                }
+            });
+        } catch (Exception e) {
+            log.error("Modbus Rtu Master 创建通道失败", e);
+            setTunnelStatus(TunnelStatus.LISTENSERIALFAIL);
+            throw new DataExchangeException(10007, "Modbus Rtu Master创建通道失败");
+        }
+        if (!TunnelStatus.LISTENSERIALFAIL.equals(getTunnelStatus())) {
+            setTunnelStatus(TunnelStatus.LISTENSERIALSUCCESS);
+        }
+        return this;
+    }
+
+    @Override
+    public void getDataFromProtocol() throws DataExchangeException {
+        try {
+            super.getDataFromProtocol();
+            this.consecutiveExceptions = 0;
+            this.setTunnelStatus(TunnelStatus.LISTENSERIALSUCCESS);
+        } catch (Exception e) {
+            this.consecutiveExceptions++;
+            if (consecutiveExceptions > 10) {
+                this.setTunnelStatus(TunnelStatus.LISTENSERIALSUCCESSANDCOMMERROR);
+            }
+        }
+    }
+
+    @Override
+    public BaseProtocolTunnel tunnelStop() throws DataExchangeException {
+        if (this.protocolBuilder != null) {
+            this.protocolBuilder.stop();
+        }
+        log.info("关闭 Modbus Rtu Master 通道 {}", this.tunnelInfo.getTunnelName());
+        return super.tunnelStop();
+    }
+}
+
+

+ 118 - 0
src/main/java/com/syjy/tunnelworker/gathers/iml/ModbusTcpDataGather.java

@@ -0,0 +1,118 @@
+package com.syjy.tunnelworker.gathers.iml;
+
+
+import com.syjy.DataExchangeException;
+import com.syjy.tunnelinfo.TunnelStatus;
+import com.syjy.tunnelinfo.gathertunnelinfo.GatherModbusTcpTunnelInfo;
+import com.syjy.tunnelworker.BaseProtocolTunnel;
+import com.syjy.tunnelworker.workassist.SingleThreadPoolExecutorUtil;
+import wei.yigulu.modbus.domain.FunctionCode;
+import wei.yigulu.modbus.netty.HSModbusTcpMasterBuilder;
+
+/**
+ * modbus协议TCP数据采集
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+
+public class ModbusTcpDataGather extends AbstractModbusDataGather<GatherModbusTcpTunnelInfo, HSModbusTcpMasterBuilder> {
+
+	/**
+	 * 构造方法
+	 *
+	 * @param gatherModbusTcpTunnelInfo 通道信息
+	 */
+	public ModbusTcpDataGather(final GatherModbusTcpTunnelInfo gatherModbusTcpTunnelInfo) {
+		super(gatherModbusTcpTunnelInfo);
+	}
+
+	@Override
+	public ModbusTcpDataGather buildTunnel() throws DataExchangeException {
+		protocolBuilder = new ModbusMaster(tunnelInfo.getRemoteIp(), tunnelInfo.getRemotePort());
+		protocolBuilder.setSpareIp(tunnelInfo.getRemoteSpareIp());
+		protocolBuilder.setSparePort(tunnelInfo.getRemoteSparePort());
+		protocolBuilder.setSelfIp(tunnelInfo.getSelfIp());
+		protocolBuilder.setSelfPort(tunnelInfo.getSelfPort());
+		protocolBuilder.setLog(this.log);
+		setSlaveId(this.tunnelInfo.getSlaveId());
+		setFunctionCode(FunctionCode.valueOf(this.tunnelInfo.getFunctionCode()));
+		setTunnelStatus(TunnelStatus.BUILT);
+		protocolTunnelContainer.addTunnel(this);
+		parseGatherDataPoint();
+		log.info("成功创建ModbusTCPMaster通道对象:{}", this.tunnelInfo.getTunnelName());
+		return this;
+	}
+
+
+
+	@Override
+	public ModbusTcpDataGather startTunnel() throws DataExchangeException {
+		log.info("modbus TCP master {} 通道开始连接", getTunnelInfo().getTunnelName());
+		protocolTunnelContainer.addUpdateDateTask(this);
+		try {
+			SingleThreadPoolExecutorUtil.executeBySingleThreadExecutor(() -> {
+				try {
+					this.protocolBuilder.create();
+					log.error("modbus TCP master  创建通道失败");
+					setTunnelStatus(TunnelStatus.LISTENPORTFAIL);
+				} catch (Exception e) {
+					log.error("modbus TCP master  创建通道失败", e);
+					setTunnelStatus(TunnelStatus.LISTENPORTFAIL);
+				}
+			});
+		} catch (Exception e) {
+			log.error("modbus TCP master 创建通道失败", e);
+			setTunnelStatus(TunnelStatus.LISTENPORTFAIL);
+			throw new DataExchangeException(10007, "modbus TCP master创建通道失败");
+		}
+		if (!TunnelStatus.LISTENPORTFAIL.equals(getTunnelStatus())) {
+			setTunnelStatus(TunnelStatus.LISTENPORTSUCCESS);
+		}
+		return this;
+	}
+
+
+	@Override
+	public BaseProtocolTunnel tunnelStop() throws DataExchangeException {
+		if (this.protocolBuilder != null) {
+			this.protocolBuilder.stop();
+		}
+		log.info("关闭 modbus TCP master 通道 {}", this.tunnelInfo.getTunnelName());
+		return super.tunnelStop();
+	}
+
+
+	@Override
+	public void getDataFromProtocol() throws DataExchangeException {
+		try {
+			super.getDataFromProtocol();
+			this.consecutiveExceptions=0;
+			this.setTunnelStatus(TunnelStatus.CONNECTED);
+		}catch (Exception e){
+			this.consecutiveExceptions++;
+			if(consecutiveExceptions>10) {
+				this.setTunnelStatus(TunnelStatus.CONNECTEDANDCOMMERROR);
+			}
+		}
+	}
+
+
+	class ModbusMaster extends HSModbusTcpMasterBuilder {
+
+		public ModbusMaster(String ip, Integer port) {
+			super(ip, port);
+		}
+
+		@Override
+		public void connected() {
+			setTunnelStatus(TunnelStatus.CONNECTED);
+		}
+
+		@Override
+		public void disconnected() {
+			setTunnelStatus(TunnelStatus.LOSECONN);
+		}
+	}
+
+}

+ 11 - 0
src/main/java/com/syjy/tunnelworker/package-info.java

@@ -0,0 +1,11 @@
+/**
+ * 通道的工作者
+ * 包括发送者和接受者
+ * 以及发送者和接收者的接口
+ * workassist 是通道创建使用过程中所需的辅助类
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+package com.syjy.tunnelworker;
+

+ 77 - 0
src/main/java/com/syjy/tunnelworker/workassist/SingleThreadPoolExecutorUtil.java

@@ -0,0 +1,77 @@
+package com.syjy.tunnelworker.workassist;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+
+import java.util.concurrent.*;
+
+/**
+ * 用以获取单个线程的线程池以执行单个的线程任务
+ *
+ * @author: xiuwei
+ * @version:
+ */
+public class SingleThreadPoolExecutorUtil {
+
+	/**
+	 * 线程工厂
+	 */
+	private static final ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("SingleThreadExecutor-1").build();
+
+
+	/**
+	 * 获取执行线程  只允许有一个线程 提交的其他任务也会被拒绝
+	 * 线程池可自行销毁
+	 *
+	 * @return 执行线程池 ExecutorService
+	 */
+	public static ExecutorService getSingleThreadExecutor() {
+		return new ThreadPoolExecutor(0, 1,
+				30L, TimeUnit.MILLISECONDS,
+				new LinkedBlockingQueue<>(1), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
+	}
+
+
+	/**
+	 * 以单线程池的方式提交线程任务
+	 *
+	 * @param callable 线程任务
+	 * @return 执行的任务
+	 */
+	public static <T> Future<T> submitBySingleThreadExecutor(Callable<T> callable) {
+		return getSingleThreadExecutor().submit(callable);
+
+	}
+
+
+	/**
+	 * 以单线程池的方式执行线程任务
+	 *
+	 * @param runnable 线程任务
+	 */
+	public static void executeBySingleThreadExecutor(Runnable runnable) {
+		getSingleThreadExecutor().execute(runnable);
+	}
+
+
+	/**
+	 * 以单线程池的方式提交线程任务
+	 *
+	 * @param runnable 线程任务
+	 * @return 执行的任务
+	 */
+	public static Future submitBySingleThreadExecutor(Runnable runnable) {
+		return getSingleThreadExecutor().submit(runnable);
+	}
+
+	/**
+	 * 以单独的线程执行该任务  异常可以外抛
+	 *
+	 * @param callable 线程执行任务
+	 * @return {@link FutureTask}
+	 */
+	public static FutureTask executeByThread(Callable callable) {
+		FutureTask future = new FutureTask(callable);
+		new Thread(future).start();
+		return future;
+	}
+}

+ 36 - 0
src/main/java/com/syjy/tunnelworker/workassist/TunnelBuilder.java

@@ -0,0 +1,36 @@
+package com.syjy.tunnelworker.workassist;
+
+
+import com.syjy.DataExchangeException;
+import com.syjy.tunnelinfo.BaseTunnelInfo;
+import com.syjy.tunnelinfo.gathertunnelinfo.GatherModbusRtuTunnelInfo;
+import com.syjy.tunnelinfo.gathertunnelinfo.GatherModbusTcpTunnelInfo;
+import com.syjy.tunnelworker.BaseProtocolTunnel;
+import com.syjy.tunnelworker.gathers.iml.ModbusRtuDataGather;
+import com.syjy.tunnelworker.gathers.iml.ModbusTcpDataGather;
+
+/**
+ * 用Tunnel Info 创建通达
+ *
+ * @author: xiuwei
+ * @version:
+ */
+public class TunnelBuilder {
+
+	public static final Object RXTXSYNCLOCK = new Object();
+
+	public static BaseProtocolTunnel buildTunnel(BaseTunnelInfo tunnelInfo) throws DataExchangeException {
+		switch (tunnelInfo.getTunnelType()) {
+			case MODBUSRTUMASTER:
+				return new ModbusRtuDataGather((GatherModbusRtuTunnelInfo) tunnelInfo).buildTunnel();
+			case MODBUSTCPMASTER:
+				return new ModbusTcpDataGather((GatherModbusTcpTunnelInfo) tunnelInfo).buildTunnel();
+			default:
+				return null;
+		}
+	}
+
+
+}
+
+

BIN
src/main/resources/com/syjy/favicon.ico


+ 163 - 0
src/main/resources/com/syjy/master.html

@@ -0,0 +1,163 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>104Master</title>
+</head>
+<style>
+    td {
+        width: 90px;
+        text-align: center;
+        border: 1px black solid;
+    }
+
+    td:nth-child(2), td:nth-child(4), td:nth-child(6), td:nth-child(8) {
+        background: #ff2885;
+    }
+
+    td:nth-child(1), td:nth-child(3), td:nth-child(5), td:nth-child(7) {
+        background: #3db7aa;
+    }
+
+    .th td {
+        background: white !important;
+    }
+
+    input:focus {
+        border-style: none;
+        outline: none;
+    }
+</style>
+<body>
+<div>
+    <table onselectstart="return false" style="margin-left: 10px;margin-top: 20px">
+        <thead>
+        <tr><td colspan="8">采集数据表格</td></tr>
+        </thead>
+        <tbody id="wt">
+        <tr>
+            <td>点位</td>
+            <td>值</td>
+            <td>点位</td>
+            <td>值</td>
+            <td>点位</td>
+            <td>值</td>
+            <td>点位</td>
+            <td>值</td>
+        </tr>
+        </tbody>
+    </table>
+
+</div>
+</body>
+</html>
+<script>
+    function ajax() {
+        var ajaxData = {
+            type: arguments[0].type || "GET",
+            url: arguments[0].url || "",
+            async: arguments[0].async || "true",
+            data: arguments[0].data || null,
+            dataType: arguments[0].dataType || "text",
+            contentType: arguments[0].contentType || "application/x-www-form-urlencoded",
+            beforeSend: arguments[0].beforeSend || function () {
+            },
+            success: arguments[0].success || function () {
+            },
+            error: arguments[0].error || function () {
+            }
+        }
+        ajaxData.beforeSend()
+        var xhr = createxmlHttpRequest();
+        xhr.responseType = ajaxData.dataType;
+        xhr.open(ajaxData.type, ajaxData.url, ajaxData.async);
+        xhr.setRequestHeader("Content-Type", ajaxData.contentType);
+        xhr.setRequestHeader("Access-Control-Allow-Origin", "*");
+        xhr.send(convertData(ajaxData.data));
+        xhr.onreadystatechange = function () {
+            if (xhr.readyState == 4) {
+                if (xhr.status == 200) {
+                    ajaxData.success(xhr.response)
+                } else {
+                    ajaxData.error()
+                }
+            }
+        }
+    }
+
+    function createxmlHttpRequest() {
+        if (window.ActiveXObject) {
+            return new ActiveXObject("Microsoft.XMLHTTP");
+        } else if (window.XMLHttpRequest) {
+            return new XMLHttpRequest();
+        }
+    }
+
+    function convertData(data) {
+        if (typeof data === 'object') {
+            var convertResult = "";
+            for (var c in data) {
+                convertResult += c + "=" + data[c] + "&";
+            }
+            convertResult = convertResult.substring(0, convertResult.length - 1)
+            return convertResult;
+        } else {
+            return data;
+        }
+    }
+
+    var wTable = document.getElementById("wt")
+    var isInput = false;
+
+    function sendShortCommand() {
+        var shortCommandPoint = document.getElementById("shortCommandPoint").value
+        var shortCommandValue = document.getElementById("shortCommandValue").value
+        var commandResult = document.getElementById("commandResult")
+        ajax({
+            type: "GET",
+            url: "/sendShortCommand?point=" + shortCommandPoint + "&value=" + shortCommandValue,
+            success: function (msg) {
+                commandResult.innerHTML = msg
+            },
+            error: function () {
+                commandResult.innerHTML = "error"
+            }
+        })
+    }
+
+    function buildPage() {
+        ajax({
+            type: "GET",
+            url: "/getAllPoints",
+            success: function (msg) {
+                wTable.innerHTML = ""
+                var points = JSON.parse(msg);
+                var tr;
+                var td;
+                var i = 0;
+                for (var p in points) {
+                    if (i % 4 == 0) {
+                        tr = wTable.appendChild(document.createElement("tr"))
+                    }
+                    td = document.createElement("td");
+                    td.id = td.id = "tdp_" + p
+                    td.innerHTML = p;
+                    tr.appendChild(td);
+                    td = document.createElement("td");
+                    td.id = "td_" + p
+                    td.innerHTML = points[p]
+                    tr.appendChild(td);
+                    i++;
+                }
+            },
+            error: function () {
+                alert("error")
+            }
+        })
+    }
+
+    buildPage();
+
+    setInterval(buildPage, 1000)
+
+</script>

+ 40 - 0
src/main/resources/logback.xml

@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration scan="true" scanPeriod="60 seconds" debug="false">
+    <property name="APP_Name" value=""/>
+    <property name="Log_Dir" value="./"/>
+    <contextName>${APP_Name}</contextName>
+
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder charset="UTF-8">
+            <pattern>${APP_Name}-%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{35} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <appender name="InfoFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${Log_Dir}/logs/info.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <FileNamePattern>${Log_Dir}/logs/%d{yyyy-MM-dd}/info.%i.log</FileNamePattern>
+            <MaxHistory>30</MaxHistory>
+            <maxFileSize>100MB</maxFileSize>
+            <totalSizeCap>2GB</totalSizeCap>
+        </rollingPolicy>
+        <encoder charset="UTF-8" class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{35} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+
+    <!--<appender name="SocketError" class="com.hrhx.logger.SocketAppend"></appender>-->
+
+
+    <!--子节点<root>:它也是<loger>元素,但是它是根loger,是所有<loger>的上级。
+    只有一个level属性,因为name已经被命名为"root",且已经是最上级了。
+  level: 用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL和OFF,
+    不能设置为INHERITED或者同义词NULL。 默认是DEBUG。-->
+    <root level="DEBUG">
+        <appender-ref ref="STDOUT"/>
+        <appender-ref ref="InfoFile"/>
+        <!--<appender-ref ref="SocketE-->
+    </root>
+
+</configuration>

BIN
src/main/resources/pointconfig-example.xls


BIN
src/main/resources/pointconfig.xls


BIN
src/main/resources/pointconfig111.xls


+ 12 - 0
src/test/java/Test.java

@@ -0,0 +1,12 @@
+
+/**
+/**
+ * @author: xiuwei
+ * @version:
+ */
+public class Test {
+
+	public static void main(String[] args) throws Exception {
+
+	}
+}