weiyigulu преди 4 години
ревизия
429290b394
променени са 100 файла, в които са добавени 8426 реда и са изтрити 0 реда
  1. 116 0
      pom.xml
  2. 35 0
      protocol-all/pom.xml
  3. 84 0
      protocol-cdt/pom.xml
  4. 88 0
      protocol-cdt/src/main/java/wei/yigulu/cdt/cdtframe/AbstractCDTDataHandler.java
  5. 64 0
      protocol-cdt/src/main/java/wei/yigulu/cdt/cdtframe/AbstractCDTDataTransmitter.java
  6. 67 0
      protocol-cdt/src/main/java/wei/yigulu/cdt/cdtframe/BaseDateType.java
  7. 102 0
      protocol-cdt/src/main/java/wei/yigulu/cdt/cdtframe/BooleanDataType.java
  8. 140 0
      protocol-cdt/src/main/java/wei/yigulu/cdt/cdtframe/CDTFrameBean.java
  9. 40 0
      protocol-cdt/src/main/java/wei/yigulu/cdt/cdtframe/CDTType.java
  10. 173 0
      protocol-cdt/src/main/java/wei/yigulu/cdt/cdtframe/IntegerDataType.java
  11. 43 0
      protocol-cdt/src/main/java/wei/yigulu/cdt/netty/CDTMaster.java
  12. 37 0
      protocol-cdt/src/main/java/wei/yigulu/cdt/netty/CDTSlaver.java
  13. 80 0
      protocol-cdt/src/main/java/wei/yigulu/cdt/netty/MasterHandler.java
  14. 117 0
      protocol-cdt/src/main/java/wei/yigulu/cdt/netty/SlaverHandler.java
  15. 4 0
      protocol-cdt/src/main/java/wei/yigulu/cdt/package-info.java
  16. 24 0
      protocol-core/pom.xml
  17. 22 0
      protocol-core/src/main/java/wei/yigulu/connectfilterofslave/ConnectFilter.java
  18. 54 0
      protocol-core/src/main/java/wei/yigulu/connectfilterofslave/ConnectFilterManager.java
  19. 47 0
      protocol-core/src/main/java/wei/yigulu/netty/AbstractDelimiterHandler.java
  20. 104 0
      protocol-core/src/main/java/wei/yigulu/netty/AbstractHSTcpMasterBuilder.java
  21. 147 0
      protocol-core/src/main/java/wei/yigulu/netty/AbstractMasterBuilder.java
  22. 108 0
      protocol-core/src/main/java/wei/yigulu/netty/AbstractRtuModeBuilder.java
  23. 143 0
      protocol-core/src/main/java/wei/yigulu/netty/AbstractTcpMasterBuilder.java
  24. 185 0
      protocol-core/src/main/java/wei/yigulu/netty/AbstractTcpSlaverBuilder.java
  25. 39 0
      protocol-core/src/main/java/wei/yigulu/netty/BaseProtocolBuilder.java
  26. 76 0
      protocol-core/src/main/java/wei/yigulu/netty/HSConnectionListener.java
  27. 21 0
      protocol-core/src/main/java/wei/yigulu/netty/ProtocolChannelInitializer.java
  28. 41 0
      protocol-core/src/main/java/wei/yigulu/netty/RtuModeConnectionListener.java
  29. 54 0
      protocol-core/src/main/java/wei/yigulu/netty/SimpleTcpConnectionListener.java
  30. 246 0
      protocol-core/src/main/java/wei/yigulu/purejavacomm/DefaultPureJavaCommChannelConfig.java
  31. 172 0
      protocol-core/src/main/java/wei/yigulu/purejavacomm/PureJavaCommChannel.java
  32. 288 0
      protocol-core/src/main/java/wei/yigulu/purejavacomm/PureJavaCommChannelConfig.java
  33. 41 0
      protocol-core/src/main/java/wei/yigulu/purejavacomm/PureJavaCommChannelOption.java
  34. 30 0
      protocol-core/src/main/java/wei/yigulu/purejavacomm/PureJavaCommDeviceAddress.java
  35. 49 0
      protocol-core/src/main/java/wei/yigulu/threadpool/LocalThreadPool.java
  36. 7 0
      protocol-core/src/main/java/wei/yigulu/threadpool/package-info.java
  37. 66 0
      protocol-core/src/main/java/wei/yigulu/utils/CrcUtils.java
  38. 64 0
      protocol-core/src/main/java/wei/yigulu/utils/DataConvertor.java
  39. 139 0
      protocol-iec104/README.en.md
  40. 139 0
      protocol-iec104/README.md
  41. 71 0
      protocol-iec104/pom.xml
  42. 47 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/annotation/AsduType.java
  43. 320 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/apdumodel/Apdu.java
  44. 193 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/apdumodel/Asdu.java
  45. 94 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/apdumodel/Cot.java
  46. 69 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/apdumodel/Vsq.java
  47. 17 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/apdumodel/package-info.java
  48. 63 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/AbstractDataFrameType.java
  49. 167 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/BooleanType.java
  50. 173 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/NoQualityNormalizedIntegerType.java
  51. 204 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/NormalizedIntegerType.java
  52. 77 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/ProofreadTimeType.java
  53. 206 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/ShortFloatType.java
  54. 204 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/ShortIntegerType.java
  55. 43 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/SimpleDataFrameType.java
  56. 75 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/TotalSummonType.java
  57. 6 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/package-info.java
  58. 89 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/qualitydescription/IeAbstractQuality.java
  59. 56 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/qualitydescription/IeMeasuredQuality.java
  60. 7 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/qualitydescription/package-info.java
  61. 51 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/typemodel/IeBoolean.java
  62. 45 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/typemodel/IeFourBitInteger.java
  63. 77 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/typemodel/IeProofreadTime.java
  64. 52 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/typemodel/IeShortFloat.java
  65. 43 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/typemodel/IeShortInteger.java
  66. 65 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/typemodel/InformationBodyAddress.java
  67. 4 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/typemodel/package-info.java
  68. 125 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/container/AsduTypeAnnotationContainer.java
  69. 29 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/container/DataTypeClasses.java
  70. 93 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/container/Iec104Link.java
  71. 98 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/container/LinkContainer.java
  72. 5 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/container/package-info.java
  73. 30 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/exception/Iec104Exception.java
  74. 122 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/nettyconfig/AllCustomDelimiterHandler.java
  75. 40 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/nettyconfig/Iec104HSMasterBuilder.java
  76. 62 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/nettyconfig/Iec104SMasterBuilder.java
  77. 39 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/nettyconfig/Iec104SlaverBuilder.java
  78. 144 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/nettyconfig/Master104Handle.java
  79. 106 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/nettyconfig/Slave104Handle.java
  80. 120 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/nettyconfig/TechnicalTerm.java
  81. 6 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/nettyconfig/package-info.java
  82. 4 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/package-info.java
  83. 203 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/util/PropertiesReader.java
  84. 140 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/util/SendAndReceiveNumUtil.java
  85. 270 0
      protocol-iec104/src/main/java/wei/yigulu/iec104/util/SendDataFrameHelper.java
  86. BIN
      protocol-modbus/MODBUS通讯协议中文版.pdf
  87. 24 0
      protocol-modbus/README.md
  88. 68 0
      protocol-modbus/pom.xml
  89. 61 0
      protocol-modbus/src/main/java/wei/yigulu/modbus/domain/FunctionCode.java
  90. 143 0
      protocol-modbus/src/main/java/wei/yigulu/modbus/domain/ModbusSlaveDataContainer.java
  91. 50 0
      protocol-modbus/src/main/java/wei/yigulu/modbus/domain/datatype/BooleanModbusDataInCoil.java
  92. 65 0
      protocol-modbus/src/main/java/wei/yigulu/modbus/domain/datatype/BooleanModbusDataInRegister.java
  93. 10 0
      protocol-modbus/src/main/java/wei/yigulu/modbus/domain/datatype/CoilValue.java
  94. 40 0
      protocol-modbus/src/main/java/wei/yigulu/modbus/domain/datatype/IModbusDataType.java
  95. 84 0
      protocol-modbus/src/main/java/wei/yigulu/modbus/domain/datatype/ModbusDataTypeEnum.java
  96. 57 0
      protocol-modbus/src/main/java/wei/yigulu/modbus/domain/datatype/NumericModbusData.java
  97. 23 0
      protocol-modbus/src/main/java/wei/yigulu/modbus/domain/datatype/Register.java
  98. 27 0
      protocol-modbus/src/main/java/wei/yigulu/modbus/domain/datatype/RegisterValue.java
  99. 62 0
      protocol-modbus/src/main/java/wei/yigulu/modbus/domain/datatype/numeric/ABCD.java
  100. 62 0
      protocol-modbus/src/main/java/wei/yigulu/modbus/domain/datatype/numeric/CDAB.java

+ 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>wei.yigulu</groupId>
+    <artifactId>protocol</artifactId>
+    <version>1.0</version>
+    <packaging>pom</packaging>
+    <modules>
+        <module>protocol-core</module>
+        <module>protocol-iec104</module>
+        <module>protocol-cdt</module>
+        <module>protocol-modbus</module>
+        <module>protocol-all</module>
+    </modules>
+
+
+    <profiles>
+        <profile>
+            <id>disable-javadoc-doclint</id>
+            <activation>
+                <jdk>[1.8,)</jdk>
+            </activation>
+            <properties>
+                <additionalparam>-Xdoclint:none</additionalparam>
+            </properties>
+        </profile>
+    </profiles>
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <java.version>1.8</java.version>
+        <protocol.version>1.0</protocol.version>
+        <iec104.version>1.4.7</iec104.version>
+        <modbus.version>1.2.0</modbus.version>
+        <cdt.version>1.0.0</cdt.version>
+    </properties>
+    <distributionManagement>
+        <repository>
+            <id>jiayue-releases</id>
+            <name>Releases Repository of SmartGap</name>
+            <url>http://49.4.68.219:8888/nexus/content/repositories/jiayue-releases/</url>
+        </repository>
+        <snapshotRepository>
+            <id>jiayue-snapshots</id>
+            <name>Snapshots Repository of SmartGap</name>
+            <url>http://49.4.68.219:8888/nexus/content/repositories/jiayue-snapshots/</url>
+        </snapshotRepository>
+    </distributionManagement>
+    <build>
+        <resources>
+            <resource>
+                <directory>src/main/resources</directory>
+            </resource>
+        </resources>
+        <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-surefire-plugin</artifactId>
+                <version>2.18.1</version>
+                <configuration>
+                    <skipTests>true</skipTests>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+    <dependencies>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>1.16.20</version>
+        </dependency>
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-all</artifactId>
+            <version>4.1.6.Final</version>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <version>1.7.25</version>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+            <version>1.2.3</version>
+        </dependency>
+        <!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-core -->
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-core</artifactId>
+            <version>1.2.3</version>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>1.2.47</version>
+        </dependency>
+        <dependency>
+            <groupId>joda-time</groupId>
+            <artifactId>joda-time</artifactId>
+            <version>2.9.9</version>
+        </dependency>
+    </dependencies>
+
+</project>

+ 35 - 0
protocol-all/pom.xml

@@ -0,0 +1,35 @@
+<?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>
+    <parent>
+        <artifactId>protocol</artifactId>
+        <groupId>wei.yigulu</groupId>
+        <version>1.0</version>
+    </parent>
+
+
+    <packaging>jar</packaging>
+    <artifactId>protocol-all</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>wei.yigulu</groupId>
+            <artifactId>protocol-iec104</artifactId>
+            <version>${iec104.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>wei.yigulu</groupId>
+            <artifactId>protocol-modbus</artifactId>
+            <version>${modbus.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>wei.yigulu</groupId>
+            <artifactId>protocol-cdt</artifactId>
+            <version>${cdt.version}</version>
+        </dependency>
+    </dependencies>
+
+
+</project>

+ 84 - 0
protocol-cdt/pom.xml

@@ -0,0 +1,84 @@
+<?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">
+    <parent>
+        <artifactId>protocol</artifactId>
+        <groupId>wei.yigulu</groupId>
+        <version>1.0</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+
+    <artifactId>protocol-cdt</artifactId>
+    <version>${cdt.version}</version>
+    <dependencies>
+        <dependency>
+            <groupId>wei.yigulu</groupId>
+            <artifactId>protocol-core</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-source-plugin</artifactId>
+                <version>2.1</version>
+                <configuration>
+                    <attach>true</attach>
+                </configuration>
+                <executions>
+                    <execution>
+                        <phase>compile</phase>
+                        <goals>
+                            <goal>jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-javadoc-plugin</artifactId>
+                <version>2.10.2</version>
+                <configuration>
+                    <encoding>UTF-8</encoding>
+                    <aggregate>true</aggregate>
+                    <charset>UTF-8</charset>
+                    <docencoding>UTF-8</docencoding>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>attach-javadocs</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>jar</goal>
+                        </goals>
+                        <configuration>
+                            <additionalparam>-Xdoclint:none</additionalparam>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.sonatype.plugins</groupId>
+                <artifactId>nexus-staging-maven-plugin</artifactId>
+                <version>1.5.1</version>
+                <executions>
+                    <execution>
+                        <id>default-deploy</id>
+                        <phase>deploy</phase>
+                        <goals>
+                            <goal>deploy</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <serverId>ias-releases</serverId>
+                    <nexusUrl>https://maven.mangoautomation.net/</nexusUrl>
+                    <skipStaging>true</skipStaging>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 88 - 0
protocol-cdt/src/main/java/wei/yigulu/cdt/cdtframe/AbstractCDTDataHandler.java

@@ -0,0 +1,88 @@
+package wei.yigulu.cdt.cdtframe;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+
+/**
+ * CDT 数据的处理类
+ *
+ * @author: xiuwei
+ * @version:
+ */
+public abstract class AbstractCDTDataHandler {
+
+
+	@Setter
+	@Getter
+	@Accessors(chain = true)
+	protected Logger log = LoggerFactory.getLogger(this.getClass());
+
+	public void processFrame(CDTFrameBean frameBean) {
+		List<BaseDateType> dates = frameBean.getDates();
+		switch (frameBean.getCdtType()) {
+			//重要遥测
+			case IMPORTANTYC:
+				processImportantYc(dates);
+				break;
+			//次要遥测
+			case SECONDYC:
+				processSecondYc(dates);
+				break;
+			//一般遥测
+			case COMMONYC:
+				processCommonYc(dates);
+				break;
+			// 遥信
+			case YX:
+				processYx(dates);
+				break;
+			default:
+		}
+	}
+
+	/**
+	 * 处理重要遥测数据
+	 *
+	 * @param dates 数据帧集合
+	 */
+	protected abstract void processImportantYc(List<BaseDateType> dates);
+
+	/**
+	 * 处理次要遥测数据
+	 *
+	 * @param dates 数据帧集合
+	 */
+	protected abstract void processSecondYc(List<BaseDateType> dates);
+
+	/**
+	 * 处理一般遥测数据
+	 *
+	 * @param dates 数据帧集合
+	 */
+	protected abstract void processCommonYc(List<BaseDateType> dates);
+
+	/**
+	 * 处理遥信数据
+	 *
+	 * @param dates 数据帧集合
+	 */
+	protected abstract void processYx(List<BaseDateType> dates);
+
+
+	public void connected() {
+
+	}
+
+
+	public void disconnected() {
+
+	}
+
+
+}

+ 64 - 0
protocol-cdt/src/main/java/wei/yigulu/cdt/cdtframe/AbstractCDTDataTransmitter.java

@@ -0,0 +1,64 @@
+package wei.yigulu.cdt.cdtframe;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+
+/**
+ * CDT 数据的处理类
+ *
+ * @author: xiuwei
+ * @version:
+ */
+public abstract class AbstractCDTDataTransmitter {
+
+	@Setter
+	@Getter
+	@Accessors(chain = true)
+	protected Logger log = LoggerFactory.getLogger(this.getClass());
+
+	/**
+	 * 发送重要遥测
+	 *
+	 * @return 数据帧list
+	 */
+	public abstract List<CDTFrameBean> transmitImportantYc();
+
+	/**
+	 * 发送次要遥测
+	 *
+	 * @return 数据帧list
+	 */
+	public abstract List<CDTFrameBean> transmitSecondYc();
+
+	/**
+	 * 发送一般遥测
+	 *
+	 * @return 数据帧list
+	 */
+	public abstract List<CDTFrameBean> transmitCommonYc();
+
+	/**
+	 * 发送遥信
+	 *
+	 * @return 数据帧list
+	 */
+	public abstract List<CDTFrameBean> transmitYx();
+
+
+	public void connected() {
+
+	}
+
+
+	public void disconnected() {
+
+	}
+
+
+}

+ 67 - 0
protocol-cdt/src/main/java/wei/yigulu/cdt/cdtframe/BaseDateType.java

@@ -0,0 +1,67 @@
+package wei.yigulu.cdt.cdtframe;
+
+import io.netty.buffer.ByteBuf;
+import wei.yigulu.utils.CrcUtils;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Map;
+
+/**
+ * cdt数据类型的基类
+ *
+ * @author 修唯xiuwei
+ **/
+public abstract class BaseDateType<T> {
+
+	protected int functionNum;
+
+	Map<Integer, T> dates;
+
+	protected int crc;
+
+	public int getCrc() {
+		return crc;
+	}
+
+	public int getFunctionNum() {
+		return functionNum;
+	}
+
+	public void loadBytes(ByteBuf byteBuf) {
+		byte[] bs = new byte[5];
+		if (byteBuf.readableBytes() > 5) {
+			byteBuf.readBytes(bs);
+			this.functionNum = bs[0] & 0xff;
+			this.crc = byteBuf.readByte();
+			if ((this.crc & 0xff) == CrcUtils.generateCRC8(bs)) {
+				readDates(Arrays.copyOfRange(bs, 1, bs.length));
+			}
+		}
+	}
+
+	/**
+	 * 各子类根据自身的属性 进行赋值
+	 *
+	 * @param bs 字节数组
+	 */
+	public abstract void readDates(byte[] bs);
+
+
+	/**
+	 * 获取到帧内数据
+	 *
+	 * @return 数据点位   数据
+	 */
+	public abstract Map<Integer, T> getDates();
+
+
+	/**
+	 * 对数据库进行编码
+	 *
+	 * @param byteBuffer 字节缓冲串
+	 */
+	protected abstract void encode(ByteBuffer byteBuffer);
+
+
+}

+ 102 - 0
protocol-cdt/src/main/java/wei/yigulu/cdt/cdtframe/BooleanDataType.java

@@ -0,0 +1,102 @@
+package wei.yigulu.cdt.cdtframe;
+
+import lombok.NoArgsConstructor;
+import wei.yigulu.utils.CrcUtils;
+
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 遥信数据类型
+ *
+ * @author 修唯xiuwei
+ **/
+@NoArgsConstructor
+public class BooleanDataType extends BaseDateType<Boolean> {
+
+	/**
+	 * CDT协议00-f0是遥测  f0之后是遥信
+	 */
+	private static final Integer YXSTART = 0xf0;
+
+	/**
+	 * 构造方法  map中的数据必须是32个以内 且必须是连续的  不足32个   其他位皆视为 false
+	 * 如果数据不连续将可能在上送过程中改变被越过数据的原有值  可以不足32位 因为数据总量可能不足32的整数倍
+	 * 由于cdt协议中一个功能码中有32个布尔值  所有 最小的点位须为32的整数倍
+	 *
+	 * @param dates 数据  点位---值
+	 */
+	public BooleanDataType(Map<Integer, Boolean> dates) {
+		if (dates.size() == 0) {
+			throw new RuntimeException("数据个数不能为0");
+		}
+		if (dates.size() > 32) {
+			throw new RuntimeException("数据个数超过三十二个");
+		}
+		Set<Integer> set = dates.keySet();
+		if (Collections.min(set) % 32 != 0) {
+			throw new RuntimeException("数据点位不是以32的整数倍开头");
+		}
+		if ((Collections.max(set) - Collections.min(set)) != dates.size() - 1) {
+			throw new RuntimeException("数据点位不连续");
+		}
+		this.dates = dates;
+
+	}
+
+	@Override
+	public void readDates(byte[] bs) {
+		this.dates = new HashMap<>(32);
+		int num = (super.getFunctionNum() - YXSTART) * 32;
+		for (int i = 0; i < bs.length; i++) {
+			this.dates.put(num + i * 8, (bs[i] & 0x01) == 0x01);
+			this.dates.put(num + i * 8 + 1, (bs[i] & 0x02) == 0x02);
+			this.dates.put(num + i * 8 + 2, (bs[i] & 0x04) == 0x04);
+			this.dates.put(num + i * 8 + 3, (bs[i] & 0x08) == 0x08);
+			this.dates.put(num + i * 8 + 4, (bs[i] & 0x10) == 0x10);
+			this.dates.put(num + i * 8 + 5, (bs[i] & 0x20) == 0x20);
+			this.dates.put(num + i * 8 + 6, (bs[i] & 0x40) == 0x40);
+			this.dates.put(num + i * 8 + 7, (bs[i] & 0x80) == 0x80);
+		}
+	}
+
+	@Override
+	public Map<Integer, Boolean> getDates() {
+		return this.dates;
+	}
+
+	@Override
+	protected void encode(ByteBuffer byteBuffer) {
+		int min = Collections.min(getDates().keySet());
+		this.functionNum = min / 32 + YXSTART;
+		byte[] bytes = new byte[]{(byte) this.functionNum, 0, 0, 0, 0};
+		int position;
+		for (int i = 1; i < 5; i++) {
+			for (int j = 0; j < 8; j++) {
+				position = (i - 1) * 8 + j + min;
+				if (getDates().containsKey(position)) {
+					if (getDates().get(position)) {
+						bytes[i] = (byte) (bytes[i] | (1 << j));
+					}
+				}
+			}
+		}
+		byteBuffer.put(bytes);
+		byteBuffer.put((byte) CrcUtils.generateCRC8(bytes));
+	}
+
+
+	@Override
+	public String toString() {
+		String s = "";
+		if (this.dates != null) {
+			for (Integer i : this.getDates().keySet()) {
+				s += "遥信点位:" + i + ";值:" + this.getDates().get(i) + "\n";
+			}
+		}
+		return s;
+	}
+}

+ 140 - 0
protocol-cdt/src/main/java/wei/yigulu/cdt/cdtframe/CDTFrameBean.java

@@ -0,0 +1,140 @@
+package wei.yigulu.cdt.cdtframe;
+
+import io.netty.buffer.ByteBuf;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import wei.yigulu.utils.CrcUtils;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * cdt数据帧的模型
+ *
+ * @author 修唯xiuwei
+ **/
+@NoArgsConstructor
+@Getter
+public class CDTFrameBean {
+
+	/**
+	 * 同步字
+	 */
+	private final byte[] HEAD = new byte[]{(byte) 0xEB, (byte) 0x90, (byte) 0xEB, (byte) 0x90, (byte) 0xEB, (byte) 0x90};
+
+	/**
+	 * 控制字节 b7 默认0x71
+	 */
+	@Setter
+	private byte control = 0x71;
+
+	/**
+	 * 帧类别码 b8
+	 */
+	@Setter
+	private CDTType cdtType;
+
+	/**
+	 * 信息字数 b9  信息字的个数   即dates的长度
+	 */
+	private int num;
+
+	/**
+	 * 源地址 b10
+	 */
+	private int sourceAddress = 1;
+
+	/**
+	 * 目的地址 b11
+	 */
+	private int destinationAddress = 1;
+
+
+	/**
+	 * 校验码 b12
+	 */
+	private int crc;
+
+	private List<BaseDateType> dates = new ArrayList<>();
+
+	public CDTFrameBean(ByteBuf byteBuf) throws InstantiationException, IllegalAccessException {
+		loadBytes(byteBuf);
+	}
+
+	public CDTFrameBean(List<BaseDateType> dates) {
+		if (dates.size() > 0) {
+			this.dates = dates;
+			this.num = dates.size();
+			if (dates.get(0) instanceof IntegerDataType) {
+				this.cdtType = CDTType.COMMONYC;
+			} else {
+				this.cdtType = CDTType.YX;
+			}
+		}
+
+
+	}
+
+
+	public void loadBytes(ByteBuf byteBuf) throws IllegalAccessException, InstantiationException {
+		byte[] bs = new byte[5];
+		byteBuf.readBytes(bs);
+		if (this.control != bs[0]) {
+			return;
+		}
+		this.crc = byteBuf.readByte();
+		if ((this.crc & 0xff) != CrcUtils.generateCRC8(bs)) {
+			return;
+		}
+		this.cdtType = CDTType.getByNo(bs[1]);
+		if (this.cdtType == null) {
+			return;
+		}
+		this.num = Math.abs(bs[2]);
+		this.sourceAddress = bs[3];
+		this.destinationAddress = bs[4];
+		BaseDateType dateType;
+		this.dates = new ArrayList<>(this.num);
+		for (int i = 0; i < this.num; i++) {
+			dateType = (BaseDateType) this.cdtType.typeClass.newInstance();
+			dateType.loadBytes(byteBuf);
+			dates.add(dateType);
+		}
+	}
+
+
+	public byte[] encode() {
+		ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
+		byteBuffer.put(HEAD);
+		byte[] bs = new byte[]{this.control, (byte) this.cdtType.no, (byte) this.num, (byte) this.sourceAddress, (byte) this.destinationAddress};
+		byteBuffer.put(bs);
+		byteBuffer.put((byte) CrcUtils.generateCRC8(bs));
+		for (BaseDateType data : dates) {
+			data.encode(byteBuffer);
+		}
+		byte[] bytes = new byte[byteBuffer.position()];
+		byteBuffer.rewind();
+		byteBuffer.get(bytes);
+		return bytes;
+	}
+
+
+	@Override
+	public String toString() {
+
+		String s = "\n----------------------------------\n";
+		if (this.cdtType != null) {
+			s += "数据类型:" + this.cdtType.name + "\n";
+		}
+		s += "源地址:" + this.sourceAddress + "\n";
+		s += "目标地址:" + this.destinationAddress + "\n";
+		if (this.dates != null) {
+			for (BaseDateType<?> b : this.dates) {
+				s += b.toString();
+			}
+		}
+		return s;
+	}
+}

+ 40 - 0
protocol-cdt/src/main/java/wei/yigulu/cdt/cdtframe/CDTType.java

@@ -0,0 +1,40 @@
+package wei.yigulu.cdt.cdtframe;
+
+/**
+ * CDT 数据类型
+ *
+ * @author xiuwei
+ */
+public enum CDTType {
+
+
+	IMPORTANTYC("重要遥测", 0x61, IntegerDataType.class),
+	SECONDYC("次要遥测", 0xc2, IntegerDataType.class),
+	COMMONYC("一般遥测", 0xb3, IntegerDataType.class),
+	YX("遥信状态", 0xf4, BooleanDataType.class);
+
+	String name;
+	int no;
+	Class typeClass;
+
+	CDTType(String name, int no, Class typeClass) {
+		this.name = name;
+		this.no = no;
+		this.typeClass = typeClass;
+	}
+
+	public static CDTType getByNo(int no) {
+		switch (no & 0xff) {
+			case 0x61:
+				return IMPORTANTYC;
+			case 0xc2:
+				return SECONDYC;
+			case 0xb3:
+				return COMMONYC;
+			case 0xf4:
+				return YX;
+			default:
+				return null;
+		}
+	}
+}

+ 173 - 0
protocol-cdt/src/main/java/wei/yigulu/cdt/cdtframe/IntegerDataType.java

@@ -0,0 +1,173 @@
+package wei.yigulu.cdt.cdtframe;
+
+
+import lombok.Data;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import wei.yigulu.utils.CrcUtils;
+
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 遥测数据
+ *
+ * @author 修唯xiuwei
+ **/
+@NoArgsConstructor
+public class IntegerDataType extends BaseDateType<Integer> {
+
+	/**
+	 * 质量位描述的map
+	 */
+	@Getter
+	private Map<Integer, QualityDescription> qualityDescriptionMap;
+
+
+	/**
+	 * 整数数据类型
+	 * 构造方法  map中的数据必须是2个以内 且必须是连续的  不足2个   其他位皆视为 false
+	 * 如果数据不连续将可能在上送过程中改变被越过数据的原有值  可以不足2位 因为数据总量可能不足2的整数倍
+	 * 由于cdt协议中一个功能码中有2个整数值  所有 最小的点位须为2的整数倍
+	 *
+	 * @param dates     数据
+	 * @param qualities 品质
+	 */
+
+	public IntegerDataType(Map<Integer, Integer> dates, Map<Integer, QualityDescription> qualities) {
+		if (dates.size() == 0) {
+			throw new RuntimeException("数据个数不能为0");
+		}
+		if (dates.size() > 2) {
+			throw new RuntimeException("数据个数超过二个");
+		}
+		Set<Integer> set = dates.keySet();
+		if (Collections.min(set) % 2 != 0) {
+			throw new RuntimeException("数据点位不是以2的整数倍开头");
+		}
+		if ((Collections.max(set) - Collections.min(set)) != dates.size() - 1) {
+			throw new RuntimeException("数据点位不连续");
+		}
+
+		this.dates = dates;
+		this.qualityDescriptionMap = qualities == null ? new HashMap<>() : qualities;
+	}
+
+
+	@Override
+	public void readDates(byte[] bs) {
+		//功能码处于00H-7FH之间的是遥测  86H-89H是总加遥测 忽略总加遥测
+		//遥测二进制  b14位为1代表溢出,b15位为1 代表无效  有效数据位 为 b0-b10  b11为1时代表负数  以2的补码表述
+		if (getFunctionNum() <= 0x7f) {
+			this.dates = new HashMap<>(2);
+			this.qualityDescriptionMap = new HashMap<>(2);
+			this.dates.put(super.getFunctionNum() * 2, decode2Int(bs[0], bs[1]));
+			this.qualityDescriptionMap.put(super.getFunctionNum() * 2, new QualityDescription(bs[1]));
+			this.dates.put(super.getFunctionNum() * 2 + 1, decode2Int(bs[2], bs[3]));
+			this.qualityDescriptionMap.put(super.getFunctionNum() * 2 + 1, new QualityDescription(bs[3]));
+		}
+	}
+
+	/**
+	 * 转化成CDT的int型
+	 *
+	 * @param b1
+	 * @param b2
+	 * @return
+	 */
+	private Integer decode2Int(Byte b1, Byte b2) {
+		int i = (b1 & 0xff) | (b2 & 0x07) << 8;
+		if ((b2 >> 3 & 0x01) == 1) {
+			i = (2048 - i) * -1;
+		}
+		return i;
+	}
+
+
+	@Override
+	public Map<Integer, Integer> getDates() {
+		return this.dates;
+	}
+
+	@Override
+	protected void encode(ByteBuffer byteBuffer) {
+		int min = Collections.min(getDates().keySet());
+		int val;
+		int iVal;
+		this.functionNum = min / 2;
+		byte[] bytes = new byte[]{(byte) this.functionNum, 0, 0, 0, 0};
+		for (int i = 0; i < 2; i++) {
+			if (getDates().containsKey(min + i)) {
+				val = getDates().get(min + i);
+				if (val >= 00) {
+					//正数
+					bytes[2 * i + 1] = (byte) val;
+					bytes[2 * i + 2] = (byte) (((byte) (val >> 8)) & 07);
+				} else {
+					//负数
+					//清零前五位的数值
+					iVal = (val * -1) & 0x07ff;
+					//先减一再反转
+					iVal = 2048 - iVal;
+					bytes[2 * i + 1] = (byte) iVal;
+					bytes[2 * i + 2] = (byte) (iVal >> 8);
+					bytes[2 * i + 2] = (byte) (bytes[2 * i + 2] | (1 << 3));
+				}
+				if (val > 2047 || val < -2048) {
+					//溢出
+					bytes[2 * i + 2] = (byte) (bytes[2 * i + 2] | (1 << 6));
+				}
+				if (getQualityDescriptionMap().containsKey(min + i) &&
+						!getQualityDescriptionMap().get(min + i).getInvalid()) {
+					//无效
+					bytes[2 * i + 2] = (byte) (bytes[2 * i + 2] | (1 << 7));
+				}
+			}
+		}
+		byteBuffer.put(bytes);
+		byteBuffer.put((byte) CrcUtils.generateCRC8(bytes));
+	}
+
+
+	@Override
+	public String toString() {
+		String s = "";
+		if (this.dates != null) {
+			for (Integer i : this.getDates().keySet()) {
+				s += "遥测点位:" + i + ";值:" + this.getDates().get(i) + " " + getQualityDescriptionMap().get(i) + "\n";
+			}
+		}
+		return s;
+	}
+
+
+	@Data
+	class QualityDescription {
+		/**
+		 * 是否溢出 false 即为不溢出
+		 */
+		Boolean overflow = false;
+		/**
+		 * 是否无效 false 即为有效
+		 */
+		Boolean invalid = false;
+
+		public QualityDescription(Byte b) {
+			if ((b >> 6 & 0x01) == 1) {
+				this.overflow = true;
+			}
+			if ((b >> 7 & 0x01) == 1) {
+				this.invalid = true;
+			}
+		}
+
+		@Override
+		public String toString() {
+			return (overflow ? "溢出" : "") + " " + (invalid ? "无效" : "有效");
+		}
+
+	}
+}

+ 43 - 0
protocol-cdt/src/main/java/wei/yigulu/cdt/netty/CDTMaster.java

@@ -0,0 +1,43 @@
+package wei.yigulu.cdt.netty;
+
+
+import io.netty.buffer.Unpooled;
+import io.netty.handler.codec.DelimiterBasedFrameDecoder;
+import lombok.Getter;
+import wei.yigulu.cdt.cdtframe.AbstractCDTDataHandler;
+import wei.yigulu.netty.AbstractRtuModeBuilder;
+import wei.yigulu.netty.ProtocolChannelInitializer;
+import wei.yigulu.purejavacomm.PureJavaCommChannel;
+
+
+/**
+ * cdt读取端
+ *
+ * @author 修唯xiuwei
+ **/
+public class CDTMaster extends AbstractRtuModeBuilder {
+
+	private static final int MAXLEN = 10240;
+
+	private static final byte[] HEAD = new byte[]{(byte) 0xEB, (byte) 0x90, (byte) 0xEB, (byte) 0x90, (byte) 0xEB, (byte) 0x90};
+
+	@Getter
+	private final AbstractCDTDataHandler dataHandler;
+
+	public CDTMaster(String commPortId, AbstractCDTDataHandler dataHandler) {
+		super(commPortId);
+		this.dataHandler = dataHandler;
+	}
+
+
+	@Override
+	protected ProtocolChannelInitializer getOrCreateChannelInitializer() {
+		return new ProtocolChannelInitializer<PureJavaCommChannel>(this) {
+			@Override
+			protected void initChannel(PureJavaCommChannel ch) throws Exception {
+				ch.pipeline().addLast(new DelimiterBasedFrameDecoder(MAXLEN, Unpooled.copiedBuffer(HEAD)));
+				ch.pipeline().addLast(new MasterHandler((CDTMaster) builder));
+			}
+		};
+	}
+}

+ 37 - 0
protocol-cdt/src/main/java/wei/yigulu/cdt/netty/CDTSlaver.java

@@ -0,0 +1,37 @@
+package wei.yigulu.cdt.netty;
+
+import lombok.Getter;
+import wei.yigulu.cdt.cdtframe.AbstractCDTDataTransmitter;
+import wei.yigulu.netty.AbstractRtuModeBuilder;
+import wei.yigulu.netty.ProtocolChannelInitializer;
+import wei.yigulu.purejavacomm.PureJavaCommChannel;
+
+
+/**
+ * cdt
+ *
+ * @author: xiuwei
+ * @version:
+ */
+public class CDTSlaver extends AbstractRtuModeBuilder {
+
+	@Getter
+	private final AbstractCDTDataTransmitter dataTransmitter;
+
+	public CDTSlaver(String commPortId, AbstractCDTDataTransmitter dataTransmitter) {
+		super(commPortId);
+		this.dataTransmitter = dataTransmitter;
+	}
+
+
+	@Override
+	protected ProtocolChannelInitializer getOrCreateChannelInitializer() {
+		return new ProtocolChannelInitializer<PureJavaCommChannel>(this) {
+
+			@Override
+			protected void initChannel(PureJavaCommChannel ch) throws Exception {
+				ch.pipeline().addLast(new SlaverHandler((CDTSlaver) builder));
+			}
+		};
+	}
+}

+ 80 - 0
protocol-cdt/src/main/java/wei/yigulu/cdt/netty/MasterHandler.java

@@ -0,0 +1,80 @@
+package wei.yigulu.cdt.netty;
+
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.EventLoop;
+import io.netty.channel.SimpleChannelInboundHandler;
+import lombok.Data;
+import org.slf4j.Logger;
+import wei.yigulu.cdt.cdtframe.CDTFrameBean;
+import wei.yigulu.utils.DataConvertor;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 16进制报文解析工具
+ *
+ * @author xiuwei
+ */
+@Data
+public class MasterHandler extends SimpleChannelInboundHandler<ByteBuf> {
+
+	private static final int MINLEN = 6;
+	private Logger log;
+	private CDTMaster cdtMaster;
+
+	public MasterHandler(CDTMaster cdtMaster) {
+		this.cdtMaster = cdtMaster;
+		this.log = cdtMaster.getLog();
+	}
+
+	@Override
+	public void channelActive(ChannelHandlerContext ctx) throws Exception {
+		log.info("-----连接串口{}成功-----", this.cdtMaster.getCommPortId());
+		this.cdtMaster.getDataHandler().connected();
+	}
+
+	/**
+	 * channel断连及不稳定时调用的方法
+	 *
+	 * @param ctx 通道对象
+	 * @throws Exception 异常
+	 */
+	@Override
+	public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+		log.error("串口{}连接中断,正在启动重连机制... ", this.cdtMaster.getCommPortId());
+		//在客户端与服务端连接过程中如果断连,就会调用的方法
+		this.cdtMaster.getDataHandler().disconnected();
+		final EventLoop eventLoop = ctx.channel().eventLoop();
+		eventLoop.schedule((Callable) () -> {
+			log.info("正在重连串口{}", cdtMaster.getCommPortId());
+			cdtMaster.create();
+			return null;
+		}, 3L, TimeUnit.SECONDS);
+	}
+
+	/**
+	 * channel连接及传输报错时调用的方法
+	 *
+	 * @param ctx   通道上下文
+	 * @param cause 异常对象
+	 */
+	@Override
+	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+		cause.printStackTrace();
+		log.error(cause.getMessage());
+		log.error("串口异常消息:{}", cause.getMessage());
+	}
+
+	@Override
+	protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
+		log.info("接收到串口{}发来数据帧:" + DataConvertor.ByteBuf2String(msg), this.cdtMaster.getCommPortId());
+		if (msg.readableBytes() > MINLEN) {
+			CDTFrameBean cdtFrameBean = new CDTFrameBean(msg);
+			log.info(cdtFrameBean.toString());
+			this.cdtMaster.getDataHandler().processFrame(cdtFrameBean);
+		}
+	}
+}

+ 117 - 0
protocol-cdt/src/main/java/wei/yigulu/cdt/netty/SlaverHandler.java

@@ -0,0 +1,117 @@
+package wei.yigulu.cdt.netty;
+
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.EventLoop;
+import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.util.concurrent.ScheduledFuture;
+import lombok.Data;
+import org.slf4j.Logger;
+import wei.yigulu.cdt.cdtframe.CDTFrameBean;
+import wei.yigulu.utils.DataConvertor;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 16进制报文解析工具
+ *
+ * @author xiuwei
+ */
+@Data
+public class SlaverHandler extends SimpleChannelInboundHandler<ByteBuf> {
+	CDTSlaver cdtSlaver;
+	List<ScheduledFuture> scheduledFutures;
+	private Logger log;
+
+	public SlaverHandler(CDTSlaver cdtSlaver) {
+		this.cdtSlaver = cdtSlaver;
+		this.log = cdtSlaver.getLog();
+	}
+
+	@Override
+	public void channelActive(ChannelHandlerContext ctx) throws Exception {
+		log.info("-----连接串口{}成功-----", this.cdtSlaver.getCommPortId());
+		this.cdtSlaver.getDataTransmitter().connected();
+		this.scheduledFutures = new ArrayList<>();
+		scheduledFutures.add(ctx.channel().eventLoop().scheduleAtFixedRate(() -> {
+			write(cdtSlaver.getDataTransmitter().transmitImportantYc(), ctx);
+		}, 0, 3, TimeUnit.SECONDS));
+		//次要遥测 6秒一次
+		scheduledFutures.add(ctx.channel().eventLoop().scheduleAtFixedRate(() -> {
+			write(cdtSlaver.getDataTransmitter().transmitSecondYc(), ctx);
+		}, 0, 6, TimeUnit.SECONDS));
+		//一般遥测20秒一次
+		scheduledFutures.add(ctx.channel().eventLoop().scheduleAtFixedRate(() -> {
+			write(cdtSlaver.getDataTransmitter().transmitCommonYc(), ctx);
+		}, 0, 20, TimeUnit.SECONDS));
+		//遥信 量 间隙发送
+		scheduledFutures.add(ctx.channel().eventLoop().scheduleAtFixedRate(() -> {
+			write(cdtSlaver.getDataTransmitter().transmitYx(), ctx);
+		}, 0, 10, TimeUnit.SECONDS));
+	}
+
+	/**
+	 * 频道不活跃
+	 * channel断连及不稳定时调用的方法
+	 *
+	 * @param ctx 通道对象
+	 * @throws Exception 异常
+	 */
+	@Override
+	public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+		for (ScheduledFuture scheduledFuture : this.scheduledFutures) {
+			if (!scheduledFuture.isCancelled()) {
+				scheduledFuture.cancel(true);
+			}
+		}
+		this.cdtSlaver.getDataTransmitter().disconnected();
+		log.error("串口{}连接中断,正在启动重连机制... ", this.cdtSlaver.getCommPortId());
+		//在客户端与服务端连接过程中如果断连,就会调用的方法
+		final EventLoop eventLoop = ctx.channel().eventLoop();
+		eventLoop.schedule((Callable) () -> {
+			log.info("正在重连串口{}", cdtSlaver.getCommPortId());
+			cdtSlaver.create();
+			return null;
+		}, 3L, TimeUnit.SECONDS);
+
+	}
+
+	/**
+	 * channel连接及传输报错时调用的方法
+	 *
+	 * @param ctx   通道对象
+	 * @param cause 异常
+	 */
+	@Override
+	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+		cause.printStackTrace();
+		log.error(cause.getMessage());
+		log.error("串口异常消息:{}", cause.getMessage());
+	}
+
+	@Override
+	protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
+		log.info("接收到串口{}发来数据帧:" + DataConvertor.ByteBuf2String(msg), this.cdtSlaver.getCommPortId());
+	}
+
+
+	private void write(List<CDTFrameBean> list, ChannelHandlerContext ctx) {
+		try {
+			if (list != null && list.size() != 0) {
+				for (CDTFrameBean c : list) {
+					byte[] bb = c.encode();
+					log.info("向串口{}发送数据帧:" + DataConvertor.Byte2String(bb), this.cdtSlaver.getCommPortId());
+					ctx.writeAndFlush(Unpooled.copiedBuffer(bb));
+				}
+			}
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+
+	}
+}

+ 4 - 0
protocol-cdt/src/main/java/wei/yigulu/cdt/package-info.java

@@ -0,0 +1,4 @@
+/**
+ * cdt RTU  通讯所依赖的编解码  连接管理等依赖
+ */
+package wei.yigulu.cdt;

+ 24 - 0
protocol-core/pom.xml

@@ -0,0 +1,24 @@
+<?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">
+    <parent>
+        <artifactId>protocol</artifactId>
+        <groupId>wei.yigulu</groupId>
+        <version>1.0</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>protocol-core</artifactId>
+
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.github.purejavacomm</groupId>
+            <artifactId>purejavacomm</artifactId>
+            <version>1.0.1.RELEASE</version>
+        </dependency>
+    </dependencies>
+</project>
+

+ 22 - 0
protocol-core/src/main/java/wei/yigulu/connectfilterofslave/ConnectFilter.java

@@ -0,0 +1,22 @@
+package wei.yigulu.connectfilterofslave;
+
+import io.netty.channel.Channel;
+
+/**
+ * 连接的适配器
+ *
+ * @author: xiuwei
+ * @version:
+ */
+public interface ConnectFilter {
+
+	/**
+	 * 过滤器
+	 * 对接入的设备进行过滤
+	 *
+	 * @param channel 通道
+	 * @return 1:允许通过  0:交给下一个过滤器判断  -1:不允许通过
+	 */
+	int filter(Channel channel);
+
+}

+ 54 - 0
protocol-core/src/main/java/wei/yigulu/connectfilterofslave/ConnectFilterManager.java

@@ -0,0 +1,54 @@
+package wei.yigulu.connectfilterofslave;
+
+import io.netty.channel.Channel;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 过滤器管理器
+ *
+ * @author: xiuwei
+ * @version:
+ */
+public class ConnectFilterManager {
+
+	private List<ConnectFilter> filters = new ArrayList<>();
+
+	public void appendFilter(ConnectFilter connectFilter) {
+		this.filters.add(connectFilter);
+	}
+
+	public void removeFilter(ConnectFilter connectFilter) {
+		this.filters.remove(connectFilter);
+	}
+
+	/**
+	 * 判断该连接是否通过判断链
+	 *
+	 * @return true  允许通过  false  不允许通过
+	 */
+	public boolean verdict(Channel channel) {
+		if (this.filters.size() == 0) {
+			return true;
+		}
+		int i;
+		for (ConnectFilter filter : this.filters) {
+			try {
+				i = filter.filter(channel);
+			} catch (Exception e) {
+				i = 0;
+			}
+			if (i == 1) {
+				return true;
+			} else if (i == -1) {
+				return false;
+			} else if (i == 0) {
+				continue;
+			}
+		}
+		return true;
+	}
+
+
+}

+ 47 - 0
protocol-core/src/main/java/wei/yigulu/netty/AbstractDelimiterHandler.java

@@ -0,0 +1,47 @@
+package wei.yigulu.netty;
+
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 未继承netty的数据帧处理拆包类
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+
+public abstract class AbstractDelimiterHandler extends ChannelInboundHandlerAdapter {
+
+	@Setter
+	@Accessors(chain = true)
+	protected Logger log = LoggerFactory.getLogger(this.getClass());
+	protected ByteBuf cumulation;
+
+	protected DateTime timeMark = DateTime.now();
+
+	protected static ByteBuf expandCumulation(ByteBuf byteBuf1, ByteBuf byteBuf2) {
+		ByteBuf oldCumulation = byteBuf1;
+		byteBuf1 = byteBuf1.alloc().buffer(oldCumulation.readableBytes() + byteBuf2.readableBytes());
+		byteBuf1.writeBytes(oldCumulation);
+		byteBuf1.writeBytes(byteBuf2);
+		byteBuf1.readerIndex(0);
+		while (!oldCumulation.release()) {
+		}
+		while (!byteBuf2.release()) {
+		}
+		return byteBuf1;
+	}
+
+
+	@Override
+	public abstract void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception;
+
+
+}

+ 104 - 0
protocol-core/src/main/java/wei/yigulu/netty/AbstractHSTcpMasterBuilder.java

@@ -0,0 +1,104 @@
+package wei.yigulu.netty;
+
+
+import io.netty.channel.ChannelFutureListener;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+
+
+/**
+ * 104的主站  向子站发送总召唤 获取子站的数据
+ * <p>
+ * 主备模式的 主站 可以切换主备机
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@EqualsAndHashCode(callSuper = true)
+@Accessors(chain = true)
+public abstract class AbstractHSTcpMasterBuilder extends AbstractTcpMasterBuilder {
+
+
+	/**
+	 * Hs master builder
+	 *
+	 * @param ip   ip
+	 * @param port port
+	 */
+	public AbstractHSTcpMasterBuilder(String ip, Integer port) {
+		super(ip, port);
+	}
+
+	/**
+	 * Hs master builder
+	 *
+	 * @param ip        ip
+	 * @param port      port
+	 * @param spareIp   备 ip
+	 * @param sparePort 备 port
+	 */
+	public AbstractHSTcpMasterBuilder(String ip, Integer port, String spareIp, Integer sparePort) {
+		super(ip, port);
+		this.spareIp = spareIp;
+		this.sparePort = sparePort;
+	}
+
+	/**
+	 * 备对端ip
+	 */
+	@Getter
+	@Setter
+	private String spareIp;
+
+	/**
+	 * 备对端port
+	 */
+	@Getter
+	@Setter
+	private Integer sparePort;
+
+	/**
+	 * 本端的ip
+	 */
+	@Getter
+	@Setter
+	private String selfIp;
+
+	/**
+	 * 本端的端口
+	 */
+	@Getter
+	@Setter
+	private Integer selfPort;
+
+
+	@Override
+	public ChannelFutureListener getOrCreateConnectionListener() {
+		if (this.connectionListener == null) {
+			this.connectionListener = new HSConnectionListener(this);
+		}
+		return this.connectionListener;
+	}
+
+	/**
+	 * 切换主备机
+	 */
+	public void switchover() {
+		String temporaryIp;
+		int temporaryPort;
+		if (spareIp != null && !"".equals(spareIp)) {
+			temporaryIp = this.ip;
+			this.ip = this.spareIp;
+			this.spareIp = temporaryIp;
+		}
+		if (sparePort != 0) {
+			temporaryPort = this.port;
+			this.port = this.sparePort;
+			this.sparePort = temporaryPort;
+		}
+	}
+
+
+}

+ 147 - 0
protocol-core/src/main/java/wei/yigulu/netty/AbstractMasterBuilder.java

@@ -0,0 +1,147 @@
+package wei.yigulu.netty;
+
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.EventLoopGroup;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+import wei.yigulu.threadpool.LocalThreadPool;
+import wei.yigulu.utils.DataConvertor;
+
+
+/**
+ * 主站  向子站发送召唤 获取子站的数据
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+
+@Accessors(chain = true)
+public abstract class AbstractMasterBuilder extends BaseProtocolBuilder {
+
+
+	/**
+	 * Work group
+	 */
+	protected EventLoopGroup workGroup = null;
+	/**
+	 * Bootstrap
+	 */
+	protected Bootstrap bootstrap = null;
+
+	/**
+	 * Future
+	 */
+	@Getter
+	@Setter
+	protected ChannelFuture future;
+	/**
+	 * Connection listener
+	 */
+	protected ChannelFutureListener connectionListener = null;
+
+
+	protected ProtocolChannelInitializer channelInitializer = null;
+
+
+	public void stop() {
+		if (this.future != null) {
+			this.future.removeListener(getOrCreateConnectionListener());
+			if (!this.future.channel().eventLoop().isShutdown()) {
+				this.future.channel().close();
+			}
+		}
+		if (this.workGroup != null) {
+			this.workGroup.shutdownGracefully();
+		}
+	}
+
+
+	/**
+	 * 向对端 发送数据帧
+	 *
+	 * @param bytes
+	 */
+	public void sendFrameToOpposite(byte[] bytes) {
+		if (getFuture() != null && getFuture().channel().isActive()) {
+			getLog().debug("se ==> " + DataConvertor.Byte2String(bytes));
+			getFuture().channel().writeAndFlush(Unpooled.copiedBuffer(bytes));
+		}
+	}
+
+	/**
+	 * 向对端 发送数据帧
+	 *
+	 * @param byteBuf
+	 */
+	public void sendFrameToOpposite(ByteBuf byteBuf) {
+		if (getFuture() != null && getFuture().channel().isActive()) {
+			getLog().debug("se ==> " + DataConvertor.ByteBuf2String(byteBuf));
+			getFuture().channel().writeAndFlush(Unpooled.copiedBuffer(byteBuf));
+		}
+	}
+
+	/**
+	 * 创建Master 连接
+	 */
+	public abstract void create();
+
+	/**
+	 * Create by un block
+	 */
+	public void createByUnBlock() {
+		LocalThreadPool.getInstance().getLocalPool().execute(this::create);
+	}
+
+
+	/**
+	 * null则创建,有则获取获取EventLoopGroup 用与bootstrap的绑定
+	 *
+	 * @return or create work group
+	 */
+	public abstract EventLoopGroup getOrCreateWorkGroup();
+
+
+	/**
+	 * null则创建,有则获取获取bootstrap
+	 *
+	 * @return or create bootstrap
+	 */
+	public abstract Bootstrap getOrCreateBootstrap();
+
+
+	/**
+	 * null则创建,有则获取获取ConnectionListener
+	 *
+	 * @return or create connection listener
+	 */
+	public abstract ChannelFutureListener getOrCreateConnectionListener();
+
+	/**
+	 * null则创建,有则获取获取ChannelInitializer
+	 *
+	 * @return or create ChannelInitializer
+	 */
+	protected abstract ProtocolChannelInitializer getOrCreateChannelInitializer();
+
+	/**
+	 * 通道连接成功
+	 */
+	public void connected() {
+
+	}
+
+	/**
+	 * 通道断开连接
+	 */
+	public void disconnected() {
+
+	}
+
+
+}

+ 108 - 0
protocol-core/src/main/java/wei/yigulu/netty/AbstractRtuModeBuilder.java

@@ -0,0 +1,108 @@
+package wei.yigulu.netty;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.oio.OioEventLoopGroup;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+import lombok.extern.slf4j.Slf4j;
+import wei.yigulu.purejavacomm.PureJavaCommChannel;
+import wei.yigulu.purejavacomm.PureJavaCommChannelConfig;
+import wei.yigulu.purejavacomm.PureJavaCommChannelOption;
+import wei.yigulu.purejavacomm.PureJavaCommDeviceAddress;
+
+/**
+ * 使用rtu的客户端
+ *
+ * @author: xiuwei
+ * @version:
+ */
+@Accessors(chain = true)
+@Slf4j
+public abstract class AbstractRtuModeBuilder extends AbstractMasterBuilder {
+
+
+	/**
+	 * com口名称
+	 */
+	@Getter
+	@Setter
+	private String commPortId;
+	/**
+	 * 波特率
+	 */
+	@Getter
+	@Setter
+	private int baudRate = 9600;
+	/**
+	 * 数据位
+	 */
+	@Getter
+	@Setter
+	private PureJavaCommChannelConfig.Databits dataBits = PureJavaCommChannelConfig.Databits.DATABITS_8;
+	/**
+	 * 停止位
+	 */
+	@Getter
+	@Setter
+	private PureJavaCommChannelConfig.Stopbits stopBits = PureJavaCommChannelConfig.Stopbits.STOPBITS_1;
+	/**
+	 * 校验位
+	 */
+	@Getter
+	@Setter
+	private PureJavaCommChannelConfig.Paritybit parity = PureJavaCommChannelConfig.Paritybit.NONE;
+
+
+	public AbstractRtuModeBuilder(String commPortId) {
+		this.commPortId = commPortId;
+	}
+
+
+	@Override
+	public void create() {
+		try {
+			this.future = getOrCreateBootstrap().connect(new PureJavaCommDeviceAddress(this.commPortId));
+			future.addListener(getOrCreateConnectionListener());
+			future.channel().closeFuture().sync();
+		} catch (InterruptedException e) {
+			e.printStackTrace();
+		}
+	}
+
+	@Override
+	public EventLoopGroup getOrCreateWorkGroup() {
+		if (this.workGroup == null) {
+			this.workGroup = new OioEventLoopGroup();
+		}
+		return this.workGroup;
+	}
+
+	@Override
+	public Bootstrap getOrCreateBootstrap() {
+		if (this.bootstrap == null) {
+			this.bootstrap = new Bootstrap();
+			bootstrap.group(getOrCreateWorkGroup());
+			bootstrap.channel(PureJavaCommChannel.class);
+			bootstrap.handler(getOrCreateChannelInitializer());
+			bootstrap.option(PureJavaCommChannelOption.BAUD_RATE, baudRate);
+			bootstrap.option(PureJavaCommChannelOption.DATA_BITS, dataBits);
+			bootstrap.option(PureJavaCommChannelOption.STOP_BITS, stopBits);
+			bootstrap.option(PureJavaCommChannelOption.PARITY_BIT, parity);
+		}
+		return this.bootstrap;
+	}
+
+	@Override
+	public ChannelFutureListener getOrCreateConnectionListener() {
+		if (this.connectionListener == null) {
+			this.connectionListener = new RtuModeConnectionListener(this);
+		}
+		return this.connectionListener;
+	}
+
+	@Override
+	protected abstract ProtocolChannelInitializer getOrCreateChannelInitializer();
+}

+ 143 - 0
protocol-core/src/main/java/wei/yigulu/netty/AbstractTcpMasterBuilder.java

@@ -0,0 +1,143 @@
+package wei.yigulu.netty;
+
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.experimental.Accessors;
+
+/**
+ * TCP主站  向子站发送总召唤 获取子站的数据
+ * <p>
+ * 简单的主站  相对于主备机主站    仅有主机 不支持切换
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@EqualsAndHashCode(callSuper = true)
+@Accessors(chain = true)
+public abstract class AbstractTcpMasterBuilder extends AbstractMasterBuilder {
+
+
+	/**
+	 * 对端slave的ip
+	 */
+	@Getter
+	String ip;
+
+
+	/**
+	 * 对端slave的端口
+	 */
+	@Getter
+	Integer port;
+
+	/**
+	 * 构造方法
+	 *
+	 * @param ip   ip
+	 * @param port port
+	 */
+	public AbstractTcpMasterBuilder(String ip, Integer port) {
+		this.ip = ip;
+		this.port = port;
+	}
+
+	@Override
+	public void create() {
+		synchronized (this) {
+			if (future != null) {
+				future.removeListener(getOrCreateConnectionListener());
+				if (!future.channel().eventLoop().isShutdown()) {
+					future.channel().close();
+				}
+				future = null;
+			}
+			log.debug("创建连接");
+			try {
+				future = getOrCreateBootstrap().connect(this.ip, this.port);
+				log.debug("为连接添加监听");
+				future.addListener(getOrCreateConnectionListener());
+			} catch (Exception e) {
+				log.debug("创建连接时发生异常");
+				try {
+					this.connectionListener.operationComplete(future);
+				} catch (Exception ex) {
+					ex.printStackTrace();
+				}
+				return;
+			}
+		}
+		try {
+			future.channel().closeFuture().sync();
+		} catch (InterruptedException e) {
+			e.printStackTrace();
+		}
+	}
+
+	@Override
+	public EventLoopGroup getOrCreateWorkGroup() {
+		if (this.workGroup == null || this.workGroup.isShutdown()) {
+			this.workGroup = new NioEventLoopGroup();
+		}
+		return this.workGroup;
+	}
+
+
+	@Override
+	public Bootstrap getOrCreateBootstrap() {
+		if (this.bootstrap == null) {
+			bootstrap = new Bootstrap();
+			bootstrap.group(getOrCreateWorkGroup())
+					.channel(NioSocketChannel.class)
+					.handler(getOrCreateChannelInitializer());
+			bootstrap.option(ChannelOption.SO_KEEPALIVE, false);
+			bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1000);
+		}
+		return this.bootstrap;
+	}
+
+	@Override
+	public ChannelFutureListener getOrCreateConnectionListener() {
+		if (this.connectionListener == null) {
+			this.connectionListener = new SimpleTcpConnectionListener(this);
+		}
+		return this.connectionListener;
+	}
+
+	@Override
+	protected abstract ProtocolChannelInitializer getOrCreateChannelInitializer();
+
+
+	/**
+	 * 重新建立loopgroup 关闭线程池中负责的所有链接
+	 * 若是公用loopgroup 则可不做任何动作或者 谨慎启停所有链接
+	 *
+	 * @return event loop group
+	 */
+	public EventLoopGroup refreshLoopGroup() {
+		try {
+			if (!this.workGroup.isShutdown()) {
+				this.workGroup.shutdownGracefully();
+				log.debug("刷新连接线程,关闭后重启");
+			}
+			this.workGroup = new NioEventLoopGroup();
+			this.bootstrap = null;
+			log.error(" TCP Master 断开重连");
+		} catch (Exception e) {
+			try {
+				Thread.sleep(10000);
+			} catch (InterruptedException e1) {
+				e1.printStackTrace();
+			}
+			return refreshLoopGroup();
+		}
+		return this.workGroup;
+	}
+
+}

+ 185 - 0
protocol-core/src/main/java/wei/yigulu/netty/AbstractTcpSlaverBuilder.java

@@ -0,0 +1,185 @@
+package wei.yigulu.netty;
+
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+import wei.yigulu.connectfilterofslave.ConnectFilterManager;
+import wei.yigulu.threadpool.LocalThreadPool;
+
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * slave是向主站提供数据的 主站发送总召唤 子站响应主站的召唤
+ * 向主站上送数据
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@EqualsAndHashCode(callSuper = true)
+@Accessors(chain = true)
+public abstract class AbstractTcpSlaverBuilder extends BaseProtocolBuilder {
+
+
+	private EventLoopGroup group;
+
+	private ServerBootstrap serverBootstrap;
+
+	@Setter
+	@Getter
+	private int port = 2404;
+
+	@Setter
+	@Getter
+	private String ip = null;
+
+
+	/**
+	 * 连接过滤器管理器
+	 */
+	@Setter
+	@Getter
+	private ConnectFilterManager connectFilterManager = new ConnectFilterManager();
+
+	protected ProtocolChannelInitializer channelInitializer = null;
+
+	/**
+	 * 子channel集合
+	 */
+	@Getter
+	private List<Channel> channels = new ArrayList<>();
+	/**
+	 * 父channel
+	 */
+	@Getter
+	private Channel fatherChannel;
+
+
+	public AbstractTcpSlaverBuilder(int port) {
+		this.port = port;
+	}
+
+	/**
+	 * 创建104 slave 监听
+	 *
+	 * @throws Exception 异常
+	 */
+	public void create() throws Exception {
+		// 服务器异步创建绑定
+		ChannelFuture cf = getOrCrateServerBootstrap().bind().sync();
+		log.info("Slaver端启动成功;端口" + port);
+		// 关闭服务器通道
+		cf.channel().closeFuture().sync();
+		// 释放线程池资源
+		group.shutdownGracefully().sync();
+	}
+
+
+	/**
+	 * null则创建,有则获取获取ChannelInitializer
+	 *
+	 * @return or create ChannelInitializer
+	 */
+	protected abstract ProtocolChannelInitializer getOrCreateChannelInitializer();
+
+	/**
+	 * 获取ServerBootstrap
+	 * 如果==null 那么就创建
+	 *
+	 * @return
+	 */
+	protected ServerBootstrap getOrCrateServerBootstrap() {
+		if (this.serverBootstrap == null) {
+			AbstractTcpSlaverBuilder slaverBuilder = this;
+			this.serverBootstrap = new ServerBootstrap();
+			// 绑定线程池
+			this.serverBootstrap.group(getOrCrateLoopGroup())
+					// 指定使用的channel
+					.channel(NioServerSocketChannel.class)
+					// 绑定客户端连接时候触发操作
+					.childHandler(getOrCreateChannelInitializer());
+			// 绑定监听端口
+			if (this.ip != null) {
+				this.serverBootstrap.localAddress(this.ip, this.port);
+			} else {
+				this.serverBootstrap.localAddress(this.port);
+			}
+		}
+		return this.serverBootstrap;
+	}
+
+	/**
+	 * 获取或创建 循环任务组
+	 *
+	 * @return {@link EventLoopGroup}
+	 */
+	protected EventLoopGroup getOrCrateLoopGroup() {
+		if (this.group == null) {
+			this.group = new NioEventLoopGroup();
+		}
+		return this.group;
+	}
+
+
+	/**
+	 * 停止通道监听
+	 */
+	public void stop() {
+		if (this.fatherChannel != null) {
+			this.fatherChannel.close();
+			fatherChannel = null;
+		}
+		for (Channel c : this.channels) {
+			c.close();
+		}
+		this.channels = new ArrayList<>();
+		if (this.group != null) {
+			this.group.shutdownGracefully();
+		}
+
+	}
+
+	/**
+	 * 以非阻塞的方式启动
+	 */
+	public void createByUnBlock() {
+		AbstractTcpSlaverBuilder s = this;
+		LocalThreadPool.getInstance().getLocalPool().execute(() -> {
+			try {
+				s.create();
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		});
+	}
+
+
+	/**
+	 * 当有连接接入时触发
+	 *
+	 * @param ipSocket ip套接字
+	 */
+	public void connected(InetSocketAddress ipSocket) {
+
+	}
+
+	/**
+	 * 当有连接断开时触发
+	 *
+	 * @param ipSocket ip套接字
+	 */
+	public void disconnected(InetSocketAddress ipSocket) {
+
+	}
+
+}

+ 39 - 0
protocol-core/src/main/java/wei/yigulu/netty/BaseProtocolBuilder.java

@@ -0,0 +1,39 @@
+package wei.yigulu.netty;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * 104主从站的基类
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+public class BaseProtocolBuilder {
+
+	@Setter
+	@Getter
+	@Accessors(chain = true)
+	protected Logger log = LoggerFactory.getLogger(this.getClass());
+
+	/**
+	 * Builder id
+	 */
+	@Getter
+	protected String builderId = UUID.randomUUID().toString();
+
+	/**
+	 * Config info map
+	 */
+	@Getter
+	protected Map<String, Object> configInfoMap = new HashMap<>();
+
+
+}

+ 76 - 0
protocol-core/src/main/java/wei/yigulu/netty/HSConnectionListener.java

@@ -0,0 +1,76 @@
+package wei.yigulu.netty;
+
+
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.util.internal.StringUtil;
+import lombok.AllArgsConstructor;
+import lombok.NoArgsConstructor;
+import org.slf4j.Logger;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 负责监听启动时连接失败,重新连接功能
+ * 带有主备切换功能的 连接监听
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@AllArgsConstructor
+@NoArgsConstructor
+public class HSConnectionListener implements ChannelFutureListener {
+
+	private Logger log;
+
+	private AbstractHSTcpMasterBuilder masterBuilder;
+
+	private int retryTimes;
+
+	/**
+	 * Hs connection listener
+	 *
+	 * @param masterBuilder master builder
+	 */
+	public HSConnectionListener(AbstractHSTcpMasterBuilder masterBuilder) {
+		this.masterBuilder = masterBuilder;
+		this.log = masterBuilder.getLog();
+	}
+
+	@Override
+	public void operationComplete(ChannelFuture channelFuture) throws Exception {
+		if (channelFuture == null || channelFuture.channel() == null || !channelFuture.channel().isActive()) {
+			this.masterBuilder.getOrCreateWorkGroup().schedule(() -> {
+				try {
+					if (this.retryTimes < 10) {
+						log.error("服务端{}:{}链接不上,开始重连操作,第{}次尝试", this.masterBuilder.getIp(), this.masterBuilder.getPort(), retryTimes);
+						masterBuilder.create();
+						log.warn("重试连接失败");
+						this.retryTimes++;
+					} else {
+						if (!StringUtil.isNullOrEmpty(this.masterBuilder.getSpareIp()) || (this.masterBuilder.getSparePort() != null && this.masterBuilder.getSparePort() != 0)) {
+							log.info("服务端{}:{}链接不上,切换主备机{}:{}", this.masterBuilder.getIp(), this.masterBuilder.getPort(), this.masterBuilder.getSpareIp(), this.masterBuilder.getSparePort());
+							this.masterBuilder.switchover();
+						}
+						this.masterBuilder.refreshLoopGroup();
+						this.retryTimes = 0;
+						masterBuilder.create();
+						log.info("重置重试次数=0");
+					}
+				} catch (Exception e) {
+					log.error("ModbusMaster重试连接时发生异常", e);
+					this.masterBuilder.refreshLoopGroup();
+					this.retryTimes = 0;
+					try {
+						operationComplete(channelFuture);
+					} catch (Exception ex) {
+						ex.printStackTrace();
+					}
+				}
+			}, 3L, TimeUnit.SECONDS);
+		} else {
+			log.info("服务端{}:{}链接成功...", this.masterBuilder.getIp(), this.masterBuilder.getPort());
+		}
+	}
+}
+

+ 21 - 0
protocol-core/src/main/java/wei/yigulu/netty/ProtocolChannelInitializer.java

@@ -0,0 +1,21 @@
+package wei.yigulu.netty;
+
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelInitializer;
+
+/**
+ * modbus 的master  在netty上的通道实现
+ *
+ * @author: xiuwei
+ * @version:
+ */
+public abstract class ProtocolChannelInitializer<T extends Channel> extends ChannelInitializer<T> {
+
+	protected BaseProtocolBuilder builder;
+
+	public ProtocolChannelInitializer(BaseProtocolBuilder builder) {
+		this.builder = builder;
+	}
+
+
+}

+ 41 - 0
protocol-core/src/main/java/wei/yigulu/netty/RtuModeConnectionListener.java

@@ -0,0 +1,41 @@
+package wei.yigulu.netty;
+
+
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.EventLoop;
+import lombok.AllArgsConstructor;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 负责监听启动时连接失败,重新连接功能
+ *
+ * @author 修唯xiuwei
+ * @create 2019-03-13 14:15
+ * @Email 524710549@qq.com
+ **/
+@AllArgsConstructor
+@NoArgsConstructor
+@Slf4j
+public class RtuModeConnectionListener implements ChannelFutureListener {
+
+	private AbstractRtuModeBuilder abstractRtuClient;
+
+	@Override
+	public void operationComplete(ChannelFuture channelFuture) throws Exception {
+		if (!channelFuture.channel().isActive()) {
+			final EventLoop loop = channelFuture.channel().eventLoop();
+			loop.schedule(() -> {
+				abstractRtuClient.getLog().error("RTU:{}端链接不上,开始重连操作...", abstractRtuClient.getCommPortId());
+				channelFuture.channel().closeFuture();
+				abstractRtuClient.create();
+			}, 4L, TimeUnit.SECONDS);
+		} else {
+			abstractRtuClient.getLog().info("RTU:{}端链接成功...", abstractRtuClient.getCommPortId());
+		}
+	}
+}
+

+ 54 - 0
protocol-core/src/main/java/wei/yigulu/netty/SimpleTcpConnectionListener.java

@@ -0,0 +1,54 @@
+package wei.yigulu.netty;
+
+
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
+import org.slf4j.Logger;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 负责监听启动时连接失败,重新连接功能
+ * 相对与主备模式  该连接监听仅支持单主机模式
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+
+public class SimpleTcpConnectionListener implements ChannelFutureListener {
+
+	private Logger log;
+
+	private AbstractTcpMasterBuilder masterBuilder;
+
+	/**
+	 * Only host connection listener
+	 *
+	 * @param masterBuilder master builder
+	 */
+	public SimpleTcpConnectionListener(AbstractTcpMasterBuilder masterBuilder) {
+		this.masterBuilder = masterBuilder;
+		this.log = masterBuilder.getLog();
+	}
+
+
+	@Override
+	public void operationComplete(ChannelFuture channelFuture) throws Exception {
+		if (channelFuture == null || channelFuture.channel() == null || !channelFuture.channel().isActive()) {
+			this.masterBuilder.getOrCreateWorkGroup().schedule(() -> {
+				try {
+					log.error("服务端{}:{}链接不上,开始重连操作", this.masterBuilder.getIp(), this.masterBuilder.getPort());
+					masterBuilder.create();
+				} catch (Exception e) {
+					log.error("TcpMaster重试连接时发生异常", e);
+					try {
+						operationComplete(channelFuture);
+					} catch (Exception ex) {
+						ex.printStackTrace();
+					}
+				}
+			}, 6L, TimeUnit.SECONDS);
+		}
+	}
+}
+

+ 246 - 0
protocol-core/src/main/java/wei/yigulu/purejavacomm/DefaultPureJavaCommChannelConfig.java

@@ -0,0 +1,246 @@
+package wei.yigulu.purejavacomm;
+
+
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.DefaultChannelConfig;
+import io.netty.channel.MessageSizeEstimator;
+import io.netty.channel.RecvByteBufAllocator;
+
+import java.util.Map;
+
+import static wei.yigulu.purejavacomm.PureJavaCommChannelOption.*;
+
+/**
+ * Default configuration class for PureJavaComm device connections.
+ */
+final class DefaultPureJavaCommChannelConfig extends DefaultChannelConfig implements PureJavaCommChannelConfig {
+
+	private volatile int baudrate = 115200;
+	private volatile boolean dtr;
+	private volatile boolean rts;
+	private volatile Stopbits stopbits = Stopbits.STOPBITS_1;
+	private volatile Databits databits = Databits.DATABITS_8;
+	private volatile Paritybit paritybit = Paritybit.NONE;
+	private volatile int waitTime;
+	private volatile int readTimeout = 1000;
+
+	public DefaultPureJavaCommChannelConfig(PureJavaCommChannel channel) {
+		super(channel);
+	}
+
+	@Override
+	public Map<ChannelOption<?>, Object> getOptions() {
+		return getOptions(super.getOptions(), BAUD_RATE, DTR, RTS, STOP_BITS, DATA_BITS, PARITY_BIT, WAIT_TIME);
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	public <T> T getOption(ChannelOption<T> option) {
+		if (option == BAUD_RATE) {
+			return (T) Integer.valueOf(getBaudrate());
+		}
+		if (option == DTR) {
+			return (T) Boolean.valueOf(isDtr());
+		}
+		if (option == RTS) {
+			return (T) Boolean.valueOf(isRts());
+		}
+		if (option == STOP_BITS) {
+			return (T) getStopbits();
+		}
+		if (option == DATA_BITS) {
+			return (T) getDatabits();
+		}
+		if (option == PARITY_BIT) {
+			return (T) getParitybit();
+		}
+		if (option == WAIT_TIME) {
+			return (T) Integer.valueOf(getWaitTimeMillis());
+		}
+		if (option == READ_TIMEOUT) {
+			return (T) Integer.valueOf(getReadTimeout());
+		}
+		return super.getOption(option);
+	}
+
+	@Override
+	public <T> boolean setOption(ChannelOption<T> option, T value) {
+		validate(option, value);
+
+		if (option == BAUD_RATE) {
+			setBaudrate((Integer) value);
+		} else if (option == DTR) {
+			setDtr((Boolean) value);
+		} else if (option == RTS) {
+			setRts((Boolean) value);
+		} else if (option == STOP_BITS) {
+			setStopbits((Stopbits) value);
+		} else if (option == DATA_BITS) {
+			setDatabits((Databits) value);
+		} else if (option == PARITY_BIT) {
+			setParitybit((Paritybit) value);
+		} else if (option == WAIT_TIME) {
+			setWaitTimeMillis((Integer) value);
+		} else if (option == READ_TIMEOUT) {
+			setReadTimeout((Integer) value);
+		} else {
+			return super.setOption(option, value);
+		}
+		return true;
+	}
+
+	@Override
+	public PureJavaCommChannelConfig setBaudrate(final int baudrate) {
+		this.baudrate = baudrate;
+		return this;
+	}
+
+	@Override
+	public PureJavaCommChannelConfig setStopbits(final Stopbits stopbits) {
+		this.stopbits = stopbits;
+		return this;
+	}
+
+	@Override
+	public PureJavaCommChannelConfig setDatabits(final Databits databits) {
+		this.databits = databits;
+		return this;
+	}
+
+	@Override
+	public PureJavaCommChannelConfig setParitybit(final Paritybit paritybit) {
+		this.paritybit = paritybit;
+		return this;
+	}
+
+	@Override
+	public int getBaudrate() {
+		return baudrate;
+	}
+
+	@Override
+	public Stopbits getStopbits() {
+		return stopbits;
+	}
+
+	@Override
+	public Databits getDatabits() {
+		return databits;
+	}
+
+	@Override
+	public Paritybit getParitybit() {
+		return paritybit;
+	}
+
+	@Override
+	public boolean isDtr() {
+		return dtr;
+	}
+
+	@Override
+	public PureJavaCommChannelConfig setDtr(final boolean dtr) {
+		this.dtr = dtr;
+		return this;
+	}
+
+	@Override
+	public boolean isRts() {
+		return rts;
+	}
+
+	@Override
+	public PureJavaCommChannelConfig setRts(final boolean rts) {
+		this.rts = rts;
+		return this;
+	}
+
+	@Override
+	public int getWaitTimeMillis() {
+		return waitTime;
+	}
+
+	@Override
+	public PureJavaCommChannelConfig setWaitTimeMillis(final int waitTimeMillis) {
+		if (waitTimeMillis < 0) {
+			throw new IllegalArgumentException("Wait time must be >= 0");
+		}
+		waitTime = waitTimeMillis;
+		return this;
+	}
+
+	@Override
+	public PureJavaCommChannelConfig setReadTimeout(int readTimeout) {
+		if (readTimeout < 0) {
+			throw new IllegalArgumentException("readTime must be >= 0");
+		}
+		this.readTimeout = readTimeout;
+		return this;
+	}
+
+	@Override
+	public int getReadTimeout() {
+		return readTimeout;
+	}
+
+	@Override
+	public PureJavaCommChannelConfig setConnectTimeoutMillis(int connectTimeoutMillis) {
+		super.setConnectTimeoutMillis(connectTimeoutMillis);
+		return this;
+	}
+
+	@Override
+	public PureJavaCommChannelConfig setMaxMessagesPerRead(int maxMessagesPerRead) {
+		super.setMaxMessagesPerRead(maxMessagesPerRead);
+		return this;
+	}
+
+	@Override
+	public PureJavaCommChannelConfig setWriteSpinCount(int writeSpinCount) {
+		super.setWriteSpinCount(writeSpinCount);
+		return this;
+	}
+
+	@Override
+	public PureJavaCommChannelConfig setAllocator(ByteBufAllocator allocator) {
+		super.setAllocator(allocator);
+		return this;
+	}
+
+	@Override
+	public PureJavaCommChannelConfig setRecvByteBufAllocator(RecvByteBufAllocator allocator) {
+		super.setRecvByteBufAllocator(allocator);
+		return this;
+	}
+
+	@Override
+	public PureJavaCommChannelConfig setAutoRead(boolean autoRead) {
+		super.setAutoRead(autoRead);
+		return this;
+	}
+
+	@Override
+	public PureJavaCommChannelConfig setAutoClose(boolean autoClose) {
+		super.setAutoClose(autoClose);
+		return this;
+	}
+
+	@Override
+	public PureJavaCommChannelConfig setWriteBufferHighWaterMark(int writeBufferHighWaterMark) {
+		super.setWriteBufferHighWaterMark(writeBufferHighWaterMark);
+		return this;
+	}
+
+	@Override
+	public PureJavaCommChannelConfig setWriteBufferLowWaterMark(int writeBufferLowWaterMark) {
+		super.setWriteBufferLowWaterMark(writeBufferLowWaterMark);
+		return this;
+	}
+
+	@Override
+	public PureJavaCommChannelConfig setMessageSizeEstimator(MessageSizeEstimator estimator) {
+		super.setMessageSizeEstimator(estimator);
+		return this;
+	}
+}

+ 172 - 0
protocol-core/src/main/java/wei/yigulu/purejavacomm/PureJavaCommChannel.java

@@ -0,0 +1,172 @@
+package wei.yigulu.purejavacomm;
+
+
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelPromise;
+import io.netty.channel.oio.OioByteStreamChannel;
+import purejavacomm.CommPort;
+import purejavacomm.CommPortIdentifier;
+import purejavacomm.SerialPort;
+
+import java.net.SocketAddress;
+import java.util.concurrent.TimeUnit;
+
+import static wei.yigulu.purejavacomm.PureJavaCommChannelOption.*;
+
+/**
+ * A channel to a serial device using the PureJavaComm library.
+ */
+public class PureJavaCommChannel extends OioByteStreamChannel {
+
+	private static final PureJavaCommDeviceAddress LOCAL_ADDRESS = new PureJavaCommDeviceAddress("localhost");
+
+	private final PureJavaCommChannelConfig config;
+
+	private boolean open = true;
+	private PureJavaCommDeviceAddress deviceAddress;
+	private SerialPort serialPort;
+
+	public PureJavaCommChannel() {
+		super(null);
+
+		config = new DefaultPureJavaCommChannelConfig(this);
+	}
+
+	@Override
+	public PureJavaCommChannelConfig config() {
+		return config;
+	}
+
+	@Override
+	public boolean isOpen() {
+		return open;
+	}
+
+	@Override
+	protected AbstractUnsafe newUnsafe() {
+		return new PureJavaCommUnsafe();
+	}
+
+	@Override
+	protected void doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
+		PureJavaCommDeviceAddress remote = (PureJavaCommDeviceAddress) remoteAddress;
+		final CommPortIdentifier cpi = CommPortIdentifier.getPortIdentifier(remote.value());
+		final CommPort commPort = cpi.open(getClass().getName(), 1000);
+
+		deviceAddress = remote;
+
+		serialPort = (SerialPort) commPort;
+	}
+
+	protected void doInit() throws Exception {
+		serialPort.setSerialPortParams(
+				config().getOption(BAUD_RATE),
+				config().getOption(DATA_BITS).value(),
+				config().getOption(STOP_BITS).value(),
+				config().getOption(PARITY_BIT).value()
+		);
+		serialPort.setDTR(config().getOption(DTR));
+		serialPort.setRTS(config().getOption(RTS));
+		serialPort.enableReceiveTimeout(config().getOption(READ_TIMEOUT));
+
+		activate(serialPort.getInputStream(), serialPort.getOutputStream());
+	}
+
+	@Override
+	public PureJavaCommDeviceAddress localAddress() {
+		return (PureJavaCommDeviceAddress) super.localAddress();
+	}
+
+	@Override
+	public PureJavaCommDeviceAddress remoteAddress() {
+		return (PureJavaCommDeviceAddress) super.remoteAddress();
+	}
+
+	@Override
+	protected PureJavaCommDeviceAddress localAddress0() {
+		return LOCAL_ADDRESS;
+	}
+
+	@Override
+	protected PureJavaCommDeviceAddress remoteAddress0() {
+		return deviceAddress;
+	}
+
+	@Override
+	protected void doBind(SocketAddress localAddress) throws Exception {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	protected void doDisconnect() throws Exception {
+		doClose();
+	}
+
+	@Override
+	protected void doClose() throws Exception {
+		open = false;
+		try {
+			super.doClose();
+		} finally {
+			if (serialPort != null) {
+				serialPort.removeEventListener();
+				serialPort.close();
+				serialPort = null;
+			}
+		}
+	}
+
+	@Override
+	protected boolean isInputShutdown() {
+		return !open;
+	}
+
+	@Override
+	protected ChannelFuture shutdownInput() {
+		return newFailedFuture(new UnsupportedOperationException("shutdownInput"));
+	}
+
+	private final class PureJavaCommUnsafe extends AbstractUnsafe {
+		@Override
+		public void connect(
+				final SocketAddress remoteAddress,
+				final SocketAddress localAddress, final ChannelPromise promise) {
+			if (!ensureOpen(promise)) {
+				return;
+			}
+
+			try {
+				final boolean wasActive = isActive();
+				doConnect(remoteAddress, localAddress);
+
+				int waitTime = config().getOption(WAIT_TIME);
+				if (waitTime > 0) {
+					eventLoop().schedule(new Runnable() {
+						@Override
+						public void run() {
+							try {
+								doInit();
+								promise.setSuccess();
+								if (!wasActive && isActive()) {
+									pipeline().fireChannelActive();
+								}
+							} catch (Throwable t) {
+								promise.setFailure(t);
+								closeIfClosed();
+							}
+						}
+					}, waitTime, TimeUnit.MILLISECONDS);
+				} else {
+					doInit();
+					promise.setSuccess();
+					if (!wasActive && isActive()) {
+						pipeline().fireChannelActive();
+					}
+				}
+			} catch (Throwable t) {
+				promise.setFailure(t);
+				closeIfClosed();
+			}
+		}
+	}
+}

+ 288 - 0
protocol-core/src/main/java/wei/yigulu/purejavacomm/PureJavaCommChannelConfig.java

@@ -0,0 +1,288 @@
+package wei.yigulu.purejavacomm;
+
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.channel.ChannelConfig;
+import io.netty.channel.MessageSizeEstimator;
+import io.netty.channel.RecvByteBufAllocator;
+import purejavacomm.SerialPort;
+
+/**
+ * A configuration class for PureJavaComm device connections.
+ *
+ * <h3>Available options</h3>
+ * <p>
+ * In addition to the options provided by {@link ChannelConfig},
+ * {@link DefaultPureJavaCommChannelConfig} allows the following options in the option map:
+ *
+ * <table border="1" cellspacing="0" cellpadding="6">
+ * <tr>
+ * <th>Name</th><th>Associated setter method</th>
+ * </tr><tr>
+ * <td>{@link PureJavaCommChannelOption#BAUD_RATE}</td><td>{@link #setBaudrate(int)}</td>
+ * </tr><tr>
+ * <td>{@link PureJavaCommChannelOption#DTR}</td><td>{@link #setDtr(boolean)}</td>
+ * </tr><tr>
+ * <td>{@link PureJavaCommChannelOption#RTS}</td><td>{@link #setRts(boolean)}</td>
+ * </tr><tr>
+ * <td>{@link PureJavaCommChannelOption#STOP_BITS}</td><td>{@link #setStopbits(Stopbits)}</td>
+ * </tr><tr>
+ * <td>{@link PureJavaCommChannelOption#DATA_BITS}</td><td>{@link #setDatabits(Databits)}</td>
+ * </tr><tr>
+ * <td>{@link PureJavaCommChannelOption#PARITY_BIT}</td><td>{@link #setParitybit(Paritybit)}</td>
+ * </tr><tr>
+ * <td>{@link PureJavaCommChannelOption#WAIT_TIME}</td><td>{@link #setWaitTimeMillis(int)}</td>
+ * </tr>
+ * </table>
+ */
+public interface PureJavaCommChannelConfig extends ChannelConfig {
+	enum Stopbits {
+		/**
+		 * 1 stop bit will be sent at the end of every character
+		 */
+		STOPBITS_1(SerialPort.STOPBITS_1),
+		/**
+		 * 2 stop bits will be sent at the end of every character
+		 */
+		STOPBITS_2(SerialPort.STOPBITS_2),
+		/**
+		 * 1.5 stop bits will be sent at the end of every character
+		 */
+		STOPBITS_1_5(SerialPort.STOPBITS_1_5);
+
+		private final int value;
+
+		Stopbits(int value) {
+			this.value = value;
+		}
+
+		public int value() {
+			return value;
+		}
+
+		public static Stopbits valueOf(int value) {
+			for (Stopbits stopbit : Stopbits.values()) {
+				if (stopbit.value == value) {
+					return stopbit;
+				}
+			}
+			throw new IllegalArgumentException("unknown " + Stopbits.class.getSimpleName() + " value: " + value);
+		}
+	}
+
+	enum Databits {
+		/**
+		 * 5 data bits will be used for each character (ie. Baudot code)
+		 */
+		DATABITS_5(SerialPort.DATABITS_5),
+		/**
+		 * 6 data bits will be used for each character
+		 */
+		DATABITS_6(SerialPort.DATABITS_6),
+		/**
+		 * 7 data bits will be used for each character (ie. ASCII)
+		 */
+		DATABITS_7(SerialPort.DATABITS_7),
+		/**
+		 * 8 data bits will be used for each character (ie. binary data)
+		 */
+		DATABITS_8(SerialPort.DATABITS_8);
+
+		private final int value;
+
+		Databits(int value) {
+			this.value = value;
+		}
+
+		public int value() {
+			return value;
+		}
+
+		public static Databits valueOf(int value) {
+			for (Databits databit : Databits.values()) {
+				if (databit.value == value) {
+					return databit;
+				}
+			}
+			throw new IllegalArgumentException("unknown " + Databits.class.getSimpleName() + " value: " + value);
+		}
+	}
+
+	enum Paritybit {
+		/**
+		 * No parity bit will be sent with each data character at all
+		 */
+		NONE(SerialPort.PARITY_NONE),
+		/**
+		 * An odd parity bit will be sent with each data character, ie. will be set
+		 * to 1 if the data character contains an even number of bits set to 1.
+		 */
+		ODD(SerialPort.PARITY_ODD),
+		/**
+		 * An even parity bit will be sent with each data character, ie. will be set
+		 * to 1 if the data character contains an odd number of bits set to 1.
+		 */
+		EVEN(SerialPort.PARITY_EVEN),
+		/**
+		 * A mark parity bit (ie. always 1) will be sent with each data character
+		 */
+		MARK(SerialPort.PARITY_MARK),
+		/**
+		 * A space parity bit (ie. always 0) will be sent with each data character
+		 */
+		SPACE(SerialPort.PARITY_SPACE);
+
+		private final int value;
+
+		Paritybit(int value) {
+			this.value = value;
+		}
+
+		public int value() {
+			return value;
+		}
+
+		public static Paritybit valueOf(int value) {
+			for (Paritybit paritybit : Paritybit.values()) {
+				if (paritybit.value == value) {
+					return paritybit;
+				}
+			}
+			throw new IllegalArgumentException("unknown " + Paritybit.class.getSimpleName() + " value: " + value);
+		}
+	}
+
+	/**
+	 * Sets the baud rate (ie. bits per second) for communication with the serial device.
+	 * The baud rate will include bits for framing (in the form of stop bits and parity),
+	 * such that the effective data rate will be lower than this value.
+	 *
+	 * @param baudrate The baud rate (in bits per second)
+	 */
+	PureJavaCommChannelConfig setBaudrate(int baudrate);
+
+	/**
+	 * Sets the number of stop bits to include at the end of every character to aid the
+	 * serial device in synchronising with the data.
+	 *
+	 * @param stopbits The number of stop bits to use
+	 */
+	PureJavaCommChannelConfig setStopbits(Stopbits stopbits);
+
+	/**
+	 * Sets the number of data bits to use to make up each character sent to the serial
+	 * device.
+	 *
+	 * @param databits The number of data bits to use
+	 */
+	PureJavaCommChannelConfig setDatabits(Databits databits);
+
+	/**
+	 * Sets the type of parity bit to be used when communicating with the serial device.
+	 *
+	 * @param paritybit The type of parity bit to be used
+	 */
+	PureJavaCommChannelConfig setParitybit(Paritybit paritybit);
+
+	/**
+	 * @return The configured baud rate, defaulting to 115200 if unset
+	 */
+	int getBaudrate();
+
+	/**
+	 * @return The configured stop bits, defaulting to {@link Stopbits#STOPBITS_1} if unset
+	 */
+	Stopbits getStopbits();
+
+	/**
+	 * @return The configured data bits, defaulting to {@link Databits#DATABITS_8} if unset
+	 */
+	Databits getDatabits();
+
+	/**
+	 * @return The configured parity bit, defaulting to {@link Paritybit#NONE} if unset
+	 */
+	Paritybit getParitybit();
+
+	/**
+	 * @return true if the serial device should support the Data Terminal Ready signal
+	 */
+	boolean isDtr();
+
+	/**
+	 * Sets whether the serial device supports the Data Terminal Ready signal, used for
+	 * flow control
+	 *
+	 * @param dtr true if DTR is supported, false otherwise
+	 */
+	PureJavaCommChannelConfig setDtr(boolean dtr);
+
+	/**
+	 * @return true if the serial device should support the Ready to Send signal
+	 */
+	boolean isRts();
+
+	/**
+	 * Sets whether the serial device supports the Request To Send signal, used for flow
+	 * control
+	 *
+	 * @param rts true if RTS is supported, false otherwise
+	 */
+	PureJavaCommChannelConfig setRts(boolean rts);
+
+	/**
+	 * @return The number of milliseconds to wait between opening the serial port and
+	 * initialising.
+	 */
+	int getWaitTimeMillis();
+
+	/**
+	 * Sets the time to wait after opening the serial port and before sending it any
+	 * configuration information or data. A value of 0 indicates that no waiting should
+	 * occur.
+	 *
+	 * @param waitTimeMillis The number of milliseconds to wait, defaulting to 0 (no
+	 *                       wait) if unset
+	 * @throws IllegalArgumentException if the supplied value is < 0
+	 */
+	PureJavaCommChannelConfig setWaitTimeMillis(int waitTimeMillis);
+
+	/**
+	 * Sets the maximal time (in ms) to block while try to read from the serial port. Default is 1000ms
+	 */
+	PureJavaCommChannelConfig setReadTimeout(int readTimout);
+
+	/**
+	 * Return the maximal time (in ms) to block and wait for something to be ready to read.
+	 */
+	int getReadTimeout();
+
+	@Override
+	PureJavaCommChannelConfig setConnectTimeoutMillis(int connectTimeoutMillis);
+
+	@Override
+	PureJavaCommChannelConfig setMaxMessagesPerRead(int maxMessagesPerRead);
+
+	@Override
+	PureJavaCommChannelConfig setWriteSpinCount(int writeSpinCount);
+
+	@Override
+	PureJavaCommChannelConfig setAllocator(ByteBufAllocator allocator);
+
+	@Override
+	PureJavaCommChannelConfig setRecvByteBufAllocator(RecvByteBufAllocator allocator);
+
+	@Override
+	PureJavaCommChannelConfig setAutoRead(boolean autoRead);
+
+	@Override
+	PureJavaCommChannelConfig setAutoClose(boolean autoClose);
+
+	@Override
+	PureJavaCommChannelConfig setWriteBufferHighWaterMark(int writeBufferHighWaterMark);
+
+	@Override
+	PureJavaCommChannelConfig setWriteBufferLowWaterMark(int writeBufferLowWaterMark);
+
+	@Override
+	PureJavaCommChannelConfig setMessageSizeEstimator(MessageSizeEstimator estimator);
+}

+ 41 - 0
protocol-core/src/main/java/wei/yigulu/purejavacomm/PureJavaCommChannelOption.java

@@ -0,0 +1,41 @@
+package wei.yigulu.purejavacomm;
+
+
+import io.netty.channel.ChannelOption;
+import wei.yigulu.purejavacomm.PureJavaCommChannelConfig.Databits;
+import wei.yigulu.purejavacomm.PureJavaCommChannelConfig.Paritybit;
+import wei.yigulu.purejavacomm.PureJavaCommChannelConfig.Stopbits;
+
+/**
+ * Option for configuring a serial port connection
+ */
+public final class PureJavaCommChannelOption<T> extends ChannelOption<T> {
+	public static final PureJavaCommChannelOption<Integer> BAUD_RATE =
+			new PureJavaCommChannelOption<Integer>("BAUD_RATE");
+
+	public static final PureJavaCommChannelOption<Boolean> DTR =
+			new PureJavaCommChannelOption<Boolean>("DTR");
+
+	public static final PureJavaCommChannelOption<Boolean> RTS =
+			new PureJavaCommChannelOption<Boolean>("RTS");
+
+	public static final PureJavaCommChannelOption<Stopbits> STOP_BITS =
+			new PureJavaCommChannelOption<Stopbits>("STOP_BITS");
+
+	public static final PureJavaCommChannelOption<Databits> DATA_BITS =
+			new PureJavaCommChannelOption<Databits>("DATA_BITS");
+
+	public static final PureJavaCommChannelOption<Paritybit> PARITY_BIT =
+			new PureJavaCommChannelOption<Paritybit>("PARITY_BIT");
+
+	public static final PureJavaCommChannelOption<Integer> WAIT_TIME =
+			new PureJavaCommChannelOption<Integer>("WAIT_TIME");
+
+	public static final PureJavaCommChannelOption<Integer> READ_TIMEOUT =
+			new PureJavaCommChannelOption<Integer>("READ_TIMEOUT");
+
+	@SuppressWarnings("deprecation")
+	private PureJavaCommChannelOption(String name) {
+		super(name);
+	}
+}

+ 30 - 0
protocol-core/src/main/java/wei/yigulu/purejavacomm/PureJavaCommDeviceAddress.java

@@ -0,0 +1,30 @@
+package wei.yigulu.purejavacomm;
+
+import java.net.SocketAddress;
+
+/**
+ * A {@link SocketAddress} subclass to wrap the serial port address of a PureJavaComm
+ * device (e.g. COM1, /dev/ttyUSB0).
+ */
+public class PureJavaCommDeviceAddress extends SocketAddress {
+
+	private static final long serialVersionUID = -2907820090993709523L;
+
+	private final String value;
+
+	/**
+	 * Creates a PureJavaCommDeviceAddress representing the address of the serial port.
+	 *
+	 * @param value the address of the device (e.g. COM1, /dev/ttyUSB0, ...)
+	 */
+	public PureJavaCommDeviceAddress(String value) {
+		this.value = value;
+	}
+
+	/**
+	 * @return The serial port address of the device (e.g. COM1, /dev/ttyUSB0, ...)
+	 */
+	public String value() {
+		return value;
+	}
+}

+ 49 - 0
protocol-core/src/main/java/wei/yigulu/threadpool/LocalThreadPool.java

@@ -0,0 +1,49 @@
+package wei.yigulu.threadpool;
+
+
+import lombok.Getter;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * 104工具自带线程池
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+
+public class LocalThreadPool {
+
+	private static class LazyHolder {
+		private static final LocalThreadPool INSTANCE = new LocalThreadPool();
+	}
+
+	/**
+	 * Local pool
+	 */
+	@Getter
+	ExecutorService localPool;
+
+
+	/**
+	 * Gets instance *
+	 *
+	 * @return the instance
+	 */
+	public static final LocalThreadPool getInstance() {
+		return LazyHolder.INSTANCE;
+	}
+
+	private LocalThreadPool() {
+
+        /*
+          由于本场景下应用的线程任务均是长时间的线程任务,不涉及线程的超时和回收,这样选择缓存线程池
+         */
+		localPool = Executors.newCachedThreadPool();
+	}
+
+
+}
+
+

+ 7 - 0
protocol-core/src/main/java/wei/yigulu/threadpool/package-info.java

@@ -0,0 +1,7 @@
+package wei.yigulu.threadpool;
+
+/*
+ * 包括主线程池
+ * 心跳检测线程
+ * 非阻塞式启动
+ * */

+ 66 - 0
protocol-core/src/main/java/wei/yigulu/utils/CrcUtils.java

@@ -0,0 +1,66 @@
+package wei.yigulu.utils;
+
+/**
+ * CRC校验工具
+ *
+ * @author: xiuwei
+ * @version:
+ */
+public class CrcUtils {
+
+	/**
+	 * 计算CRC16校验码
+	 *
+	 * @param bytes
+	 * @return
+	 */
+	public static Integer generateCRC16(byte[] bytes) {
+		int CRC = 0x0000ffff;
+		int POLYNOMIAL = 0x0000a001;
+
+		int i, j;
+		for (i = 0; i < bytes.length; i++) {
+			CRC ^= ((int) bytes[i] & 0x000000ff);
+			for (j = 0; j < 8; j++) {
+				if ((CRC & 0x00000001) != 0) {
+					CRC >>= 1;
+					CRC ^= POLYNOMIAL;
+				} else {
+					CRC >>= 1;
+				}
+			}
+		}
+		return CRC;
+	}
+
+	/**
+	 * 生成crc8
+	 * 宽度 8
+	 * 多项式  07-------x8+x2+x+1----10000111------00000111
+	 * 初始值 0x00;
+	 * 结果异或值 0xff;
+	 *
+	 * @param data 数据
+	 * @return int
+	 */
+	public static int generateCRC8(byte[] data) {
+		int init = 0x00;
+		int genPoly = 0x07;
+		int width = 8;
+		int xorout = 0xff;
+		for (int i = 0; i < data.length; i++) {
+			init ^= data[i];
+			for (int j = 0; j < width; j++) {
+				if ((init & 0x80) != 0) {
+					init = (init << 1) ^ genPoly;
+				} else {
+					init <<= 1;
+				}
+			}
+		}
+		init ^= xorout;
+		return init & 0xff;
+	}
+
+
+}

+ 64 - 0
protocol-core/src/main/java/wei/yigulu/utils/DataConvertor.java

@@ -0,0 +1,64 @@
+package wei.yigulu.utils;
+
+import io.netty.buffer.ByteBuf;
+
+import java.util.Formatter;
+
+/**
+ * 对支付串的处理工具
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+public class DataConvertor {
+
+	/**
+	 * 字节数字转16进制字符串
+	 *
+	 * @param ba ba
+	 * @return string
+	 */
+	public static String Byte2String(byte[] ba) {
+		if (ba == null || ba.length == 0) {
+			return null;
+		}
+		Formatter f = new Formatter();
+		for (int i = 0; i < ba.length; ++i) {
+			f.format("%02x ", ba[i]);
+		}
+		return f.toString();
+	}
+
+
+	/**
+	 * Byte append string
+	 *
+	 * @param bytes bytes
+	 * @return the string
+	 */
+	public static String byteAppend(byte[] bytes) {
+		StringBuffer stringBuffer = new StringBuffer();
+		for (int i = bytes.length - 1; i >= 0; i--) {
+			stringBuffer.append(String.format("%02d", bytes[i]));
+		}
+		return stringBuffer.toString();
+	}
+
+	/**
+	 * 字节数缓冲区字转16进制字符串
+	 *
+	 * @param buf buf
+	 * @return string
+	 */
+	public static String ByteBuf2String(ByteBuf buf) {
+		if (!buf.isReadable()) {
+			return null;
+		}
+		ByteBuf b1 = buf.copy();
+		byte[] bs = new byte[b1.readableBytes()];
+		b1.readBytes(bs);
+		b1.release();
+		return Byte2String(bs);
+	}
+
+}

+ 139 - 0
protocol-iec104/README.en.md

@@ -0,0 +1,139 @@
+###### 本工具是本人基于工作所需开发的针对于IEC104通讯的辅助工具
+
+该工具针对U帧和S帧已经做好了收发逻辑,但是I帧的具体处理逻辑需要使用者自己实现
+
+本工具基于netty框架。建议有netty使用经验和对104通讯规约的人使用。如果不了解104通讯规约,理解本工具的工作流程可能会有些麻烦。
+
+# 使用方式 
+
+## Matser的创建 以及数据的接收处理:
+
+### Matser的创建 
+
+```java
+SimpleMasterBuilder simpleMasterBuilder=new SimpleMasterBuilder("127.0.0.1",2404);
+        simpleMasterBuilder.create();
+```
+
+ 上面 这种master仅支持一个ip和端口号,与之相对的是HSMasterBuilder 支持主备链接 ,主动切换。
+
+```java
+ HSMasterBuilder masterBuilder=new HSMasterBuilder("127.0.0.1",2404).setSpareIp("127.0.0.2");
+        masterBuilder.create();
+```
+
+create() 方法会阻塞线程,如果不希望阻塞线程可以使用createByUnBlock(),以工具内的单线程池执行。
+
+### 数据的接收
+
+首先向slave端发送总召唤
+
+```java
+        //创建总召唤类型I帧
+        TotalSummonType totalSummonType=new TotalSummonType();
+        //反向生成asdu
+        Asdu asdu = totalSummonType.generateBack();
+        //配置总召唤发送原因
+        asdu.setNot(6);
+        //配置公共地址位
+        asdu.setCommonAddress(1);
+        Apdu apdu=new Apdu().setAsdu(asdu);
+        masterBuilder.sendFrame(apdu);
+```
+
+实现对应数据类型的数据接收后的处理,以数据类型为13的浮点数为例
+
+```java
+@Slf4j
+@AsduType(typeId=13)
+public class HandleShortFloat extends ShortFloatType {
+
+
+    /**
+     * 处理短浮点数据
+     *
+     * @param apdu
+     * @return
+     */
+    @Override
+    public byte[][] handleAndAnswer(Apdu apdu) {
+        log.info("----------处理短浮点数据---------");
+        Map<Integer, Float> map = new HashMap<>();
+        HandleShortFloat handleShortFloat = (HandleShortFloat) apdu.getAsdu().getDataFrame();
+        List<InformationBodyAddress> address = handleShortFloat.getAddresses();
+        Map<IeMeasuredQuality, Float> datas = handleShortFloat.getDatas();
+        int i = 0;
+        //存入共享服务端
+        if (apdu.getAsdu().getVsq().getSq() == 0) {
+            log.warn("------处理短浮点单一寻址-----");
+            for (Map.Entry<IeMeasuredQuality, Float> e : datas.entrySet()) {
+                map.put(address.get(i++).getAddress(), e.getValue());
+            }
+        } else if (apdu.getAsdu().getVsq().getSq() == 1) {
+            log.warn("------处理短浮点连续寻址-----");
+            i = address.get(0).getAddress();
+            for (Map.Entry<IeMeasuredQuality, Float> e : datas.entrySet()) {
+                map.put(i++, e.getValue());
+            }
+        }
+        //将map 储起来
+       //TODO
+        
+        //如果有需要返回数据帧可以创建byte数据  向内置入要返回的数据帧encode()后的数组
+        return null;
+    }
+}
+```
+
+## Slave端的创建及数据的发送
+
+### Slave端的创建
+
+```java
+SlaverBuilder slaverBuilder=new SlaverBuilder();
+slaverBuilder.create();
+```
+
+数据响应 响应总召唤
+
+```java
+@AsduType(typeId = 100)
+@Slf4j
+public class HandleTotalSummonType extends TotalSummonType {
+
+
+	@Override
+	public byte[][] handleAndAnswer(Apdu apdu) throws Exception {
+		TotalSummonType dataFrameType = (TotalSummonType) (apdu.getAsdu().getDataFrame());
+		Channel channel = apdu.getChannel();
+		int commonAddress = apdu.getAsdu().getCommonAddress();
+		if (dataFrameType.getValue() == 20 && apdu.getAsdu().getCot().getNot() == 6) {
+			//1.回应激活确认总召唤
+			SendDataFrameHelper.sendTotalSummonFrame(channel, commonAddress, 7);
+			//2.回应连续寻址短浮点i帧
+			SendDataFrameHelper.sendYcDataFrame(channel, //TODO 传入<Integer,Number>类型的数据map
+                                                , commonAddress, 20);
+			//3.回应激活终止总召唤总召唤
+			SendDataFrameHelper.sendTotalSummonFrame(channel, commonAddress, 8);
+		}
+		return null;
+	}
+
+
+```
+
+突发上送数据 向所有接入的master突发上送map中的数据
+
+```java
+for (Channel channel : slaverBuilder.getChannels()) {
+				try {
+					SendDataFrameHelper.sendYcDataFrameDiscontinuity(channel, //TODO 传入<Integer,Number>类型的数据map
+                                                                     , 1, 3);
+				} catch (Exception e) {
+					e.printStackTrace();
+				}
+			}
+```
+
+##### 更加深入的应用请研究代码  apdu asdu 和各个类型的报文帧  都支持重写,包括拆包工具也支持重写,如果有疑问可以向 weiyigulu524710549@gmail.com  邮箱留言
+

+ 139 - 0
protocol-iec104/README.md

@@ -0,0 +1,139 @@
+###### 本工具是本人基于工作所需开发的针对于IEC104通讯的辅助工具
+
+该工具针对U帧和S帧已经做好了收发逻辑,但是I帧的具体处理逻辑需要使用者自己实现
+
+本工具基于netty框架。建议有netty使用经验和对104通讯规约的人使用。如果不了解104通讯规约,理解本工具的工作流程可能会有些麻烦。
+
+# 使用方式 
+
+## Matser的创建 以及数据的接收处理:
+
+### Matser的创建 
+
+```java
+SimpleMasterBuilder simpleMasterBuilder=new SimpleMasterBuilder("127.0.0.1",2404);
+        simpleMasterBuilder.create();
+```
+
+ 上面 这种master仅支持一个ip和端口号,与之相对的是HSMasterBuilder 支持主备链接 ,主动切换。
+
+```java
+ HSMasterBuilder masterBuilder=new HSMasterBuilder("127.0.0.1",2404).setSpareIp("127.0.0.2");
+        masterBuilder.create();
+```
+
+create() 方法会阻塞线程,如果不希望阻塞线程可以使用createByUnBlock(),以工具内的单线程池执行。
+
+### 数据的接收
+
+首先向slave端发送总召唤
+
+```java
+        //创建总召唤类型I帧
+        TotalSummonType totalSummonType=new TotalSummonType();
+        //反向生成asdu
+        Asdu asdu = totalSummonType.generateBack();
+        //配置总召唤发送原因
+        asdu.setNot(6);
+        //配置公共地址位
+        asdu.setCommonAddress(1);
+        Apdu apdu=new Apdu().setAsdu(asdu);
+        masterBuilder.sendFrame(apdu);
+```
+
+实现对应数据类型的数据接收后的处理,以数据类型为13的浮点数为例
+
+```java
+@Slf4j
+@AsduType(typeId=13)
+public class HandleShortFloat extends ShortFloatType {
+
+
+    /**
+     * 处理短浮点数据
+     *
+     * @param apdu
+     * @return
+     */
+    @Override
+    public byte[][] handleAndAnswer(Apdu apdu) {
+        log.info("----------处理短浮点数据---------");
+        Map<Integer, Float> map = new HashMap<>();
+        HandleShortFloat handleShortFloat = (HandleShortFloat) apdu.getAsdu().getDataFrame();
+        List<InformationBodyAddress> address = handleShortFloat.getAddresses();
+        Map<IeMeasuredQuality, Float> datas = handleShortFloat.getDatas();
+        int i = 0;
+        //存入共享服务端
+        if (apdu.getAsdu().getVsq().getSq() == 0) {
+            log.warn("------处理短浮点单一寻址-----");
+            for (Map.Entry<IeMeasuredQuality, Float> e : datas.entrySet()) {
+                map.put(address.get(i++).getAddress(), e.getValue());
+            }
+        } else if (apdu.getAsdu().getVsq().getSq() == 1) {
+            log.warn("------处理短浮点连续寻址-----");
+            i = address.get(0).getAddress();
+            for (Map.Entry<IeMeasuredQuality, Float> e : datas.entrySet()) {
+                map.put(i++, e.getValue());
+            }
+        }
+        //将map 储起来
+       //TODO
+        
+        //如果有需要返回数据帧可以创建byte数据  向内置入要返回的数据帧encode()后的数组
+        return null;
+    }
+}
+```
+
+## Slave端的创建及数据的发送
+
+### Slave端的创建
+
+```java
+SlaverBuilder slaverBuilder=new SlaverBuilder();
+slaverBuilder.create();
+```
+
+数据响应 响应总召唤
+
+```java
+@AsduType(typeId = 100)
+@Slf4j
+public class HandleTotalSummonType extends TotalSummonType {
+
+
+	@Override
+	public byte[][] handleAndAnswer(Apdu apdu) throws Exception {
+		TotalSummonType dataFrameType = (TotalSummonType) (apdu.getAsdu().getDataFrame());
+		Channel channel = apdu.getChannel();
+		int commonAddress = apdu.getAsdu().getCommonAddress();
+		if (dataFrameType.getValue() == 20 && apdu.getAsdu().getCot().getNot() == 6) {
+			//1.回应激活确认总召唤
+			SendDataFrameHelper.sendTotalSummonFrame(channel, commonAddress, 7);
+			//2.回应连续寻址短浮点i帧
+			SendDataFrameHelper.sendYcDataFrame(channel, //TODO 传入<Integer,Number>类型的数据map
+                                                , commonAddress, 20);
+			//3.回应激活终止总召唤总召唤
+			SendDataFrameHelper.sendTotalSummonFrame(channel, commonAddress, 8);
+		}
+		return null;
+	}
+
+
+```
+
+突发上送数据 向所有接入的master突发上送map中的数据
+
+```java
+for (Channel channel : slaverBuilder.getChannels()) {
+				try {
+					SendDataFrameHelper.sendYcDataFrameDiscontinuity(channel, //TODO 传入<Integer,Number>类型的数据map
+                                                                     , 1, 3);
+				} catch (Exception e) {
+					e.printStackTrace();
+				}
+			}
+```
+
+##### 更加深入的应用请研究代码  apdu asdu 和各个类型的报文帧  都支持重写,包括拆包工具也支持重写,如果有疑问可以向 weiyigulu524710549@gmail.com  邮箱留言
+

+ 71 - 0
protocol-iec104/pom.xml

@@ -0,0 +1,71 @@
+<?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">
+    <parent>
+        <artifactId>protocol</artifactId>
+        <groupId>wei.yigulu</groupId>
+        <version>1.0</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+
+    <artifactId>protocol-iec104</artifactId>
+
+    <version>${iec104.version}</version>
+    <dependencies>
+        <dependency>
+            <groupId>org.reflections</groupId>
+            <artifactId>reflections</artifactId>
+            <version>0.9.10</version>
+        </dependency>
+        <dependency>
+            <groupId>wei.yigulu</groupId>
+            <artifactId>protocol-core</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-source-plugin</artifactId>
+                <version>2.1</version>
+                <configuration>
+                    <attach>true</attach>
+                </configuration>
+                <executions>
+                    <execution>
+                        <phase>compile</phase>
+                        <goals>
+                            <goal>jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-javadoc-plugin</artifactId>
+                <version>2.10.2</version>
+                <configuration>
+                    <encoding>UTF-8</encoding>
+                    <aggregate>true</aggregate>
+                    <charset>UTF-8</charset>
+                    <docencoding>UTF-8</docencoding>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>attach-javadocs</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>jar</goal>
+                        </goals>
+                        <configuration>
+                            <additionalparam>-Xdoclint:none</additionalparam>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 47 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/annotation/AsduType.java

@@ -0,0 +1,47 @@
+package wei.yigulu.iec104.annotation;
+
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Asdu type
+ *
+ * @author xiuwei
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface AsduType {
+
+	/**
+	 * Value string
+	 *
+	 * @return the string
+	 */
+	String value() default "";
+
+	/**
+	 * Type id int
+	 *
+	 * @return the int
+	 */
+	int typeId() default 0;
+
+	/**
+	 * Is prior boolean
+	 *
+	 * @return the boolean
+	 */
+	boolean isPrior() default false;
+
+	/**
+	 * Builder name string
+	 *
+	 * @return the string
+	 */
+	String builderName() default "";
+
+
+}

+ 320 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/apdumodel/Apdu.java

@@ -0,0 +1,320 @@
+package wei.yigulu.iec104.apdumodel;
+
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.Channel;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import wei.yigulu.iec104.exception.Iec104Exception;
+import wei.yigulu.iec104.nettyconfig.TechnicalTerm;
+import wei.yigulu.iec104.util.SendAndReceiveNumUtil;
+import wei.yigulu.netty.BaseProtocolBuilder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 104协议内核处理类
+ * 104信息体模型类
+ * 消息分为 S I U 格式帧
+ * I格式帧具有ASDU消息体
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@Data
+@Accessors(chain = true)
+@NoArgsConstructor
+@AllArgsConstructor
+public class Apdu {
+
+
+	private Logger log = LoggerFactory.getLogger(this.getClass());
+
+	/**
+	 * 本端的发送序号
+	 */
+	protected int sendSeqNum = 0;
+	/**
+	 * 本端的接收序号
+	 */
+	protected int receiveSeqNum = 0;
+	/**
+	 * 本帧的类型
+	 */
+	protected ApciType apciType = ApciType.I_FORMAT;
+
+	/**
+	 * Asdu
+	 */
+	protected Asdu asdu = null;
+
+	/**
+	 * Iec 104 builder
+	 */
+	protected BaseProtocolBuilder iec104Builder;
+
+	/**
+	 * 记录了该条APDU是在哪条通道里传输的
+	 */
+	protected Channel channel;
+
+	/**
+	 * 枚举,APCI类型,即I帧,S帧,U帧
+	 */
+	public enum ApciType {
+		/**
+		 * I帧
+		 */
+		I_FORMAT,
+		/**
+		 * S帧
+		 */
+		S_FORMAT,
+		/**
+		 * U帧,测试确认
+		 */
+		TESTFR_CON,
+		/**
+		 * U帧,测试命令
+		 */
+		TESTFR_ACT,
+		/**
+		 * U帧,停止确认
+		 */
+		STOPDT_CON,
+		/**
+		 * U帧,停止命令
+		 */
+		STOPDT_ACT,
+		/**
+		 * U帧,启动确认
+		 */
+		STARTDT_CON,
+		/**
+		 * U帧,启动命令
+		 */
+		STARTDT_ACT
+	}
+
+
+	/**
+	 * 读取字节流 将数据帧转化为APDU
+	 *
+	 * @param dis dis
+	 * @return apdu
+	 * @throws Exception exception
+	 */
+	public Apdu loadByteBuf(ByteBuf dis) throws Exception {
+		this.channel = channel;
+		int start = dis.readByte() & 0xff;
+		int len = dis.readByte() & 0xff;
+		log.debug("APDU长度:" + len);
+		byte[] controlFields = new byte[4];
+		if (start != 0x68) {
+			new Iec104Exception("起始字符错误" + start);
+		} else if (len < 4 || len > 253) {
+			new Iec104Exception("帧长度有误" + len);
+		} else {
+			//读4字节控制域
+			dis.readBytes(controlFields);
+			//第一比特=0  ===》I格式
+			if ((controlFields[0] & 0x01) == 0) {
+				//I帧
+				this.apciType = ApciType.I_FORMAT;
+				//发送序列号  先是最低有效位 ,接下来是最高有效位,最高有效位拿到后面,最低有效位后面补0,LSB 0;MSB
+				sendSeqNum = ((controlFields[0] & 0xfe) >> 1) + ((controlFields[1] & 0xff) << 7);
+				//接收序列号 原理同发送序列号
+				receiveSeqNum = ((controlFields[2] & 0xfe) >> 1) + ((controlFields[3] & 0xff) << 7);
+				log.debug("I帧,发送序列号:" + sendSeqNum + ",接收序列号:" + receiveSeqNum);
+				if (this.channel != null) {
+					SendAndReceiveNumUtil.receiveIFrame(this, this.channel.id());
+				}
+				//第一比特=1  第二比特=0 ===》S格式
+			} else if ((controlFields[0] & 0x03) == 1) {
+				//S帧
+				this.apciType = ApciType.S_FORMAT;
+				receiveSeqNum = ((controlFields[2] & 0xfe) >> 1) + ((controlFields[3] & 0xff) << 7);
+				log.debug("S帧,接收序列号:" + receiveSeqNum);
+				//第一比特=1  第二比特=1 ===》S格式
+			} else if ((controlFields[0] & 0x03) == 3) {
+				//U帧
+				switch (controlFields[0]) {
+					case 0x07:
+						this.apciType = ApciType.STARTDT_ACT;
+						log.debug("U帧,启动命令");
+						break;
+					case 0x0B:
+						this.apciType = ApciType.STARTDT_CON;
+						log.debug("U帧启动确认");
+						break;
+					case 0x13:
+						this.apciType = ApciType.STOPDT_ACT;
+						log.debug("U帧停止命令");
+						break;
+					case 0x23:
+						this.apciType = ApciType.STOPDT_CON;
+						log.debug("U帧停止确认");
+						break;
+					case 0x43:
+						this.apciType = ApciType.TESTFR_ACT;
+						log.debug("U帧测试命令");
+						break;
+					case (byte) 0x83:
+						this.apciType = ApciType.TESTFR_CON;
+						log.debug("U帧测试确认");
+						break;
+					default:
+						log.debug("U帧类型异常");
+						break;
+				}
+			}
+			//构建数据单元
+			if (len > 6) {
+				this.asdu = new Asdu().setLog(log).loadByteBuf(dis);
+			}
+		}
+		return this;
+	}
+
+
+	/**
+	 * APDU编码由当前的apdu编码成数据帧
+	 *
+	 * @return byte [ ]
+	 * @throws Exception exception
+	 */
+	public byte[] encode() throws Exception {
+		List<Byte> buffer = new ArrayList<>();
+		buffer.add((byte) 0x68);
+		buffer.add((byte) 0x00);
+		if (apciType == ApciType.I_FORMAT) {
+			buffer.add((byte) (sendSeqNum << 1));
+			buffer.add((byte) (sendSeqNum >> 7));
+			buffer.add((byte) (receiveSeqNum << 1));
+			buffer.add((byte) (receiveSeqNum >> 7));
+			asdu.encode(buffer);
+		} else if (apciType == ApciType.STARTDT_ACT) {
+			buffer.add((byte) 0x07);
+			buffer.add((byte) 0x00);
+			buffer.add((byte) 0x00);
+			buffer.add((byte) 0x00);
+		} else if (apciType == ApciType.STARTDT_CON) {
+			buffer.add((byte) 0x0b);
+			buffer.add((byte) 0x00);
+			buffer.add((byte) 0x00);
+			buffer.add((byte) 0x00);
+		} else if (apciType == ApciType.S_FORMAT) {
+			buffer.add((byte) 0x01);
+			buffer.add((byte) 0x00);
+			buffer.add((byte) (receiveSeqNum << 1));
+			buffer.add((byte) (receiveSeqNum >> 7));
+		}
+		buffer.set(1, (byte) (buffer.size() - 2));
+		byte[] bs = new byte[buffer.size()];
+		for (int i = 0; i < buffer.size(); i++) {
+			bs[i] = buffer.get(i);
+		}
+		return bs;
+	}
+
+
+	/**
+	 * Sets asdu *
+	 *
+	 * @param asdu asdu
+	 * @return the asdu
+	 */
+	public Apdu setAsdu(Asdu asdu) {
+		this.asdu = asdu;
+		this.apciType = ApciType.I_FORMAT;
+		return this;
+	}
+
+
+	/**
+	 * 接收帧后的应答措施
+	 *
+	 * @throws Iec104Exception iec exception
+	 */
+	public void answer() throws Iec104Exception {
+		byte[][] bb;
+		if (this.apciType == ApciType.I_FORMAT) {
+			try {
+				bb = this.asdu.getDataFrame().handleAndAnswer(this);
+			} catch (Exception e) {
+				if (e instanceof NullPointerException) {
+					log.error("数据帧解析后的逻辑处理出现异常", e);
+					throw new Iec104Exception("该I帧无数据体");
+				}
+				log.error("数据帧解析后的逻辑处理出现异常", e);
+				throw new Iec104Exception("I帧响应帧编译出错");
+			}
+		} else if (this.apciType == ApciType.S_FORMAT) {
+			bb = sHandleAndAnswer();
+		} else {
+			bb = uHandleAndAnswer();
+		}
+		ByteBuf buffer = Unpooled.compositeBuffer();
+		if (bb != null) {
+			for (byte[] b : bb) {
+				buffer.writeBytes(b);
+			}
+		}
+		this.channel.pipeline().writeAndFlush(buffer);
+	}
+
+	/**
+	 * U帧的应答措施
+	 *
+	 * @return byte [ ] [ ]
+	 * @throws Iec104Exception iec exception
+	 */
+	public byte[][] uHandleAndAnswer() throws Iec104Exception {
+		byte[][] bb = null;
+		if (this.apciType == ApciType.STARTDT_ACT) {
+			bb = new byte[1][];
+			bb[0] = TechnicalTerm.STARTBACK;
+		} else if (this.apciType == ApciType.STARTDT_CON) {
+			bb = new byte[1][];
+			bb[0] = TechnicalTerm.GENERALINTERROGATION;
+		} else if (this.apciType == ApciType.STOPDT_ACT) {
+			bb = new byte[1][];
+			bb[0] = TechnicalTerm.STOPBACK;
+		} else if (this.apciType == ApciType.TESTFR_ACT) {
+			bb = new byte[1][];
+			bb[0] = TechnicalTerm.TESTBACK;
+		}
+		return bb;
+	}
+
+
+	/**
+	 * 对方响应S帧的应答措施
+	 *
+	 * @return byte [ ] [ ]
+	 * @throws Iec104Exception iec exception
+	 */
+	public byte[][] sHandleAndAnswer() throws Iec104Exception {
+		return null;
+	}
+
+	/**
+	 * 我方丢失 通道对方放出的 i帧
+	 */
+	public void loseReceive() {
+	}
+
+	/**
+	 * 通道对方丢失 我方发出的i帧
+	 */
+	public void loseSend() {
+	}
+
+
+}

+ 193 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/apdumodel/Asdu.java

@@ -0,0 +1,193 @@
+package wei.yigulu.iec104.apdumodel;
+
+
+import io.netty.buffer.ByteBuf;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import wei.yigulu.iec104.asdudataframe.AbstractDataFrameType;
+import wei.yigulu.iec104.container.AsduTypeAnnotationContainer;
+import wei.yigulu.iec104.container.DataTypeClasses;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * ASDU实体
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class Asdu {
+
+	@Accessors(chain = true)
+	private Logger log = LoggerFactory.getLogger(this.getClass());
+
+	/**
+	 * 位于该帧的第六位 ASDU第一位
+	 * 应用数据单元类型
+	 * 列举几个常用的type
+	 * 表1 在监视方向的报文类型
+	 * 1     := 单点信息                           M_SP_NA_1
+	 * 3     := 双点信息                           M_DP_NA_1
+	 * 9    := 测量值, 规一化值                    M_ME_NA_1
+	 * 13    := 测量值, 短浮点数                    M_ME_NC_1
+	 * 30    := 带CP56Time2a时标的单点信息          M_SP_TB_1
+	 * 31    := 带CP56Time2a时标的双点信息          M_DP_TB_1
+	 * 在控制方向的系统命令
+	 * 100:= 总召唤命令                           C_IC_NA_1
+	 */
+	protected int typeId;
+
+
+	/**
+	 * 位于该帧的第七位  asdu的第二位
+	 * vsq 可变限定词  分为 sq 和 num
+	 * 可变结构限定词  ASDU第一位
+	 * 该值为二位16进制数  先转成8位二进制
+	 * 二进制第8位 为0顺序信息元素寻址
+	 * 二进制第8位 为1 单一信息元素寻址
+	 * 剩下7位转为10进制 数值为信息元素数目
+	 */
+	protected Vsq vsq = new Vsq();
+
+
+	/**
+	 * 传送原因   包含测试状态;认可方式;原因序号  位于该帧的第8位  asdu的第三位
+	 * causeOfTransmission
+	 * 3:突发,自发
+	 * 4:初始化
+	 * 6:激活
+	 * 7:激活确认
+	 * 8:停止激活
+	 * 9:停止激活确认
+	 * 10:激活终止
+	 * 20:响应站召唤
+	 */
+	protected Cot cot = new Cot();
+
+	/**
+	 * Set test *
+	 *
+	 * @param test test
+	 */
+	public void setTest(boolean test) {
+		this.getCot().setTest(test);
+	}
+
+	/**
+	 * Set negative confirm *
+	 *
+	 * @param negativeConfirm negative confirm
+	 */
+	public void setNegativeConfirm(boolean negativeConfirm) {
+		this.getCot().setNegativeConfirm(negativeConfirm);
+	}
+
+
+	/**
+	 * Set not *
+	 *
+	 * @param not not
+	 */
+	public void setNot(int not) {
+		this.getCot().setNot(not);
+	}
+
+
+	/**
+	 * 源地址  位于该帧的第9位  asdu的第4位
+	 */
+	protected int originatorAddress;
+
+
+	/**
+	 * 公共地址
+	 * 子站端保持和主站端一致即可
+	 * 应用数据单元地址 位于该帧的第10,11位  asdu的第5,6位
+	 * 低位在前,高位在后
+	 */
+	protected int commonAddress;
+
+
+	/**
+	 * 数据单元 数据单元的类型是有typeId决定的
+	 * 类型不同里面说承载的数据也不同
+	 */
+	protected AbstractDataFrameType dataFrame;
+
+
+	/**
+	 * 隐藏信息,大部分是没有的这个看双方协商
+	 */
+	protected byte[] privateInformation;
+
+	/**
+	 * Load byte buf asdu
+	 *
+	 * @param dataInputStream data input stream
+	 * @return the asdu
+	 * @throws Exception exception
+	 */
+	public Asdu loadByteBuf(ByteBuf dataInputStream) throws Exception {
+		//获取类型表示配置文件
+		this.typeId = dataInputStream.readByte() & 0xff;
+		vsq = new Vsq().readByte(dataInputStream.readByte());
+		cot = new Cot().readByte(dataInputStream.readByte());
+		originatorAddress = dataInputStream.readByte();
+		//公共地址
+		byte[] commAddress = new byte[2];
+		dataInputStream.readBytes(commAddress);
+		commonAddress = commAddress[0] + ((commAddress[1] & 0xff) << 8);
+		//信息体
+		if (typeId < 128) {
+			Map<Integer, DataTypeClasses> map = AsduTypeAnnotationContainer.getInstance().getDataTypes();
+			if (map.containsKey(typeId)) {
+				dataFrame = (AbstractDataFrameType) (map.get(typeId).getTypeClass().newInstance());
+				Method load = map.get(typeId).getLoad();
+				load.invoke(dataFrame, dataInputStream, this.getVsq());
+			} else {
+				byte[] unknown = new byte[dataInputStream.readableBytes()];
+				dataInputStream.readBytes(unknown);
+				throw new IOException("无法转换信息对象,由于类型标识未知: " + typeId);
+			}
+			log.debug(dataFrame.toString());
+			privateInformation = null;
+		} else {
+			log.debug("");
+		}
+		return this;
+	}
+
+	/**
+	 * Encode *
+	 *
+	 * @param buffer buffer
+	 */
+	public void encode(List<Byte> buffer) {
+
+		buffer.add((byte) typeId);
+
+		vsq.encode(buffer);
+
+		cot.encode(buffer);
+
+		buffer.add((byte) originatorAddress);
+
+		buffer.add((byte) commonAddress);
+
+		buffer.add((byte) (commonAddress >> 8));
+
+		dataFrame.encode(buffer);
+	}
+
+}
+

+ 94 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/apdumodel/Cot.java

@@ -0,0 +1,94 @@
+package wei.yigulu.iec104.apdumodel;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * Cot
+ */
+@AllArgsConstructor
+@Data
+@NoArgsConstructor
+
+/**
+ * 传输原因
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+public class Cot {
+
+	/**
+	 * Read byte cot
+	 *
+	 * @param value value
+	 * @return the cot
+	 */
+	public Cot readByte(Byte value) {
+		original = value;
+		String cotFormat = value > 0 ? String.format("%08d", Integer.parseInt(Integer.toBinaryString(original))) : "00000011";
+		//可变结构限定词,转为二进制后获取第8位
+		if (Integer.parseInt(cotFormat.substring(0, 1)) == 1) {
+			test = true;
+		}
+		if (Integer.parseInt(cotFormat.substring(1, 2)) == 1) {
+			negativeConfirm = true;
+		}
+		not = Integer.parseInt(cotFormat.substring(2, 8), 2);
+		return this;
+	}
+
+	/**
+	 * cot 的具体值
+	 */
+	byte original;
+	/**
+	 * 测试状态   第八位
+	 * 0---false---未试验
+	 * 1---true----已试验
+	 */
+	private boolean test;
+
+	/**
+	 * 认证方式   第七位
+	 * 0---false----肯定认可
+	 * 1---true-----否定认可
+	 */
+	boolean negativeConfirm;
+
+	/**
+	 * 传输原因序号   1-6位
+	 * 3:突发,自发
+	 * 4:初始化
+	 * 6:激活
+	 * 7:激活确认
+	 * 8:停止激活
+	 * 9:停止激活确认
+	 * 10:激活终止
+	 * 20:响应站召唤
+	 */
+	int not;
+
+	/**
+	 * Encode *
+	 *
+	 * @param buffer buffer
+	 */
+	public void encode(List<Byte> buffer) {
+		if (this.isTest()) {
+			if (this.isNegativeConfirm()) {
+				buffer.add((byte) (this.getNot() | 0xC0));
+			} else {
+				buffer.add((byte) (this.getNot() | 0x80));
+			}
+		} else {
+			if (this.isNegativeConfirm()) {
+				buffer.add((byte) (this.getNot() | 0x40));
+			} else {
+				buffer.add((byte) this.getNot());
+			}
+		}
+	}
+}

+ 69 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/apdumodel/Vsq.java

@@ -0,0 +1,69 @@
+package wei.yigulu.iec104.apdumodel;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * Vsq
+ */
+@AllArgsConstructor
+@Data
+@NoArgsConstructor
+/**
+ * vsq 可变限定词  分为 sq 和 num
+ * 可变结构限定词  ASDU第一位
+ * 该值为二位16进制数  先转成8位二进制
+ * 二进制第8位 为0 单一信息元素寻址
+ * 二进制第8位 为1 连续信息元素寻址
+ * 剩下7位转为10进制 数值为信息元素数目
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+public class Vsq {
+
+	/**
+	 * Read byte vsq
+	 *
+	 * @param value value
+	 * @return the vsq
+	 */
+	public Vsq readByte(Byte value) {
+		original = value;
+		String vsqFormat = String.format("%08d", Integer.parseInt(Integer.toBinaryString(this.original & 0xff)));
+		//可变结构限定词,转为二进制后获取第8位
+		sq = Integer.parseInt(vsqFormat.substring(0, 1));
+		num = Integer.parseInt(vsqFormat.substring(1, 8), 2);
+		return this;
+	}
+
+	/**
+	 * vsq 的具体值
+	 */
+	byte original;
+
+	/**
+	 * 标制  是 顺序元素 还是 单一元素   第8位
+	 */
+	int sq;
+
+	/**
+	 * 信息体元素地址数量   0-7位
+	 */
+	int num;
+
+	/**
+	 * Encode *
+	 *
+	 * @param buffer buffer
+	 */
+	public void encode(List<Byte> buffer) {
+		if (this.getSq() == 1) {
+			buffer.add((byte) (this.getNum() | 0x80));
+		} else {
+			buffer.add((byte) this.getNum());
+		}
+	}
+}

+ 17 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/apdumodel/package-info.java

@@ -0,0 +1,17 @@
+package wei.yigulu.iec104.apdumodel;
+/*
+ *该包是帧的apdu的数据结构构成的集合
+ * ApplicationProtocolDataUnit--应用协议数据单元
+ * APDU是104帧的数据结构
+ * APDU包含APCI ASDU
+ * U和S帧的APDU仅包含APCI
+ * I帧的APDU包含APCI和ASDU
+ * 由于APCI所含的信息量较少故将APCI概念省略
+ * 故将APDU作为基础的数据结构,
+ * 其中包含起始字符0x68,报文长度,控制域。
+ * APDU中的ASDU==null则为U或S帧
+ * ASDU为应用数据单元其中包含
+ * ASDU数据单元类型,可变限定词,传输原因,源地址,应用数据单元地址,数据帧
+ * 数据帧由于类型不同结构也不尽相同。
+ * 详见asdu_data_frame 包
+ * */

+ 63 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/AbstractDataFrameType.java

@@ -0,0 +1,63 @@
+package wei.yigulu.iec104.asdudataframe;
+
+
+import io.netty.buffer.ByteBuf;
+import lombok.Getter;
+import lombok.Setter;
+import wei.yigulu.iec104.apdumodel.Apdu;
+import wei.yigulu.iec104.apdumodel.Asdu;
+import wei.yigulu.iec104.apdumodel.Vsq;
+
+import java.util.List;
+
+/**
+ * ASDU 数据内容的基类
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+public abstract class AbstractDataFrameType {
+
+	/**
+	 * Work 4 builders
+	 */
+	@Getter
+	@Setter
+	protected List<String> work4Builders;
+
+
+	/**
+	 * 读取ByteBuf组装DataType
+	 *
+	 * @param is  is
+	 * @param vsq vsq
+	 */
+	public abstract void loadByteBuf(ByteBuf is, Vsq vsq);
+
+
+	/**
+	 * 由DataType编码为byte的list
+	 *
+	 * @param buffer buffer
+	 */
+	public abstract void encode(List<Byte> buffer);
+
+	/**
+	 * 由DataType(asdu的数据部分) 新建Asdu
+	 *
+	 * @return asdu
+	 */
+	public abstract Asdu generateBack();
+
+
+	/**
+	 * 用以对该格式帧进行处理
+	 *
+	 * @param apdu apdu
+	 * @return byte [ ] [ ]
+	 * @throws Exception exception
+	 */
+	public abstract byte[][] handleAndAnswer(Apdu apdu) throws Exception;
+
+
+}

+ 167 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/BooleanType.java

@@ -0,0 +1,167 @@
+package wei.yigulu.iec104.asdudataframe;
+
+
+import io.netty.buffer.ByteBuf;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import wei.yigulu.iec104.apdumodel.Apdu;
+import wei.yigulu.iec104.apdumodel.Asdu;
+import wei.yigulu.iec104.apdumodel.Vsq;
+import wei.yigulu.iec104.asdudataframe.typemodel.IeBoolean;
+import wei.yigulu.iec104.asdudataframe.typemodel.InformationBodyAddress;
+import wei.yigulu.iec104.exception.Iec104Exception;
+import wei.yigulu.iec104.nettyconfig.TechnicalTerm;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 单点信息的数据类型帧
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+@Data
+public class BooleanType extends AbstractDataFrameType {
+
+	/**
+	 * TYPEID
+	 */
+	public static final int TYPEID = TechnicalTerm.SINGEL_POINT_TYPE;
+
+	private List<InformationBodyAddress> addresses = new ArrayList<>();
+
+	private List<IeBoolean> datas = new ArrayList<>();
+
+	/**
+	 * Boolean type
+	 *
+	 * @param addresses addresses
+	 * @param datas     datas
+	 * @throws Iec104Exception iec exception
+	 */
+	public BooleanType(List<InformationBodyAddress> addresses, List<IeBoolean> datas) throws Iec104Exception {
+		if ((this.datas.size() + this.addresses.size() * 4) > 240) {
+			throw new Iec104Exception("长度超长,创建对象失败,请切割数据。");
+		}
+		this.addresses = addresses;
+		this.datas = datas;
+	}
+
+
+	/**
+	 * 向datas中添加数据,默认的质量描述
+	 *
+	 * @param f f
+	 * @throws Iec104Exception iec exception
+	 */
+	public void addData(boolean f) throws Iec104Exception {
+		validateLen(1);
+		this.datas.add(new IeBoolean(f));
+	}
+
+
+	/**
+	 * 向datas中添加数据和数据地址
+	 *
+	 * @param address address
+	 * @param f       f
+	 * @throws Iec104Exception iec exception
+	 */
+	public void addDataAndAdd(InformationBodyAddress address, boolean f) throws Iec104Exception {
+		this.addresses.add(address);
+		addData(f);
+	}
+
+
+	/**
+	 * 向datas中添加数据和数据地址
+	 *
+	 * @param address address
+	 * @throws Iec104Exception iec exception
+	 */
+	public void addAddress(InformationBodyAddress address) throws Iec104Exception {
+		validateLen(4);
+		this.addresses.add(address);
+	}
+
+	/**
+	 * Validate len *
+	 *
+	 * @param increase increase
+	 * @throws Iec104Exception iec exception
+	 */
+	protected void validateLen(int increase) throws Iec104Exception {
+		if ((this.datas.size() * 5 + this.addresses.size() * 4 + increase) > 240) {
+			throw new Iec104Exception("长度超长,不能再向此对象中添加元素");
+		}
+	}
+
+	@Override
+	public void encode(List<Byte> buffer) {
+		if (addresses.size() == 1) {
+			addresses.get(0).encode(buffer);
+			for (IeBoolean i : datas) {
+				buffer.add((byte) i.encode());
+			}
+		} else {
+			for (int i = 0; i < datas.size(); i++) {
+				addresses.get(i).encode(buffer);
+				buffer.add((byte) (datas.get(i).encode()));
+			}
+		}
+	}
+
+	@Override
+	public Asdu generateBack() {
+		Asdu asdu = new Asdu();
+		asdu.setTypeId(1);
+		asdu.setDataFrame(this);
+		asdu.getVsq().setSq(this.addresses.size() == 1 ? 1 : 0);
+		asdu.getVsq().setNum(this.datas.size());
+		asdu.setOriginatorAddress(0);
+		asdu.setCommonAddress(1);
+		return asdu;
+	}
+
+	@Override
+	public void loadByteBuf(ByteBuf is, Vsq vsq) {
+		if (vsq.getSq() == 0) {
+			for (int i = 0; i < vsq.getNum(); i++) {
+				addresses.add(new InformationBodyAddress(is));
+				datas.add(new IeBoolean(is));
+			}
+		} else {
+			addresses.add(new InformationBodyAddress(is));
+			for (int i = 0; i < vsq.getNum(); i++) {
+				datas.add(new IeBoolean(is));
+			}
+		}
+	}
+
+	@Override
+	public byte[][] handleAndAnswer(Apdu apdu) throws Exception {
+		return null;
+	}
+
+	@Override
+	public String toString() {
+		StringBuilder s = new StringBuilder("单点信息\n");
+		if (addresses.size() == 1) {
+			s.append(addresses.get(0).toString()).append("\n");
+			for (IeBoolean i : datas) {
+				s.append(i.toString());
+			}
+		} else {
+			for (int i = 0; i < datas.size(); i++) {
+				s.append(addresses.get(i).toString());
+				s.append(datas.get(i).toString());
+			}
+		}
+		return s.toString();
+	}
+
+}

+ 173 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/NoQualityNormalizedIntegerType.java

@@ -0,0 +1,173 @@
+package wei.yigulu.iec104.asdudataframe;
+
+
+import io.netty.buffer.ByteBuf;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import wei.yigulu.iec104.apdumodel.Apdu;
+import wei.yigulu.iec104.apdumodel.Asdu;
+import wei.yigulu.iec104.apdumodel.Vsq;
+import wei.yigulu.iec104.asdudataframe.typemodel.IeShortInteger;
+import wei.yigulu.iec104.asdudataframe.typemodel.InformationBodyAddress;
+import wei.yigulu.iec104.exception.Iec104Exception;
+import wei.yigulu.iec104.nettyconfig.TechnicalTerm;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 没有品质位的归一化值
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@Data
+@NoArgsConstructor
+public class NoQualityNormalizedIntegerType extends AbstractDataFrameType {
+
+	/**
+	 * TYPEID
+	 */
+	public static final int TYPEID = TechnicalTerm.NOQUALITY_NORMALIZED_INTEGER_TYPE;
+
+	private List<InformationBodyAddress> addresses = new ArrayList<>();
+
+	private List<Integer> datas = new ArrayList<>();
+
+	/**
+	 * No quality normalized integer type
+	 *
+	 * @param addresses addresses
+	 * @param datas     datas
+	 * @throws Iec104Exception iec exception
+	 */
+	public NoQualityNormalizedIntegerType(List<InformationBodyAddress> addresses, List<Integer> datas) throws Iec104Exception {
+		if ((this.datas.size() * 3 + this.addresses.size() * 4) > 240) {
+			throw new Iec104Exception("长度超长,创建对象失败,请切割数据。");
+		}
+		this.addresses = addresses;
+		this.datas = datas;
+	}
+
+
+	@Override
+	public void loadByteBuf(ByteBuf is, Vsq vsq) {
+		Integer f;
+		if (vsq.getSq() == 0) {
+			for (int i = 0; i < vsq.getNum(); i++) {
+				addresses.add(new InformationBodyAddress(is));
+				f = new IeShortInteger(is).getValue();
+				datas.add(f);
+			}
+		} else {
+			addresses.add(new InformationBodyAddress(is));
+			for (int i = 0; i < vsq.getNum(); i++) {
+				f = new IeShortInteger(is).getValue();
+				datas.add(f);
+			}
+		}
+	}
+
+	/**
+	 * 向datas中添加数据,默认的质量描述
+	 *
+	 * @param f f
+	 */
+	public void addData(int f) {
+		this.datas.add(f);
+	}
+
+
+	/**
+	 * 向datas中添加数据和数据地址
+	 *
+	 * @param address address
+	 * @param f       f
+	 */
+	public void addDataAndAdd(InformationBodyAddress address, int f) {
+		this.addresses.add(address);
+		addData(f);
+	}
+
+
+	/**
+	 * 向datas中添加数据和数据地址
+	 *
+	 * @param address address
+	 * @throws Iec104Exception iec exception
+	 */
+	public void addAddress(InformationBodyAddress address) throws Iec104Exception {
+		validateLen(4);
+		this.addresses.add(address);
+	}
+
+
+	@Override
+	public void encode(List<Byte> buffer) {
+		if (addresses.size() == 1) {
+			addresses.get(0).encode(buffer);
+			for (Integer i : datas) {
+				new IeShortInteger(i).encode(buffer);
+			}
+		} else {
+			int s = 0;
+			for (Integer i : datas) {
+				addresses.get(s++).encode(buffer);
+				new IeShortInteger(i).encode(buffer);
+			}
+		}
+	}
+
+	@Override
+	public Asdu generateBack() {
+		Asdu asdu = new Asdu();
+		asdu.setTypeId(11);
+		asdu.setDataFrame(this);
+		asdu.getVsq().setSq(this.addresses.size() == 1 ? 1 : 0);
+		asdu.getVsq().setNum(this.datas.size());
+		asdu.setOriginatorAddress(0);
+		asdu.setCommonAddress(1);
+		return asdu;
+	}
+
+
+	/**
+	 * Validate len *
+	 *
+	 * @param increase increase
+	 * @throws Iec104Exception iec exception
+	 */
+	protected void validateLen(int increase) throws Iec104Exception {
+		if ((this.datas.size() * 3 + this.addresses.size() * 4 + increase) > 240) {
+			throw new Iec104Exception("长度超长,不能再向此对象中添加元素");
+		}
+	}
+
+	@Override
+	public byte[][] handleAndAnswer(Apdu apdu) throws Exception {
+
+
+		return null;
+	}
+
+
+	@Override
+	public String toString() {
+		String s = "无质量归一化值";
+		if (addresses.size() == 1) {
+			s += "连续寻址\n";
+			s += addresses.get(0).toString() + "\n";
+			for (Integer e : datas) {
+				s += "值为 :" + e + "\n";
+			}
+		} else {
+			s += "单一寻址\n";
+			int f = 0;
+			for (Integer i : datas) {
+				s += addresses.get(f++).toString();
+				s += i.toString() + "\n";
+			}
+		}
+		return s;
+	}
+}

+ 204 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/NormalizedIntegerType.java

@@ -0,0 +1,204 @@
+package wei.yigulu.iec104.asdudataframe;
+
+
+import io.netty.buffer.ByteBuf;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import wei.yigulu.iec104.apdumodel.Apdu;
+import wei.yigulu.iec104.apdumodel.Asdu;
+import wei.yigulu.iec104.apdumodel.Vsq;
+import wei.yigulu.iec104.asdudataframe.qualitydescription.IeMeasuredQuality;
+import wei.yigulu.iec104.asdudataframe.typemodel.IeShortInteger;
+import wei.yigulu.iec104.asdudataframe.typemodel.InformationBodyAddress;
+import wei.yigulu.iec104.exception.Iec104Exception;
+import wei.yigulu.iec104.nettyconfig.TechnicalTerm;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 104的归一化值数据帧数据帧
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@NoArgsConstructor
+@Data
+public class NormalizedIntegerType extends AbstractDataFrameType {
+
+	/**
+	 * TYPEID
+	 */
+	public static final int TYPEID = TechnicalTerm.NORMALIZED_INTEGER_TYPE;
+
+	private List<InformationBodyAddress> addresses = new ArrayList<>();
+
+	private Map<IeMeasuredQuality, Integer> datas = new LinkedHashMap<>();
+
+	/**
+	 * Normalized integer type
+	 *
+	 * @param addresses addresses
+	 * @param datas     datas
+	 * @throws Iec104Exception iec exception
+	 */
+	public NormalizedIntegerType(List<InformationBodyAddress> addresses, Map<IeMeasuredQuality, Integer> datas) throws Iec104Exception {
+		if ((this.datas.size() * 3 + this.addresses.size() * 4) > 240) {
+			throw new Iec104Exception("长度超长,创建对象失败,请切割数据。");
+		}
+		this.addresses = addresses;
+		this.datas = datas;
+	}
+
+
+	@Override
+	public void loadByteBuf(ByteBuf is, Vsq vsq) {
+		Integer f;
+		if (vsq.getSq() == 0) {
+			for (int i = 0; i < vsq.getNum(); i++) {
+				addresses.add(new InformationBodyAddress(is));
+				f = new IeShortInteger(is).getValue();
+				datas.put(new IeMeasuredQuality(is), f);
+			}
+		} else {
+			addresses.add(new InformationBodyAddress(is));
+			for (int i = 0; i < vsq.getNum(); i++) {
+				f = new IeShortInteger(is).getValue();
+				datas.put(new IeMeasuredQuality(is), f);
+			}
+		}
+	}
+
+	/**
+	 * 向datas中添加数据,默认的质量描述
+	 *
+	 * @param f f
+	 * @throws Iec104Exception iec exception
+	 */
+	public void addData(int f) throws Iec104Exception {
+		addData(f, new IeMeasuredQuality());
+	}
+
+	/**
+	 * 向datas中添加数据
+	 *
+	 * @param f       f
+	 * @param quality quality
+	 * @throws Iec104Exception iec exception
+	 */
+	public void addData(int f, IeMeasuredQuality quality) throws Iec104Exception {
+		validateLen(3);
+		this.datas.put(quality, f);
+	}
+
+
+	/**
+	 * 向datas中添加数据和数据地址
+	 *
+	 * @param address address
+	 * @param f       f
+	 * @param quality quality
+	 * @throws Iec104Exception iec exception
+	 */
+	public void addDataAndAdd(InformationBodyAddress address, int f, IeMeasuredQuality quality) throws Iec104Exception {
+		addAddress(address);
+		addData(f, quality);
+	}
+
+	/**
+	 * 向datas中添加数据和数据地址
+	 *
+	 * @param address address
+	 * @param f       f
+	 * @throws Iec104Exception iec exception
+	 */
+	public void addDataAndAdd(InformationBodyAddress address, int f) throws Iec104Exception {
+		this.addresses.add(address);
+		addData(f);
+	}
+
+
+	/**
+	 * 向datas中添加数据和数据地址
+	 *
+	 * @param address address
+	 * @throws Iec104Exception iec exception
+	 */
+	public void addAddress(InformationBodyAddress address) throws Iec104Exception {
+		validateLen(4);
+		this.addresses.add(address);
+	}
+
+
+	@Override
+	public void encode(List<Byte> buffer) {
+		if (addresses.size() == 1) {
+			addresses.get(0).encode(buffer);
+			for (Map.Entry<IeMeasuredQuality, Integer> i : datas.entrySet()) {
+				new IeShortInteger(i.getValue()).encode(buffer);
+				buffer.add((byte) i.getKey().encode());
+			}
+		} else {
+			int s = 0;
+			for (Map.Entry<IeMeasuredQuality, Integer> i : datas.entrySet()) {
+				addresses.get(s++).encode(buffer);
+				new IeShortInteger(i.getValue()).encode(buffer);
+				buffer.add((byte) i.getKey().encode());
+			}
+		}
+	}
+
+	@Override
+	public Asdu generateBack() {
+		Asdu asdu = new Asdu();
+		asdu.setTypeId(11);
+		asdu.setDataFrame(this);
+		asdu.getVsq().setSq(this.addresses.size() == 1 ? 1 : 0);
+		asdu.getVsq().setNum(this.datas.size());
+		asdu.setOriginatorAddress(0);
+		asdu.setCommonAddress(1);
+		return asdu;
+	}
+
+
+	/**
+	 * Validate len *
+	 *
+	 * @param increase increase
+	 * @throws Iec104Exception iec exception
+	 */
+	protected void validateLen(int increase) throws Iec104Exception {
+		if ((this.datas.size() * 3 + this.addresses.size() * 4 + increase) > 240) {
+			throw new Iec104Exception("长度超长,不能再向此对象中添加元素");
+		}
+	}
+
+	@Override
+	public byte[][] handleAndAnswer(Apdu apdu) throws Exception {
+		return null;
+	}
+
+
+	@Override
+	public String toString() {
+		String s = "归一化值";
+		if (addresses.size() == 1) {
+			s += "连续寻址\n";
+			s += addresses.get(0).toString() + "\n";
+			for (Map.Entry<IeMeasuredQuality, Integer> e : datas.entrySet()) {
+				s += "值为 :" + e.getValue() + ";" + e.getKey().toString() + "\n";
+			}
+		} else {
+			s += "单一寻址\n";
+			int f = 0;
+			for (Map.Entry<IeMeasuredQuality, Integer> i : datas.entrySet()) {
+				s += addresses.get(f++).toString();
+				s += i.getValue().toString();
+				s += i.getKey().toString() + "\n";
+			}
+		}
+		return s;
+	}
+}

+ 77 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/ProofreadTimeType.java

@@ -0,0 +1,77 @@
+package wei.yigulu.iec104.asdudataframe;
+
+
+import io.netty.buffer.ByteBuf;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import wei.yigulu.iec104.apdumodel.Apdu;
+import wei.yigulu.iec104.apdumodel.Asdu;
+import wei.yigulu.iec104.apdumodel.Vsq;
+import wei.yigulu.iec104.asdudataframe.typemodel.IeProofreadTime;
+import wei.yigulu.iec104.asdudataframe.typemodel.InformationBodyAddress;
+import wei.yigulu.iec104.nettyconfig.TechnicalTerm;
+
+import java.util.List;
+
+/**
+ * 时间校对帧
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@AllArgsConstructor
+@NoArgsConstructor
+@Data
+public class ProofreadTimeType extends AbstractDataFrameType {
+
+	/**
+	 * TYPEID
+	 */
+	public static final int TYPEID = TechnicalTerm.DATESYNCHRONIZATION_TYPE;
+
+	private InformationBodyAddress address;
+
+	private IeProofreadTime ieProofreadTime;
+
+
+	@Override
+	public void encode(List<Byte> buffer) {
+		address.encode(buffer);
+		ieProofreadTime.encode(buffer);
+	}
+
+	@Override
+	public Asdu generateBack() {
+		Asdu asdu = new Asdu();
+		asdu.setTypeId(103);
+		asdu.setDataFrame(this);
+		asdu.getVsq().setNum(1);
+		asdu.getVsq().setSq(0);
+		asdu.getCot().setNot(7);
+		asdu.setOriginatorAddress(0);
+		asdu.setCommonAddress(1);
+		return asdu;
+	}
+
+
+	@Override
+	public void loadByteBuf(ByteBuf is, Vsq vsq) {
+		address = new InformationBodyAddress(is);
+		ieProofreadTime = new IeProofreadTime(is);
+	}
+
+	@Override
+	public byte[][] handleAndAnswer(Apdu Apdu) {
+		return null;
+	}
+
+	@Override
+	public String toString() {
+		String s = address.toString() + "\n";
+		s += "时间校对帧,时间为:" + ieProofreadTime.toString();
+		return s;
+	}
+
+
+}

+ 206 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/ShortFloatType.java

@@ -0,0 +1,206 @@
+package wei.yigulu.iec104.asdudataframe;
+
+
+import io.netty.buffer.ByteBuf;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import wei.yigulu.iec104.apdumodel.Apdu;
+import wei.yigulu.iec104.apdumodel.Asdu;
+import wei.yigulu.iec104.apdumodel.Vsq;
+import wei.yigulu.iec104.asdudataframe.qualitydescription.IeMeasuredQuality;
+import wei.yigulu.iec104.asdudataframe.typemodel.IeShortFloat;
+import wei.yigulu.iec104.asdudataframe.typemodel.InformationBodyAddress;
+import wei.yigulu.iec104.exception.Iec104Exception;
+import wei.yigulu.iec104.nettyconfig.TechnicalTerm;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 短浮点型帧
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@NoArgsConstructor
+@Data
+public class ShortFloatType extends AbstractDataFrameType {
+
+	/**
+	 * TYPEID
+	 */
+	public static final int TYPEID = TechnicalTerm.SHORT_FLOAT_TYPE;
+
+	private List<InformationBodyAddress> addresses = new ArrayList<>();
+
+	private Map<IeMeasuredQuality, Float> datas = new LinkedHashMap<>();
+
+
+	/**
+	 * Short float type
+	 *
+	 * @param addresses addresses
+	 * @param datas     datas
+	 * @throws Iec104Exception iec exception
+	 */
+	public ShortFloatType(List<InformationBodyAddress> addresses, Map<IeMeasuredQuality, Float> datas) throws Iec104Exception {
+		if ((this.datas.size() * 5 + this.addresses.size() * 4) > 240) {
+			throw new Iec104Exception("长度超长,创建对象失败,请切割数据。");
+		}
+		this.addresses = addresses;
+		this.datas = datas;
+	}
+
+
+	/**
+	 * 向datas中添加数据,默认的质量描述
+	 *
+	 * @param f f
+	 * @throws Iec104Exception iec exception
+	 */
+	public void addData(float f) throws Iec104Exception {
+		addData(f, new IeMeasuredQuality());
+	}
+
+	/**
+	 * 向datas中添加数据
+	 *
+	 * @param f       f
+	 * @param quality quality
+	 * @throws Iec104Exception iec exception
+	 */
+	public void addData(float f, IeMeasuredQuality quality) throws Iec104Exception {
+		validateLen(5);
+		this.datas.put(quality, f);
+	}
+
+
+	/**
+	 * 向datas中添加数据和数据地址
+	 *
+	 * @param address address
+	 * @param f       f
+	 * @param quality quality
+	 * @throws Iec104Exception iec exception
+	 */
+	public void addDataAndAdd(InformationBodyAddress address, float f, IeMeasuredQuality quality) throws Iec104Exception {
+		addAddress(address);
+		addData(f, quality);
+	}
+
+	/**
+	 * 向datas中添加数据和数据地址
+	 *
+	 * @param address address
+	 * @param f       f
+	 * @throws Iec104Exception iec exception
+	 */
+	public void addDataAndAdd(InformationBodyAddress address, float f) throws Iec104Exception {
+		this.addresses.add(address);
+		addData(f);
+	}
+
+
+	/**
+	 * 向datas中添加数据和数据地址
+	 *
+	 * @param address address
+	 * @throws Iec104Exception iec exception
+	 */
+	public void addAddress(InformationBodyAddress address) throws Iec104Exception {
+		validateLen(4);
+		this.addresses.add(address);
+	}
+
+
+	@Override
+	public void encode(List<Byte> buffer) {
+		if (addresses.size() == 1) {
+			addresses.get(0).encode(buffer);
+			for (Map.Entry<IeMeasuredQuality, Float> i : datas.entrySet()) {
+				new IeShortFloat(i.getValue()).encode(buffer);
+				buffer.add((byte) i.getKey().encode());
+			}
+		} else {
+			int s = 0;
+			for (Map.Entry<IeMeasuredQuality, Float> i : datas.entrySet()) {
+				addresses.get(s++).encode(buffer);
+				new IeShortFloat(i.getValue()).encode(buffer);
+				buffer.add((byte) i.getKey().encode());
+			}
+		}
+
+	}
+
+	@Override
+	public Asdu generateBack() {
+		Asdu asdu = new Asdu();
+		asdu.setTypeId(TYPEID);
+		asdu.setDataFrame(this);
+		asdu.getVsq().setSq(this.addresses.size() == 1 ? 1 : 0);
+		asdu.getVsq().setNum(this.datas.size());
+		asdu.setOriginatorAddress(0);
+		asdu.setCommonAddress(1);
+		return asdu;
+	}
+
+
+	/**
+	 * Validate len *
+	 *
+	 * @param increase increase
+	 * @throws Iec104Exception iec exception
+	 */
+	protected void validateLen(int increase) throws Iec104Exception {
+		if ((this.datas.size() * 5 + this.addresses.size() * 4 + increase) > 240) {
+			throw new Iec104Exception("长度超长,不能再向此对象中添加元素");
+		}
+	}
+
+	@Override
+	public void loadByteBuf(ByteBuf is, Vsq vsq) {
+		float f;
+		if (vsq.getSq() == 0) {
+			for (int i = 0; i < vsq.getNum(); i++) {
+				addresses.add(new InformationBodyAddress(is));
+				f = new IeShortFloat(is).getValue();
+				datas.put(new IeMeasuredQuality(is), f);
+			}
+		} else {
+			addresses.add(new InformationBodyAddress(is));
+			for (int i = 0; i < vsq.getNum(); i++) {
+				f = new IeShortFloat(is).getValue();
+				datas.put(new IeMeasuredQuality(is), f);
+			}
+		}
+	}
+
+	@Override
+	public byte[][] handleAndAnswer(Apdu apdu) throws Exception {
+		return null;
+	}
+
+	@Override
+	public String toString() {
+		String s = "短浮点";
+		if (addresses.size() == 1) {
+			s += "连续寻址\n";
+			s += addresses.get(0).toString() + "\n";
+			for (Map.Entry<IeMeasuredQuality, Float> e : datas.entrySet()) {
+				s += "值为 :" + e.getValue() + ";" + e.getKey().toString() + "\n";
+			}
+		} else {
+			s += "单一寻址\n";
+			int f = 0;
+			for (Map.Entry<IeMeasuredQuality, Float> i : datas.entrySet()) {
+				s += addresses.get(f++).toString();
+				s += i.getValue().toString();
+				s += i.getKey().toString() + "\n";
+			}
+		}
+		return s;
+	}
+
+}

+ 204 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/ShortIntegerType.java

@@ -0,0 +1,204 @@
+package wei.yigulu.iec104.asdudataframe;
+
+
+import io.netty.buffer.ByteBuf;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import wei.yigulu.iec104.apdumodel.Apdu;
+import wei.yigulu.iec104.apdumodel.Asdu;
+import wei.yigulu.iec104.apdumodel.Vsq;
+import wei.yigulu.iec104.asdudataframe.qualitydescription.IeMeasuredQuality;
+import wei.yigulu.iec104.asdudataframe.typemodel.IeShortInteger;
+import wei.yigulu.iec104.asdudataframe.typemodel.InformationBodyAddress;
+import wei.yigulu.iec104.exception.Iec104Exception;
+import wei.yigulu.iec104.nettyconfig.TechnicalTerm;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 104的标度化值数据帧
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@NoArgsConstructor
+@Data
+public class ShortIntegerType extends AbstractDataFrameType {
+
+	/**
+	 * TYPEID
+	 */
+	public static final int TYPEID = TechnicalTerm.SCALING_INTEGER_TYPE;
+
+	private List<InformationBodyAddress> addresses = new ArrayList<>();
+
+	private Map<IeMeasuredQuality, Integer> datas = new LinkedHashMap<>();
+
+	/**
+	 * Short integer type
+	 *
+	 * @param addresses addresses
+	 * @param datas     datas
+	 * @throws Iec104Exception iec exception
+	 */
+	public ShortIntegerType(List<InformationBodyAddress> addresses, Map<IeMeasuredQuality, Integer> datas) throws Iec104Exception {
+		if ((this.datas.size() * 3 + this.addresses.size() * 4) > 240) {
+			throw new Iec104Exception("长度超长,创建对象失败,请切割数据。");
+		}
+		this.addresses = addresses;
+		this.datas = datas;
+	}
+
+
+	@Override
+	public void loadByteBuf(ByteBuf is, Vsq vsq) {
+		Integer f;
+		if (vsq.getSq() == 0) {
+			for (int i = 0; i < vsq.getNum(); i++) {
+				addresses.add(new InformationBodyAddress(is));
+				f = new IeShortInteger(is).getValue();
+				datas.put(new IeMeasuredQuality(is), f);
+			}
+		} else {
+			addresses.add(new InformationBodyAddress(is));
+			for (int i = 0; i < vsq.getNum(); i++) {
+				f = new IeShortInteger(is).getValue();
+				datas.put(new IeMeasuredQuality(is), f);
+			}
+		}
+	}
+
+	/**
+	 * 向datas中添加数据,默认的质量描述
+	 *
+	 * @param f f
+	 * @throws Iec104Exception iec exception
+	 */
+	public void addData(int f) throws Iec104Exception {
+		addData(f, new IeMeasuredQuality());
+	}
+
+	/**
+	 * 向datas中添加数据
+	 *
+	 * @param f       f
+	 * @param quality quality
+	 * @throws Iec104Exception iec exception
+	 */
+	public void addData(int f, IeMeasuredQuality quality) throws Iec104Exception {
+		validateLen(3);
+		this.datas.put(quality, f);
+	}
+
+
+	/**
+	 * 向datas中添加数据和数据地址
+	 *
+	 * @param address address
+	 * @param f       f
+	 * @param quality quality
+	 * @throws Iec104Exception iec exception
+	 */
+	public void addDataAndAdd(InformationBodyAddress address, int f, IeMeasuredQuality quality) throws Iec104Exception {
+		addAddress(address);
+		addData(f, quality);
+	}
+
+	/**
+	 * 向datas中添加数据和数据地址
+	 *
+	 * @param address address
+	 * @param f       f
+	 * @throws Iec104Exception iec exception
+	 */
+	public void addDataAndAdd(InformationBodyAddress address, int f) throws Iec104Exception {
+		this.addresses.add(address);
+		addData(f);
+	}
+
+
+	/**
+	 * 向datas中添加数据和数据地址
+	 *
+	 * @param address address
+	 * @throws Iec104Exception iec exception
+	 */
+	public void addAddress(InformationBodyAddress address) throws Iec104Exception {
+		validateLen(4);
+		this.addresses.add(address);
+	}
+
+
+	@Override
+	public void encode(List<Byte> buffer) {
+		if (addresses.size() == 1) {
+			addresses.get(0).encode(buffer);
+			for (Map.Entry<IeMeasuredQuality, Integer> i : datas.entrySet()) {
+				new IeShortInteger(i.getValue()).encode(buffer);
+				buffer.add((byte) i.getKey().encode());
+			}
+		} else {
+			int s = 0;
+			for (Map.Entry<IeMeasuredQuality, Integer> i : datas.entrySet()) {
+				addresses.get(s++).encode(buffer);
+				new IeShortInteger(i.getValue()).encode(buffer);
+				buffer.add((byte) i.getKey().encode());
+			}
+		}
+	}
+
+	@Override
+	public Asdu generateBack() {
+		Asdu asdu = new Asdu();
+		asdu.setTypeId(11);
+		asdu.setDataFrame(this);
+		asdu.getVsq().setSq(this.addresses.size() == 1 ? 1 : 0);
+		asdu.getVsq().setNum(this.datas.size());
+		asdu.setOriginatorAddress(0);
+		asdu.setCommonAddress(1);
+		return asdu;
+	}
+
+
+	/**
+	 * Validate len *
+	 *
+	 * @param increase increase
+	 * @throws Iec104Exception iec exception
+	 */
+	protected void validateLen(int increase) throws Iec104Exception {
+		if ((this.datas.size() * 3 + this.addresses.size() * 4 + increase) > 240) {
+			throw new Iec104Exception("长度超长,不能再向此对象中添加元素");
+		}
+	}
+
+	@Override
+	public byte[][] handleAndAnswer(Apdu apdu) throws Exception {
+		return null;
+	}
+
+
+	@Override
+	public String toString() {
+		String s = "短整型";
+		if (addresses.size() == 1) {
+			s += "连续寻址\n";
+			s += addresses.get(0).toString() + "\n";
+			for (Map.Entry<IeMeasuredQuality, Integer> e : datas.entrySet()) {
+				s += "值为 :" + e.getValue() + ";" + e.getKey().toString() + "\n";
+			}
+		} else {
+			s += "单一寻址\n";
+			int f = 0;
+			for (Map.Entry<IeMeasuredQuality, Integer> i : datas.entrySet()) {
+				s += addresses.get(f++).toString();
+				s += i.getValue().toString();
+				s += i.getKey().toString() + "\n";
+			}
+		}
+		return s;
+	}
+}

+ 43 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/SimpleDataFrameType.java

@@ -0,0 +1,43 @@
+package wei.yigulu.iec104.asdudataframe;
+
+
+import io.netty.buffer.ByteBuf;
+import wei.yigulu.iec104.apdumodel.Apdu;
+import wei.yigulu.iec104.apdumodel.Asdu;
+import wei.yigulu.iec104.apdumodel.Vsq;
+
+import java.util.List;
+
+/**
+ * 数据帧的简单实现类 方便继承者简单实现
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+public class SimpleDataFrameType extends AbstractDataFrameType {
+
+	/**
+	 * TYPEID
+	 */
+	public static final int TYPEID = -1;
+
+	@Override
+	public void loadByteBuf(ByteBuf is, Vsq vsq) {
+
+	}
+
+	@Override
+	public void encode(List<Byte> buffer) {
+
+	}
+
+	@Override
+	public Asdu generateBack() {
+		return null;
+	}
+
+	@Override
+	public byte[][] handleAndAnswer(Apdu apdu) throws Exception {
+		return new byte[0][];
+	}
+}

+ 75 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/TotalSummonType.java

@@ -0,0 +1,75 @@
+package wei.yigulu.iec104.asdudataframe;
+
+
+import io.netty.buffer.ByteBuf;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import wei.yigulu.iec104.apdumodel.Apdu;
+import wei.yigulu.iec104.apdumodel.Asdu;
+import wei.yigulu.iec104.apdumodel.Vsq;
+import wei.yigulu.iec104.asdudataframe.typemodel.InformationBodyAddress;
+import wei.yigulu.iec104.nettyconfig.TechnicalTerm;
+
+import java.util.List;
+
+/**
+ * 总召唤帧
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@NoArgsConstructor
+@AllArgsConstructor
+@Data
+public class TotalSummonType extends AbstractDataFrameType {
+
+	/**
+	 * TYPEID
+	 */
+	public static final int TYPEID = TechnicalTerm.TOTAL_SUMMONTYPE_TYPE;
+
+	private InformationBodyAddress address;
+
+	private int value;
+
+
+	@Override
+	public void encode(List<Byte> buffer) {
+		address.encode(buffer);
+		buffer.add((byte) value);
+	}
+
+	@Override
+	public Asdu generateBack() {
+		Asdu asdu = new Asdu();
+		asdu.setTypeId(100);
+		asdu.setDataFrame(this);
+		asdu.getVsq().setNum(1);
+		asdu.getVsq().setSq(0);
+		asdu.getCot().setNot(7);
+		asdu.setOriginatorAddress(0);
+		asdu.setCommonAddress(1);
+		return asdu;
+	}
+
+
+	@Override
+	public void loadByteBuf(ByteBuf is, Vsq vsq) {
+		this.address = new InformationBodyAddress(is);
+		this.value = (is.readByte() & 0xff);
+	}
+
+	@Override
+	public byte[][] handleAndAnswer(Apdu apdu) throws Exception {
+		return null;
+	}
+
+
+	@Override
+	public String toString() {
+		String s = address.toString();
+		return "总召唤令;" + s + "\n召唤值:" + value + "\n";
+	}
+
+}

+ 6 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/package-info.java

@@ -0,0 +1,6 @@
+package wei.yigulu.iec104.asdudataframe;
+/*
+ *
+ *
+ * 各种数据类型的I帧数据部分的解析
+ * */

+ 89 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/qualitydescription/IeAbstractQuality.java

@@ -0,0 +1,89 @@
+package wei.yigulu.iec104.asdudataframe.qualitydescription;
+
+import io.netty.buffer.ByteBuf;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+
+/**
+ * 品质描述 的抽象类
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class IeAbstractQuality {
+
+
+	/**
+	 * 品质的值
+	 */
+	protected int value;
+
+	/**
+	 * 封锁标志 0-未被封锁;1被封锁·
+	 */
+	protected boolean blocked;
+
+	/**
+	 * 取代标识  0-未被取代;1被取代
+	 */
+	protected boolean substituted;
+
+	/**
+	 * 当前值标志 0当前值;1非当前值
+	 */
+	protected boolean notTopical;
+
+	/**
+	 * 有效标志 0有效;1无效
+	 */
+	protected boolean invalid;
+
+
+	/**
+	 * Ie abstract quality
+	 *
+	 * @param is is
+	 */
+	public IeAbstractQuality(ByteBuf is) {
+		this.value = (is.readByte() & 0xff);
+		this.blocked = (value & 0x10) == 0x10;
+		this.substituted = (value & 0x20) == 0x20;
+		this.notTopical = (value & 0x40) == 0x40;
+		this.invalid = (value & 0x80) == 0x80;
+	}
+
+
+	/**
+	 * 描述品质位 为1位
+	 *
+	 * @return int
+	 */
+	public int encode() {
+		int v = 0x00;
+		if (blocked) {
+			v |= 0x10;
+		}
+		if (substituted) {
+			v |= 0x20;
+		}
+		if (notTopical) {
+			v |= 0x40;
+		}
+		if (invalid) {
+			v |= 0x80;
+		}
+		return v;
+	}
+
+
+	@Override
+	public String toString() {
+		return "被封锁: " + isBlocked() + ", 被取代: " + isSubstituted() + ", 非当前值: " + isNotTopical()
+				+ ", 无效: " + isInvalid();
+	}
+}

+ 56 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/qualitydescription/IeMeasuredQuality.java

@@ -0,0 +1,56 @@
+package wei.yigulu.iec104.asdudataframe.qualitydescription;
+
+import io.netty.buffer.ByteBuf;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * 测量值品质描述
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@AllArgsConstructor
+@NoArgsConstructor
+@Data
+@EqualsAndHashCode
+public class IeMeasuredQuality extends IeAbstractQuality {
+
+	/**
+	 * 是否溢出 0 代表未溢出;1 代表溢出
+	 */
+	protected boolean overflow;
+
+	/**
+	 * Ie measured quality
+	 *
+	 * @param is is
+	 */
+	public IeMeasuredQuality(ByteBuf is) {
+		super(is);
+		this.overflow = (value & 0x01) == 0x01;
+	}
+
+	@Override
+	public int encode() {
+		int v = super.encode();
+		if (overflow) {
+			v |= 0x01;
+		}
+		return v;
+	}
+
+
+	@Override
+	public boolean equals(Object o) {
+		return false;
+	}
+
+
+	@Override
+	public String toString() {
+		return "品质描述 : { 溢出: " + overflow + ", " + super.toString() + "}";
+	}
+}

+ 7 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/qualitydescription/package-info.java

@@ -0,0 +1,7 @@
+package wei.yigulu.iec104.asdudataframe.qualitydescription;
+
+/*
+ *
+ *  质量描述
+ *
+ * */

+ 51 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/typemodel/IeBoolean.java

@@ -0,0 +1,51 @@
+package wei.yigulu.iec104.asdudataframe.typemodel;
+
+
+import io.netty.buffer.ByteBuf;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import wei.yigulu.iec104.asdudataframe.qualitydescription.IeAbstractQuality;
+
+
+/**
+ * 遥信值
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@AllArgsConstructor
+@NoArgsConstructor
+@Data
+public class IeBoolean extends IeAbstractQuality {
+
+	/**
+	 * 信息状态,是否开闸 1:合;2:开
+	 */
+	protected boolean on;
+
+	/**
+	 * Ie boolean
+	 *
+	 * @param is is
+	 */
+	public IeBoolean(ByteBuf is) {
+		super(is);
+		this.on = (value & 0x01) == 0x01;
+	}
+
+	@Override
+	public int encode() {
+		int v = super.encode();
+		if (on) {
+			v |= 0x01;
+		}
+		return v;
+	}
+
+
+	@Override
+	public String toString() {
+		return "开关量, 是否开闸: " + isOn() + ", " + super.toString() + ";\n";
+	}
+}

+ 45 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/typemodel/IeFourBitInteger.java

@@ -0,0 +1,45 @@
+package wei.yigulu.iec104.asdudataframe.typemodel;
+
+import io.netty.buffer.ByteBuf;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 四字节补位int
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class IeFourBitInteger {
+
+	private Integer value;
+
+	/**
+	 * Ie four bit integer
+	 *
+	 * @param is is
+	 */
+	public IeFourBitInteger(ByteBuf is) {
+		value = ((is.readByte() & 0xff) | ((is.readByte() & 0xff) << 8) | ((is.readByte() & 0xff) << 16) | ((is.readByte() & 0xff) << 24));
+	}
+
+	/**
+	 * Encode *
+	 *
+	 * @param buffer buffer
+	 */
+	public void encode(List<Byte> buffer) {
+		int tempVal = value & 0xff;
+		buffer.add((byte) tempVal);
+		buffer.add((byte) (tempVal >> 8));
+		buffer.add((byte) (tempVal >> 16));
+		buffer.add((byte) (tempVal >> 24));
+	}
+
+}

+ 77 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/typemodel/IeProofreadTime.java

@@ -0,0 +1,77 @@
+package wei.yigulu.iec104.asdudataframe.typemodel;
+
+import io.netty.buffer.ByteBuf;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.joda.time.DateTime;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+
+import java.util.List;
+
+/**
+ * 对时帧的具体时标实体类
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@NoArgsConstructor
+@AllArgsConstructor
+@Data
+public class IeProofreadTime {
+
+	private DateTime time = new DateTime();
+
+	private static final DateTimeFormatter FORMATTER = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss:SSS");
+
+	/**
+	 * Ie proofread time
+	 *
+	 * @param is is
+	 */
+	public IeProofreadTime(ByteBuf is) {
+		byte[] btime = new byte[8];
+		is.readBytes(btime);
+		int milliSecond = (btime[0] & 0xff) + ((btime[1] & 0xff) << 8);
+		int minute = btime[2] & 0xff;
+		int hour = btime[3] & 0xff;
+		int day = btime[4] & 0xff;
+		int month = btime[5] & 0xff;
+		int year = btime[6] & 0xff;
+		String s = "20" + String.format("%02d", year) + "-" + String.format("%02d", month) + "-" + String.format("%02d", day) + " "
+				+ String.format("%02d", hour) + ":" + String.format("%02d", minute) + ":" + String.format("%02d", milliSecond / 1000) + ":" +
+				String.format("%02d", milliSecond % 1000);
+		time = FORMATTER.parseDateTime(s);
+	}
+
+	/**
+	 * Encode *
+	 *
+	 * @param buffer buffer
+	 */
+	public void encode(List<Byte> buffer) {
+		int year = time.getYear();
+		int month = time.getMonthOfYear();
+		int day = time.getDayOfMonth();
+		int hour = time.getHourOfDay();
+		int minute = time.getMinuteOfHour();
+		int second = time.getSecondOfMinute();
+		int milliSecond = time.getMillisOfSecond();
+		String nums = Integer.toBinaryString(second * 1000 + milliSecond);
+		String secondStr = nums.substring(nums.length() - 8);
+		String milliSecondStr = nums.substring(0, nums.length() - 8);
+		buffer.add((byte) (Integer.parseInt(Integer.toHexString(Integer.parseInt(milliSecondStr, 2)), 16)));
+		buffer.add((byte) (Integer.parseInt(Integer.toHexString(Integer.parseInt(secondStr, 2)), 16)));
+		buffer.add((byte) (Integer.parseInt(Integer.toHexString(minute), 16)));
+		buffer.add((byte) (Integer.parseInt(Integer.toHexString(hour), 16)));
+		buffer.add((byte) (Integer.parseInt(Integer.toHexString(day), 16)));
+		buffer.add((byte) (Integer.parseInt(Integer.toHexString(month), 16)));
+		buffer.add((byte) (Integer.parseInt(Integer.toHexString(year - 2000), 16)));
+	}
+
+	@Override
+	public String toString() {
+		return FORMATTER.print(time.getMillis());
+	}
+}

+ 52 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/typemodel/IeShortFloat.java

@@ -0,0 +1,52 @@
+package wei.yigulu.iec104.asdudataframe.typemodel;
+
+import io.netty.buffer.ByteBuf;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 短浮点值
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class IeShortFloat {
+
+	private float value;
+
+	/**
+	 * Ie short float
+	 *
+	 * @param is is
+	 */
+	public IeShortFloat(ByteBuf is) {
+		value = Float.intBitsToFloat((is.readByte() & 0xff) | ((is.readByte() & 0xff) << 8)
+				| ((is.readByte() & 0xff) << 16) | ((is.readByte() & 0xff) << 24));
+	}
+
+
+	/**
+	 * Encode *
+	 *
+	 * @param buffer buffer
+	 */
+	public void encode(List<Byte> buffer) {
+		int tempVal = Float.floatToIntBits(value);
+		buffer.add((byte) tempVal);
+		buffer.add((byte) (tempVal >> 8));
+		buffer.add((byte) (tempVal >> 16));
+		buffer.add((byte) (tempVal >> 24));
+	}
+
+
+	@Override
+	public String toString() {
+		return "短浮点数值: " + value;
+	}
+}

+ 43 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/typemodel/IeShortInteger.java

@@ -0,0 +1,43 @@
+package wei.yigulu.iec104.asdudataframe.typemodel;
+
+import io.netty.buffer.ByteBuf;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 104短整型数据
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class IeShortInteger {
+
+	private Integer value;
+
+	/**
+	 * Ie short integer
+	 *
+	 * @param is is
+	 */
+	public IeShortInteger(ByteBuf is) {
+		value = ((is.readByte() & 0xff) | ((is.readByte() & 0xff) << 8));
+	}
+
+	/**
+	 * Encode *
+	 *
+	 * @param buffer buffer
+	 */
+	public void encode(List<Byte> buffer) {
+		int tempVal = value;
+		buffer.add((byte) tempVal);
+		buffer.add((byte) (tempVal >> 8));
+	}
+
+}

+ 65 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/typemodel/InformationBodyAddress.java

@@ -0,0 +1,65 @@
+package wei.yigulu.iec104.asdudataframe.typemodel;
+
+import io.netty.buffer.ByteBuf;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 应用数据单元类型的基类
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class InformationBodyAddress {
+
+	/**
+	 * 信息体地址
+	 * 共三位16进制字节
+	 * 高低位逆转
+	 */
+	protected int address;
+
+	/**
+	 * Information body address
+	 *
+	 * @param b1 b 1
+	 * @param b2 b 2
+	 * @param b3 b 3
+	 */
+	public InformationBodyAddress(byte b1, byte b2, byte b3) {
+		this.address = (b1 & 0xff) | ((b2 & 0xff) << 8) | ((b3 & 0xff) << 16);
+	}
+
+
+	/**
+	 * Information body address
+	 *
+	 * @param is is
+	 */
+	public InformationBodyAddress(ByteBuf is) {
+		this.address = (is.readByte() & 0xff) | ((is.readByte() & 0xff) << 8) | ((is.readByte() & 0xff) << 16);
+	}
+
+	/**
+	 * Encode *
+	 *
+	 * @param buffer buffer
+	 */
+	public void encode(List<Byte> buffer) {
+		buffer.add((byte) address);
+		buffer.add((byte) (address >> 8));
+		buffer.add((byte) (address >> 16));
+	}
+
+	@Override
+	public String toString() {
+		return "信息体地址为:" + address + ";";
+	}
+
+}

+ 4 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/asdudataframe/typemodel/package-info.java

@@ -0,0 +1,4 @@
+package wei.yigulu.iec104.asdudataframe.typemodel;
+/**
+ * 各种数据类型
+ */

+ 125 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/container/AsduTypeAnnotationContainer.java

@@ -0,0 +1,125 @@
+package wei.yigulu.iec104.container;
+
+
+import io.netty.buffer.ByteBuf;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.reflections.Reflections;
+import wei.yigulu.iec104.annotation.AsduType;
+import wei.yigulu.iec104.apdumodel.Vsq;
+import wei.yigulu.iec104.asdudataframe.AbstractDataFrameType;
+import wei.yigulu.iec104.exception.Iec104Exception;
+import wei.yigulu.iec104.util.PropertiesReader;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 使用了AsduType的注解类的读取
+ * 将所有使用了该注解的类统计在一起
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@Getter
+@Slf4j
+public class AsduTypeAnnotationContainer {
+
+	private static class LazyHolder {
+		private static final AsduTypeAnnotationContainer INSTANCE = new AsduTypeAnnotationContainer();
+	}
+
+	/**
+	 * Gets instance *
+	 *
+	 * @return the instance
+	 */
+	public static final AsduTypeAnnotationContainer getInstance() {
+		return AsduTypeAnnotationContainer.LazyHolder.INSTANCE;
+	}
+
+	private Map<Integer, DataTypeClasses> dataTypes = null;
+
+	/**
+	 * 框架中的i格式帧数据帧的类型继承类的所在包
+	 */
+	private static final String DATAFRAMEPAKAGENAME = "wei/yigulu/iec104/asdudataframe";
+
+
+	/**
+	 * 使用者的i格式帧数据帧的类型继承类的所在包
+	 * 扫描所有包 故值为空串
+	 */
+	private static final String DATAFRAMEPAKAGENAME1 = PropertiesReader.getInstance().getProp("asduDateType", "");
+
+
+	/**
+	 * 获取继承类的TYPEID的属性名
+	 */
+	private static final String TYPEIDATTRIBUTENAME = "TYPEID";
+
+
+	/**
+	 * 获取继承类的TYPEID的属性名
+	 */
+	private static final String LOADMETHODNAME = "loadByteBuf";
+
+
+	private AsduTypeAnnotationContainer() {
+
+
+	}
+
+	/**
+	 * Gets data types *
+	 *
+	 * @return the data types
+	 * @throws Iec104Exception iec exception
+	 */
+	public Map<Integer, DataTypeClasses> getDataTypes() throws Iec104Exception {
+		if (this.dataTypes == null) {
+			Reflections f = new Reflections(DATAFRAMEPAKAGENAME);
+			Set<Class<? extends AbstractDataFrameType>> set = f.getSubTypesOf(AbstractDataFrameType.class);
+			Reflections f1 = new Reflections(DATAFRAMEPAKAGENAME1);
+			Set<Class<?>> set1 = f1.getTypesAnnotatedWith(AsduType.class);
+			Field check;
+			Method load;
+			dataTypes = new ConcurrentHashMap<>();
+			int typeId;
+			try {
+				for (Class<?> c : set) {
+					log.debug("扫描到APDU处理类" + c.getSimpleName());
+					try {
+						check = c.getField(TYPEIDATTRIBUTENAME);
+						load = c.getMethod(LOADMETHODNAME, ByteBuf.class, Vsq.class);
+						load.setAccessible(true);
+						dataTypes.put(check.getInt(null), new DataTypeClasses(c, check.getInt(null), load));
+					} catch (Exception e) {
+						e.printStackTrace();
+					}
+				}
+				for (Class<?> c : set1) {
+					log.debug("扫描到APDU处理类" + c.getSimpleName());
+					check = c.getField(TYPEIDATTRIBUTENAME);
+					typeId = check.getInt(null);
+					if (c.getAnnotation(AsduType.class).typeId() != 0) {
+						typeId = c.getAnnotation(AsduType.class).typeId();
+					}
+					load = c.getMethod(LOADMETHODNAME, ByteBuf.class, Vsq.class);
+					load.setAccessible(true);
+					dataTypes.put(check.getInt(null), new DataTypeClasses(c, typeId, load));
+
+				}
+			} catch (Exception e) {
+				e.printStackTrace();
+				throw new Iec104Exception("初始化获取Asdu类时发生异常");
+			}
+		}
+		return this.dataTypes;
+	}
+}
+
+

+ 29 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/container/DataTypeClasses.java

@@ -0,0 +1,29 @@
+package wei.yigulu.iec104.container;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.lang.reflect.Method;
+
+
+/**
+ * 使用了AsduType的注解类的存储容器为适配asdu类型服务
+ * 将类  解析类型(方法),组装数据(方法)放入其中,读取后放入内存
+ * 避免重复读取
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class DataTypeClasses {
+
+	private Class typeClass;
+
+	private int typeId;
+
+	private Method load;
+
+}

+ 93 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/container/Iec104Link.java

@@ -0,0 +1,93 @@
+package wei.yigulu.iec104.container;
+
+import io.netty.channel.Channel;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+
+/**
+ * 在netty 中的104连接
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@Data
+@NoArgsConstructor
+public class Iec104Link {
+
+	/**
+	 * Iec 104 link
+	 *
+	 * @param channel      channel
+	 * @param ip           ip
+	 * @param port         port
+	 * @param oppositeRole opposite role
+	 */
+	public Iec104Link(Channel channel, String ip, Integer port, Role oppositeRole) {
+		this.channel = channel;
+		this.oppositeIp = ip;
+		this.oppositePort = port;
+		this.oppositeRole = oppositeRole;
+		iReceive = 0;
+		iSend = 0;
+	}
+
+	private String oppositeIp;
+
+	private Integer oppositePort;
+
+	private Channel channel;
+
+	/**
+	 * Role
+	 */
+	public enum Role {
+		/**
+		 * 通道对端在该通道扮演的104角色
+		 */
+		SLAVER,
+		/**
+		 * Master role
+		 */
+		MASTER
+	}
+
+	/**
+	 * Opposite role
+	 */
+	public Role oppositeRole;
+
+	private int iReceive;
+
+	private int iSend;
+
+	private LinkState linkState = LinkState.NORMAL;
+
+	/**
+	 * Link state
+	 */
+	public enum LinkState {
+		/**
+		 * 通道正常
+		 */
+		NORMAL,
+		/**
+		 * 通道对方失去响应
+		 */
+		LOSSRES,
+		/**
+		 * 通道对方断开连接
+		 */
+		DISCONN,
+		/**
+		 * 我方丢失 通道对方放出的 i帧
+		 */
+		LOSEREC,
+		/**
+		 * 通道对方丢失 我方发出的i帧
+		 */
+		LOSESEND
+	}
+
+
+}

+ 98 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/container/LinkContainer.java

@@ -0,0 +1,98 @@
+package wei.yigulu.iec104.container;
+
+import io.netty.channel.ChannelId;
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 储存着104的所有连接
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@Data
+public class LinkContainer {
+
+	private static class LazyHolder {
+		private static final LinkContainer INSTANCE = new LinkContainer();
+	}
+
+	/**
+	 * Gets instance *
+	 *
+	 * @return the instance
+	 */
+	public static final LinkContainer getInstance() {
+		return LinkContainer.LazyHolder.INSTANCE;
+	}
+
+	private LinkContainer() {
+	}
+
+	private Map<ChannelId, Iec104Link> links = new ConcurrentHashMap<>();
+
+
+	/**
+	 * Get link iec 104 link
+	 *
+	 * @param channelId channel id
+	 * @return the iec 104 link
+	 */
+	public Iec104Link getLink(ChannelId channelId) {
+		return this.links.get(channelId);
+	}
+
+	/**
+	 * Get link iec 104 link
+	 *
+	 * @param ip   ip
+	 * @param port port
+	 * @return the iec 104 link
+	 */
+	public Iec104Link getLink(String ip, int port) {
+		for (Map.Entry<ChannelId, Iec104Link> e : this.links.entrySet()) {
+			if (e.getValue().getOppositeIp().equals(ip) && e.getValue().getOppositePort() == port) {
+				return e.getValue();
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Get slave link iec 104 link
+	 *
+	 * @param ip   ip
+	 * @param port port
+	 * @return the iec 104 link
+	 */
+	public Iec104Link getSlaveLink(String ip, int port) {
+		for (Map.Entry<ChannelId, Iec104Link> e : this.links.entrySet()) {
+			if (e.getValue().getOppositeIp().equals(ip) && e.getValue().getOppositePort() == port && e.getValue().getOppositeRole() == Iec104Link.Role.SLAVER) {
+				return e.getValue();
+			}
+		}
+		return null;
+	}
+
+
+	/**
+	 * Get master links list
+	 *
+	 * @param ip ip
+	 * @return the list
+	 */
+	public List<Iec104Link> getMasterLinks(String ip) {
+		List<Iec104Link> links = new ArrayList<>();
+		for (Map.Entry<ChannelId, Iec104Link> e : this.links.entrySet()) {
+			if (e.getValue().getOppositeIp().equals(ip) && e.getValue().getOppositeRole() == Iec104Link.Role.MASTER) {
+				links.add(e.getValue());
+			}
+		}
+		return links;
+	}
+
+}

+ 5 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/container/package-info.java

@@ -0,0 +1,5 @@
+package wei.yigulu.iec104.container;
+
+/*
+ *  存放全局数据的容器
+ */

+ 30 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/exception/Iec104Exception.java

@@ -0,0 +1,30 @@
+package wei.yigulu.iec104.exception;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * IEC104的异常类
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@AllArgsConstructor
+@NoArgsConstructor
+@Data
+public class Iec104Exception extends Exception {
+
+	/**
+	 * Iec exception
+	 *
+	 * @param msg msg
+	 */
+	public Iec104Exception(String msg) {
+		this.msg = msg;
+	}
+
+	private String msg;
+	private int code;
+
+}

+ 122 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/nettyconfig/AllCustomDelimiterHandler.java

@@ -0,0 +1,122 @@
+package wei.yigulu.iec104.nettyconfig;
+
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.channel.ChannelHandlerContext;
+import org.joda.time.DateTime;
+import wei.yigulu.netty.AbstractDelimiterHandler;
+import wei.yigulu.utils.DataConvertor;
+
+
+/**
+ * 未继承netty的数据帧处理拆包类
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+
+public class AllCustomDelimiterHandler extends AbstractDelimiterHandler {
+
+
+	private static final byte[] HEAD = new byte[]{0x68};
+
+
+	@Override
+	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+		if (cumulation == null) {
+			cumulation = (ByteBuf) msg;
+			if (cumulation.readableBytes() > 10240) {
+				while (!cumulation.release()) {
+				}
+				cumulation = null;
+				log.warn("报文超长舍弃");
+				return;
+			}
+		} else {
+			if (timeMark.plusMillis(10).isBeforeNow()) {
+				log.warn("上一帧数据长度不足,但两帧时间间隔较长上一帧被舍弃");
+				while (!cumulation.release()) {
+				}
+				cumulation = (ByteBuf) msg;
+			} else {
+				//拓展寄居buffer
+				cumulation = expandCumulation(cumulation, (ByteBuf) msg);
+			}
+		}
+		//数据帧长度不足 记录时间 等待下一帧进入
+		if (cumulation.readableBytes() < 6) {
+			timeMark = DateTime.now();
+			return;
+		}
+		int len;
+		//查看第一个 HEAD 的头位置
+		int headIndex = getHeadIndex(0, cumulation.writerIndex(), cumulation);
+		//当数据帧里存在头字节 且长度大于3时进入循环
+		while (cumulation.readableBytes() >= 6 && headIndex != -1) {
+			//如果头字节不在第一个字节 那么读取标志向后推到头字节位置
+			if (headIndex > cumulation.readerIndex()) {
+				log.warn("舍弃了一无用段报文:" + DataConvertor.ByteBuf2String(cumulation.readBytes(headIndex - cumulation.readerIndex())));
+			}
+			//标记读取位置
+			cumulation.markReaderIndex();
+			//向后读取一位 即0x68的占位
+			cumulation.readBytes(1);
+			//获取到该帧的长度 帧内标定的长度
+			len = cumulation.readByte() & 0xff;
+			//如果帧的真实长度少于 帧内标定长度则代表数据帧不完整,退出循环等待下一数据帧进入进行粘帧
+			if (cumulation.readableBytes() < len) {
+				cumulation.resetReaderIndex();
+				//数据帧长度不足 记录时间
+				timeMark = DateTime.now();
+				return;
+			} else {
+				cumulation.resetReaderIndex();
+				//如果数据帧长度足够 将规定长度的直接加入out 队列
+				ctx.fireChannelRead(cumulation.readBytes(len + 2));
+				//查看后续的字节里面头字节的位置
+				headIndex = getHeadIndex(cumulation.readerIndex(), cumulation.writerIndex(), cumulation);
+			}
+		}
+		if (cumulation.readableBytes() != 0 && headIndex >= cumulation.readerIndex()) {
+			//buffer中还有数据 而且其中有数据头
+			timeMark = DateTime.now();
+			return;
+		} else {
+			//buffer没有数据 或剩余这段字节中没有数据头
+			if (cumulation.readableBytes() != 0) {
+				log.warn("这段字节中没有数据头,舍弃:" + DataConvertor.ByteBuf2String(cumulation.readBytes(cumulation.readableBytes())));
+			}
+			while (!cumulation.release()) {
+			}
+			cumulation = null;
+		}
+	}
+
+
+	private int getHeadIndex(int from, int end, ByteBuf byteBuf) {
+		if (byteBuf.readableBytes() < HEAD.length) {
+			return -1;
+		}
+		for (int i = from; i < end; i++) {
+			if (isEqualByteArr(HEAD, ByteBufUtil.getBytes(byteBuf, i, HEAD.length))) {
+				return i;
+			}
+		}
+		return -1;
+	}
+
+	private boolean isEqualByteArr(byte[] b1, byte[] b2) {
+		if (b1.length != b2.length) {
+			return false;
+		}
+		for (int i = 0; i < b1.length; i++) {
+			if (b1[i] != b2[i]) {
+				return false;
+			}
+		}
+		return true;
+	}
+
+
+}

+ 40 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/nettyconfig/Iec104HSMasterBuilder.java

@@ -0,0 +1,40 @@
+package wei.yigulu.iec104.nettyconfig;
+
+
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+import wei.yigulu.netty.AbstractHSTcpMasterBuilder;
+import wei.yigulu.netty.ProtocolChannelInitializer;
+
+
+/**
+ * 104的主站  向子站发送总召唤 获取子站的数据
+ * <p>
+ * 主备模式的 主站 可以切换主备机
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@EqualsAndHashCode(callSuper = true)
+@Accessors(chain = true)
+public class Iec104HSMasterBuilder extends AbstractHSTcpMasterBuilder {
+
+
+	/**
+	 * Hs master builder
+	 *
+	 * @param ip   ip
+	 * @param port port
+	 */
+	public Iec104HSMasterBuilder(String ip, Integer port) {
+		super(ip, port);
+	}
+
+
+	@Override
+	protected ProtocolChannelInitializer getOrCreateChannelInitializer() {
+		return Iec104SMasterBuilder.getDefaultChannelInitializer(this);
+	}
+
+
+}

+ 62 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/nettyconfig/Iec104SMasterBuilder.java

@@ -0,0 +1,62 @@
+package wei.yigulu.iec104.nettyconfig;
+
+
+import io.netty.channel.socket.SocketChannel;
+import io.netty.handler.timeout.IdleStateHandler;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+import wei.yigulu.iec104.util.PropertiesReader;
+import wei.yigulu.netty.AbstractTcpMasterBuilder;
+import wei.yigulu.netty.ProtocolChannelInitializer;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 104的主站  向子站发送总召唤 获取子站的数据
+ * <p>
+ * 简单的主站  相对于主备机主站    仅有主机 不支持切换
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@EqualsAndHashCode(callSuper = true)
+@Accessors(chain = true)
+public class Iec104SMasterBuilder extends AbstractTcpMasterBuilder {
+
+	private static final String HEARTBEATPROPNAME = "heartBeatIntervalTime";
+
+	private static final int HEARTBEATDEFVAL = 30;
+
+	private static final int HEARTBEAT = PropertiesReader.getInstance().getIntProp(HEARTBEATPROPNAME, HEARTBEATDEFVAL);
+
+	/**
+	 * Simple master builder
+	 *
+	 * @param ip   ip
+	 * @param port port
+	 */
+	public Iec104SMasterBuilder(String ip, Integer port) {
+		super(ip, port);
+	}
+
+
+	@Override
+	protected ProtocolChannelInitializer getOrCreateChannelInitializer() {
+		return getDefaultChannelInitializer(this);
+	}
+
+	public static ProtocolChannelInitializer<SocketChannel> getDefaultChannelInitializer(AbstractTcpMasterBuilder masterBuilder) {
+		return new ProtocolChannelInitializer<SocketChannel>(masterBuilder) {
+			@Override
+			protected void initChannel(SocketChannel ch) throws Exception {
+				ch.pipeline().addLast(new IdleStateHandler(HEARTBEAT, 0, 0, TimeUnit.SECONDS));
+				AllCustomDelimiterHandler handler = new AllCustomDelimiterHandler();
+				handler.setLog(masterBuilder.getLog());
+				ch.pipeline().addLast(handler);
+				ch.pipeline().addLast(new Master104Handle((AbstractTcpMasterBuilder) builder));
+			}
+		};
+	}
+
+
+}

+ 39 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/nettyconfig/Iec104SlaverBuilder.java

@@ -0,0 +1,39 @@
+package wei.yigulu.iec104.nettyconfig;
+
+
+import io.netty.channel.socket.SocketChannel;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+import wei.yigulu.netty.AbstractTcpSlaverBuilder;
+import wei.yigulu.netty.ProtocolChannelInitializer;
+
+
+/**
+ * 104的子站  是向主站提供数据的 主站发送总召唤 子站响应主站的召唤
+ * 向主站上送数据
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@EqualsAndHashCode(callSuper = true)
+@Accessors(chain = true)
+public class Iec104SlaverBuilder extends AbstractTcpSlaverBuilder {
+
+
+	public Iec104SlaverBuilder(int port) {
+		super(port);
+	}
+
+	@Override
+	protected ProtocolChannelInitializer getOrCreateChannelInitializer() {
+		return new ProtocolChannelInitializer<SocketChannel>(this) {
+			@Override
+			protected void initChannel(SocketChannel ch) throws Exception {
+				ch.pipeline().addLast(new AllCustomDelimiterHandler());
+				ch.pipeline().addLast(new Slave104Handle((Iec104SlaverBuilder) builder));
+			}
+
+		};
+	}
+
+}

+ 144 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/nettyconfig/Master104Handle.java

@@ -0,0 +1,144 @@
+package wei.yigulu.iec104.nettyconfig;
+
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.handler.timeout.IdleStateEvent;
+import lombok.NoArgsConstructor;
+import org.slf4j.Logger;
+import wei.yigulu.iec104.apdumodel.Apdu;
+import wei.yigulu.iec104.container.Iec104Link;
+import wei.yigulu.iec104.container.LinkContainer;
+import wei.yigulu.netty.AbstractTcpMasterBuilder;
+import wei.yigulu.utils.DataConvertor;
+
+import java.net.InetSocketAddress;
+
+/**
+ * 消息处理类
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+
+@NoArgsConstructor
+public class Master104Handle extends ChannelInboundHandlerAdapter {
+
+	private Logger log;
+
+	/**
+	 * Master 104 handle
+	 *
+	 * @param masterBuilder master builder
+	 */
+	public Master104Handle(AbstractTcpMasterBuilder masterBuilder) {
+		this.masterBuilder = masterBuilder;
+		this.log = masterBuilder.getLog();
+	}
+
+	public Master104Handle(AbstractTcpMasterBuilder masterBuilder, Class<? extends Apdu> apduClass) {
+		this(masterBuilder);
+		this.apduClass = apduClass;
+	}
+
+	private AbstractTcpMasterBuilder masterBuilder;
+
+	private int testNum;
+
+	private int exceptionNum;
+
+	/**
+	 * 是否是主动断开连接
+	 */
+	private boolean isInitiative;
+
+	private Class<? extends Apdu> apduClass = Apdu.class;
+
+
+	@Override
+	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+		//收数据
+		log.debug("----------------------------------------------------------------------------------");
+		log.debug(DataConvertor.ByteBuf2String((ByteBuf) msg));
+		Apdu apdu = apduClass.newInstance().setChannel(ctx.channel()).setIec104Builder(masterBuilder).setLog(log).loadByteBuf((ByteBuf) msg);
+		if (apdu.getApciType() == Apdu.ApciType.I_FORMAT) {
+			this.testNum = 0;
+		}
+		apdu.answer();
+
+	}
+
+
+	@Override
+	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+		if (this.exceptionNum > 10) {
+			this.isInitiative = true;
+			reconnect(ctx);
+		}
+		this.exceptionNum++;
+		ctx.flush();
+		cause.printStackTrace();
+		log.error("发生{}次异常,异常内容{}", this.exceptionNum, cause.getLocalizedMessage());
+
+	}
+
+
+	@Override
+	public void channelActive(ChannelHandlerContext ctx) throws Exception {
+		log.debug("发出U帧,启动命令");
+		this.isInitiative = false;
+		InetSocketAddress ipSocket = (InetSocketAddress) ctx.channel().remoteAddress();
+		String clientIp = ipSocket.getAddress().getHostAddress();
+		Integer clientPort = ipSocket.getPort();
+		log.info("连接" + clientIp + ":" + clientPort + "服务端成功");
+		LinkContainer.getInstance().getLinks().put(ctx.channel().id(), new Iec104Link(ctx.channel(), clientIp, clientPort, Iec104Link.Role.SLAVER));
+		ctx.writeAndFlush(Unpooled.copiedBuffer(TechnicalTerm.START));
+		this.masterBuilder.connected();
+	}
+
+
+	@Override
+	public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
+		log.debug("第{}次发送测试帧:" + DataConvertor.Byte2String(TechnicalTerm.TEST), testNum);
+		ctx.writeAndFlush(Unpooled.copiedBuffer(TechnicalTerm.TEST));
+		IdleStateEvent i = (IdleStateEvent) evt;
+		if (!i.isFirst()) {
+			log.debug("链路长时间无响应,重新连接");
+			this.isInitiative = true;
+			reconnect(ctx);
+		}
+		if (this.testNum >= 5) {
+			log.debug("链路长时间无数据传输,重新连接");
+			this.isInitiative = true;
+			reconnect(ctx);
+		}
+		this.testNum++;
+	}
+
+	@Override
+	public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+		if (isInitiative) {
+			this.isInitiative = false;
+		} else {
+			reconnect(ctx);
+		}
+		this.masterBuilder.disconnected();
+
+	}
+
+
+	private synchronized void reconnect(ChannelHandlerContext ctx) throws Exception {
+		LinkContainer.getInstance().getLinks().remove(ctx.channel().id());
+		ctx.close();
+		if (masterBuilder != null) {
+			this.testNum = 0;
+			this.exceptionNum = 0;
+			this.isInitiative = false;
+			log.error(masterBuilder.getIp() + ":" + masterBuilder.getPort() + "断线,尝试重连");
+			masterBuilder.getOrCreateConnectionListener().operationComplete(masterBuilder.getFuture());
+		}
+	}
+}
+

+ 106 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/nettyconfig/Slave104Handle.java

@@ -0,0 +1,106 @@
+package wei.yigulu.iec104.nettyconfig;
+
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import lombok.NoArgsConstructor;
+import org.slf4j.Logger;
+import wei.yigulu.iec104.apdumodel.Apdu;
+import wei.yigulu.iec104.container.Iec104Link;
+import wei.yigulu.iec104.container.LinkContainer;
+import wei.yigulu.iec104.util.PropertiesReader;
+import wei.yigulu.utils.DataConvertor;
+
+import java.net.InetSocketAddress;
+
+/**
+ * 消息处理类
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+@NoArgsConstructor
+public class Slave104Handle extends ChannelInboundHandlerAdapter {
+
+	private Logger log;
+
+	private static final String STARTASKPROPNAME = "haveStartAsk";
+
+	private static final boolean STARTASKDEFVAL = true;
+
+	private static final boolean STARTASK = PropertiesReader.getInstance().getBooleanProp(STARTASKPROPNAME, STARTASKDEFVAL);
+
+	/**
+	 * Slave 104 handle
+	 *
+	 * @param slaverBuilder slaver builder
+	 */
+	public Slave104Handle(Iec104SlaverBuilder slaverBuilder) {
+		this.slaverBuilder = slaverBuilder;
+		this.log = slaverBuilder.getLog();
+	}
+
+	public Slave104Handle(Iec104SlaverBuilder slaverBuilder, Class<? extends Apdu> apduClass) {
+		this(slaverBuilder);
+		this.apduClass = apduClass;
+	}
+
+	private Iec104SlaverBuilder slaverBuilder;
+
+
+	private Class<? extends Apdu> apduClass = Apdu.class;
+
+	@Override
+	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+		//收数据
+		log.debug("----------------------------------------------------------------------------------");
+		log.debug(DataConvertor.ByteBuf2String((ByteBuf) msg));
+		Apdu apdu = apduClass.newInstance().setChannel(ctx.channel()).setIec104Builder(slaverBuilder).loadByteBuf((ByteBuf) msg);
+		apdu.answer();
+	}
+
+
+	@Override
+	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+		log.error("104Slave交互时发生异常", cause);
+	}
+
+
+	@Override
+	public void channelActive(ChannelHandlerContext ctx) throws Exception {
+		log.debug("发出U帧,启动命令");
+		InetSocketAddress ipSocket = (InetSocketAddress) ctx.channel().remoteAddress();
+		String clientIp = ipSocket.getAddress().getHostAddress();
+		Integer clientPort = ipSocket.getPort();
+		if (!this.slaverBuilder.getConnectFilterManager().verdict(ctx.channel())) {
+			ctx.channel().close();
+			log.info(clientIp + ":" + clientPort + "客户端被过滤链拦截,已关闭通道");
+			return;
+		}
+		log.info(clientIp + ":" + clientPort + "客户端连接");
+		LinkContainer.getInstance().getLinks().put(ctx.channel().id(), new Iec104Link(ctx.channel(), clientIp, clientPort, Iec104Link.Role.MASTER));
+		this.slaverBuilder.connected(ipSocket);
+		this.slaverBuilder.getChannels().add(ctx.channel());
+		if (STARTASK) {
+			ctx.writeAndFlush(Unpooled.copiedBuffer(TechnicalTerm.START));
+		}
+
+
+	}
+
+
+	@Override
+	public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+		InetSocketAddress ipSocket = (InetSocketAddress) ctx.channel().remoteAddress();
+		String clientIp = ipSocket.getAddress().getHostAddress();
+		Integer clientPort = ipSocket.getPort();
+		log.info(clientIp + ":" + clientPort + "客户端断开连接");
+		this.slaverBuilder.getChannels().remove(ctx.channel());
+		LinkContainer.getInstance().getLinks().remove(ctx.channel().id());
+		this.slaverBuilder.disconnected(ipSocket);
+	}
+
+}
+

+ 120 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/nettyconfig/TechnicalTerm.java

@@ -0,0 +1,120 @@
+package wei.yigulu.iec104.nettyconfig;
+
+/**
+ * 104的专业术语
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+public class TechnicalTerm {
+
+	/**
+	 * 建立连接时发送的起始帧
+	 */
+	public static byte[] START = new byte[]{0x68, 0x04, 0x07, 0x00, 0x00, 0x00};
+
+	/**
+	 * 建立连接时对起始帧的应答
+	 */
+	public static byte[] STARTBACK = new byte[]{0x68, 0x04, 0x0B, 0x00, 0x00, 0x00};
+
+	/**
+	 * STOP
+	 */
+	public static byte[] STOP = new byte[]{0x68, 0x04, 0x13, 0x00, 0x00, 0x00};
+
+	/**
+	 * STOPBACK
+	 */
+	public static byte[] STOPBACK = new byte[]{0x68, 0x04, 0x23, 0x00, 0x00, 0x00};
+
+	/**
+	 * TEST
+	 */
+	public static byte[] TEST = new byte[]{0x68, 0x04, 0x43, 0x00, 0x00, 0x00};
+
+	/**
+	 * TESTBACK
+	 */
+	public static byte[] TESTBACK = new byte[]{0x68, 0x04, (byte) 0x83, 0x00, 0x00, 0x00};
+
+	/**
+	 * 总召唤
+	 */
+	public static byte[] GENERALINTERROGATION = new byte[]{0x68, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x64, 0x01, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x14};
+
+
+	/**
+	 *
+	 *
+	 * 遥测:09----带品质描述的遥测量,每个遥测值占3个字节
+	 *      0a----带3个字节时标的且具有品质描述的遥测值,每个遥测值占6个字节
+	 *      0b---不带时标的标度化值,每个遥测值占3个字节
+	 *      0c---带3个字节时标的标度化值,每个遥测值占6个字节
+	 *      0d---带品质描述的浮点值,每个遥测值占5个字节
+	 *      0e---带3个字节时标且具有品质描述的浮点值,每个遥测值占8个字节
+	 *      15---不带品质描述的遥测值,每个遥测值占2个字节
+	 * 遥信:01---不带时标的单点遥信,每个遥信占1个字节
+	 *      03---不带时标的双点遥信,每个遥信占1个字节
+	 *      14---具有状态变位检测的成组单点遥信,每个字节包括8个遥信
+	 * SOE:02---带3个字节短时标的单点遥信
+	 *      04---带3个字节短时标的双点遥信
+	 *      1e---带7个字节时标的单点遥信
+	 *      1f---带7个字节时标的双点遥信
+	 * 遥脉:0f---不带时标的电度量,每个电度量占5个字节
+	 *      10---带3个字节短时标的电度量,每个电度量占8个字节
+	 *      25---带7个字节长时标的电度量,每个电度量占12个字节
+	 * 其他:2d---单点遥控
+	 * 2e---双点遥控
+	 *      2f---双电遥调
+	 *      64---召唤全数据
+	 *      65---召唤全电度
+	 *      67---时钟同步命令
+	 *
+	 * */
+
+	/**
+	 * 单点信息
+	 */
+	public static final Integer SINGEL_POINT_TYPE = 1;
+
+
+	/**
+	 * 双点信息
+	 */
+	public static final Integer DOUBLE_POINT_TYPE = 3;
+
+
+	/**
+	 * 测量值,规一化值
+	 */
+	public static final Integer NORMALIZED_INTEGER_TYPE = 9;
+
+	/**
+	 * 测量值,标度化值
+	 */
+	public static final Integer SCALING_INTEGER_TYPE = 11;
+	/**
+	 * 测量值,短浮点数
+	 */
+	public static final Integer SHORT_FLOAT_TYPE = 13;
+
+
+	/**
+	 * 测量值,无品质位规一化值
+	 */
+	public static final Integer NOQUALITY_NORMALIZED_INTEGER_TYPE = 21;
+
+
+	/**
+	 * 总召唤
+	 */
+	public static final Integer TOTAL_SUMMONTYPE_TYPE = 0x64;
+
+
+	/**
+	 * 对时帧
+	 */
+	public static final Integer DATESYNCHRONIZATION_TYPE = 0x67;
+
+}

+ 6 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/nettyconfig/package-info.java

@@ -0,0 +1,6 @@
+package wei.yigulu.iec104.nettyconfig;
+
+/*
+ * netty的配置  客户端和服务端
+ *
+ * */

+ 4 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/package-info.java

@@ -0,0 +1,4 @@
+/**
+ * 104通讯所依赖的编解码  连接管理等依赖
+ */
+package wei.yigulu.iec104;

+ 203 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/util/PropertiesReader.java

@@ -0,0 +1,203 @@
+package wei.yigulu.iec104.util;
+
+
+import wei.yigulu.iec104.exception.Iec104Exception;
+
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+/**
+ * 配置信息读取
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+public class PropertiesReader {
+
+
+	private static final String FILENAME = "IEC104";
+
+	private static class LazyHolder {
+		private static final PropertiesReader INSTANCE = new PropertiesReader();
+	}
+
+	private PropertiesReader() {
+	}
+
+	/**
+	 * Gets instance *
+	 *
+	 * @return the instance
+	 */
+	public static final PropertiesReader getInstance() {
+		return LazyHolder.INSTANCE;
+	}
+
+
+	private ResourceBundle resourceBundle;
+
+
+	/**
+	 * Gets resource bundle *
+	 *
+	 * @return the resource bundle
+	 * @throws Iec104Exception iec exception
+	 */
+	public synchronized ResourceBundle getResourceBundle() throws Iec104Exception {
+		if (resourceBundle == null) {
+			try {
+				resourceBundle = ResourceBundle.getBundle(FILENAME);
+			} catch (MissingResourceException e) {
+				throw new Iec104Exception("缺少文件名为" + FILENAME + "的properties配置文件");
+			}
+		}
+		return resourceBundle;
+	}
+
+
+	/**
+	 * Gets prop *
+	 *
+	 * @param propName prop name
+	 * @return the prop
+	 * @throws Iec104Exception iec exception
+	 */
+	public String getProp(String propName) throws Iec104Exception {
+		String str;
+		try {
+			str = getResourceBundle().getString(propName);
+		} catch (MissingResourceException e) {
+			throw new Iec104Exception("缺少该属性信息");
+		}
+		return str;
+	}
+
+	/**
+	 * Gets prop *
+	 *
+	 * @param propName     prop name
+	 * @param defaultValue default value
+	 * @return the prop
+	 */
+	public String getProp(String propName, String defaultValue) {
+		String str;
+		try {
+			str = getResourceBundle().getString(propName);
+		} catch (Exception e) {
+			str = defaultValue;
+		}
+		return str;
+	}
+
+	/**
+	 * Gets int prop *
+	 *
+	 * @param propName prop name
+	 * @return the int prop
+	 * @throws Iec104Exception iec exception
+	 */
+	public int getIntProp(String propName) throws Iec104Exception {
+		int i;
+		try {
+			i = Integer.parseInt(getResourceBundle().getString(propName));
+		} catch (NumberFormatException e) {
+			throw new Iec104Exception(propName + ":该属性的配置只能为数字");
+		} catch (Exception e) {
+			throw new Iec104Exception("缺少该属性信息");
+		}
+		return i;
+	}
+
+	/**
+	 * Gets int prop *
+	 *
+	 * @param propName     prop name
+	 * @param defaultValue default value
+	 * @return the int prop
+	 */
+	public int getIntProp(String propName, int defaultValue) {
+		int i;
+		try {
+			i = Integer.parseInt(getResourceBundle().getString(propName));
+		} catch (Exception e) {
+			i = defaultValue;
+		}
+		return i;
+	}
+
+
+	/**
+	 * Gets long prop *
+	 *
+	 * @param propName prop name
+	 * @return the long prop
+	 * @throws Iec104Exception iec exception
+	 */
+	public Long getLongProp(String propName) throws Iec104Exception {
+		Long i;
+		try {
+			i = Long.parseLong(getResourceBundle().getString(propName));
+		} catch (MissingResourceException e) {
+			throw new Iec104Exception("缺少该属性信息");
+		} catch (NumberFormatException e) {
+			throw new Iec104Exception(propName + ":该属性的配置只能为数字");
+		}
+		return i;
+	}
+
+
+	/**
+	 * Get long prop long
+	 *
+	 * @param propName     prop name
+	 * @param defaultValue default value
+	 * @return the long
+	 */
+	public Long getLongProp(String propName, Long defaultValue) {
+		Long i;
+		try {
+			i = Long.parseLong(getResourceBundle().getString(propName));
+		} catch (Exception e) {
+			i = defaultValue;
+		}
+		return i;
+	}
+
+
+	/**
+	 * Gets boolean prop *
+	 *
+	 * @param propName prop name
+	 * @return the boolean prop
+	 * @throws Iec104Exception iec exception
+	 */
+	public boolean getBooleanProp(String propName) throws Iec104Exception {
+		boolean i;
+		try {
+			i = Boolean.parseBoolean(getResourceBundle().getString(propName));
+		} catch (MissingResourceException e) {
+			throw new Iec104Exception("缺少该属性信息");
+		}
+		return i;
+	}
+
+
+	/**
+	 * Gets boolean prop *
+	 *
+	 * @param propName     prop name
+	 * @param defaultValue default value
+	 * @return the boolean prop
+	 */
+	public boolean getBooleanProp(String propName, boolean defaultValue) {
+		boolean i;
+		try {
+			i = Boolean.parseBoolean(getResourceBundle().getString(propName));
+		} catch (Exception e) {
+			i = defaultValue;
+		}
+		return i;
+	}
+
+
+}

+ 140 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/util/SendAndReceiveNumUtil.java

@@ -0,0 +1,140 @@
+package wei.yigulu.iec104.util;
+
+
+import io.netty.buffer.Unpooled;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import wei.yigulu.iec104.apdumodel.Apdu;
+import wei.yigulu.iec104.container.Iec104Link;
+import wei.yigulu.iec104.container.LinkContainer;
+import wei.yigulu.utils.DataConvertor;
+
+
+/**
+ * 用以处理接收和发送序列号的处理类
+ *
+ * @author 修唯xiuwei
+ * @version 3.0
+ */
+public class SendAndReceiveNumUtil {
+
+	private static Logger log = LoggerFactory.getLogger(SendAndReceiveNumUtil.class);
+
+	private static final String INTERVALPROPNAME = "sFrameInterval";
+
+	private static final int INTERVALDEFVAL = 5;
+
+	private static final int INTERVAL = PropertiesReader.getInstance().getIntProp(INTERVALPROPNAME, INTERVALDEFVAL);
+
+
+	/**
+	 * 为发送的i帧组装接收和发送序号
+	 *
+	 * @param apdu      apdu
+	 * @param channelId channel id
+	 */
+	public static void setSendAndReceiveNum(Apdu apdu, ChannelId channelId) {
+		Iec104Link link = LinkContainer.getInstance().getLink(channelId);
+		int send = link.getISend();
+		int receive = link.getIReceive();
+		apdu.setSendSeqNum(send++);
+		apdu.setReceiveSeqNum(receive);
+		link.setISend(send);
+		LinkContainer.getInstance().getLinks().put(channelId, link);
+	}
+
+
+	/**
+	 * 组装i帧的发送和接收序号 后发出
+	 *
+	 * @param apdu    apdu
+	 * @param channel channel
+	 * @throws Exception exception
+	 */
+	public static void sendIFrame(Apdu apdu, Channel channel) throws Exception {
+		setSendAndReceiveNum(apdu, channel.id());
+		byte[] bb = apdu.encode();
+		//TODO  改变日志模式
+		log.debug("向104对端发出数据帧:" + DataConvertor.Byte2String(bb));
+		channel.writeAndFlush(Unpooled.copiedBuffer(bb));
+	}
+
+
+	/**
+	 * 组装i帧的发送和接收序号 后发出
+	 *
+	 * @param apdu    apdu
+	 * @param channel channel
+	 * @throws Exception exception
+	 */
+	public static void sendIFrame(Apdu apdu, Channel channel, Logger log) throws Exception {
+		setSendAndReceiveNum(apdu, channel.id());
+		byte[] bb = apdu.encode();
+		//TODO  改变日志模式
+		if (log != null) {
+			log.debug("向104对端发出数据帧:" + DataConvertor.Byte2String(bb));
+		} else {
+			SendAndReceiveNumUtil.log.debug("向104对端发出数据帧:" + DataConvertor.Byte2String(bb));
+		}
+		channel.writeAndFlush(Unpooled.copiedBuffer(bb));
+	}
+
+	/**
+	 * 接收到i帧,处理接收和发送序号
+	 *
+	 * @param apdu      apdu
+	 * @param channelId channel id
+	 */
+	public static void receiveIFrame(Apdu apdu, ChannelId channelId) {
+		Iec104Link link = LinkContainer.getInstance().getLink(channelId);
+		int send = link.getISend();
+		int receive = link.getIReceive();
+		int send1 = apdu.getSendSeqNum();
+		int receive1 = apdu.getReceiveSeqNum();
+		link.setLinkState(Iec104Link.LinkState.NORMAL);
+		if (receive < send1) {
+			/**
+			 * 我方丢失 通道对方放出的 i帧
+			 */
+			apdu.loseReceive();
+			link.setLinkState(Iec104Link.LinkState.LOSEREC);
+		}
+		if (send < receive1) {
+			/**
+			 * 通道对方丢失 我方发出的i帧
+			 */
+			apdu.loseSend();
+			link.setLinkState(Iec104Link.LinkState.LOSESEND);
+		}
+		link.setIReceive(++send1);
+		link.setISend(receive1);
+		sendSFrame(link);
+		LinkContainer.getInstance().getLinks().put(channelId, link);
+	}
+
+	/**
+	 * 向通道内发送s帧
+	 *
+	 * @param link link
+	 */
+	public static void sendSFrame(Iec104Link link) {
+
+		if (link.getIReceive() != 0 && link.getIReceive() % INTERVAL == 0) {
+			Apdu apdu1 = new Apdu();
+			apdu1.setReceiveSeqNum(link.getIReceive());
+			apdu1.setApciType(Apdu.ApciType.S_FORMAT);
+			try {
+				byte[] bs = apdu1.encode();
+				//TODO  改变日志模式
+				log.debug("发送s帧:" + DataConvertor.Byte2String(bs));
+				link.getChannel().writeAndFlush(Unpooled.copiedBuffer(bs));
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		}
+	}
+
+
+}

+ 270 - 0
protocol-iec104/src/main/java/wei/yigulu/iec104/util/SendDataFrameHelper.java

@@ -0,0 +1,270 @@
+package wei.yigulu.iec104.util;
+
+
+import io.netty.channel.Channel;
+import org.slf4j.Logger;
+import wei.yigulu.iec104.apdumodel.Apdu;
+import wei.yigulu.iec104.apdumodel.Asdu;
+import wei.yigulu.iec104.asdudataframe.BooleanType;
+import wei.yigulu.iec104.asdudataframe.ShortFloatType;
+import wei.yigulu.iec104.asdudataframe.TotalSummonType;
+import wei.yigulu.iec104.asdudataframe.typemodel.InformationBodyAddress;
+
+import java.util.*;
+
+/**
+ * 发送数据帧的工具类
+ *
+ * @author: xiuwei
+ * @version:
+ */
+public class SendDataFrameHelper {
+
+	/**
+	 * 发送遥信数据帧
+	 *
+	 * @param channel 通达对象
+	 * @param dates   要发送的数据
+	 * @param address 公共地址位
+	 * @param cause   发送原因
+	 * @throws Exception 异常信息
+	 */
+	public static void sendYxDataFrame(Channel channel, Map<Integer, Boolean> dates, Integer address, Integer cause, Logger log) throws Exception {
+		BooleanType booleanType;
+		Apdu apdu;
+		Asdu asdu;
+		Set<Integer> keys;
+		Integer max;
+		Integer min;
+		if (dates.size() > 0) {
+			keys = dates.keySet();
+			max = Collections.max(keys);
+			min = Collections.min(keys);
+			if ((max - min) == (keys.size() - 1)) {
+				for (List<Integer> li : splitAndSort(keys, 400)) {
+					booleanType = new BooleanType();
+					booleanType.addAddress(new InformationBodyAddress(li.get(0)));
+					for (Integer i : li) {
+						booleanType.addData(dates.get(i));
+					}
+					apdu = new Apdu();
+					asdu = booleanType.generateBack();
+					asdu.setNot(cause);
+					asdu.setCommonAddress(address);
+					apdu.setAsdu(asdu);
+					SendAndReceiveNumUtil.sendIFrame(apdu, channel, log);
+					Thread.sleep(20);
+				}
+			} else {
+				for (Map<Integer, Boolean> m : split(dates, 100)) {
+					booleanType = new BooleanType();
+					for (Map.Entry<Integer, Boolean> em : m.entrySet()) {
+						booleanType.addDataAndAdd(new InformationBodyAddress(em.getKey()), em.getValue());
+					}
+					apdu = new Apdu();
+					asdu = booleanType.generateBack();
+					asdu.setNot(cause);
+					asdu.setCommonAddress(address);
+					apdu.setAsdu(asdu);
+					SendAndReceiveNumUtil.sendIFrame(apdu, channel, log);
+					Thread.sleep(20);
+				}
+			}
+		}
+	}
+
+
+	/**
+	 * 发送遥信数据帧 不连续
+	 *
+	 * @param channel 通达对象
+	 * @param dates   要发送的数据
+	 * @param address 公共地址位
+	 * @param cause   发送原因
+	 * @throws Exception 异常信息
+	 */
+	public static void sendYxDataFrameDiscontinuity(Channel channel, Map<Integer, Boolean> dates, Integer address, Integer cause, Logger log) throws Exception {
+		BooleanType booleanType;
+		Apdu apdu;
+		Asdu asdu;
+		if (dates.size() > 0) {
+			for (Map<Integer, Boolean> m : split(dates, 100)) {
+				booleanType = new BooleanType();
+				for (Map.Entry<Integer, Boolean> em : m.entrySet()) {
+					booleanType.addDataAndAdd(new InformationBodyAddress(em.getKey()), em.getValue());
+				}
+				apdu = new Apdu();
+				asdu = booleanType.generateBack();
+				asdu.setNot(cause);
+				asdu.setCommonAddress(address);
+				apdu.setAsdu(asdu);
+				SendAndReceiveNumUtil.sendIFrame(apdu, channel, log);
+				Thread.sleep(20);
+			}
+		}
+	}
+
+	/**
+	 * 发送遥测 数据帧
+	 *
+	 * @param channel 通道对象
+	 * @param dates   需要发送的数据
+	 * @param address 公共地址位
+	 * @param cause   发送的原因
+	 * @throws Exception 异常
+	 */
+	public static void sendYcDataFrame(Channel channel, Map<Integer, Number> dates, Integer address, Integer cause, Logger log) throws Exception {
+		Apdu apdu;
+		Asdu asdu;
+		ShortFloatType shortFloatType;
+		Set<Integer> keys;
+		Integer max;
+		Integer min;
+		if (dates.size() > 0) {
+			keys = dates.keySet();
+			max = Collections.max(keys);
+			min = Collections.min(keys);
+			if ((max - min) == (keys.size() - 1)) {
+				for (List<Integer> li : splitAndSort(keys, 45)) {
+					shortFloatType = new ShortFloatType();
+					shortFloatType.addAddress(new InformationBodyAddress(li.get(0)));
+					for (Integer i : li) {
+						shortFloatType.addData(dates.get(i).floatValue());
+					}
+					apdu = new Apdu();
+					asdu = shortFloatType.generateBack();
+					asdu.setNot(cause);
+					asdu.setCommonAddress(address);
+					apdu.setAsdu(asdu);
+					SendAndReceiveNumUtil.sendIFrame(apdu, channel, log);
+					Thread.sleep(20);
+				}
+			} else {
+				for (Map<Integer, Number> m : split(dates, 25)) {
+					shortFloatType = new ShortFloatType();
+					for (Map.Entry<Integer, Number> em : m.entrySet()) {
+						shortFloatType.addDataAndAdd(new InformationBodyAddress(em.getKey()), em.getValue().floatValue());
+					}
+					apdu = new Apdu();
+					asdu = shortFloatType.generateBack();
+					asdu.setNot(cause);
+					asdu.setCommonAddress(address);
+					apdu.setAsdu(asdu);
+					SendAndReceiveNumUtil.sendIFrame(apdu, channel, log);
+					Thread.sleep(20);
+				}
+			}
+		}
+	}
+
+
+	/**
+	 * 发送遥测 数据帧 不连续
+	 *
+	 * @param channel 通道对象
+	 * @param dates   需要发送的数据
+	 * @param address 公共地址位
+	 * @param cause   发送的原因
+	 * @throws Exception 异常
+	 */
+	public static void sendYcDataFrameDiscontinuity(Channel channel, Map<Integer, Number> dates, Integer address, Integer cause, Logger log) throws Exception {
+		Apdu apdu;
+		Asdu asdu;
+		ShortFloatType shortFloatType;
+		if (dates.size() > 0) {
+			for (Map<Integer, Number> m : split(dates, 25)) {
+				shortFloatType = new ShortFloatType();
+				for (Map.Entry<Integer, Number> em : m.entrySet()) {
+					shortFloatType.addDataAndAdd(new InformationBodyAddress(em.getKey()), em.getValue().floatValue());
+				}
+				apdu = new Apdu();
+				asdu = shortFloatType.generateBack();
+				asdu.setNot(cause);
+				asdu.setCommonAddress(address);
+				apdu.setAsdu(asdu);
+				SendAndReceiveNumUtil.sendIFrame(apdu, channel, log);
+				Thread.sleep(20);
+			}
+		}
+	}
+
+	/**
+	 * 发送总召唤 帧
+	 *
+	 * @param channel 通道对象
+	 * @param address 公共地址位
+	 * @param cause   发送的原因
+	 * @throws Exception 异常
+	 */
+	public static void sendTotalSummonFrame(Channel channel, Integer address, Integer cause, Logger log) throws Exception {
+		Apdu apdu = new Apdu();
+		Asdu asdu;
+		TotalSummonType dataFrameType = new TotalSummonType();
+		dataFrameType.setAddress(new InformationBodyAddress(0));
+		dataFrameType.setValue(20);
+		asdu = dataFrameType.generateBack();
+		asdu.setNot(cause);
+		asdu.setCommonAddress(address);
+		apdu.setAsdu(asdu);
+		SendAndReceiveNumUtil.sendIFrame(apdu, channel, log);
+	}
+
+	/**
+	 * 将发送的数据集合拆成n个长度合适的集合
+	 *
+	 * @param map    总集合
+	 * @param maxLen 设定最大的长度
+	 * @param <T>    类型T
+	 * @return 分集合
+	 */
+	public static <T> List<HashMap<Integer, T>> split(Map<Integer, T> map, int maxLen) {
+		List<HashMap<Integer, T>> list = new ArrayList<>();
+		HashMap transfer = new HashMap(maxLen);
+		int j = 0;
+		for (Integer o : map.keySet()) {
+			if (j < maxLen) {
+				transfer.put(o, map.get(o));
+				j++;
+			} else {
+				list.add(transfer);
+				transfer = new HashMap(maxLen);
+				transfer.put(o, map.get(o));
+				j = 1;
+			}
+		}
+		list.add(transfer);
+		return list;
+	}
+
+
+	/**
+	 * 进行拆分并排序 主要是排序map 中的keyset 对keyset的int型进行排序
+	 *
+	 * @param set    要排序的set
+	 * @param maxLen 设定的最长长度
+	 * @return 拆分后的集合 的集合
+	 */
+	public static List<List<Integer>> splitAndSort(Set<Integer> set, int maxLen) {
+		List<List<Integer>> list = new ArrayList<>();
+		List<Integer> ls = new ArrayList<>();
+		List<Integer> transfer = new ArrayList<>(maxLen);
+		for (Integer i : set) {
+			ls.add(i);
+		}
+		Collections.sort(ls);
+		int j = 0;
+		for (Integer i : ls) {
+			if (j < maxLen) {
+				transfer.add(i);
+				j++;
+			} else {
+				list.add(transfer);
+				transfer = new ArrayList<>(maxLen);
+				transfer.add(i);
+				j = 1;
+			}
+		}
+		list.add(transfer);
+		return list;
+	}
+}

BIN
protocol-modbus/MODBUS通讯协议中文版.pdf


+ 24 - 0
protocol-modbus/README.md

@@ -0,0 +1,24 @@
+###### 本工具是本人基于工作所需开发的针对于Modbus通讯的辅助工具
+
+
+本工具基于netty框架。建议有netty使用经验和对Modbus通讯规约的人使用。如果不了解Modbus通讯规约,理解本工具的工作流程可能会有些麻烦。
+默认的通讯机制将一问一答式的异步变同步程序获取方式
+
+# 使用方式 
+
+## Matser的创建 以及数据的接收处理:
+
+### Matser的创建 
+
+```java
+	SimpleTcpMasterBuilder master = new SimpleTcpMasterBuilder("127.0.0.1", 5002);
+		master.create();
+```
+
+create() 方法会阻塞线程,如果不希望阻塞线程可以使用createByUnBlock(),以工具内的单线程池执行。
+
+### 数据的接收
+
+
+##### 如果有疑问可以向 weiyigulu524710549@gmail.com  邮箱留言
+

+ 68 - 0
protocol-modbus/pom.xml

@@ -0,0 +1,68 @@
+<?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">
+    <parent>
+        <artifactId>protocol</artifactId>
+        <groupId>wei.yigulu</groupId>
+        <version>1.0</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <version>${modbus.version}</version>
+
+    <artifactId>protocol-modbus</artifactId>
+    <dependencies>
+        <dependency>
+            <groupId>wei.yigulu</groupId>
+            <artifactId>protocol-core</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>29.0-jre</version>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-source-plugin</artifactId>
+                <version>2.1</version>
+                <configuration>
+                    <attach>true</attach>
+                </configuration>
+                <executions>
+                    <execution>
+                        <phase>compile</phase>
+                        <goals>
+                            <goal>jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-javadoc-plugin</artifactId>
+                <version>2.10.2</version>
+                <configuration>
+                    <encoding>UTF-8</encoding>
+                    <aggregate>true</aggregate>
+                    <charset>UTF-8</charset>
+                    <docencoding>UTF-8</docencoding>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>attach-javadocs</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>jar</goal>
+                        </goals>
+                        <configuration>
+                            <additionalparam>-Xdoclint:none</additionalparam>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 61 - 0
protocol-modbus/src/main/java/wei/yigulu/modbus/domain/FunctionCode.java

@@ -0,0 +1,61 @@
+package wei.yigulu.modbus.domain;
+
+import lombok.AllArgsConstructor;
+
+/**
+ * 功能码 copy from modbus4j
+ *
+ * @author: xiuwei
+ * @version:
+ */
+@AllArgsConstructor
+public enum FunctionCode {
+	/**
+	 * Constant <code>READ_COILS 1</code>
+	 */
+	READ_COILS(1),
+	/**
+	 * Constant <code>READ_DISCRETE_INPUTS 2</code>
+	 */
+	READ_DISCRETE_INPUTS(2),
+	/**
+	 * Constant <code>READ_HOLDING_REGISTERS 3</code>
+	 */
+	READ_HOLDING_REGISTERS(3),
+	/**
+	 * Constant <code>READ_INPUT_REGISTERS 4</code>
+	 */
+	READ_INPUT_REGISTERS(4),
+	/**
+	 * Constant <code>WRITE_COIL 5</code>
+	 */
+	WRITE_COIL(5),
+	/**
+	 * Constant <code>WRITE_REGISTER 6</code>
+	 */
+	WRITE_REGISTER(6),
+	/**
+	 * Constant <code>READ_EXCEPTION_STATUS 7</code>
+	 */
+	READ_EXCEPTION_STATUS(7),
+	/**
+	 * Constant <code>WRITE_COILS 15</code>
+	 */
+	WRITE_COILS(15),
+	/**
+	 * Constant <code>WRITE_REGISTERS 16</code>
+	 */
+	WRITE_REGISTERS(16),
+	/**
+	 * Constant <code>REPORT_SLAVE_ID 17</code>
+	 */
+	REPORT_SLAVE_ID(17),
+	/**
+	 * Constant <code>WRITE_MASK_REGISTER 22</code>
+	 */
+	WRITE_MASK_REGISTER(22);
+
+	private Integer code;
+
+
+}

+ 143 - 0
protocol-modbus/src/main/java/wei/yigulu/modbus/domain/ModbusSlaveDataContainer.java

@@ -0,0 +1,143 @@
+package wei.yigulu.modbus.domain;
+
+
+import com.google.common.primitives.Bytes;
+import wei.yigulu.modbus.domain.datatype.BooleanModbusDataInCoil;
+import wei.yigulu.modbus.domain.datatype.CoilValue;
+import wei.yigulu.modbus.domain.datatype.Register;
+import wei.yigulu.modbus.domain.datatype.RegisterValue;
+import wei.yigulu.modbus.domain.datatype.numeric.P_AB;
+import wei.yigulu.modbus.domain.request.AbstractModbusRequest;
+import wei.yigulu.modbus.exceptiom.ModbusException;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * modbus  slave的数据容器
+ *
+ * @author: xiuwei
+ * @version:
+ */
+public class ModbusSlaveDataContainer {
+
+	Map<Integer, DataDrawer> slaveDataDrawer = new ConcurrentHashMap<>();
+
+
+	public byte[] getDataBytesOfSlave(AbstractModbusRequest abstractModbusRequest) throws ModbusException {
+		Integer byteNum = abstractModbusRequest.getQuantity();
+		if (byteNum == null || byteNum == 0) {
+			throw new ModbusException(3001, "请求体缺少数据个数 ");
+		}
+		Integer functionCode = abstractModbusRequest.getFunctionCode();
+		if (functionCode == null || functionCode > 4 || functionCode < 0) {
+			throw new ModbusException(3002, "不支持请求体的功能码 " + functionCode);
+		}
+		Integer position = abstractModbusRequest.getStartAddress();
+		if (position == null || position < 0) {
+			throw new ModbusException(3003, "请求体请求起始地址不合法 " + position);
+		}
+		Integer slaveId = abstractModbusRequest.getSlaveId();
+		if (slaveId == null || slaveId < 0) {
+			throw new ModbusException(3004, "请求体请求设备地址不合法 " + position);
+		}
+		if (this.slaveDataDrawer.containsKey(slaveId)) {
+			if (functionCode == 1 || functionCode == 2) {
+				return this.slaveDataDrawer.get(slaveId).getCoilDataBytes(position, byteNum);
+			} else {
+				return this.slaveDataDrawer.get(slaveId).getRegisterDataBytes(position, byteNum);
+			}
+		} else {
+			throw new ModbusException(3004, "请求体请求设备地址不合法 " + position);
+		}
+	}
+
+	public void setRegister(int slaveId, int position, RegisterValue value) {
+		getOrCreate(slaveId).setRegister(position, value);
+	}
+
+	public void setRegister(int slaveId, int position, List<RegisterValue> value) {
+		getOrCreate(slaveId).setRegister(position, value);
+	}
+
+	public void setRegister(int slaveId, List<RegisterValue> value) {
+		getOrCreate(slaveId).setRegister(value);
+	}
+
+	private DataDrawer getOrCreate(int slave) {
+		if (!this.slaveDataDrawer.containsKey(slave)) {
+			this.slaveDataDrawer.put(slave, new DataDrawer());
+		}
+		return this.slaveDataDrawer.get(slave);
+	}
+
+	/**
+	 * 数据抽屉
+	 */
+	private class DataDrawer {
+
+		List<CoilValue> coils = new CopyOnWriteArrayList<>();
+
+		List<Register> registers = new CopyOnWriteArrayList<>();
+
+		public void setRegister(int position, RegisterValue value) {
+			if (registers.size() < position + value.getModbusDataTypeEnum().getOccupiedRegister()) {
+				int num = position + value.getModbusDataTypeEnum().getOccupiedRegister() - registers.size();
+				for (int i = 0; i < num; i++) {
+					this.registers.addAll(new P_AB(BigDecimal.ZERO).getRegisters());
+				}
+			}
+			for (int i = 0; i < value.getModbusDataTypeEnum().getOccupiedRegister(); i++) {
+				this.registers.remove(position);
+			}
+			this.registers.addAll(position, value.getRegisters());
+		}
+
+		public void setRegister(int position, List<RegisterValue> value) {
+			for (RegisterValue r : value) {
+				setRegister(position, r);
+				position += r.getModbusDataTypeEnum().getOccupiedRegister();
+			}
+		}
+
+
+		public void setRegister(List<RegisterValue> value) {
+			setRegister(0, value);
+		}
+
+		public void setCoil(int position, CoilValue value) {
+			if (coils.size() <= position) {
+				int num = position + 1 - coils.size();
+				for (int i = 0; i < num; i++) {
+					this.coils.add(new BooleanModbusDataInCoil());
+				}
+			}
+			this.coils.set(position, value);
+		}
+
+
+		public byte[] getRegisterDataBytes(int position, int bitNum) {
+			List<Byte> bytes = new ArrayList<>(bitNum * 2);
+			int registersSize = this.registers.size();
+			for (int i = position; i < position + bitNum; i++) {
+				if (registersSize > i) {
+					bytes.add(this.registers.get(i).getB1());
+					bytes.add(this.registers.get(i).getB2());
+				} else {
+					bytes.add((byte) 0);
+					bytes.add((byte) 0);
+				}
+			}
+			return Bytes.toArray(bytes);
+		}
+
+		public byte[] getCoilDataBytes(int position, int bitNum) {
+
+			return null;
+		}
+	}
+}

+ 50 - 0
protocol-modbus/src/main/java/wei/yigulu/modbus/domain/datatype/BooleanModbusDataInCoil.java

@@ -0,0 +1,50 @@
+package wei.yigulu.modbus.domain.datatype;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+/**
+ * 8个布尔值组成的数据类型
+ * 仅对应 01 和 02 功能码
+ *
+ * @author: xiuwei
+ * @version:
+ */
+public class BooleanModbusDataInCoil extends CoilValue {
+
+	/**
+	 * 八个布尔 对应一字节 八比特 从
+	 */
+	boolean[] values = new boolean[8];
+
+
+	@Override
+	public IModbusDataType decode(byte[] bytes, int offset) {
+		return getValFormByte(bytes[2 * offset]);
+	}
+
+	@Override
+	public IModbusDataType decode(ByteBuffer byteBuf) {
+		return getValFormByte(byteBuf.get());
+
+	}
+
+	private IModbusDataType getValFormByte(byte b) {
+		for (int i = 0; i < values.length; i++) {
+			this.values[i] = (byte) ((b >> i) & 0x01) == 0x01;
+		}
+		return this;
+	}
+
+	@Override
+	public IModbusDataType encode(List<Byte> bytes) {
+		byte b = 0;
+		for (int i = 0; i < values.length; i++) {
+			if (this.values[i]) {
+				b |= (0x01 << i);
+			}
+		}
+		bytes.add(b);
+		return this;
+	}
+}

+ 65 - 0
protocol-modbus/src/main/java/wei/yigulu/modbus/domain/datatype/BooleanModbusDataInRegister.java

@@ -0,0 +1,65 @@
+package wei.yigulu.modbus.domain.datatype;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+/**
+ * 16个布尔值组成的数据类型
+ * 仅对应 01 和 02 功能码
+ *
+ * @author: xiuwei
+ * @version:
+ */
+public class BooleanModbusDataInRegister extends RegisterValue {
+
+	/**
+	 * 16个bit  构成16个布尔
+	 */
+	boolean[] values = new boolean[16];
+
+
+	@Override
+	public BooleanModbusDataInRegister decode(byte[] bytes, int offset) {
+		return getValFormByte(bytes[2 * offset], bytes[2 * offset + 1]);
+	}
+
+	@Override
+	public BooleanModbusDataInRegister decode(ByteBuffer byteBuf) {
+		return getValFormByte(byteBuf.get(), byteBuf.get());
+
+	}
+
+	private BooleanModbusDataInRegister getValFormByte(byte b1, byte b2) {
+		for (int i = 0; i < 8; i++) {
+			this.values[i] = (byte) ((b1 >> i) & 0x01) == 0x01;
+		}
+		for (int i = 8; i < 16; i++) {
+			this.values[i] = (byte) ((b2 >> (i - 8)) & 0x01) == 0x01;
+		}
+		return this;
+	}
+
+	@Override
+	public BooleanModbusDataInRegister encode(List<Byte> bytes) {
+		byte b = 0;
+		for (int i = 0; i < 8; i++) {
+			if (this.values[i]) {
+				b |= (0x01 << i);
+			}
+		}
+		bytes.add(b);
+		b = 0;
+		for (int i = 8; i < 16; i++) {
+			if (this.values[i]) {
+				b |= (0x01 << (i - 8));
+			}
+		}
+		bytes.add(b);
+		return this;
+	}
+
+	@Override
+	public List<Register> getRegisters() {
+		return null;
+	}
+}

+ 10 - 0
protocol-modbus/src/main/java/wei/yigulu/modbus/domain/datatype/CoilValue.java

@@ -0,0 +1,10 @@
+package wei.yigulu.modbus.domain.datatype;
+
+/**
+ * @program: modbus
+ * @description: 线圈值
+ * @author: xiuwei
+ * @create: 2020-08-17 16:07
+ */
+public abstract class CoilValue implements IModbusDataType {
+}

+ 40 - 0
protocol-modbus/src/main/java/wei/yigulu/modbus/domain/datatype/IModbusDataType.java

@@ -0,0 +1,40 @@
+package wei.yigulu.modbus.domain.datatype;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+/**
+ * modbus的数据类型的抽象类
+ *
+ * @author: xiuwei
+ * @version:
+ */
+public interface IModbusDataType {
+
+
+	/**
+	 * 解码
+	 *
+	 * @param bytes  字节
+	 * @param offset 偏移量 偏移量是相对寄存器讲的
+	 */
+	IModbusDataType decode(byte[] bytes, int offset);
+
+	/**
+	 * 解码
+	 *
+	 * @param byteBuf 字节缓冲区
+	 * @return {@link IModbusDataType}
+	 */
+	IModbusDataType decode(ByteBuffer byteBuf);
+
+
+	/**
+	 * 编码
+	 *
+	 * @param bytes 字节
+	 */
+	IModbusDataType encode(List<Byte> bytes);
+
+
+}

+ 84 - 0
protocol-modbus/src/main/java/wei/yigulu/modbus/domain/datatype/ModbusDataTypeEnum.java

@@ -0,0 +1,84 @@
+package wei.yigulu.modbus.domain.datatype;
+
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import wei.yigulu.modbus.domain.datatype.numeric.*;
+
+/**
+ * modbus数据类型的枚举
+ *
+ * @author: xiuwei
+ */
+@AllArgsConstructor
+public enum ModbusDataTypeEnum {
+
+
+	/**
+	 * modbus 各种类型
+	 */
+	A16(1),
+	/*AB 的modbus 2 字节 无符号 整型数据*/
+	P_AB(1),
+	/*BA 的modbus 2 字节 无符号 整型数据*/
+	P_BA(1),
+	/*AB 的modbus 2 字节 有符号 整型数据*/
+	PM_AB(1),
+	/*BA 的modbus 2 字节 有符号 整型数据*/
+	PM_BA(1),
+	/*AABB 的modbus 4 字节 无符号 整型数据*/
+	P_ABCD(2),
+	/*BBAA 的modbus 4 字节 无符号 整型数据*/
+	P_CDAB(2),
+	/*AABB 的modbus 4 字节 有符号 整型数据*/
+	PM_ABCD(2),
+	/*BBAA 的modbus 4 字节 有符号 整型数据*/
+	PM_CDAB(2),
+	/*ABCD 的modbus 4 字节  浮点数据*/
+	ABCD(2),
+	/*CDAB 的modbus 4 字节  浮点数据*/
+	CDAB(2),
+	/*DCBA 的modbus 4 字节  浮点数据*/
+	DCBA(2);
+
+
+	/**
+	 * 占据寄存器数量
+	 */
+	@Getter
+	private Integer occupiedRegister;
+
+	/**
+	 * 根据本枚举类型返回相对数据对象
+	 *
+	 * @return
+	 */
+	public IModbusDataType getObject() {
+		switch (this) {
+			case P_BA:
+				return new P_BA();
+			case PM_AB:
+				return new PM_AB();
+			case PM_BA:
+				return new PM_BA();
+			case ABCD:
+				return new ABCD();
+			case P_ABCD:
+				return new P_ABCD();
+			case P_CDAB:
+				return new P_CDAB();
+			case PM_ABCD:
+				return new PM_ABCD();
+			case PM_CDAB:
+				return new PM_CDAB();
+			case CDAB:
+				return new CDAB();
+			case DCBA:
+				return new DCBA();
+			default:
+				return new P_AB();
+		}
+	}
+
+
+}

+ 57 - 0
protocol-modbus/src/main/java/wei/yigulu/modbus/domain/datatype/NumericModbusData.java

@@ -0,0 +1,57 @@
+package wei.yigulu.modbus.domain.datatype;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+
+import java.math.BigDecimal;
+import java.nio.ByteBuffer;
+import java.util.List;
+
+/**
+ * 数字的类型
+ *
+ * @author: xiuwei
+ * @version:
+ */
+@NoArgsConstructor
+public abstract class NumericModbusData extends RegisterValue {
+
+	public NumericModbusData(BigDecimal value) {
+		this.value = value;
+	}
+
+	@Setter
+	@Getter
+	@Accessors(chain = true)
+	protected BigDecimal value;
+
+	/**
+	 * 解码
+	 *
+	 * @param bytes  字节
+	 * @param offset 偏移量 偏移量是相对寄存器讲的
+	 */
+	@Override
+	public abstract IModbusDataType decode(byte[] bytes, int offset);
+
+	/**
+	 * 解码
+	 *
+	 * @param byteBuf 字节缓冲区
+	 * @return {@link IModbusDataType}
+	 */
+	@Override
+	public abstract IModbusDataType decode(ByteBuffer byteBuf);
+
+
+	/**
+	 * 编码
+	 *
+	 * @param bytes 字节
+	 */
+	@Override
+	public abstract IModbusDataType encode(List<Byte> bytes);
+
+}

+ 23 - 0
protocol-modbus/src/main/java/wei/yigulu/modbus/domain/datatype/Register.java

@@ -0,0 +1,23 @@
+package wei.yigulu.modbus.domain.datatype;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+/**
+ * 寄存器的对象 两个字节
+ *
+ * @author: xiuwei
+ * @version:
+ */
+@AllArgsConstructor
+@NoArgsConstructor
+public class Register {
+
+	@Getter
+	byte b1 = 0;
+	@Getter
+	byte b2 = 0;
+
+
+}

+ 27 - 0
protocol-modbus/src/main/java/wei/yigulu/modbus/domain/datatype/RegisterValue.java

@@ -0,0 +1,27 @@
+package wei.yigulu.modbus.domain.datatype;
+
+import lombok.Getter;
+
+import java.util.List;
+
+/**
+ * 寄存器值
+ *
+ * @author: xiuwei
+ * @version:
+ */
+public abstract class RegisterValue implements IModbusDataType {
+
+	@Getter
+	protected ModbusDataTypeEnum modbusDataTypeEnum = ModbusDataTypeEnum.P_AB;
+
+
+	/**
+	 * 转化成register字串
+	 *
+	 * @return {@link List<Register>}
+	 */
+	public abstract List<Register> getRegisters();
+
+
+}

+ 62 - 0
protocol-modbus/src/main/java/wei/yigulu/modbus/domain/datatype/numeric/ABCD.java

@@ -0,0 +1,62 @@
+package wei.yigulu.modbus.domain.datatype.numeric;
+
+import lombok.NoArgsConstructor;
+import wei.yigulu.modbus.domain.datatype.ModbusDataTypeEnum;
+import wei.yigulu.modbus.domain.datatype.NumericModbusData;
+import wei.yigulu.modbus.domain.datatype.Register;
+
+import java.math.BigDecimal;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * ABCD 的modbus 4 字节 浮点型数据
+ *
+ * @author: xiuwei
+ * @version:
+ */
+@NoArgsConstructor
+public class ABCD extends NumericModbusData {
+
+	{
+		super.modbusDataTypeEnum = ModbusDataTypeEnum.ABCD;
+	}
+
+	public ABCD(BigDecimal value) {
+		super(value);
+	}
+
+	@Override
+	public ABCD decode(byte[] bytes, int offset) {
+		this.value = BigDecimal.valueOf(Float.intBitsToFloat((bytes[offset * 2] & 0xff) << 24 | ((bytes[offset * 2 + 1] & 0xff) << 16)
+				| ((bytes[offset * 2 + 2] & 0xff) << 8) | ((bytes[offset * 2 + 3] & 0xff))));
+		return this;
+	}
+
+	@Override
+	public ABCD decode(ByteBuffer byteBuf) {
+		this.value = BigDecimal.valueOf(Float.intBitsToFloat((byteBuf.get() & 0xff) << 24 | ((byteBuf.get() & 0xff) << 16)
+				| ((byteBuf.get() & 0xff) << 8) | ((byteBuf.get() & 0xff))));
+		return this;
+	}
+
+	@Override
+	public ABCD encode(List<Byte> bytes) {
+		int tempVal = Float.floatToIntBits(this.value.floatValue());
+		bytes.add((byte) (tempVal >> 24));
+		bytes.add((byte) (tempVal >> 16));
+		bytes.add((byte) (tempVal >> 8));
+		bytes.add((byte) tempVal);
+		return this;
+	}
+
+	@Override
+	public List<Register> getRegisters() {
+		List<Register> registers = new ArrayList<>();
+		int tempVal = Float.floatToIntBits(this.value.floatValue());
+		registers.add(new Register((byte) (tempVal >> 24), (byte) (tempVal >> 16)));
+		registers.add(new Register((byte) (tempVal >> 8), (byte) (tempVal)));
+		return registers;
+	}
+}

+ 62 - 0
protocol-modbus/src/main/java/wei/yigulu/modbus/domain/datatype/numeric/CDAB.java

@@ -0,0 +1,62 @@
+package wei.yigulu.modbus.domain.datatype.numeric;
+
+import lombok.NoArgsConstructor;
+import wei.yigulu.modbus.domain.datatype.ModbusDataTypeEnum;
+import wei.yigulu.modbus.domain.datatype.NumericModbusData;
+import wei.yigulu.modbus.domain.datatype.Register;
+
+import java.math.BigDecimal;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * CDAB 的modbus 4 字节 浮点型数据
+ *
+ * @author: xiuwei
+ * @version:
+ */
+@NoArgsConstructor
+public class CDAB extends NumericModbusData {
+
+	{
+		super.modbusDataTypeEnum = ModbusDataTypeEnum.CDAB;
+	}
+
+	public CDAB(BigDecimal value) {
+		super(value);
+	}
+
+	@Override
+	public CDAB decode(byte[] bytes, int offset) {
+		this.value = BigDecimal.valueOf(Float.intBitsToFloat((bytes[offset * 2] & 0xff) << 8 | ((bytes[offset * 2 + 1] & 0xff))
+				| ((bytes[offset * 2 + 2] & 0xff) << 24) | ((bytes[offset * 2 + 3] & 0xff) << 16)));
+		return this;
+	}
+
+	@Override
+	public CDAB decode(ByteBuffer byteBuf) {
+		this.value = BigDecimal.valueOf(Float.intBitsToFloat((byteBuf.get() & 0xff) << 8 | ((byteBuf.get() & 0xff))
+				| ((byteBuf.get() & 0xff) << 24) | ((byteBuf.get() & 0xff) << 16)));
+		return this;
+	}
+
+	@Override
+	public CDAB encode(List<Byte> bytes) {
+		int tempVal = Float.floatToIntBits(this.value.floatValue());
+		bytes.add((byte) (tempVal >> 8));
+		bytes.add((byte) tempVal);
+		bytes.add((byte) (tempVal >> 24));
+		bytes.add((byte) (tempVal >> 16));
+		return this;
+	}
+
+	@Override
+	public List<Register> getRegisters() {
+		List<Register> registers = new ArrayList<>();
+		int tempVal = Float.floatToIntBits(this.value.floatValue());
+		registers.add(new Register((byte) (tempVal >> 8), (byte) (tempVal)));
+		registers.add(new Register((byte) (tempVal >> 24), (byte) (tempVal >> 16)));
+		return registers;
+	}
+}

Някои файлове не бяха показани, защото твърде много файлове са промени