Browse Source

国电宁夏分公司代码合并

songwenbin 2 years ago
parent
commit
8ea442f918
58 changed files with 5584 additions and 0 deletions
  1. 17 0
      gdnxfgs/README.md
  2. 28 0
      gdnxfgs/build.gradle
  3. 84 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/Bootstrap.java
  4. 46 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/config/Database.java
  5. 40 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/config/Iec104Config.java
  6. 26 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/config/Point.java
  7. 167 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/config/PointService.java
  8. 97 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/config/RedisConfig.java
  9. 62 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/exception/CommunicationException.java
  10. 16 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/exception/CustomException.java
  11. 63 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/exception/DispatchException.java
  12. 17 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/exception/IllegalFormatException.java
  13. 24 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/exception/InitErrorException.java
  14. 20 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/exception/LengthException.java
  15. 62 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/exception/LostConnectException.java
  16. 19 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/exception/UnknownLinkCodeException.java
  17. 19 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/exception/UnknownTransferReasonException.java
  18. 15 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/exception/UnknownTypeIdentifierException.java
  19. 152 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/Iec104Message.java
  20. 272 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/Iec104Session.java
  21. 262 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/Iec104Util.java
  22. 38 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/MessageInfo.java
  23. 31 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/SessionState.java
  24. 30 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/builder/CachedThreadPool.java
  25. 121 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/builder/ControlManageUtil.java
  26. 247 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/builder/Decoder104.java
  27. 90 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/builder/Encoder104.java
  28. 57 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/builder/Iec104ThreadLocal.java
  29. 169 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/builder/ScheduledTaskPool.java
  30. 49 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/handler/Check104Handler.java
  31. 59 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/handler/EchoServerHandler.java
  32. 51 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/handler/FrameDecoder.java
  33. 34 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/handler/Iec104Decoder.java
  34. 52 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/handler/Iec104Encoder.java
  35. 70 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/handler/Iec104ServerHandler.java
  36. 219 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/protocol/ASDU.java
  37. 125 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/protocol/Analysis.java
  38. 32 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/protocol/BalancedLinkCode.java
  39. 180 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/protocol/BasicInstruction104.java
  40. 54 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/protocol/Iec104Constant.java
  41. 151 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/protocol/ParamePreset104.java
  42. 90 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/protocol/QualifiersEnum.java
  43. 77 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/protocol/Telecontrol104.java
  44. 170 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/protocol/Telemetry104.java
  45. 150 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/protocol/Telesignalling104.java
  46. 56 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/protocol/TransferReason.java
  47. 45 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/protocol/TypeIdentifier.java
  48. 98 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/protocol/TypeIdentifierEnum.java
  49. 42 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/protocol/UControlEnum.java
  50. 111 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/redis/RedisDataService.java
  51. 406 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/utils/ByteUtil.java
  52. 597 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/utils/RedisUtil.java
  53. 62 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/utils/SpringContextUtil.java
  54. 261 0
      gdnxfgs/src/main/java/com/gyee/edge/gddlly/utils/Util.java
  55. 44 0
      gdnxfgs/src/main/resources/application.yaml
  56. 7 0
      gdnxfgs/src/main/resources/banner.txt
  57. BIN
      gdnxfgs/src/main/resources/gdnxzs.sqlite
  58. 1 0
      settings.gradle

+ 17 - 0
gdnxfgs/README.md

@@ -0,0 +1,17 @@
+# gddlly
+
+
+## 国电电力给龙源数据服务程序
+
+### 数据源: redis
+### 通信协议: IEC104
+
+
+
+
+
+
+
+
+
+

+ 28 - 0
gdnxfgs/build.gradle

@@ -0,0 +1,28 @@
+buildscript {
+    repositories {
+        mavenLocal()
+        maven {
+            allowInsecureProtocol = true
+            url "http://maven.aliyun.com/nexus/content/groups/public" }
+        mavenCentral()
+    }
+    dependencies {
+        classpath("$bootGroup:spring-boot-gradle-plugin:$springBootVersion")
+    }
+}
+
+apply plugin: "$bootGroup"
+apply plugin: 'io.spring.dependency-management'
+
+dependencies {
+    implementation project(":common:utils")
+    implementation("io.netty:netty-all:$nettyVersion")
+    implementation("$bootGroup:spring-boot-starter:$springBootVersion")
+    implementation("com.google.code.gson:gson:$gsonVersion")
+    implementation("org.apache.logging.log4j:log4j-core:$log4jVersion")
+    implementation("org.apache.logging.log4j:log4j-jul:$log4jVersion")
+    implementation("org.apache.logging.log4j:log4j-api:$log4jVersion")
+    implementation("org.apache.logging.log4j:log4j-slf4j-impl:$log4jVersion")
+    implementation("org.xerial:sqlite-jdbc:$sqliteJdbc")
+    implementation("org.springframework.boot:spring-boot-starter-data-redis:$springBootRedis")
+}

+ 84 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/Bootstrap.java

@@ -0,0 +1,84 @@
+
+package com.gyee.edge.gddlly;
+
+import com.gyee.edge.gddlly.config.Iec104Config;
+import com.gyee.edge.gddlly.config.Point;
+import com.gyee.edge.gddlly.config.PointService;
+import com.gyee.edge.gddlly.iec104.handler.*;
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.*;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.logging.LogLevel;
+import io.netty.handler.logging.LoggingHandler;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+import java.util.Date;
+import java.util.Map;
+
+@Slf4j
+@SpringBootApplication()
+public class Bootstrap implements CommandLineRunner {
+
+    @Autowired
+    Iec104Config iec104Config;
+
+    @Autowired
+    PointService pointService;
+
+    public static void main(String[] args) {
+        SpringApplication.run(Bootstrap.class, args);
+    }
+
+
+    @Override
+    public void run(String... args) throws Exception {
+        log.info("国电电力到龙源IEC104数据服务程序启动..............");
+        log.info("config : " + iec104Config.toString());
+
+        //加载测点映射数据
+        pointService.initPointMap();
+
+        // Configure the server.
+        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
+        EventLoopGroup workerGroup = new NioEventLoopGroup();
+        try {
+            ServerBootstrap b = new ServerBootstrap();
+            b.group(bossGroup, workerGroup)
+                    .channel(NioServerSocketChannel.class)
+                    .option(ChannelOption.SO_BACKLOG, 100)
+                    .handler(new LoggingHandler(LogLevel.INFO))
+                    .childHandler(new ChannelInitializer<SocketChannel>() {
+                        @Override
+                        public void initChannel(SocketChannel ch) throws Exception {
+                            ChannelPipeline p = ch.pipeline();
+
+                            p.addLast(new FrameDecoder());
+                            p.addLast(new Check104Handler());
+                            p.addLast(new Iec104Encoder());
+                            p.addLast(new Iec104Decoder());
+                            p.addLast(new Iec104ServerHandler());
+
+                           // p.addLast(new EchoServerHandler());
+                        }
+                    });
+
+            // Start the server.
+            ChannelFuture f = b.bind(iec104Config.getPort()).sync();
+
+            // Wait until the server socket is closed.
+            f.channel().closeFuture().sync();
+        } finally {
+            // Shut down all event loops to terminate all threads.
+            bossGroup.shutdownGracefully();
+            workerGroup.shutdownGracefully();
+        }
+
+
+    }
+}

+ 46 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/config/Database.java

@@ -0,0 +1,46 @@
+package com.gyee.edge.gddlly.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+@Component
+public class Database {
+
+    @Value("${spring.datasource.driver-class-name}")
+    private String driver;
+
+    @Value("${spring.datasource.url}")
+    private String url;
+
+    Connection connection = null;
+
+    public Connection getConnection() throws Exception {
+        try{
+            Class.forName(driver);
+            connection = DriverManager.getConnection(url);
+            System.out.println("数据库连接成功");
+
+        }catch (Exception e){
+            throw new Exception(e.getMessage());
+        }
+
+        return connection;
+    }
+
+    //释放资源
+    public void releaseResource(){
+
+
+        if(connection!=null){
+            try{
+                connection.close();
+            }catch (SQLException e){
+                e.printStackTrace();
+            }
+        }
+    }
+}

+ 40 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/config/Iec104Config.java

@@ -0,0 +1,40 @@
+package com.gyee.edge.gddlly.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * 104规约的配置
+ */
+@Data
+@Component
+@ConfigurationProperties("iec104")
+public class Iec104Config {
+
+    //服务监听端口
+    private short port = 8019;
+
+    //全量推送时间间隔
+    private int fullPushInterval = 30000;
+    //变化推送时间间隔
+    private int changedPushInterval = 10000;
+
+    //接收到帧的数量到该值就要发一个确认帧
+    private short frameAmountMax;
+
+    //每帧遥测点最大点数
+    private int frameAiMax = 16;
+    //每帧遥测点最大点数
+    private int frameDiMax = 22;
+
+
+
+
+
+    @Override
+    public String toString() {
+        return "port = " + port + ", pollingInteral = " + fullPushInterval;
+    }
+
+}

+ 26 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/config/Point.java

@@ -0,0 +1,26 @@
+package com.gyee.edge.gddlly.config;
+
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class Point {
+    private int id;
+    private String point;
+    private String pointType;
+    private int publicAddr;
+    private int pointAddr;
+    private float coef;
+    private String org;
+
+    //最新值
+    private double value;
+    //false -- 未发送, true -- 已发送
+    // 如果value变化,将consumed置为false;
+    private boolean consumed;
+
+    //此处赋初始值,主要是为了防止redis中不存在点表中的点,空值引起后续数据转换错误!
+    private Date lastUpdateTime = new Date(0);
+
+}

+ 167 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/config/PointService.java

@@ -0,0 +1,167 @@
+package com.gyee.edge.gddlly.config;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.stereotype.Service;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@Component
+public class PointService {
+
+    @Autowired
+    private Database db;
+
+    //外层: key = 分组号(iec104 asduAddress), Value = 该分组下所有点的map结构
+    private Map<Integer, ArrayList<Point>> aiMap;
+    private Map<Integer, ArrayList<Point>> diMap;
+
+    private Map<Integer, ArrayList<String>> aiKeyMap;
+    private Map<Integer, ArrayList<String>> diKeyMap;
+
+    public void initPointMap() {
+        aiMap = loadAiPointMap();
+        diMap = loadDiPointMap();
+    }
+
+    public Map<Integer, ArrayList<Point>> getAiList(){
+
+        if (aiMap == null || aiMap.isEmpty()){
+            aiMap = loadAiPointMap();
+        }
+
+        return aiMap;
+    }
+
+    private Map<Integer, ArrayList<Point>> loadAiPointMap() {
+        Map<Integer, ArrayList<Point>> map = new HashMap<>();
+        aiKeyMap = new HashMap<>();
+
+        try {
+            log.info("加载AI测点......");
+            Connection conn = db.getConnection();
+            //确保地址连续
+            String sql = "select * from point where pointtype = 'AI' order by publicaddr, pointaddr asc";
+            log.info("执行SQL:" + sql);
+            PreparedStatement ps = conn.prepareStatement(sql);
+            ResultSet rs = ps.executeQuery();
+            while (rs.next()) {
+                Point point = new Point();
+                point.setId(rs.getInt("id"));
+                point.setPoint(rs.getString("point"));
+                point.setPointType("AI");
+                point.setPublicAddr(rs.getInt("publicaddr"));
+                point.setPointAddr(rs.getInt("pointaddr"));
+                point.setCoef(rs.getShort("coef"));
+                point.setOrg(rs.getString("org"));
+
+                if (map.containsKey(point.getPublicAddr()) == false) {
+                    ArrayList<Point> subMap = new ArrayList<>();
+                    map.put((Integer)point.getPublicAddr(), subMap);
+                    ArrayList<String> lstStr = new ArrayList<>();
+                    aiKeyMap.put((Integer)point.getPublicAddr(), lstStr);
+                }
+                ArrayList<Point> subMap = map.get(point.getPublicAddr());
+                subMap.add(point);
+                ArrayList<String> lstStr = aiKeyMap.get(point.getPublicAddr());
+                lstStr.add(point.getPoint());
+
+            }
+        } catch (Exception e) {
+            log.info("sqlite查询失败",e);
+            e.printStackTrace();
+        }finally {
+            db.releaseResource();
+        }
+        return map;
+    }
+
+    public Map<Integer, ArrayList<Point>> getDiList(){
+        if (diMap == null || diMap.isEmpty()){
+            diMap = loadDiPointMap();
+        }
+
+        return diMap;
+    }
+
+    private Map<Integer, ArrayList<Point>> loadDiPointMap() {
+        Map<Integer, ArrayList<Point>> map = new HashMap<>();
+        diKeyMap = new HashMap<>();
+        try {
+            log.info("加载DI测点......");
+            Connection conn = db.getConnection();
+            String sql = "select * from point where pointtype = 'DI' order by publicaddr, pointaddr";
+            log.info("执行SQL:" + sql);
+            PreparedStatement ps = conn.prepareStatement(sql);
+            ResultSet rs = ps.executeQuery();
+            while (rs.next()) {
+                Point point = new Point();
+                point.setId(rs.getInt("id"));
+                point.setPoint(rs.getString("point"));
+                point.setPointType("DI");
+                point.setPublicAddr(rs.getInt("publicaddr"));
+                point.setPointAddr(rs.getInt("pointaddr"));
+                point.setCoef(rs.getShort("coef"));
+                point.setOrg(rs.getString("org"));
+
+                if (map.containsKey(point.getPublicAddr()) == false) {
+                    ArrayList<Point> subMap = new ArrayList<>();
+                    map.put((Integer)point.getPublicAddr(), subMap);
+                    ArrayList<String> lstStr = new ArrayList<>();
+                    diKeyMap.put((Integer)point.getPublicAddr(), lstStr);
+                }
+                ArrayList<Point> subMap = map.get(point.getPublicAddr());
+                subMap.add(point);
+                ArrayList<String> lstStr = diKeyMap.get(point.getPublicAddr());
+                lstStr.add(point.getPoint());
+            }
+        } catch (Exception e) {
+            log.info("sqlite查询失败",e);
+            e.printStackTrace();
+        }finally {
+            db.releaseResource();
+        }
+        return map;
+    }
+
+
+    public ArrayList<Point> getAiList(int addr) {
+        if (aiMap == null || aiMap.isEmpty()){
+            aiMap = loadAiPointMap();
+        }
+
+        return aiMap.get(addr);
+    }
+
+    public ArrayList<Point> getDiList(int addr) {
+        if (diMap == null || diMap.isEmpty()){
+            diMap = loadDiPointMap();
+        }
+
+        return diMap.get(addr);
+    }
+
+    public ArrayList<String> getAiKeys(int addr) {
+        if (aiKeyMap == null || aiKeyMap.isEmpty()){
+            loadAiPointMap();
+        }
+
+        return aiKeyMap.get(addr);
+    }
+
+    public ArrayList<String> getDiKeys(int addr) {
+        if (diKeyMap == null || diKeyMap.isEmpty()){
+            loadDiPointMap();
+        }
+
+        return diKeyMap.get(addr);
+    }
+
+}

+ 97 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/config/RedisConfig.java

@@ -0,0 +1,97 @@
+package com.gyee.edge.gddlly.config;
+
+import org.springframework.cache.annotation.CachingConfigurerSupport;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.*;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+
+@Configuration
+@EnableCaching
+public class RedisConfig extends CachingConfigurerSupport {
+
+    /**
+     * retemplate相关配置
+     * @param factory
+     * @return
+     */
+    @Bean
+    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
+
+        RedisTemplate<String, Object> template = new RedisTemplate<>();
+        // 配置连接工厂
+        template.setConnectionFactory(factory);
+
+        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
+
+        template.setValueSerializer(stringRedisSerializer);
+        //使用StringRedisSerializer来序列化和反序列化redis的key值
+        template.setKeySerializer(stringRedisSerializer);
+
+        // 设置hash key 和value序列化模式
+        template.setHashKeySerializer(stringRedisSerializer);
+        template.setHashValueSerializer(stringRedisSerializer);
+        template.afterPropertiesSet();
+
+        return template;
+    }
+
+
+    /**
+     * 对hash类型的数据操作
+     *
+     * @param redisTemplate
+     * @return
+     */
+    @Bean
+    public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
+        return redisTemplate.opsForHash();
+    }
+
+    /**
+     * 对redis字符串类型数据操作
+     *
+     * @param redisTemplate
+     * @return
+     */
+    @Bean
+    public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {
+        return redisTemplate.opsForValue();
+    }
+
+    /**
+     * 对链表类型的数据操作
+     *
+     * @param redisTemplate
+     * @return
+     */
+    @Bean
+    public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
+        return redisTemplate.opsForList();
+    }
+
+    /**
+     * 对无序集合类型的数据操作
+     *
+     * @param redisTemplate
+     * @return
+     */
+    @Bean
+    public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
+        return redisTemplate.opsForSet();
+    }
+
+    /**
+     * 对有序集合类型的数据操作
+     *
+     * @param redisTemplate
+     * @return
+     */
+    @Bean
+    public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
+        return redisTemplate.opsForZSet();
+    }
+}

+ 62 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/exception/CommunicationException.java

@@ -0,0 +1,62 @@
+
+package com.gyee.edge.gddlly.exception;
+
+public class CommunicationException extends RuntimeException {
+
+    /**
+     * Constructs a new exception with <code>null</code> as its detail message.
+     * The cause is not initialized, and may subsequently be initialized by a
+     * call to {@link #initCause}.
+     */
+    public CommunicationException() {
+        super();
+    }
+
+    /**
+     * Constructs a new exception with the specified detail message.  The
+     * cause is not initialized, and may subsequently be initialized by
+     * a call to {@link #initCause}.
+     *
+     * @param message the detail message. The detail message is saved for
+     *                later retrieval by the {@link #getMessage()} method.
+     */
+    public CommunicationException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new exception with the specified detail message and
+     * cause.  <p>Note that the detail message associated with
+     * <code>cause</code> is <i>not</i> automatically incorporated in
+     * this exception's detail message.
+     *
+     * @param message the detail message (which is saved for later retrieval
+     *                by the {@link #getMessage()} method).
+     * @param cause   the cause (which is saved for later retrieval by the
+     *                {@link #getCause()} method).  (A <tt>null</tt> value is
+     *                permitted, and indicates that the cause is nonexistent or
+     *                unknown.)
+     * @since 1.4
+     */
+    public CommunicationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Constructs a new exception with the specified cause and a detail
+     * message of <tt>(cause==null ? null : cause.toString())</tt> (which
+     * typically contains the class and detail message of <tt>cause</tt>).
+     * This constructor is useful for exceptions that are little more than
+     * wrappers for other throwables (for example, {@link
+     * java.security.PrivilegedActionException}).
+     *
+     * @param cause the cause (which is saved for later retrieval by the
+     *              {@link #getCause()} method).  (A <tt>null</tt> value is
+     *              permitted, and indicates that the cause is nonexistent or
+     *              unknown.)
+     * @since 1.4
+     */
+    public CommunicationException(Throwable cause) {
+        super(cause);
+    }
+}

+ 16 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/exception/CustomException.java

@@ -0,0 +1,16 @@
+package com.gyee.edge.gddlly.exception;
+
+
+public class CustomException extends Exception {
+    private String content;
+
+    public CustomException(String content) {
+        this.content = content;
+    }
+
+    @Override
+    public String toString() {
+        return this.content;
+    }
+
+}

+ 63 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/exception/DispatchException.java

@@ -0,0 +1,63 @@
+
+package com.gyee.edge.gddlly.exception;
+
+public class DispatchException extends Exception {
+
+    /**
+     * Constructs a new exception with <code>null</code> as its detail message.
+     * The cause is not initialized, and may subsequently be initialized by a
+     * call to {@link #initCause}.
+     */
+    public DispatchException() {
+        super();
+    }
+
+    /**
+     * Constructs a new exception with the specified detail message.  The
+     * cause is not initialized, and may subsequently be initialized by
+     * a call to {@link #initCause}.
+     *
+     * @param message the detail message. The detail message is saved for
+     *                later retrieval by the {@link #getMessage()} method.
+     */
+    public DispatchException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new exception with the specified detail message and
+     * cause.  <p>Note that the detail message associated with
+     * <code>cause</code> is <i>not</i> automatically incorporated in
+     * this exception's detail message.
+     *
+     * @param message the detail message (which is saved for later retrieval
+     *                by the {@link #getMessage()} method).
+     * @param cause   the cause (which is saved for later retrieval by the
+     *                {@link #getCause()} method).  (A <tt>null</tt> value is
+     *                permitted, and indicates that the cause is nonexistent or
+     *                unknown.)
+     * @since 1.4
+     */
+    public DispatchException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Constructs a new exception with the specified cause and a detail
+     * message of <tt>(cause==null ? null : cause.toString())</tt> (which
+     * typically contains the class and detail message of <tt>cause</tt>).
+     * This constructor is useful for exceptions that are little more than
+     * wrappers for other throwables (for example, {@link
+     * java.security.PrivilegedActionException}).
+     *
+     * @param cause the cause (which is saved for later retrieval by the
+     *              {@link #getCause()} method).  (A <tt>null</tt> value is
+     *              permitted, and indicates that the cause is nonexistent or
+     *              unknown.)
+     * @since 1.4
+     */
+    public DispatchException(Throwable cause) {
+        super(cause);
+    }
+
+}

+ 17 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/exception/IllegalFormatException.java

@@ -0,0 +1,17 @@
+package com.gyee.edge.gddlly.exception;
+
+
+public class IllegalFormatException extends Exception {
+    /**
+	 * 
+	 */
+	private static final long serialVersionUID = 3986445004211566058L;
+
+	public IllegalFormatException() {
+        super();
+    }
+
+    public String toString() {
+        return "非法报文。报文应为16进制数字组成,并且由0x68或者0x10为报文头,0x16为报文尾(104规约无)。";
+    }
+}

+ 24 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/exception/InitErrorException.java

@@ -0,0 +1,24 @@
+package com.gyee.edge.gddlly.exception;
+
+public class InitErrorException extends RuntimeException {
+
+    private static final long serialVersionUID = 4401440531171871948L;
+
+    private int errorCode = 1;
+
+    private String errorMsg;
+
+    protected InitErrorException() {
+
+    }
+
+    public InitErrorException(String errorMsg, Throwable e) {
+        super(errorMsg, e);
+        this.errorMsg = errorMsg;
+    }
+
+    public InitErrorException(String errorMsg) {
+        super(errorMsg);
+        this.errorMsg = errorMsg;
+    }
+}

+ 20 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/exception/LengthException.java

@@ -0,0 +1,20 @@
+package com.gyee.edge.gddlly.exception;
+
+
+public class LengthException extends Exception {
+
+    private long desired;//期望的长度
+    private long reality;//实际的长度
+
+    public LengthException(long desired, long reality) {
+        this.desired = desired;
+        this.reality = reality;
+    }
+
+    @Override
+    public String toString() {
+        return "报文长度错误,期望报文长度为" + desired +
+                "字节。而真实的报文长度为" + reality +
+                "字节";
+    }
+}

+ 62 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/exception/LostConnectException.java

@@ -0,0 +1,62 @@
+package com.gyee.edge.gddlly.exception;
+
+public class LostConnectException extends CommunicationException {
+
+    /**
+     * Constructs a new exception with <code>null</code> as its detail message.
+     * The cause is not initialized, and may subsequently be initialized by a
+     * call to {@link #initCause}.
+     */
+    public LostConnectException() {
+        super();
+    }
+
+    /**
+     * Constructs a new exception with the specified detail message.  The
+     * cause is not initialized, and may subsequently be initialized by
+     * a call to {@link #initCause}.
+     *
+     * @param message the detail message. The detail message is saved for
+     *                later retrieval by the {@link #getMessage()} method.
+     */
+    public LostConnectException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new exception with the specified detail message and
+     * cause.  <p>Note that the detail message associated with
+     * <code>cause</code> is <i>not</i> automatically incorporated in
+     * this exception's detail message.
+     *
+     * @param message the detail message (which is saved for later retrieval
+     *                by the {@link #getMessage()} method).
+     * @param cause   the cause (which is saved for later retrieval by the
+     *                {@link #getCause()} method).  (A <tt>null</tt> value is
+     *                permitted, and indicates that the cause is nonexistent or
+     *                unknown.)
+     * @since 1.4
+     */
+    public LostConnectException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Constructs a new exception with the specified cause and a detail
+     * message of <tt>(cause==null ? null : cause.toString())</tt> (which
+     * typically contains the class and detail message of <tt>cause</tt>).
+     * This constructor is useful for exceptions that are little more than
+     * wrappers for other throwables (for example, {@link
+     * java.security.PrivilegedActionException}).
+     *
+     * @param cause the cause (which is saved for later retrieval by the
+     *              {@link #getCause()} method).  (A <tt>null</tt> value is
+     *              permitted, and indicates that the cause is nonexistent or
+     *              unknown.)
+     * @since 1.4
+     */
+    public LostConnectException(Throwable cause) {
+        super(cause);
+    }
+
+}

+ 19 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/exception/UnknownLinkCodeException.java

@@ -0,0 +1,19 @@
+package com.gyee.edge.gddlly.exception;
+
+
+public class UnknownLinkCodeException extends Exception {
+
+    /**
+	 * 
+	 */
+	private static final long serialVersionUID = -8260695109983771420L;
+
+	public UnknownLinkCodeException() {
+        super();
+    }
+
+    @Override
+    public String toString() {
+        return "未知类型的链路功能码(平衡式)";
+    }
+}

+ 19 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/exception/UnknownTransferReasonException.java

@@ -0,0 +1,19 @@
+package com.gyee.edge.gddlly.exception;
+
+
+public class UnknownTransferReasonException extends Exception {
+
+    /**
+	 * 
+	 */
+	private static final long serialVersionUID = 4977251357848627763L;
+
+	public UnknownTransferReasonException() {
+        super();
+    }
+
+    @Override
+    public String toString() {
+        return "未定义的传送原因。";
+    }
+}

+ 15 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/exception/UnknownTypeIdentifierException.java

@@ -0,0 +1,15 @@
+package com.gyee.edge.gddlly.exception;
+
+/**
+ * @Author sun
+ */
+public class UnknownTypeIdentifierException extends Exception {
+    public UnknownTypeIdentifierException() {
+        super();
+    }
+
+    @Override
+    public String toString() {
+        return "未知类型标识符";
+    }
+}

+ 152 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/Iec104Message.java

@@ -0,0 +1,152 @@
+package com.gyee.edge.gddlly.iec104;
+
+import com.gyee.edge.gddlly.config.Point;
+import com.gyee.edge.gddlly.iec104.protocol.QualifiersEnum;
+import com.gyee.edge.gddlly.iec104.protocol.TypeIdentifierEnum;
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ *  一条报文对应的消息体
+ */
+@Data
+public class Iec104Message {
+	
+	/**
+	 * 启动字符 固定 一个字节
+	 */
+	private byte start = 0x68;
+	
+	/**
+	 * APDU 长度1个字节
+	 */
+	private int apduLength = 0;
+
+	
+	/**
+	 * 控制域 四个字节
+	 */
+	private byte[] control;
+
+	private short acceptSeq;
+
+	private short sendSeq;
+
+	
+	/**
+	 * 类型标识 1字节
+	 */
+	private TypeIdentifierEnum typeIdentifier;
+	
+	
+	/**
+	 * 可变结构限定词 1个字节 
+	 * true SQ = 0   true 数目number 是 信息对象的数目
+	 * false SQ = 1 单个对象的信息元素或者信息元素的集合的数目
+	 */
+	
+	private boolean  isContinuous;
+
+	/**
+	 * 消息数量
+	 */
+	private int infosize;
+	/**
+	 * 传输原因 两个字节
+	 */
+	private  short transferReason;
+	
+	/**
+	 *  终端地址 也就是应用服务数据单元公共地址
+	 */
+	private short terminalAddress;
+	
+	/**
+	 * 消息地址 字节
+	 */
+	private int messageAddress;
+	
+	/**
+	 * 消息结构
+	 */
+	private List<MessageInfo> messages;
+
+	//直接关联到点表
+	private List<Point> aiPointList;
+	private List<Point> diPointList;
+	
+	/**
+	 * 判断是否有消息元素
+	 */
+	private boolean isMessage;
+	/**
+	 * 判断是否有限定词
+	 */
+	private boolean isQualifiers;
+	/**
+	 * 判断是否有时标
+	 */
+	private boolean isTimeScaleExit;
+	
+	private QualifiersEnum qualifiers;
+	/**
+	 * 
+	 * 时标
+	 */
+	private  Date timeScale;
+
+	/**
+	 * 十六进制 字符串
+	 */
+	private String hexString;
+
+	public Iec104Message() {
+	}
+
+	/**
+	 * 
+	 * @param control 控制域
+	 * @param typeIdentifierEnum 类型标识
+	 * @param sq  0 地址不连续  1 地址连续
+	 * @param isTest 传输原因  0 未试验 1 试验
+	 * @param isPn 肯定确认 和否定确认
+	 * @param transferReason 传输原因 后六个比特位
+	 * @param terminalAddress 服务地址
+	 * @param messageAddress 消息地址
+	 * @param messages 消息列表
+	 * @param timeScale 时间
+	 * @param qualifiers 限定词
+	 * @return
+	 */
+	public Iec104Message(byte[] control, TypeIdentifierEnum typeIdentifierEnum, boolean sq,
+						 boolean isTest, boolean isPn, short transferReason, short terminalAddress, int messageAddress,
+						 List<MessageInfo> messages, Date timeScale, QualifiersEnum qualifiers) {
+		this.control = control;
+		this.typeIdentifier = typeIdentifierEnum;
+		this.isContinuous = sq;
+		this.infosize = messages.size();
+		this.transferReason = transferReason; // Iec104Util.getTransferReasonShort(isTest, isPn, transferReason);
+		this.messages = messages;
+		this.terminalAddress = terminalAddress;
+		this.timeScale = timeScale;
+		if (timeScale != null) {
+			this.isTimeScaleExit = true;
+		}
+		this.qualifiers = qualifiers;
+	}
+	
+	
+	/**
+	 *  U 帧或者S帧
+	 * @param control 控制域
+	 */
+	public Iec104Message(byte[] control) {
+		this.control = control;
+		this.messages = new ArrayList<>();
+	}
+
+
+}

+ 272 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/Iec104Session.java

@@ -0,0 +1,272 @@
+package com.gyee.edge.gddlly.iec104;
+
+import com.gyee.edge.gddlly.config.Iec104Config;
+import com.gyee.edge.gddlly.config.Point;
+import com.gyee.edge.gddlly.config.PointService;
+import com.gyee.edge.gddlly.iec104.protocol.BasicInstruction104;
+import com.gyee.edge.gddlly.iec104.protocol.TransferReason;
+import com.gyee.edge.gddlly.iec104.protocol.TypeIdentifierEnum;
+import com.gyee.edge.gddlly.iec104.protocol.UControlEnum;
+import com.gyee.edge.gddlly.redis.RedisDataService;
+import com.gyee.edge.gddlly.utils.SpringContextUtil;
+import io.netty.channel.*;
+import lombok.extern.slf4j.Slf4j;
+
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+@Slf4j
+public class Iec104Session {
+    private String clientIp;
+    private int clientPort;
+    private short publicAddress;
+    private Date connectTime;
+    private SessionState state;
+    private Date lastSessionTime;
+    private short acceptSeq = 0;    //接收序列号
+    private short sendSeq = 0;      //发送序列号
+    private boolean isRunning = false;
+
+    private ChannelHandlerContext channelHandlerContext;
+
+    private PointService pointService = SpringContextUtil.getBean(PointService.class);
+    private RedisDataService redisService = SpringContextUtil.getBean(RedisDataService.class);
+    private Iec104Config iec104Config = SpringContextUtil.getBean(Iec104Config.class);
+
+    private ArrayList<Point> aiList;
+    private ArrayList<Point> diList;
+
+    public Iec104Session(ChannelHandlerContext ctx) {
+        channelHandlerContext = ctx;
+        InetSocketAddress ipSocket = (InetSocketAddress)ctx.channel().remoteAddress();
+        clientIp = ipSocket.getAddress().getHostAddress();
+        clientPort = ipSocket.getPort();
+        connectTime = new Date();
+        lastSessionTime = new Date();
+        state = SessionState.ACTIVE;
+    }
+
+    public String getId() {
+        return clientIp + ":" + clientPort;
+    }
+
+    public SessionState getState() {
+        return state;
+    }
+
+    public void processMessage(Iec104Message request) {
+        if (request == null)
+            return;
+
+        try {
+            acceptSeq++;
+            if (request.getTypeIdentifier() == null) {
+                //U帧或S帧处理
+                //TODO: 标准化处理,状态校验
+                Iec104Message response = null;
+                byte fun = request.getControl()[0];
+                switch (fun) {
+                    case 0x07: //启动命令
+                        if(state == SessionState.NEW || state == SessionState.INACTIVE)
+                            state = SessionState.START104;
+                        acceptSeq = 0;
+                        sendSeq = 0;
+                        response = BasicInstruction104.createSysMessage(UControlEnum.STARTDT_YES);
+                        break;
+                    case 0x43: //测试命令
+                        response = BasicInstruction104.createSysMessage(UControlEnum.TESTFR_YES);
+                    case 0x13: //停止命令
+                        state = SessionState.STOP104;
+                        //todo: 停止数据的发送
+                        this.stop();
+                        response = BasicInstruction104.createSysMessage(UControlEnum.STOPDT_YES);
+                        break;
+                    default:
+                        break;
+                }
+                if (response != null)
+                    sendMessage(response);
+                    //channelHandlerContext.writeAndFlush(response);
+            } else {
+                //acceptSeq = (short)(request.getSendSeq() + 1);
+                if (request.getTypeIdentifier() == TypeIdentifierEnum.generalCall) {
+                    // 总召唤命令
+                    publicAddress = request.getTerminalAddress();
+                    aiList = pointService.getAiList().get((int)publicAddress);
+                    diList = pointService.getDiList().get((int)publicAddress);
+                    if (TransferReason.ACTIVATE == TransferReason.valueOf(request.getTransferReason())) {
+                        state = SessionState.CALL_ALL;
+                        sendMessage(BasicInstruction104.getYesGeneralCallRuleDetail104(acceptSeq, sendSeq));
+                        //todo: 启动全量传输
+                        this.start();
+
+                    } else if (TransferReason.STOP_ACTIVATION == TransferReason.valueOf(request.getTransferReason())) {
+                        state = SessionState.CALL_ALL_END;
+                        //todo: 启动变化传输
+                        this.start();
+                    }
+
+                } else {
+                    //todo: 其它命令处理
+                }
+            }
+
+        }catch (Exception ex) {
+            log.error(ex.getMessage());
+            ex.printStackTrace();
+        }
+    }
+
+    public void start() {
+        if (isRunning) {
+            log.warn("数据轮询和转发线程已启动!client = " +getId());
+            return;
+        }
+
+        getPushThread().start();
+    }
+
+    public void stop() {
+        isRunning = false;
+    }
+
+    private Thread getPushThread() {
+        return new Thread(new Runnable() {
+            public void run() {
+                log.info("开始启动数据轮询和转发线程...");
+                try {
+                    isRunning = true;
+                    while(isRunning) {
+                        //todo: 从redis读取数据,发送到客户端
+                        //1、从redis读取数据,填充aiMap、diMap中,point的value,consumed,lastUpdateTime;
+                        if (redisService.readRedisData(publicAddress,"AI")) {
+                            if (state == SessionState.CALL_ALL) {
+                                //2、如果SessionState.CALL_ALL, 全量传输遥测点
+                                sendPoints(aiList, true, "AI");
+                            } else if (state == SessionState.CALL_ALL_END) {
+                                //3、如果SessionState.CALL_ALL_END,变化传输遥测点
+                                sendPoints(aiList, false, "AI");;
+                            } else {
+                                break;
+                            }
+                        }
+
+                        if (redisService.readRedisData(publicAddress,"DI")) {
+                            if (state == SessionState.CALL_ALL) {
+                                //2、如果SessionState.CALL_ALL, 全量传输遥测点
+                                sendPoints(diList, true, "DI");
+                            } else if (state == SessionState.CALL_ALL_END) {
+                                //3、如果SessionState.CALL_ALL_END,变化传输遥测点
+                                sendPoints(diList, false, "DI");
+                            } else {
+                                break;
+                            }
+                        }
+
+                        if (state == SessionState.CALL_ALL_END)
+                            Thread.sleep(iec104Config.getChangedPushInterval());
+                        else
+                            Thread.sleep(iec104Config.getFullPushInterval());
+                    }
+                } catch (Exception e) {
+                    //todo: 异常处理
+                    log.error(e.getMessage());
+
+                } finally {
+                    isRunning = false;
+                }
+            }
+        });
+    }
+
+    private boolean sendPoints(ArrayList<Point> allPoints, boolean isContinous, String pointType) {
+        List<Point> pointList = new ArrayList<>();
+        //每个消息内测点的数量
+        int maxPointsPerMessage = 16;
+        if ("AI".equals(pointType.toUpperCase())) {
+            maxPointsPerMessage = iec104Config.getFrameAiMax();
+        } else {
+            maxPointsPerMessage = iec104Config.getFrameDiMax();
+        }
+
+        Iec104Message msg104 = createNewMessage(isContinous, pointType);
+
+        if (isContinous) {
+            //消息地址为信息体中第一个元素的地址
+            int firstMsgAddr = allPoints.get(0).getPointAddr();
+            msg104.setMessageAddress(firstMsgAddr);
+        }
+       // log.info(allPoints.get(0).getPoint() + allPoints.get(0).getValue());
+        for(int i=0;i<allPoints.size();i++) {
+            Point point = allPoints.get(i);
+
+            if (!isContinous && point.isConsumed())
+                continue;
+
+            if (pointList.size() >= maxPointsPerMessage) {
+                if ("AI".equals(pointType.toUpperCase())) {
+                    msg104.setAiPointList(pointList);
+                } else {
+                    msg104.setDiPointList(pointList);
+                }
+
+                msg104.setInfosize(pointList.size());
+                sendMessage(msg104);
+
+
+                //todo:考虑实现确认S帧
+
+                //重置point列表缓存,消息地址,其它内容不变
+                if (i < allPoints.size()-1) {
+                    pointList = new ArrayList<>();
+                    msg104 = createNewMessage(isContinous,pointType);
+                    if (isContinous)
+                        msg104.setMessageAddress(allPoints.get(i).getPointAddr());
+                }
+
+                ///////////////////todo :debug
+                //return true;
+                //////////////////
+            }
+            pointList.add(point);
+            point.setConsumed(true);
+        }
+
+        if (pointList.size() > 0) {
+            if ("AI".equals(pointType.toUpperCase())) {
+                msg104.setAiPointList(pointList);
+            } else {
+                msg104.setDiPointList(pointList);
+            }
+            msg104.setInfosize(pointList.size());
+            sendMessage(msg104);
+        }
+
+        return true;
+    }
+
+    private Iec104Message createNewMessage(boolean isContinous, String pointType) {
+        Iec104Message msg104 = new Iec104Message();
+        msg104.setAcceptSeq(acceptSeq);
+        msg104.setTransferReason((short)20);
+        if ("AI".equals(pointType.toUpperCase())) {
+            msg104.setTypeIdentifier(TypeIdentifierEnum.M_ME_TF_1); //M_ME_TF_1
+        } else {
+            msg104.setTypeIdentifier(TypeIdentifierEnum.M_SP_TB_1); //M_SP_TB_1
+        }
+        //todo: 设置VSQ,可变结构描述限定词
+        msg104.setContinuous(isContinous);
+        msg104.setTerminalAddress(publicAddress);
+        return msg104;
+    }
+
+    private synchronized void sendMessage(Iec104Message msg) {
+        msg.setSendSeq(sendSeq++);
+        log.info("send message:" + msg.getSendSeq() + ", address = " + msg.getMessageAddress()
+                + ",type= " + msg.getTypeIdentifier()) ;
+        channelHandlerContext.writeAndFlush(msg);
+    }
+
+}

+ 262 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/Iec104Util.java

@@ -0,0 +1,262 @@
+package com.gyee.edge.gddlly.iec104;
+
+
+import com.gyee.edge.gddlly.iec104.protocol.TypeIdentifierEnum;
+import com.gyee.edge.gddlly.iec104.protocol.UControlEnum;
+import com.gyee.edge.gddlly.utils.ByteUtil;
+
+/**
+ * 
+* @ClassName: Iec104Util
+* @Description: 工具类
+ */
+public class Iec104Util {
+	
+	private static int controlLength = 4;
+	
+	/**
+	 * I 格式 低位在前
+	 * @param accept 接收序列号
+	 * @param send 发送序列号
+	 * @return
+	 */
+	public static byte[] getIcontrol(short accept, short send) {
+		byte[] control = new byte[4];
+		// 向左移动一位 保证低位的D0 是0
+		send = (short) (send << 1);
+		control[0] =  (byte) ((send));
+		control[1]  =  (byte) ((send >> 8));
+		accept = (short) (accept << 1);
+		control[2] =   (byte) ((accept));
+		control[3]  =  (byte) ((accept >> 8));
+		return control;
+	}
+	
+	/**
+	 * 返回控制域中的接收序号
+	 * @param control
+	 * @return
+	 */
+	public  static short getAccept(byte[] control) {
+		int accept = 0;
+		short  acceptLow =   (short) (control[2] & 0xff);
+		short  acceptHigh =   (short) (control[3] & 0xff);
+		accept += acceptLow;
+		accept += acceptHigh << 8;
+		accept = accept >> 1;
+		return (short) accept;
+		
+	}
+	
+	/**
+	 * 返回控制域中的发送序号
+	 * @param control
+	 * @return
+	 */
+	public  static short getSend(byte[] control) {
+		int send = 0;
+		short  acceptLow =  (short) (control[0] & 0xff);
+		short  acceptHigh =  (short) (control[1] & 0xff);
+		send += acceptLow;
+		send += acceptHigh << 8;
+		send = send >> 1;
+		return (short) send;
+	}
+	
+	/**
+	 * S 格式
+	 * @param accept
+	 * @return
+	 */
+	public static byte[] getScontrol(short accept) {
+		byte[] control = new byte[4];
+		// 向左移动一位 保证低位的D0 是0
+		short send = 1;
+		control[0] =  (byte) ((send));
+		control[1]  =  (byte) ((send >> 8));
+		accept = (short) (accept << 1);
+		control[2] =   (byte) ((accept));
+		control[3]  =  (byte) ((accept >> 8));
+		return control;
+	}
+
+	/**
+	 * 
+	* @Title: 返回U帧
+	* @Description: 判断是否是
+	* @param @param control
+	* @param @return 
+	* @return boolean   
+	* @throws
+	 */
+	public static UControlEnum getUcontrol(byte[] control) {
+		if (control.length < controlLength || control[1] != 0 || control[3] != 0 || control[2] != 0) {
+			return null;
+		}  
+		int controlInt = ByteUtil.byteArrayToInt(control);
+		for (UControlEnum ucontrolEnum : UControlEnum.values()) {
+			if (ucontrolEnum.getValue() == controlInt) {
+				return ucontrolEnum;
+			}
+		}
+		return null;
+	}
+	
+	
+	
+	
+	
+	/**
+	 * 返回消息地址 其中低位在前
+	 * @param i
+	 * @return
+	 */
+	public static byte[] intToMessageAddress(int i) {
+        byte[] result = new byte[3];
+        result[0] = (byte) (i & 0xFF);
+        result[1] = (byte) ((i >> 8) & 0xFF);
+        result[2] = (byte) ((i >> 16) & 0xFF);
+        return result;
+    }
+	
+	
+	
+	/**
+	 * 消息地址 只有三个
+	 * @param bytes
+	 * @return
+	 */
+	public static int messageAddressToInt(byte[] bytes) {
+        int value = 0;
+        for (int i = 2; i >= 0; i--) {
+            int shift = (2 - i) * 8;
+            value += (bytes[2 - i] & 0xFF) << shift;
+        }
+        return value;
+    }
+	
+	/**
+	 * 设置可以变限定词
+	 * @param msg104
+	 * @param byteItem
+	 */
+	public static void setVSQ(Iec104Message msg104, byte byteItem) {
+		// 第一位是 0 则是有序的
+		msg104.setContinuous((byteItem & 0x80) == 0 ? false : true);
+		// 先将第一位数置零 然后转换成int
+		msg104.setInfosize(byteItem & (byte) 0x7F);
+	}
+	    
+	/**	
+	 * 返回可变限定词数组
+	 * @param msg104
+	 * @return
+	 */
+	public static byte getChangedQualifiers(Iec104Message msg104) {
+		// 将长度转换成 byte
+		byte changedQualifiers = (byte) msg104.getInfosize();
+		// 判断SQ 置   isContinuous false SQ = 0;否则 SQ =1 ,  同时将SQ置 设置在 可变限定词的 D7位置
+		int sq = msg104.isContinuous() ?  0x80 : 0;
+		changedQualifiers = (byte) (sq | changedQualifiers);
+		return changedQualifiers;
+	}
+
+	public static byte getVSQ(boolean isContinuous, int infosize) {
+		// 将长度转换成 byte
+		byte vsq = (byte)infosize;
+		// 判断SQ 置   isContinuous false SQ = 0;否则 SQ =1 ,  同时将SQ置 设置在 可变限定词的 D7位置
+		int sq = isContinuous ?  0x80 : 0;
+		vsq = (byte) (sq | vsq);
+		return vsq;
+	}
+	
+	
+	public static void setMeaageAttribute(Iec104Message msg104) {
+		boolean isMessage =  !(TypeIdentifierEnum.generalCall.equals(msg104.getTypeIdentifier())  //总召唤无此项
+				|| TypeIdentifierEnum.timeSynchronization.equals(msg104.getTypeIdentifier()) // 时钟同步
+				|| TypeIdentifierEnum.resetPprocess.equals(msg104.getTypeIdentifier()) // 复位进程
+				|| TypeIdentifierEnum.initEnd.equals(msg104.getTypeIdentifier()));
+		msg104.setMessage(isMessage);
+		
+		boolean isQualifiers = !(TypeIdentifierEnum.timeSynchronization.equals(msg104.getTypeIdentifier())  // 时钟同步
+				|| TypeIdentifierEnum.onePointTeleindication.equals(msg104.getTypeIdentifier()) //单点摇信
+				|| TypeIdentifierEnum.twoPointTeleindication.equals(msg104.getTypeIdentifier()) // 双点摇信
+				|| TypeIdentifierEnum.onePointTelecontrol.equals(msg104.getTypeIdentifier()) // 单命令遥控
+				|| TypeIdentifierEnum.twoPointTelecontrol.equals(msg104.getTypeIdentifier())); // 双命令遥控
+		msg104.setQualifiers(isQualifiers);
+		boolean isTimeScale = TypeIdentifierEnum.timeSynchronization.equals(msg104.getTypeIdentifier())  // 时钟同步
+				|| TypeIdentifierEnum.M_SP_TB_1.equals(msg104.getTypeIdentifier()) // 摇信带时标 单点
+				|| TypeIdentifierEnum.twoPointTimeTeleindication.equals(msg104.getTypeIdentifier()); //摇信带时标 双点
+		msg104.setTimeScaleExit(isTimeScale);
+	}
+	
+	/**
+	 * short 转换成两个 字节后是163  00    也就是  value[1] 中才有值
+	 * test 在D7位置 因此 值应该和  01000000 做与运算
+	 * P/N 0肯定确认  1否定确认
+	 * @return  肯定或否定确认
+	 */
+	public static boolean isYes(byte[] values) {
+		return (values[0] & 1 << 6) == 0;
+	}
+	/**
+	 *  short 转换成两个 字节后是163  00     也就是  value[1] 中才有值
+	 *  test 在D7位置 因此 值应该和 10000000 做与运算
+	 *  tets 0 为试验  1 试验
+	 * @return 是否试验
+	 */
+	public static boolean isTest(byte[] values) {
+		return (values[0] & 1 << 7) != 0;
+	}
+	
+	/**
+	 * 返回具体的原因
+	 * @param values
+	 * @return
+	 */
+	public static short getTransferReasonShort(byte[] values) {
+		byte transferReason = values[0];
+		// 前两位置零
+		transferReason = (byte) (transferReason & 0x3E);
+		return transferReason;
+	}
+	
+	
+	public static short getTransferReasonShort(boolean isTets, boolean isYes, short transferReason) {
+		int t = isTets ? 1 : 0;
+		int y = isYes ? 0 : 1;
+		int transferReasonInt = t << 7 | transferReason;
+		transferReasonInt = y << 6 | transferReasonInt;
+		
+		short transferReasonShort = (short) (transferReasonInt << 8);
+		return transferReasonShort;
+	}
+	
+	
+	/**
+	 *  返回终端地址对应的byte数组 其中低位在前
+	 * @param terminalAddress
+	 * @return
+	 */
+	public static byte[] getTerminalAddressByte(short terminalAddress) {
+		byte[] b = new byte[2];
+		b[1] = (byte) ((terminalAddress >> 8) & 0xff);
+		b[0] = (byte) (terminalAddress & 0xff);
+		return b;
+	}
+	
+	
+	/**
+	 *	返回回终端地址 其中低位在前
+	 * @param terminalAddress
+	 * @return
+	 */
+	public static short getTerminalAddressShort(byte[] terminalAddress) {
+		 short value = 0;
+		 value += (terminalAddress[0] & 0xFF);
+		 value += (terminalAddress[1] & 0xFF) << 8;
+		 return value;
+	}
+
+
+}

+ 38 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/MessageInfo.java

@@ -0,0 +1,38 @@
+package com.gyee.edge.gddlly.iec104;
+
+import com.gyee.edge.gddlly.iec104.protocol.QualifiersEnum;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 报文中 的消息部分
+ */
+@Data
+public class MessageInfo {
+	/**
+	 * 消息地址 字节
+	 */
+	private int messageAddress;
+	
+	/**
+	 * 信息元素集合 1 2 4 个字节
+	 */
+	private byte[] messageInfos;
+
+	/**
+	 * 限定词
+	 */
+	private QualifiersEnum qualifiers;
+	/**
+	 * 
+	 * 时标
+	 */
+	private  Date timeScale;
+
+	/**
+	 * 消息详情
+	 */
+	private int messageInfoLength;
+
+}

+ 31 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/SessionState.java

@@ -0,0 +1,31 @@
+package com.gyee.edge.gddlly.iec104;
+
+
+import com.gyee.edge.gddlly.exception.UnknownTransferReasonException;
+
+/**
+ * 客户端会话状态
+ */
+public enum SessionState {
+    NEW(0,"新建"),
+    ACTIVE(1, "TCP连接"),
+    START104(2, "开始iec104会话"),
+    CALL_ALL(3, "总召唤"),
+    CALL_ALL_END(4, "总召唤结束"),
+    STOP104(5, "iec104会话结束"),
+    INACTIVE(6, "TCP连接断开");
+    private int code;
+    private String describe;
+
+    SessionState(int code, String describe) {
+        this.code = code;
+        this.describe = describe;
+    }
+
+    public static String getDescribe(int code) throws Exception {
+        for (SessionState value : SessionState.values()) {
+            if (value.code == code) return value.describe;
+        }
+        throw  new Exception();
+    }
+}

+ 30 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/builder/CachedThreadPool.java

@@ -0,0 +1,30 @@
+package com.gyee.edge.gddlly.iec104.builder;
+
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * 线程池
+ */
+public final class CachedThreadPool {
+
+    private  static CachedThreadPool cachedThreadPool  = new CachedThreadPool();
+
+    private ExecutorService executorService;
+
+
+    private CachedThreadPool() {
+        executorService = Executors.newCachedThreadPool();
+    }
+
+
+    public static CachedThreadPool getCachedThreadPool() {
+        return  cachedThreadPool;
+    }
+
+    public void execute(Runnable runnable) {
+        executorService.execute(runnable);
+    }
+
+}

+ 121 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/builder/ControlManageUtil.java

@@ -0,0 +1,121 @@
+package com.gyee.edge.gddlly.iec104.builder;
+
+
+import com.gyee.edge.gddlly.iec104.Iec104Util;
+import com.gyee.edge.gddlly.iec104.Iec104Message;
+import com.gyee.edge.gddlly.iec104.protocol.Iec104Constant;
+import io.netty.channel.ChannelHandlerContext;
+
+/**
+ * 控制域的管理工具
+ */
+public class ControlManageUtil {
+
+	/**
+	 *  发送序号
+	 */
+	private Short send;
+
+	/**
+	 * 接收序号
+	 */
+	private Short accept;
+
+	/**
+	 * 接收到帧的数量
+	 */
+	private Short frameAmount;
+
+	/**
+	 *  发送S帧的锁
+	 */
+	private Boolean sendSframeLock;
+
+	/**
+	 * 接收到帧的数量最大阀值
+	 */
+
+	private short frameAmountMax;
+
+	/**
+	 * 发送消息句柄
+	 */
+	private ChannelHandlerContext ctx;
+	
+
+	
+	public ControlManageUtil(ChannelHandlerContext ctx) {
+		send = 0;
+		accept = 0;
+		frameAmount = 0;
+		sendSframeLock = true;
+		frameAmountMax = 1;
+		this.ctx = ctx;
+	}
+
+	/**
+	 * 启动S发送S确认帧 的任务
+	 */
+	public void startSendFrameTask() {
+		Runnable runnable = () -> {
+			while (true) {
+				try {
+					synchronized (sendSframeLock) {
+						if (frameAmount >= frameAmountMax) {
+							// 查过最大帧 的数量就要发送一个确认帧出去
+							byte[] control = Iec104Util.getScontrol(accept);
+							Iec104Message ruleDetail104 = new Iec104Message(control);
+							ctx.channel().writeAndFlush(Encoder104.encoder(ruleDetail104));
+							frameAmount = 0;
+						}
+						sendSframeLock.wait();
+					}
+				} catch (Exception e) {
+					e.printStackTrace();
+				}
+			}
+		};
+		CachedThreadPool.getCachedThreadPool().execute(runnable);
+	}
+	
+	
+	/**
+	 * 返回当前的发送序号
+	 */
+	public short getSend() {
+		synchronized (send) {
+			short sendRule = this.send;
+			this.send++;
+			if (send > Iec104Constant.SEND_MAX) {
+				send = Iec104Constant.SEND_MIN;
+			}
+			return sendRule;
+		}
+	}
+	public short getAccept() {
+		return accept;
+	} 
+	
+	/**
+	 * 
+	* @Title: setAccept
+	* @Description: 设置接收序号
+	* @param lastAccept
+	 */
+	public void setAccept(short lastAccept) {
+		synchronized (sendSframeLock) {
+			this.accept = lastAccept;
+			frameAmount++;
+			if (frameAmount >= frameAmountMax) {
+				this.accept = lastAccept;
+				sendSframeLock.notifyAll();
+			}
+		}
+	}
+
+
+	public ControlManageUtil setFrameAmountMax(short frameAmountMax) {
+		this.frameAmount = frameAmountMax;
+		return  this;
+	}
+}

+ 247 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/builder/Decoder104.java

@@ -0,0 +1,247 @@
+package com.gyee.edge.gddlly.iec104.builder;
+
+
+
+import com.gyee.edge.gddlly.iec104.Iec104Util;
+import com.gyee.edge.gddlly.iec104.Iec104Message;
+import com.gyee.edge.gddlly.iec104.MessageInfo;
+import com.gyee.edge.gddlly.iec104.protocol.QualifiersEnum;
+import com.gyee.edge.gddlly.iec104.protocol.TypeIdentifierEnum;
+import com.gyee.edge.gddlly.utils.ByteUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 
+* @ClassName: Decoder104
+* @Description: 104 协议转码工具
+ */
+public class Decoder104 {
+
+	/**
+	 * 将bytes 转换成指定的数据结构
+	 * @param bytes
+	 * @return
+	 */
+	public static Iec104Message decode(byte[] bytes) {
+		Iec104Message msg104 = new Iec104Message();
+		int index = 0;
+		msg104.setStart(bytes[index++]);
+		msg104.setApduLength(bytes[index++] & 0xFF);
+		byte[] control = ByteUtil.getByte(bytes, index, 4);
+		msg104.setControl(control);
+		msg104.setAcceptSeq(Iec104Util.getAccept(control));
+		msg104.setSendSeq(Iec104Util.getSend(control));
+		index += 4;
+		if (msg104.getApduLength() <= 4) {
+			msg104.setMessages(new ArrayList<>());
+			// 如果只有APCI 就此返回
+			return msg104;
+		} 
+		// 下面是返回ASDU的结构
+		msg104.setTypeIdentifier(TypeIdentifierEnum.getTypeIdentifierEnum(bytes[index++]));
+		// 添加可变结构限定词
+		Iec104Util.setVSQ(msg104, bytes[index++]);
+		msg104.setTransferReason(ByteUtil.byteToShortLittle(ByteUtil.getByte(bytes, index, 2)));
+		index += 2;
+		// 
+		msg104.setTerminalAddress(Iec104Util.getTerminalAddressShort(ByteUtil.getByte(bytes, index, 2)));
+		index += 2;
+		Iec104Util.setMeaageAttribute(msg104);
+		setMessage(msg104, bytes, index);
+		return msg104;
+	}
+
+	/**
+	 * 
+	* @Title: setMessage  
+	* @Description: 对消息进行编码  
+	* @param @param msg104
+	* @param @param bytes
+	* @param @param index 
+	* @return void   
+	* @throws
+	 */
+	public static void setMessage(Iec104Message msg104, byte[] bytes, int index) {
+		int mesageIndex = index;
+		if (msg104.isContinuous()) {
+			setContinuoustMessage(msg104, bytes, mesageIndex);
+		}  else {
+			setNoContinuoustMessage(msg104, bytes, mesageIndex);
+		}
+	} 
+	
+	
+	/**
+	 * 
+	* @Title: setContinuoustMessage  
+	* @Description: 设置连续地址的消息 
+	* @param msg104
+	* @param bytes
+	* @param index 
+	* @return void   
+	* @throws
+	 */
+	public static void setContinuoustMessage(Iec104Message msg104, byte[] bytes, int index) {
+		List<MessageInfo> messages = new ArrayList<>();
+		int mesageIndex = index;
+		// 连续的 前三个字节是地址 
+		// 如果是地址联系的只需要设置一个初始地址就可以了
+		// TODO 此处不处理地址
+		int messageAddress = Iec104Util.messageAddressToInt(ByteUtil.getByte(bytes, mesageIndex, 3));
+		msg104.setMessageAddress(messageAddress);
+		mesageIndex += 3;
+		if (msg104.isMessage()) {
+			// 获取每个消息的长度
+			int messageLength = getMessageLength(msg104);
+			int messageSize = 0;
+			while (messageSize < msg104.getInfosize()) {
+				MessageInfo messageObj = new MessageInfo();
+				messageObj.setMessageAddress(messageAddress);
+				byte[] messageInfos = ByteUtil.getByte(bytes, mesageIndex, messageLength);
+				mesageIndex += messageLength;
+				messageObj.setMessageInfos(messageInfos);
+				//对数据的值进行解析
+				setMessageValue(msg104, messageObj);
+				if (msg104.isQualifiers()) {
+					// 判断是否有限定词
+					messageObj.setQualifiers(QualifiersEnum.getQualifiersEnum(msg104.getTypeIdentifier(), bytes[mesageIndex++]));
+				}
+				if (msg104.isTimeScaleExit()) {
+					msg104.setTimeScale(ByteUtil.byte2Hdate(ByteUtil.getByte(bytes, mesageIndex, 7)));
+				}
+				messageSize++;
+				messageAddress++;
+				messages.add(messageObj);
+			}
+		} 
+		msg104.setMessages(messages);
+	} 
+	
+	
+	/**
+	 * 
+	* @Title: setNoContinuoustMessage  
+	* @Description: 设置不连续地址的消息 
+	* @param msg104
+	* @param bytes
+	* @param index 
+	* @return void   
+	* @throws
+	 */
+	public static void setNoContinuoustMessage(Iec104Message msg104, byte[] bytes, int index) {
+		List<MessageInfo> messages = new ArrayList<>();
+		int mesageIndex = index;
+		// 获取每个消息的长度
+		int messageLength = getMessageLength(msg104);
+		int messageSize = 0;
+		while (messageSize < msg104.getInfosize()) {
+			MessageInfo messageObj = new MessageInfo();
+			// 消息地址
+			messageObj.setMessageAddress(Iec104Util.messageAddressToInt(ByteUtil.getByte(bytes, mesageIndex, 3)));
+			mesageIndex += 3;
+			
+			if (msg104.isMessage()) {
+				// 消息集合
+				byte[] messageInfos = ByteUtil.getByte(bytes, mesageIndex, messageLength);
+				mesageIndex += messageLength;
+				messageObj.setMessageInfos(messageInfos);
+				//对数据的值进行解析
+				setMessageValue(msg104, messageObj);
+			} else {
+				messageObj.setMessageInfos(new byte[] {});
+			}
+			if (msg104.isQualifiers()) {
+				// 判断是否有限定词
+				messageObj.setQualifiers(QualifiersEnum.getQualifiersEnum(msg104.getTypeIdentifier(), bytes[mesageIndex++]));
+			} 
+			if (msg104.isTimeScaleExit()) {
+				messageObj.setTimeScale(ByteUtil.byte2Hdate(ByteUtil.getByte(bytes, mesageIndex, 7)));
+			} 
+			messageSize++;
+			messages.add(messageObj);
+		}
+		msg104.setMessages(messages);
+	}
+
+	/**
+	 * 根据类型对数据的值进行解析
+	 * */
+	private static void setMessageValue(Iec104Message msg104, MessageInfo messageObj) {
+		switch (msg104.getTypeIdentifier().getValue()) {
+			case  0x09:
+				// 遥测 测量值 归一化值 遥测
+				break;
+			case  0x0B:
+				// 遥测 测量值  标度化值 遥测
+				break;
+			case  0x66:
+				// 读单个参数
+				break;
+			case (byte) 0x84:
+				//  读多个参数
+				break;
+			case  0x30:
+				// 预置单个参数命令
+				break;
+			case (byte) 0x88:
+				// 预置多个个参数
+				break;
+			default :
+		}
+	}
+
+
+	/**
+	 * 根据类型标识返回消息长度
+	 */
+	private static int getMessageLength(Iec104Message msg104) {
+		int messageLength = 0;
+		switch (msg104.getTypeIdentifier().getValue()) {
+			case  0x01:
+				// 单点遥信
+				messageLength = 1;
+				break;
+			case  0x09:
+				// 遥测 测量值 归一化值 遥测
+				messageLength = 2;
+				break;
+			case  0x0B:
+				// 遥测 测量值  标度化值 遥测
+				messageLength =  2;
+				break;
+			case  0x0D:
+				// 测量值 短浮点数 遥测  
+				messageLength = 4;
+				break;
+			case  0x0F:
+				// 测量值 短浮点数 遥脉
+				messageLength = 4;
+				break;
+			case  0x66:
+				// 读单个参数
+				messageLength = 4;
+				break;
+			case (byte) 0x84:
+				//  读多个参数
+				messageLength = 4;
+				break;
+			case  0x30:
+				// 预置单个参数命令
+				messageLength = 4;
+				break;
+			case (byte) 0x88:
+				// 预置多个个参数  
+				messageLength = 4;
+				break;
+			case (byte) 0xCE:
+				// 预置多个个参数
+				messageLength = 4;
+				break;
+			default :
+				messageLength = 0;
+		}
+		return messageLength;
+	}
+}

+ 90 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/builder/Encoder104.java

@@ -0,0 +1,90 @@
+package com.gyee.edge.gddlly.iec104.builder;
+
+
+import com.gyee.edge.gddlly.config.Point;
+import com.gyee.edge.gddlly.iec104.Iec104Util;
+import com.gyee.edge.gddlly.iec104.Iec104Message;
+import com.gyee.edge.gddlly.iec104.MessageInfo;
+import com.gyee.edge.gddlly.utils.ByteUtil;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+@Slf4j
+public class Encoder104 {
+	
+	public static byte[] encoder(Iec104Message msg104) throws IOException {
+		Iec104Util.setMeaageAttribute(msg104);
+		ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+		bytes.write(msg104.getStart());
+		byte[]  apduBytes = getApduBytes(msg104);
+		int messageLen =  apduBytes.length;
+		msg104.setApduLength(messageLen);
+		bytes.write((byte) messageLen);
+		bytes.write(apduBytes);
+
+		return bytes.toByteArray();
+	}
+	
+	private static byte[] getApduBytes(Iec104Message msg104) throws IOException {
+		ByteArrayOutputStream bOutput = new ByteArrayOutputStream();
+		//  控制域
+		bOutput.write(Iec104Util.getIcontrol(msg104.getAcceptSeq(),msg104.getSendSeq()));
+		if (msg104.getTypeIdentifier() == null) {
+			// U帧或者S帧
+			return bOutput.toByteArray();
+		} 
+		// 类型标识
+		bOutput.write((byte) msg104.getTypeIdentifier().getValue());
+		// 可变结构限定词
+		//bOutput.write(Iec104Util.getChangedQualifiers(msg104));
+		bOutput.write(Iec104Util.getVSQ(msg104.isContinuous(), msg104.getInfosize()));
+		// 传输原因
+		bOutput.write(ByteUtil.shortToByteLittle(msg104.getTransferReason()));
+		// 终端地址
+		bOutput.write((Iec104Util.getTerminalAddressByte(msg104.getTerminalAddress())));
+//		如果是是连续的则数据地址 只需要在开头写以后的数据单元就不需要再写了
+		if (msg104.isContinuous()) {
+			//写首测点地址,取三个字节
+			bOutput.write(Iec104Util.intToMessageAddress(msg104.getMessageAddress()));
+			//遍历测点列表,
+			if (msg104.getAiPointList() != null && msg104.getAiPointList().size() > 0) {
+				//按照M_ME_TF_1序列化数据
+				for(Point point: msg104.getAiPointList()) {
+					bOutput.write(ByteUtil.float2Bytes((float) point.getValue()));
+					bOutput.write((byte)0);	//品质描述针对遥测值,放在浮点数之后
+					bOutput.write(ByteUtil.date2Hbyte(point.getLastUpdateTime()));
+				}
+			} else if (msg104.getDiPointList() != null && msg104.getDiPointList().size() > 0) {
+				//按照M_SP_TB_1序列化数据
+				for(Point point: msg104.getDiPointList()) {
+					bOutput.write(point.getValue() == 0 ? (byte)0 : (byte)1);
+					bOutput.write(ByteUtil.date2Hbyte(point.getLastUpdateTime()));
+				}
+			}
+
+		} else {
+			if (msg104.getAiPointList() != null && msg104.getAiPointList().size() > 0) {
+				//按照M_ME_TF_1序列化数据,带地址
+				for(Point point: msg104.getAiPointList()) {
+					bOutput.write(Iec104Util.intToMessageAddress(point.getPointAddr()));
+					bOutput.write(ByteUtil.float2Bytes((float) point.getValue()));
+					bOutput.write((byte)0);
+					bOutput.write(ByteUtil.date2Hbyte(point.getLastUpdateTime()));
+				}
+			} else if (msg104.getDiPointList() != null && msg104.getDiPointList().size() > 0) {
+				//按照M_SP_TB_1序列化数据
+				for(Point point: msg104.getDiPointList()) {
+					bOutput.write(Iec104Util.intToMessageAddress(point.getPointAddr()));
+					bOutput.write(point.getValue() == 0 ? (byte)0 : (byte)1);
+					bOutput.write(ByteUtil.date2Hbyte(point.getLastUpdateTime()));
+					//bOutput.write((byte)0);
+				}
+			}
+		}
+
+		return bOutput.toByteArray();
+	}
+
+}

+ 57 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/builder/Iec104ThreadLocal.java

@@ -0,0 +1,57 @@
+package com.gyee.edge.gddlly.iec104.builder;
+
+
+import com.gyee.edge.gddlly.config.Iec104Config;
+
+/**
+ * @ClassName:  Iec104ThreadLocal
+ * @Description:  线程变量管理
+ */
+public class Iec104ThreadLocal {
+	
+	/**
+	 * 定时发送启动链指令 测试链指令 
+	 */
+	private static ThreadLocal<ScheduledTaskPool> scheduledTaskPoolThreadLocal = new ThreadLocal<>();
+	
+	/**
+	 * 返回 发送序号 和接收序号  定时发送S帧
+	 */
+	private static ThreadLocal<ControlManageUtil> controlPoolThreadLocal = new ThreadLocal<>();
+
+	/**
+	 * 存放相关配置文件
+	 */
+	private static ThreadLocal<Iec104Config> iec104ConfigThreadLocal = new ThreadLocal<>();
+
+
+
+
+	public static void setScheduledTaskPool(ScheduledTaskPool scheduledTaskPool) {
+		scheduledTaskPoolThreadLocal.set(scheduledTaskPool);
+	}
+
+	public  static ScheduledTaskPool getScheduledTaskPool() {
+		return scheduledTaskPoolThreadLocal.get();
+	}
+
+	public static void setControlPool(ControlManageUtil controlPool) {
+		controlPoolThreadLocal.set(controlPool);
+	}
+
+	public  static ControlManageUtil getControlPool() {
+		return controlPoolThreadLocal.get();
+	}
+
+
+	public  static Iec104Config getIec104Conig() {
+		return iec104ConfigThreadLocal.get();
+	}
+
+
+	public  static void setIec104Config(Iec104Config iec104Confiig) {
+		iec104ConfigThreadLocal.set(iec104Confiig);
+	}
+
+
+}

+ 169 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/builder/ScheduledTaskPool.java

@@ -0,0 +1,169 @@
+package com.gyee.edge.gddlly.iec104.builder;
+
+import com.gyee.edge.gddlly.iec104.protocol.BasicInstruction104;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ *  这是一个定时任务管理池
+ * @ClassName:  ScheduledTaskPool
+ */
+@Slf4j
+public class ScheduledTaskPool {
+
+	private static final Logger LOGGER = LoggerFactory.getLogger(ChannelInboundHandlerAdapter.class);
+
+	/**
+	 * 发送指令
+	 */
+	private ChannelHandlerContext ctx;
+	/**
+	 * 发送启动指令的线程
+	 */
+	private Thread sendStatrThread;
+	/**
+	 * 循环发送启动指令线程的状态
+	 */
+	private Boolean sendStatrStatus = false;
+	/**
+	 * 发送测试指令的线程 类似心跳
+	 */
+	private Thread sendTestThread;
+	/**
+	 * 发送测试指令线程的状态
+	 */
+	private Boolean sendTestStatus = false;
+	/**
+	 * 发送总召唤指令状态
+	 */
+	private Boolean senGeneralCallStatus = false;
+	/**
+	 * 启动指令收到确认后固定时间内发送总召唤指令
+	 */
+	private Thread generalCallTThread;
+
+	public ScheduledTaskPool(ChannelHandlerContext ctx) {
+		this.ctx = ctx;
+	}
+
+	/**
+	 *
+	* @Title: sendStatrFrame
+	* @Description: 发送启动帧
+	 */
+	public void sendStatrFrame() {
+		synchronized (sendStatrStatus) {
+			if (sendStatrThread != null) {
+				sendStatrStatus = true;
+				sendStatrThread.start();
+			} else if (sendStatrThread == null) {
+				sendStatrStatus = true;
+				sendStatrThread = new Thread(new Runnable() {
+					@Override
+					public void run() {
+						while (sendStatrStatus) {
+							try {
+								ctx.channel().writeAndFlush(BasicInstruction104.STARTDT);
+								LOGGER.info("发送启动链路指令");
+								Thread.sleep(5000);
+							} catch (Exception e) {
+								e.printStackTrace();
+							}
+						}
+					}
+				});
+				sendStatrThread.start();
+			}
+		}
+	}
+
+	/**
+	 *
+	* @Title: stopSendStatrFrame
+	* @Description: 停止发送确认帧
+	 */
+	public void stopSendStatrFrame() {
+		if (sendStatrThread != null) {
+			sendStatrStatus = false;
+		}
+	}
+
+	
+	/**
+	 * 
+	* @Title: sendTestFrame
+	* @Description: 发送测试帧
+	 */
+	public void sendTestFrame() {
+		synchronized (sendTestStatus) {
+			if (sendTestThread != null && sendTestThread.getState() == Thread.State.TERMINATED) {
+				sendTestStatus = true;
+				sendTestThread.start();
+			} else if (sendTestThread == null) {
+				sendTestStatus = true;
+				sendTestThread = new Thread(new Runnable() {
+					@Override
+					public void run() {
+						while (sendTestStatus) {
+							try {
+								LOGGER.info("发送测试链路指令");
+								ctx.channel().writeAndFlush(BasicInstruction104.TESTFR);
+								Thread.sleep(5000);
+							} catch (Exception e) {
+								e.printStackTrace();
+							}
+						}
+					}
+				});
+				sendTestThread.start();
+			}
+		}
+	}
+	
+	/**
+	 *
+	* @Title: stopSendTestFrame
+	* @Description: 停止发送测试帧
+	 */
+	public void stopSendTestFrame() {
+		if (sendTestThread != null) {
+			sendTestStatus = false;
+		}
+	}
+
+	/**
+	 *
+	* @Title: sendGeneralCall
+	* @Description: 发送总召唤
+	 */
+	public void sendGeneralCall() {
+		synchronized (senGeneralCallStatus) {
+			if (generalCallTThread != null && generalCallTThread.getState() == Thread.State.TERMINATED) {
+				senGeneralCallStatus = true;
+				generalCallTThread.start();
+			} else if (generalCallTThread == null) {
+				senGeneralCallStatus = true;
+				generalCallTThread = new Thread(new Runnable() {
+					@Override
+					public void run() {
+						while (sendTestStatus) {
+							try {
+								LOGGER.info("发送总召唤指令");
+								ctx.channel().writeAndFlush(BasicInstruction104.getGeneralCallRuleDetail104());
+								Thread.sleep(1000 * 60);
+							} catch (Exception e) {
+								e.printStackTrace();
+							}
+						}
+					}
+				});
+				generalCallTThread.start();
+			}
+		}
+	}
+
+}

+ 49 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/handler/Check104Handler.java

@@ -0,0 +1,49 @@
+package com.gyee.edge.gddlly.iec104.handler;
+
+
+import com.gyee.edge.gddlly.iec104.protocol.Iec104Constant;
+import com.gyee.edge.gddlly.utils.ByteUtil;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufInputStream;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.util.ReferenceCountUtil;
+import lombok.extern.slf4j.Slf4j;
+
+
+import java.nio.ByteBuffer;
+
+/**
+ * 
+* @ClassName: Check104Handler  
+* @Description: 检查104报文 
+* @author sun 
+ */
+@Slf4j
+public class Check104Handler extends ChannelInboundHandlerAdapter {
+	
+
+	/**
+	 * 拦截系统消息
+	 */
+	@Override
+	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+
+		ByteBuf result = (ByteBuf) msg;
+		ByteBuffer r = result.nioBuffer();
+		ByteBufInputStream is = new ByteBufInputStream(result);
+
+
+
+		byte[] bytes = new byte[result.readableBytes()];
+		result.readBytes(bytes);
+		log.info("接收到的报文: " + ByteUtil.byteArrayToHexString(bytes));
+		if (bytes.length < Iec104Constant.APCI_LENGTH || bytes[0] != Iec104Constant.HEAD_DATA) {
+			log.error("报文无效");
+			ReferenceCountUtil.release(result);
+		} else {
+			result.writeBytes(bytes);
+			ctx.fireChannelRead(msg);
+		}
+	}
+}

+ 59 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/handler/EchoServerHandler.java

@@ -0,0 +1,59 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package com.gyee.edge.gddlly.iec104.handler;
+
+import com.gyee.edge.gddlly.config.Point;
+import com.gyee.edge.gddlly.config.PointService;
+import com.gyee.edge.gddlly.utils.SpringContextUtil;
+import io.netty.channel.ChannelHandler.Sharable;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Handler implementation for the echo server.
+ */
+@Sharable
+public class EchoServerHandler extends ChannelInboundHandlerAdapter {
+
+
+    private PointService pointService = SpringContextUtil.getBean(PointService.class);
+
+    @Override
+    public void channelRead(ChannelHandlerContext ctx, Object msg) {
+//        Map<Integer, Map<String, Point>> am = pointService.getAiMap();
+//        Map<Integer, Map<String, Point>> dm = pointService.getDiMap();
+
+        //ctx.channel().eventLoop().schedule(new Thread(),60, TimeUnit.DAYS).cancel()
+        ctx.write(msg);
+    }
+
+    @Override
+    public void channelReadComplete(ChannelHandlerContext ctx) {
+        ctx.flush();
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+        // Close the connection when an exception is raised.
+        cause.printStackTrace();
+        //ctx.close(); //关闭连接
+    }
+}

+ 51 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/handler/FrameDecoder.java

@@ -0,0 +1,51 @@
+package com.gyee.edge.gddlly.iec104.handler;
+
+import com.gyee.edge.gddlly.iec104.protocol.Iec104Constant;
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+
+import java.util.List;
+
+/**
+ * 
+* @ClassName: Unpack104Util  
+* @Description: 解决TCP 拆包和沾包的问题 
+* @author sun 
+ */
+public class FrameDecoder extends ByteToMessageDecoder {
+
+	@Override
+    protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {
+
+	    //System.out.println("Enter Iec104Decoder ......");
+        // 记录包头开始的index
+        int beginReader = 0;
+        int newDataLength = 0;
+        while (true) {  
+            // 获取包头开始的index  
+            beginReader = buffer.readerIndex();  
+            // 记录一个标志用于重置
+            buffer.markReaderIndex();  
+            // 读到了协议的开始标志,结束while循环  
+            if (buffer.readByte() == Iec104Constant.HEAD_DATA) {
+            	// 标记当前包为新包
+            	//读取包长度
+            	byte newDataLengthByte = buffer.readByte();
+                newDataLength = newDataLengthByte & 0xFF;
+
+            	while (buffer.readableBytes() < newDataLength) {
+            		break;
+            	}
+            	break;
+            }
+            continue;
+        }
+        newDataLength = newDataLength +  2;
+        //恢复指针
+        buffer.readerIndex(beginReader);
+        ByteBuf data = buffer.readBytes(newDataLength);
+        out.add(data); 
+	}
+
+}

+ 34 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/handler/Iec104Decoder.java

@@ -0,0 +1,34 @@
+package com.gyee.edge.gddlly.iec104.handler;
+
+
+import com.gyee.edge.gddlly.iec104.Iec104Message;
+import com.gyee.edge.gddlly.iec104.builder.Decoder104;
+import com.gyee.edge.gddlly.iec104.protocol.Analysis;
+import com.gyee.edge.gddlly.utils.ByteUtil;
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.List;
+
+
+/**
+ * 解码器
+ */
+@Slf4j
+public class Iec104Decoder extends ByteToMessageDecoder {
+	
+	@Override
+	protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
+		byte[] data = new byte[in.readableBytes()];
+		in.readBytes(data);
+//		log.info("收到报文:" + ByteUtil.byteArrayToHexString(data));
+//		String str= Analysis.analysis(ByteUtil.byteArrayToHexString(data));
+//		System.out.println(str);
+//		short send = Iec104Util.getSend(ByteUtil.getByte(data, 2, 4));
+//		Iec104ThreadLocal.getControlPool().setAccept(send);
+		Iec104Message msg104 = Decoder104.decode(data);
+		out.add(msg104);
+	}
+}

+ 52 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/handler/Iec104Encoder.java

@@ -0,0 +1,52 @@
+package com.gyee.edge.gddlly.iec104.handler;
+
+
+import com.gyee.edge.gddlly.iec104.Iec104Util;
+import com.gyee.edge.gddlly.iec104.Iec104Message;
+import com.gyee.edge.gddlly.iec104.builder.Encoder104;
+import com.gyee.edge.gddlly.iec104.builder.Iec104ThreadLocal;
+import com.gyee.edge.gddlly.utils.ByteUtil;
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.MessageToByteEncoder;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 编码器
+ * @author sun
+ *
+ */
+@Slf4j
+public class Iec104Encoder extends MessageToByteEncoder<Iec104Message> {
+
+	
+	@Override
+	protected void encode(ChannelHandlerContext ctx, Iec104Message msg, ByteBuf out) throws Exception {
+		try {
+			byte[] bytes = Encoder104.encoder(msg);
+			out.writeBytes(bytes);
+		} catch (Exception e) {
+//			log.error(e.getMessage());
+//			e.printStackTrace();
+			throw e;
+		}
+
+//		if(bytes.length > 6) {
+//			short accept = Iec104ThreadLocal.getControlPool().getAccept();
+//			short send = Iec104ThreadLocal.getControlPool().getSend();
+//			short terminalAddress = Iec104ThreadLocal.getIec104Conig().getTerminnalAddress();
+//			// 替换终端地址 发送序号和接收序号
+//			byte[] terminalAddressBytes = Iec104Util.getTerminalAddressByte(terminalAddress);
+//			byte[] icontrol = Iec104Util.getIcontrol(accept, send);
+//			for (int i = 0; i < icontrol.length; i++) {
+//				bytes[i + 2] = icontrol[i];
+//			}
+//			bytes[10] = terminalAddressBytes[0];
+//			bytes[11] = terminalAddressBytes[1];
+//			log.info(ByteUtil.byteArrayToHexString(bytes));
+//		}
+
+
+	}
+
+}

+ 70 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/handler/Iec104ServerHandler.java

@@ -0,0 +1,70 @@
+package com.gyee.edge.gddlly.iec104.handler;
+
+import com.gyee.edge.gddlly.iec104.Iec104Message;
+import com.gyee.edge.gddlly.iec104.Iec104Session;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.util.ReferenceCountUtil;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Slf4j
+public class Iec104ServerHandler extends SimpleChannelInboundHandler<Iec104Message> {
+
+	private Map<String, Iec104Session> sessionMap = new ConcurrentHashMap<>();
+
+	
+	@Override
+	public void channelActive(ChannelHandlerContext ctx) throws Exception {
+		// 注册Session
+		Iec104Session session = new Iec104Session(ctx);
+		if (sessionMap.containsKey(session.getId())) {
+			sessionMap.get(session.getId()).stop();
+		}
+
+		sessionMap.put(session.getId(), session);
+	}
+
+	@Override
+	public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+		// 清理Session
+		String sessionId = getSessionId(ctx);
+		if (sessionMap.containsKey(sessionId)) {
+			Iec104Session session = sessionMap.get(sessionId);
+			session.stop();
+			Thread.sleep(5000);
+			sessionMap.remove(sessionId);
+		}
+
+	}
+	
+	@Override
+	public void channelRead0(ChannelHandlerContext ctx, Iec104Message msg104) throws IOException {
+		log.debug("收到消息:" + msg104.getHexString());
+		String sessionId = getSessionId(ctx);
+		Iec104Session session = sessionMap.get(sessionId);
+		if (session == null)
+			ReferenceCountUtil.release(msg104);
+
+		session.processMessage(msg104);
+
+	}
+
+	@Override
+	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+		cause.printStackTrace();
+		//todo: 异常处理
+		//ctx.close();
+	}
+
+	private String getSessionId(ChannelHandlerContext ctx) {
+		InetSocketAddress ipSocket = (InetSocketAddress)ctx.channel().remoteAddress();
+		String clientIp = ipSocket.getAddress().getHostAddress();
+		int clientPort = ipSocket.getPort();
+		return clientIp + ":" + clientPort;
+	}
+}

+ 219 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/protocol/ASDU.java

@@ -0,0 +1,219 @@
+package com.gyee.edge.gddlly.iec104.protocol;
+
+
+import com.gyee.edge.gddlly.exception.UnknownTransferReasonException;
+import com.gyee.edge.gddlly.exception.UnknownTypeIdentifierException;
+import com.gyee.edge.gddlly.utils.Util;
+
+public class ASDU {
+
+
+    public static String ASDU_analysis(int[] asdu) throws UnknownTypeIdentifierException, UnknownTransferReasonException {
+        StringBuilder builder = new StringBuilder();
+        builder.append("类属性标识符[7 byte]:").append(TypeIdentifier.getDescribe(asdu[0])).append("\n");
+        builder.append("可变结构限定词[8 byte]:").append(VariTureDete(asdu[1])).append("\n");
+        builder.append("传送原因[9 byte - 10 byte]:").append(CotAnalysis(asdu[2], asdu[3])).append("\n");
+
+        builder.append("应用服务数据单元公共地址[11 byte - 12 byte]:").append(Util.address(asdu[4], asdu[5]));
+        int info[] = new int[asdu.length - 6];
+        // 将[信息元素+限定词+(时标)]装入数组info
+        for (int i = 0; i < info.length; i++) {
+            info[i] = asdu[6 + i];
+        }
+        builder.append(Infoanalysis(info, asdu[0],
+                ((asdu[1] & 0x80) >> 7), (asdu[1] & 0x7f)));
+        return builder.toString();
+    }
+
+    /**
+     * @param info 信息地址+信息元素+限定词+(时标)
+     * @param i    类型标识
+     * @param j    SQ
+     * @param k    信息体个数
+     * @return
+     * @throws Exception
+     */
+    private static String Infoanalysis(int[] info, int i, int j, int k) {
+        String s = "";
+        switch (i) {
+            case 1:
+                s += new Telemetry104().NoTime_Point(info, k, i, j);
+                break;
+            case 3:
+                s += new Telemetry104().NoTime_Point(info, k, i, j);
+                break;
+            case 9:// 测量值,归一化值(遥测)*
+                s += new Telesignalling104().normalization(info, k, i, j);
+                break;
+            case 11:// 测量值,标度化值(遥测)
+                s += new Telesignalling104().standardization(info, k, i, j);
+                break;
+            case 13:// 测量值,短浮点数(遥测)
+                s += new Telesignalling104().short_float(info, k, i, j);
+                break;
+            case 30:
+                s += new Telemetry104().Time_Point(info, k, i, j);
+                break;
+            case 31:
+                s += new Telemetry104().Time_Point(info, k, i, j);
+                break;
+            case 45:// 单命令(遥控)
+                s = new Telecontrol104().Single_command(info, k, i);
+                break;
+            case 46:// 双命令(遥控)
+                s = new Telecontrol104().Double_command(info, k, i);
+                break;
+            case 48:
+                s += new ParamePreset104().activate_single_parmeter(info, k, i, j);
+                break;
+            case 70:
+                s += "信息对象地址:";
+                s += InfoAddress(info[0], info[1], info[2]);
+                s += "\n";
+                s += "初始化原因:" + (info[3]) + " ";
+                s += (info[3] == 0) ? "当地电源合上" : (info[3] == 1) ? "当地手动复位"
+                        : (info[3] == 2) ? "远方复位" : "使用保留";
+                break;
+            case 100:
+                s += "信息对象地址:";
+                s += InfoAddress(info[0], info[1], info[2]);
+                s += "\n";
+                if (info[3] == 20) {
+                    s += "召唤限定词QOI:20";
+                } else {
+                    s += "召唤限定词出错!当前值为0x"
+                            + ((info[3] < 10) ? "0" + Integer.toString(info[3], 16)
+                            : Integer.toString(info[3], 16))
+                            + ",正确值应为0x14!";
+                }
+
+                break;
+            case 102:
+                s += new ParamePreset104().single_parmeter(info, k, i, j);
+                break;
+            case 103:
+                s += "信息对象地址:";
+                s += InfoAddress(info[0], info[1], info[2]);
+                s += "\n";
+                int[] time = new int[7];
+                for (int l = 0; l < time.length; l++) {
+                    time[l] = info[3 + l];
+                }
+                s += TimeScale(time);
+                break;
+
+            case 105:
+                s += "信息对象地址:";
+                s += InfoAddress(info[0], info[1], info[2]);
+                s += "\n";
+                if (info[3] == 1) {
+                    s += "复位进程限定词:1";
+                } else {
+                    s += "复位进程限定词出错!当前值为0x"
+                            + ((info[3] < 10) ? "0" + Integer.toString(info[3], 16)
+                            : Integer.toString(info[3], 16))
+                            + ",正确值应为0x01!";
+                }
+                break;
+            case 132:
+                s += new ParamePreset104().multi_parmeter(info, k, i, j);
+                break;
+            case 136:
+                s += new ParamePreset104().activate_multi_parmeter(info, k, i, j);
+                break;
+            default:
+                s = "类型标识出错,无法解析信息对象!";
+                break;
+        }
+
+        return s;
+    }
+
+    /**
+     * 信息对象地址转换
+     *
+     * @param i bit0 ~bit7
+     * @param j bit8 ~bit15
+     * @param k bit16 ~bit23
+     * @return 十进制地址(十六进制地址)
+     */
+    public static String InfoAddress(int i, int j, int k) {
+
+        int add;
+        add = (k << 16) + (j << 8) + i;
+
+        return add
+                + "("
+                + ((k < 10) ? "0" + Integer.toString(k, 16) : Integer.toString(
+                k, 16))
+                + ((j < 10) ? "0" + Integer.toString(j, 16) : Integer.toString(
+                j, 16))
+                + ((i < 10) ? "0" + Integer.toString(i, 16) : Integer.toString(
+                i, 16)) + "H)" + "";
+    }
+
+    /**
+     * 解析传送原因
+     *
+     * @param i
+     * @param j
+     * @return
+     */
+    public static String CotAnalysis(int i, int j) throws UnknownTransferReasonException {
+        StringBuilder builder = new StringBuilder();
+        if ((i & 0x80) == 128) {
+            builder.append("[T(test) bit7:1   实验]");
+        } else if ((i & 0x80) == 0) {
+            builder.append("[T(test) bit7:0  未实验]");
+        }
+        if ((i & 0x40) == 64) {
+            builder.append("[P/N  bit6:1  否定确认]");
+        } else if ((i & 0x40) == 0) {
+            builder.append("[P/N  bit6:0  肯定确认]");
+        }
+        builder.append("[原因  bit5~bit0:").append(TransferReason.getDdescribe(i & 0x3F)).append("]");
+        return builder.toString();
+    }
+
+    /**
+     * 解析可变结构限定词
+     *
+     * @param b
+     * @return
+     */
+    private static String VariTureDete(int b) {
+        String vvString = "可变结构限定词:";
+        vvString += Util.toHexString(b);
+        vvString += "   ";
+        if ((b & 0x80) == 128) {
+            vvString += "SQ=1 信息元素地址顺序     ";
+        } else if ((b & 0x80) == 0) {
+            vvString += "SQ=0  信息元素地址非顺序     ";
+        }
+        vvString += "信息元素个数:";
+        vvString += b & 0x7f;
+        return vvString;
+    }
+
+    private static  String TimeScale(int b[]) {
+
+        StringBuilder result = new StringBuilder();
+
+        int year = b[6] & 0x7F;
+        int month = b[5] & 0x0F;
+        int day = b[4] & 0x1F;
+        int week = (b[4] & 0xE0) / 32;
+        int hour = b[3] & 0x1F;
+        int minute = b[2] & 0x3F;
+        int second = (b[1] << 8) + b[0];
+
+        result.append("时标CP56Time2a:20");
+        result.append(year).append("-");
+        result.append(String.format("%02d", month)).append("-");
+        result.append(String.format("%02d", day)).append(",");
+        result.append(hour).append(":").append(minute).append(":");
+        result.append(second / 1000 + "." + second % 1000).append("\n");
+
+        return result.toString();
+    }
+}

+ 125 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/protocol/Analysis.java

@@ -0,0 +1,125 @@
+package com.gyee.edge.gddlly.iec104.protocol;
+
+
+import com.gyee.edge.gddlly.exception.IllegalFormatException;
+import com.gyee.edge.gddlly.exception.LengthException;
+import com.gyee.edge.gddlly.exception.UnknownTransferReasonException;
+import com.gyee.edge.gddlly.exception.UnknownTypeIdentifierException;
+import com.gyee.edge.gddlly.utils.Util;
+
+/**
+ * 104解析
+
+ */
+public class Analysis {
+
+    public static String analysis(String message) throws IllegalFormatException, LengthException, UnknownTransferReasonException, UnknownTypeIdentifierException {
+
+        StringBuilder contentbuilder = new StringBuilder();
+
+        String mes = message.replaceAll(" ", "");
+        if ((mes.length() == 0) || (mes.length() % 2) == 1) {
+            //104 报文没有结束字符,所以最好判断一下报文串的长度是否是偶数
+            throw new IllegalFormatException();
+        }
+        // 将报文转化成int数组
+        int msgArray[] = Util.hexStringToIntArray(mes);
+        int length = msgArray.length;// 记录报文的长度
+        if (msgArray[0] == 0x68 && length >= 2) {
+            contentbuilder.append("*APCI应用规约控制信息*").append("\n");
+            contentbuilder.append("启动字符[1 byte]: 0x68 ").append("\n");
+        } else {
+            throw new IllegalFormatException();
+        }
+        if (length != msgArray[1] + 2) {
+            throw new LengthException(msgArray[1] + 2, length);
+        }
+        contentbuilder.append("应用规约数据单元(APDU)长度[2 byte]:").append(msgArray[1]).append("字节").append("\n");
+        contentbuilder.append("控制域[3 byte - 6 byte]:").append("\n").append(Control(new int[]{msgArray[2], msgArray[3], msgArray[4], msgArray[5]}));
+
+        if ((msgArray[2] & 0x03) != 3 && (msgArray[2] & 0x03) != 1) {
+            //解析ASDU
+            contentbuilder.append("*ASDU应用服务数据单元*\n");
+            int asdu[] = new int[length - 6];
+            for (int j = 0; j < length - 6; j++) {
+                asdu[j] = msgArray[6 + j];
+            }
+            contentbuilder.append(ASDU.ASDU_analysis(asdu));
+        }
+        return contentbuilder.toString();
+
+    }
+
+    /**
+     * 解析104规约的控制域
+     *
+     * @param con
+     * @return
+     */
+    private static String Control(int[] con) {
+        StringBuilder conBuilder = new StringBuilder();
+        switch (con[0] & 0x03) {
+            case 1:
+                conBuilder.append("\t(S格式控制域标志)\n");
+                conBuilder.append("\t接受序列号:").append(((con[3] << 8) + con[2]) >> 1);
+                break;
+            case 3:
+                conBuilder.append("\t(U格式控制域标志)\n");
+                if ((con[0] & 0xC0) == 128) {
+                    conBuilder.append("\t链路测试TESTFR:确认\n");
+                } else if ((con[0] & 0xC0) == 64) {
+                    conBuilder.append("\t链路测试TESTFR:命令\n");
+                }
+                if ((con[0] & 0x30) == 32) {
+                    conBuilder.append("\t断开数据传输STOPDT:确认\n");
+                } else if ((con[0] & 0x30) == 16) {
+                    conBuilder.append("\t断开数据传输STOPDT:命令\n");
+                }
+                if ((con[0] & 0x0C) == 8) {
+                    conBuilder.append("\t启动数据传输STARTDT:确认\n");
+                } else if ((con[0] & 0x0C) == 4) {
+                    conBuilder.append("\t启动数据传输STARTDT:命令\n");
+                }
+                break;
+            default:
+                conBuilder.append("\t(I格式控制域标志)\n");
+                conBuilder.append("\t发送序列号:").append(((con[1] << 8) + con[0]) >> 1).append("\n");
+                conBuilder.append("\t接受序列号:").append(((con[3] << 8) + con[2]) >> 1).append("\n");
+                break;
+        }
+        return conBuilder.toString();
+    }
+    
+    
+    public static String analysis(byte[] data) throws IllegalFormatException, LengthException, UnknownTransferReasonException, UnknownTypeIdentifierException {
+
+        StringBuilder contentbuilder = new StringBuilder();
+        // 将报文转化成int数组
+        byte[] msgArray = data;
+        int length = msgArray.length;// 记录报文的长度
+        if (msgArray[0] == 0x68 && length >= 2) {
+            contentbuilder.append("*APCI应用规约控制信息*").append("\n");
+            contentbuilder.append("启动字符[1 byte]: 0x68 ").append("\n");
+        } else {
+            throw new IllegalFormatException();
+        }
+        if (length != msgArray[1] + 2) {
+            throw new LengthException(msgArray[1] + 2, length);
+        }
+        contentbuilder.append("应用规约数据单元(APDU)长度[2 byte]:").append(msgArray[1]).append("字节").append("\n");
+        contentbuilder.append("控制域[3 byte - 6 byte]:").append("\n").append(Control(new int[]{msgArray[2], msgArray[3], msgArray[4], msgArray[5]}));
+
+        if ((msgArray[2] & 0x03) != 3 && (msgArray[2] & 0x03) != 1) {
+            //解析ASDU
+            contentbuilder.append("*ASDU应用服务数据单元*\n");
+            int asdu[] = new int[length - 6];
+            for (int j = 0; j < length - 6; j++) {
+                asdu[j] = msgArray[6 + j];
+            }
+            contentbuilder.append(ASDU.ASDU_analysis(asdu));
+        }
+        return contentbuilder.toString();
+
+    }
+
+}

+ 32 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/protocol/BalancedLinkCode.java

@@ -0,0 +1,32 @@
+package com.gyee.edge.gddlly.iec104.protocol;
+
+import com.gyee.edge.gddlly.exception.UnknownLinkCodeException;
+
+/**
+ * @Author zhangyu
+ * @create 2019/5/27 16:09
+ */
+public enum BalancedLinkCode {
+    RESET_REMOTE_LINK(0, "复位远方链路"),
+    RESET_USER_PROCESS(1, "复位用户进程"),
+    CONFIRM_LINK_TESTING(2, "发送/确认链路测试功能 "),
+    CONFIRM_USER_DATA(3, "发送/确认用户数据"),
+    NO_ANSWERED_DATA(4, "发送/无回答用户数据"),
+    REQUEST_REQUEST_LINK_STATUS(9, "请求/响应请求链路状态"),
+    RESPONSE_LINK_STATE(11, "响应:链路状态");
+
+    private int code;
+    private String describe;
+
+    BalancedLinkCode(int code, String describe) {
+        this.code = code;
+        this.describe = describe;
+    }
+
+    public static String getDescribe(int code) throws UnknownLinkCodeException {
+        for (BalancedLinkCode balancedLinkCode : BalancedLinkCode.values()) {
+            if (balancedLinkCode.code == code) return balancedLinkCode.describe;
+        }
+        throw new UnknownLinkCodeException();
+    }
+}

+ 180 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/protocol/BasicInstruction104.java

@@ -0,0 +1,180 @@
+package com.gyee.edge.gddlly.iec104.protocol;
+
+
+import com.gyee.edge.gddlly.iec104.Iec104Util;
+import com.gyee.edge.gddlly.iec104.Iec104Message;
+import com.gyee.edge.gddlly.iec104.MessageInfo;
+import com.gyee.edge.gddlly.utils.ByteUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 104 规约的基本指令封装
+* @ClassName: BasicInstruction104  
+* @Description: 返回指定的指令
+ */
+
+public class BasicInstruction104 {
+	// 68040B 00 00 00
+	/**
+	 * 初始确认指令
+	 */
+	public static final byte[] STARTDT_YES = new byte[] {0x68, 0x04, 0x0B, 0x00, 0x00, 0x00};
+	
+	/**
+	 * 链路启动指令
+	 */
+	public static final byte[] STARTDT = new byte[] {0x68, 0x04, 0x07, 0x00, 0x00, 0x00};
+	
+	
+	/**
+	 * 测试确认
+	 */
+	public static final byte[] TESTFR_YES = new byte[] {0x68, 0x04, (byte) 0x83, 0x00, 0x00, 0x00};
+	 
+	/**
+	 * 测试命令指令
+	 */
+	public static final byte[] TESTFR = new byte[] {0x68, 0x04, (byte) 0x43, 0x00, 0x00, 0x00};
+	
+	
+	/**
+	 * 停止确认
+	 */
+	public static final byte[] STOPDT_YES = new byte[] {0x68, 0x04, 0x23, 0x00, 0x00, 0x00};
+	
+	
+	
+	
+	
+	/**
+	 * 
+	* @Title: getGeneralCallRuleDetail104  
+	* @Description: 总召唤指令 
+	* @param @return
+	* @param @throws IOException 
+	* @return MessageDetail
+	* @throws
+	 */
+	public static Iec104Message getGeneralCallRuleDetail104() {
+		TypeIdentifierEnum typeIdentifierEnum = TypeIdentifierEnum.generalCall;
+		int sq = 0;
+		boolean isContinuous = sq == 0 ? false : true;
+		// 接收序号
+		short accept = 0;
+		// 发送序号
+		short send = 0;
+		byte[] control = Iec104Util.getIcontrol(accept, send);
+		// 传输原因
+		short transferReason = 6;
+		boolean isTest = false;
+		boolean isPn = true;
+		// 终端地址 实际发生的时候会被替换
+		short terminalAddress = 1;
+		// 消息地址 总召唤地址为0
+		int messageAddress = 0;
+		QualifiersEnum qualifiers = QualifiersEnum.generalCallGroupingQualifiers;
+		List<MessageInfo> messages = new ArrayList<>();
+		MessageInfo message = new MessageInfo();
+		message.setQualifiers(qualifiers);
+		message.setMessageInfos(new byte[] {});
+		messages.add(message);
+		Iec104Message ruleDetail104 = new Iec104Message(control, typeIdentifierEnum, isContinuous, isTest, isPn, transferReason,
+				terminalAddress, messageAddress, messages, null, null);
+		return ruleDetail104;
+	}
+	
+	
+	/**
+	 * 
+	* @Title: getYesGeneralCallRuleDetail104  
+	* @Description: 总召唤确认指令 
+	* @return 
+	* @return MessageDetail
+	* @throws
+	 */
+	public static Iec104Message getYesGeneralCallRuleDetail104(short accept, short send) {
+		TypeIdentifierEnum typeIdentifierEnum = TypeIdentifierEnum.generalCall; 
+		 //SQ=0 length =1
+		int sq = 0;
+		boolean isContinuous = sq == 0 ? false : true;
+		// 接收序号
+//		short accept = 0;
+//		// 发送序号
+//		short send = 0;
+		byte[] control = Iec104Util.getIcontrol(accept, send);
+		// 传输原因
+		short transferReason = 7;
+		// true:1 ; false : 0
+		boolean isTest = false;
+		// true:0 false;1
+		boolean isPN = true;
+		
+		short terminalAddress = 1;
+		// 消息地址 总召唤地址为0
+		int messageAddress = 0;
+		
+		QualifiersEnum qualifiers = QualifiersEnum.generalCallGroupingQualifiers;
+		List<MessageInfo> messages = new ArrayList<>();
+		MessageInfo message = new MessageInfo();
+		message.setQualifiers(qualifiers);
+		message.setMessageInfos(new byte[] {});
+		
+		messages.add(message);
+		Iec104Message msg = new Iec104Message(control, typeIdentifierEnum, isContinuous, isTest, isPN, transferReason,
+				terminalAddress, messageAddress, messages, null, null);
+		msg.setAcceptSeq(accept);
+		msg.setSendSeq(send);
+		return msg;
+	}
+	
+	
+	/**
+	 * 
+	* @Title: getEndGeneralCallRuleDetail104  
+	* @Description: 总召唤结束指令 
+	* @return 
+	* @return MessageDetail
+	* @throws
+	 */
+	public static Iec104Message getEndGeneralCallRuleDetail104() {
+		TypeIdentifierEnum typeIdentifierEnum = TypeIdentifierEnum.generalCall; 
+		 //SQ=0 length =1
+		int sq = 0;
+		boolean isContinuous = sq == 0 ? false : true;
+		// 接收序号
+		short accept = 1;
+		// 发送序号
+		short send = 4;
+		byte[] control = Iec104Util.getIcontrol(accept, send);
+		// 传输原因
+		short transferReason = 0x0A;
+		// true:1 ; false : 0
+		boolean isTest = false;
+		// true:0 false;1
+		boolean isPN = true;
+		
+		short terminalAddress = 1;
+		// 消息地址 总召唤地址为0
+		int messageAddress = 0;
+		// 老板限定词
+		QualifiersEnum qualifiers = QualifiersEnum.generalCallGroupingQualifiers;
+		List<MessageInfo> messages = new ArrayList<>();
+		MessageInfo message = new MessageInfo();
+		message.setQualifiers(qualifiers);
+		message.setMessageInfos(new byte[] {});
+		
+		messages.add(message);
+		Iec104Message ruleDetail104 = new Iec104Message(control, typeIdentifierEnum, isContinuous, isTest, isPN, transferReason,
+				terminalAddress, messageAddress, messages, null, null);
+		return ruleDetail104;
+	}
+
+	//U帧消息创建
+	public static Iec104Message createSysMessage(UControlEnum cmd) {
+		byte[] control = ByteUtil.intToByteArray(cmd.getValue());
+		return new Iec104Message(control);
+	}
+
+}

+ 54 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/protocol/Iec104Constant.java

@@ -0,0 +1,54 @@
+package com.gyee.edge.gddlly.iec104.protocol;
+
+/**
+ *
+ */
+public class Iec104Constant {
+	
+	/**
+	 * 开始字符
+	 */
+	public static final  byte  HEAD_DATA = 0x68;
+	
+	/**
+	 * 控制域长度
+	 */
+	public static final  byte CPNTROL_LENGTH = 0x04;
+	
+	/**
+	 * APCI 长度
+	 */
+	public static final byte APCI_LENGTH = 0x06;
+
+	/**
+	 * APCI 中 发送序号低位坐标
+	 */
+	public static final int ACCEPT_LOW_INDEX = 2;
+	
+	/**
+	 * APCI 中 发送序号高位坐标
+	 */
+	public static final int ACCEPT_HIGH_INDEX = 3;
+
+
+	/**
+	 *最大接收序号
+	 */
+	public static final Short SEND_MAX = 32767;
+
+	/**
+	 * 最小接收序号
+	 */
+	public static final Short SEND_MIN = 0;
+
+
+//	/**
+//	 *
+//	 */
+//	public static final short FRAME_SUM_MAX  = 1;
+//
+//	/**
+//	 * 终端地址
+//	 */
+//	public static final short TERMINNAL_ADDRESS = 1;
+}

+ 151 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/protocol/ParamePreset104.java

@@ -0,0 +1,151 @@
+package com.gyee.edge.gddlly.iec104.protocol;
+
+
+import com.gyee.edge.gddlly.utils.Util;
+
+public class ParamePreset104 {
+	/**
+	 * 读单个参数命令(参数设置)
+	 * 
+	 * @param infoElement
+	 * @param num
+	 * @param tI
+	 * @param sQ
+	 * @return
+	 */
+	public String single_parmeter(int[] infoElement, int num, int tI, int sQ) {
+		String string = "";
+
+		string += "信息对象地址:";
+		string += new ASDU().InfoAddress(infoElement[0], infoElement[1],
+				infoElement[2]);
+		string += "\n";
+		string += "参数设置对象信息值:";
+		string += Util.toHexString(infoElement[3]);
+		string += "  ";
+		string += Util.toHexString(infoElement[4]);
+		string += "  ";
+		string += Util.toHexString(infoElement[5]);
+		string += "  ";
+		string += Util.toHexString(infoElement[6]);
+		string += "\n";
+
+		return string;
+	}
+
+	/**
+	 * 读多个参数命令(参数设置)
+	 * 
+	 * @param infoElement
+	 * @param num
+	 * @param tI
+	 * @param sQ
+	 * @return
+	 */
+	public String multi_parmeter(int[] infoElement, int num, int tI, int sQ) {
+		String string = "";
+		for (int i = 0; i < num; i++) {
+			string += "信息对象";
+			string += (i + 1);
+			string += "地址:";
+			string += new ASDU().InfoAddress(infoElement[i * 7],
+					infoElement[i * 7 + 1], infoElement[i * 7 + 2]);
+			string += "\n";
+			string += "参数设置对象信息值:";
+			string += Util.toHexString(infoElement[i * 7 + 3]);
+			string += "  ";
+			string += Util.toHexString(infoElement[i * 7 + 4]);
+			string += "  ";
+			string += Util.toHexString(infoElement[i * 7 + 5]);
+			string += "  ";
+			string += Util.toHexString(infoElement[i * 7 + 6]);
+			string += "\n";
+		}
+		return string;
+	}
+
+	/**
+	 * 预置/激活多个参数命令(参数设置)
+	 * 
+	 * @param infoElement
+	 * @param num
+	 * @param tI
+	 * @param sQ
+	 * @return
+	 */
+	public String activate_multi_parmeter(int[] infoElement, int num, int tI,
+			int sQ) {
+		String string = "";
+		for (int i = 0; i < num; i++) {
+			string += "信息对象";
+			string += (i + 1);
+			string += "地址:";
+			string += new ASDU().InfoAddress(infoElement[i * 7],
+					infoElement[i * 7 + 1], infoElement[i * 7 + 2]);
+			string += "\n";
+			string += "预置/激活参数命令对象信息值:";
+			string += Util.toHexString(infoElement[i * 7 + 3]);
+			string += "  ";
+			string += Util.toHexString(infoElement[i * 7 + 4]);
+			string += "  ";
+			string += Util.toHexString(infoElement[i * 7 + 5]);
+			string += "  ";
+			string += Util.toHexString(infoElement[i * 7 + 6]);
+			string += "\n";
+		}
+		string += "设定命令限定词QOS:";
+
+		string += Util
+				.toHexString(infoElement[infoElement.length - 1]);
+		string += "  ";
+		if (infoElement[infoElement.length - 1] == 0x80) {
+			string += "预置参数";
+		} else if (infoElement[infoElement.length - 1] == 0x00) {
+			string += "激活参数";
+
+		}
+		string += "\n";
+		return string;
+	}
+
+	/**
+	 * 预置/激活单个参数命令(参数设置)
+	 * 
+	 * @param infoElement
+	 * @param num
+	 * @param tI
+	 * @param sQ
+	 * @return
+	 */
+	public String activate_single_parmeter(int[] infoElement, int num, int tI,
+			int sQ) {
+		String string = "";
+
+		string += "信息对象地址:";
+		string += new ASDU().InfoAddress(infoElement[0], infoElement[1],
+				infoElement[2]);
+		string += "\n";
+		string += "预置/激活参数命令对象信息值:";
+		string += Util.toHexString(infoElement[3]);
+		string += "  ";
+		string += Util.toHexString(infoElement[4]);
+		string += "  ";
+		string += Util.toHexString(infoElement[5]);
+		string += "  ";
+		string += Util.toHexString(infoElement[6]);
+		string += "\n";
+		string += "设定命令限定词QOS:";
+
+		string += Util.toHexString(infoElement[7]);
+		string += "  ";
+		if (infoElement[7] == 0x80) {
+			string += "预置参数";
+		} else if (infoElement[7] == 0x00) {
+			string += "激活参数";
+
+		}
+		string += "\n";
+		return string;
+	}
+
+}

+ 90 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/protocol/QualifiersEnum.java

@@ -0,0 +1,90 @@
+package com.gyee.edge.gddlly.iec104.protocol;
+
+import lombok.Getter;
+
+/**
+ * 
+* @ClassName: 限定词  
+* @author sun
+ */
+public enum QualifiersEnum {
+
+	/**
+	 *  总召唤限定词
+	 */
+	generalCallQualifiers(TypeIdentifierEnum.generalCall, 0x20),
+	
+	/**
+	 * 总召唤限定词 支持 老版的分组
+	 */
+	generalCallGroupingQualifiers(TypeIdentifierEnum.generalCall, 0x14),
+	/**
+	 * 复位进程限定词
+	 */
+	resetPprocessQualifiers(TypeIdentifierEnum.resetPprocess, 0x01),
+	/**
+	 *  初始化原因 当地电源合上
+	 */
+	localCloseUpQualifiers(TypeIdentifierEnum.initEnd, 0x00),
+	/**
+	 * 初始化原因 当地手动复位
+	 */
+	localMmanualResetQualifiers(TypeIdentifierEnum.initEnd, 0x01),
+	/**
+	 * 远方复位 
+	 */
+	distanceResetQualifiers(TypeIdentifierEnum.initEnd, 0x02),
+	/**
+	 * 品质描述词  遥测
+	 */
+	qualityQualifiers(null, 0x00),
+	/**
+	 * 设置命令限定词  选择预置参数 1000 0000
+	 */
+	prefabParameterQualifiers(null, 0x40),
+	/**
+	 * 设置命令限定词  执行激活参数
+	 */
+	activationParameterQualifiers(null, 0x0F);
+
+	@Getter
+	private byte value;
+	@Getter
+	private TypeIdentifierEnum typeIdentifier;
+
+	QualifiersEnum(TypeIdentifierEnum typeIdentifier, int value) {
+		this.value = (byte) value;
+		this.typeIdentifier = typeIdentifier;
+	}
+
+
+	/**
+	 * 	根据传输类型和 限定词的关系返回 限定词的类型
+	 * @param typeIdentifier
+	 * @param value
+	 * @return
+	 */
+	public static QualifiersEnum getQualifiersEnum(TypeIdentifierEnum typeIdentifier, byte value) {
+		for (QualifiersEnum type : QualifiersEnum.values()) {
+			if (type.getValue() == value && type.getTypeIdentifier() == typeIdentifier) {
+				return type;
+			}
+		}
+		// 品质描述词和设置参数 限定词对应多个值 所以需要做特殊处理
+		QualifiersEnum qualifiersEnum = null;
+		if ((TypeIdentifierEnum.normalizedTelemetry.equals(typeIdentifier)
+				|| TypeIdentifierEnum.scaledTelemetry.equals(typeIdentifier)
+				|| TypeIdentifierEnum.shortFloatingPointTelemetry.equals(typeIdentifier))
+				&& qualityQualifiers.getValue() == value) {
+			qualifiersEnum = qualityQualifiers;
+		}
+		if ((TypeIdentifierEnum.readOneParameter.equals(typeIdentifier)
+				|| TypeIdentifierEnum.readMultipleParameter.equals(typeIdentifier)
+				|| TypeIdentifierEnum.M_SP_TB_1.equals(typeIdentifier)
+				|| TypeIdentifierEnum.prefabActivationMultipleParameter.equals(typeIdentifier))
+				&& qualityQualifiers.getValue() == value) {
+			qualifiersEnum = qualityQualifiers;
+		}
+		return qualifiersEnum;
+	}
+}

+ 77 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/protocol/Telecontrol104.java

@@ -0,0 +1,77 @@
+package com.gyee.edge.gddlly.iec104.protocol;
+
+
+import com.gyee.edge.gddlly.utils.Util;
+
+/**
+ * 遥控信息解析
+ */
+public class Telecontrol104 {
+
+    /**
+     * 单命令信息解析
+     *
+     * @param infoElement 信息元素集报文
+     * @param num         信息元素的个数
+     * @param tI          类型标识
+     * @return
+     */
+    public String Single_command(int[] infoElement, int num, int tI) {
+        StringBuilder builder = new StringBuilder();
+        builder.append("信息元素地址:");
+        builder.append(new ASDU().InfoAddress(infoElement[0], infoElement[1],
+                infoElement[2])).append("\n");
+        builder.append("单命令 SCO:").append("\t");
+        builder.append(Util.toHexString(infoElement[3])).append("\n");
+        if ((infoElement[3] & 0x80) == 128) {
+            builder.append("遥控选择命令\t");
+        }
+        if ((infoElement[3] & 0x80) == 0) {
+            builder.append("遥控执行命令\t");
+        }
+        if ((infoElement[3] & 0x01) == 1) {
+            builder.append("开关合\t");
+        }
+        if ((infoElement[3] & 0x01) == 0) {
+            builder.append("开关分\t");
+        }
+        return builder.toString();
+    }
+
+    /**
+     * 双命令信息解析
+     *
+     * @param infoElement 信息元素集报文
+     * @param num         信息元素的个数
+     * @param tI          类型标识
+     * @return
+     */
+    public String Double_command(int[] infoElement, int num, int tI) {
+        StringBuilder builder = new StringBuilder();
+        builder.append("信息元素地址:");
+        builder.append(new ASDU().InfoAddress(infoElement[0], infoElement[1],
+                infoElement[2])).append("\n");
+        builder.append("双命令 DCO:\t");
+        builder.append(Util.toHexString(infoElement[3])).append("\n");
+        if ((infoElement[3] & 0x80) == 128) {
+            builder.append("遥控选择命令\t");
+        }
+        if ((infoElement[3] & 0x80) == 0) {
+            builder.append("遥控执行命令\t");
+        }
+        if ((infoElement[2] & 0x03) == 0) {
+            builder.append("不允许,有错误\t");
+        }
+        if ((infoElement[3] & 0x03) == 1) {
+            builder.append("开关分\t");
+        }
+        if ((infoElement[3] & 0x03) == 2) {
+            builder.append("开关合\t");
+        }
+        if ((infoElement[3] & 0x03) == 3) {
+            builder.append("不允许,有错误\t");
+        }
+        return builder.toString();
+    }
+
+}

+ 170 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/protocol/Telemetry104.java

@@ -0,0 +1,170 @@
+package com.gyee.edge.gddlly.iec104.protocol;
+
+import com.gyee.edge.gddlly.utils.Util;
+
+/**
+ * 遥信信息解析
+ */
+public class Telemetry104 {
+    /**
+     * 不带时标的单双点信息(遥信)
+     *
+     * @param infoElement
+     * @param num
+     * @param tI
+     * @param sQ
+     * @return
+     */
+    public String NoTime_Point(int[] infoElement, int num, int tI, int sQ) {
+        StringBuilder builder = new StringBuilder();
+        if (sQ == 1) {
+            builder.append("信息对象地址:    ");
+            builder.append(new ASDU().InfoAddress(infoElement[0], infoElement[1],
+                    infoElement[2])).append("\n");
+            for (int i = 0; i < num; i++) {
+                builder.append("信息元素");
+                builder.append(i + 1);
+                builder.append("的信息元素值:");
+                builder.append(Util.toHexString(infoElement[i + 3]));
+                builder.append("\n");
+                builder.append(this.point(infoElement[i + 3], tI));
+                builder.append("\n");
+            }
+
+        } else {
+            for (int i = 1; i <= num; i++) {
+                builder.append("信息元素");
+                builder.append(i);
+                builder.append("的内容如下:\n");
+                builder.append("信息对象地址:    ");
+                builder.append(new ASDU().InfoAddress(infoElement[(i - 1) * 4],
+                        infoElement[(i - 1) * 4 + 1],
+                        infoElement[(i - 1) * 4 + 2]));
+                builder.append("\n");
+                builder.append("信息元素值:");
+                builder.append(Util.toHexString(infoElement[i * 4 - 1]));
+                builder.append("\n");
+                builder.append(this.point(infoElement[i * 4 - 1], tI));
+                builder.append("\n");
+            }
+
+        }
+        return builder.toString();
+    }
+
+    private String point(int i, int tI) {
+        StringBuilder builder = new StringBuilder();
+        if (tI == 1 || tI == 30) {
+            if ((i & 0x80) == 128) {
+                builder.append("无效/");
+            }
+            if ((i & 0x80) == 0) {
+                builder.append("有效/");
+            }
+            if ((i & 0x40) == 64) {
+                builder.append("非当前值/");
+            }
+            if ((i & 0x40) == 0) {
+                builder.append("当前值/");
+            }
+            if ((i & 0x20) == 32) {
+                builder.append("被取代/");
+            }
+            if ((i & 0x20) == 0) {
+                builder.append("未被取代/");
+            }
+            if ((i & 0x10) == 16) {
+                builder.append("被闭锁/");
+            }
+            if ((i & 0x10) == 0) {
+                builder.append("未被闭锁/");
+            }
+            if ((i & 0x01) == 1) {
+                builder.append("开关合	");
+            }
+            if ((i & 0x01) == 0) {
+                builder.append("开关分	");
+            }
+        }
+        if (tI == 3 || tI == 31) {
+            if ((i & 0x80) == 128) {
+                builder.append("无效/");
+            }
+            if ((i & 0x80) == 0) {
+                builder.append("有效/");
+            }
+            if ((i & 0x40) == 64) {
+                builder.append("非当前值/");
+            }
+            if ((i & 0x40) == 0) {
+                builder.append("当前值/");
+            }
+            if ((i & 0x20) == 32) {
+                builder.append("被取代/");
+            }
+            if ((i & 0x20) == 0) {
+                builder.append("未被取代/");
+            }
+            if ((i & 0x10) == 16) {
+                builder.append("被闭锁/");
+            }
+            if ((i & 0x10) == 0) {
+                builder.append("未被闭锁/");
+            }
+            if ((i & 0x03) == 0) {
+                builder.append("不确定或中间状态	");
+            }
+            if ((i & 0x03) == 1) {
+                builder.append("确定开关分	");
+            }
+            if ((i & 0x03) == 2) {
+                builder.append("确定开关合	");
+            }
+            if ((i & 0x03) == 3) {
+                builder.append("不确定	");
+            }
+
+        }
+
+        return builder.toString();
+    }
+
+    /**
+     * 带CP56Time2a时标的单双点信息(遥信)
+     *
+     * @param infoElement
+     * @param num
+     * @param tI
+     * @param sQ
+     * @return
+     */
+    public String Time_Point(int[] infoElement, int num, int tI, int sQ) {
+
+        StringBuilder builder = new StringBuilder();
+        if (sQ == 0) {
+            for (int i = 1; i <= num; i++) {
+                builder.append("信息元素");
+                builder.append(i);
+                builder.append("的内容如下:\n");
+                builder.append("信息对象地址:");
+                builder.append(new ASDU().InfoAddress(infoElement[(i - 1) * 11],
+                        infoElement[(i - 1) * 11 + 1],
+                        infoElement[(i - 1) * 11 + 2])).append("\n");
+                builder.append("信息元素值:");
+                builder.append(Util.toHexString(infoElement[(i - 1) * 11 + 3])).append("\n");
+
+                builder.append(this.point(infoElement[(i - 1) * 11 + 3], tI)).append("\n");
+
+                int time[] = new int[7];
+                for (int j = 0; j < 7; j++) {
+                    time[j] = infoElement[(i - 1) * 11 + 4 + j];
+                }
+                builder.append(Util.TimeScale(time));
+            }
+        } else {
+            builder.append("按照DL/T 634.5101-2002规定,带长时标的单/双点信息遥信报文并不存在信息元素序列(SQ=1)的情况。");
+        }
+        return builder.toString();
+
+    }
+}

+ 150 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/protocol/Telesignalling104.java

@@ -0,0 +1,150 @@
+package com.gyee.edge.gddlly.iec104.protocol;
+
+import com.gyee.edge.gddlly.utils.Util;
+
+/**
+ * 遥测信息解析
+ */
+public class Telesignalling104 {
+	/**
+	 * 测量值,归一化值
+	 * 
+	 * @param infoElement
+	 * @param num
+	 * @param tI
+	 * @param sQ
+	 * @return
+	 */
+	public String normalization(int[] infoElement, int num, int tI, int sQ) {
+		StringBuilder builder = new StringBuilder();
+		if (sQ == 1) {
+
+			builder.append("遥测信息对象地址");
+			builder.append(new ASDU().InfoAddress(infoElement[0], infoElement[1],
+					infoElement[2]));
+			builder.append("\n");
+			for (int i = 0; i < num; i++) {
+				builder.append("信息对象");
+				builder.append(i + 1);
+				builder.append("归一化值NVA:");
+				builder.append(Util.toHexString(infoElement[i * 3 + 3]));
+				builder.append("\t");
+				builder.append(Util.toHexString(infoElement[i * 3 + 4]));
+				builder.append("\n");
+				builder.append("品质描述词QDS:");
+				builder.append(Util.toHexString(infoElement[i * 3 + 5]));
+				builder.append("\n");
+			}
+		} else {
+			for (int i = 0; i < num; i++) {
+				builder.append("信息对象");
+				builder.append((i + 1));
+				builder.append("的地址:");
+				builder.append(new ASDU().InfoAddress(infoElement[i * 6],
+						infoElement[i * 6 + 1], infoElement[i * 6 + 2]));
+				builder.append("\n");
+				builder.append("归一化值NVA:");
+				builder.append(Util.toHexString(infoElement[i * 6 + 3]));
+				builder.append("\t");
+				builder.append(Util.toHexString(infoElement[i * 6 + 4]));
+				builder.append("\n");
+				builder.append("品质描述词QDS:");
+				builder.append(Util.toHexString(infoElement[i * 6 + 5]));
+				builder.append("\n");
+			}
+		}
+
+		return builder.toString();
+	}
+
+	/**
+	 * 测量值,标度化值
+	 * 
+	 * @param infoElement
+	 * @param num
+	 * @param tI
+	 * @param sQ
+	 * @return
+	 */
+	public String standardization(int[] infoElement, int num, int tI, int sQ) {
+		String string = "";
+
+		return string;
+	}
+
+	/**
+	 * 测量值,短浮点数
+	 * 
+	 * @param infoElement
+	 * @param num
+	 * @param tI
+	 * @param sQ
+	 * @return
+	 */
+	public String short_float(int[] infoElement, int num, int tI, int sQ) {
+		StringBuilder builder = new StringBuilder();
+		if (sQ == 1) {
+
+			builder.append("遥测信息对象地址");
+			builder.append(new ASDU().InfoAddress(infoElement[0], infoElement[1],
+					infoElement[2])).append("\n");
+			for (int i = 0; i < num; i++) {
+				builder.append("遥测");
+				builder.append((i + 1));
+				builder.append("IEEE STD745短浮点数:");
+				String fl=Util.toHexString(infoElement[i * 5 + 6])+Util.toHexString(infoElement[i * 5 + 5])+Util.toHexString(infoElement[i * 5 + 4])+Util.toHexString(infoElement[i * 5 + 3]);
+				fl=fl.replaceAll("0x", "");
+				int ieee754Int = Integer.parseInt(fl, 16);
+		        float realValue = Float.intBitsToFloat(ieee754Int);
+		        builder.append(realValue);
+//				builder.append(Util.toHexString(infoElement[i * 5 + 3]));
+//				builder.append("\t");
+//				builder.append(Util.toHexString(infoElement[i * 5 + 4]));
+//				builder.append("\t");
+//				builder.append(Util.toHexString(infoElement[i * 5 + 5]));
+//				builder.append("\t");
+//				builder.append(Util.toHexString(infoElement[i * 5 + 6]));
+				builder.append("\n");
+				builder.append("品质描述词QDS:");
+				builder.append(Util.toHexString(infoElement[i * 5 + 7]));
+				builder.append("\n");
+			}
+		} else {
+			for (int i = 0; i < num; i++) {
+				builder.append("信息对象");
+				builder.append((i + 1));
+				builder.append("的地址:");
+				builder.append(new ASDU().InfoAddress(infoElement[i * 8],
+						infoElement[i * 8 + 1], infoElement[i * 8 + 2]));
+				builder.append("\n");
+				builder.append("IEEE STD745短浮点数:");
+				String fl=Util.toHexString(infoElement[i * 8 + 6])+Util.toHexString(infoElement[i * 8 + 5])+Util.toHexString(infoElement[i * 8 + 4])+Util.toHexString(infoElement[i * 8 + 3]);
+				fl=fl.replaceAll("0x", "");
+				int ieee754Int = Integer.parseInt(fl, 16);
+		        float realValue = Float.intBitsToFloat(ieee754Int);
+		        builder.append(realValue);
+//				builder.append(Util.toHexString(infoElement[i * 8 + 3]));
+//				builder.append("\t");
+//				builder.append(Util.toHexString(infoElement[i * 8 + 4]));
+//				builder.append("\t");
+//				builder.append(Util.toHexString(infoElement[i * 8 + 5]));
+//				builder.append("\t");
+//				builder.append(Util.toHexString(infoElement[i * 8 + 6]));
+//				builder.append("\t");
+
+				builder.append("\n");
+				builder.append("品质描述词QDS:");
+				builder.append(Util.toHexString(infoElement[i * 8 + 7])).append("\n");
+			}
+		}
+
+		return builder.toString();
+	}
+
+	public static void main(String[] args) {
+        String str = "3F58A925";
+        int ieee754Int = Integer.parseInt(str, 16);
+        float realValue = Float.intBitsToFloat(ieee754Int);
+        System.out.println(realValue);
+}
+}

+ 56 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/protocol/TransferReason.java

@@ -0,0 +1,56 @@
+package com.gyee.edge.gddlly.iec104.protocol;
+
+
+import com.gyee.edge.gddlly.exception.UnknownTransferReasonException;
+
+/**
+ * 解析传送原因
+ */
+public enum TransferReason {
+    UNUSED(0, "未用"),
+    PERIOD(1, "周期、循环 "),
+    BG_SCANNER(2, "背景扫描"),
+    OUTBURST(3, "突发(自发)"),
+    INITIALIZATION_COMPLETE(4, "初始化完成"),
+    REQUEST(5, "请求或者被请求"),
+    ACTIVATE(6, "激活"),
+    ACTIVATE_CONFIRMATION(7, "激活确认"),
+    STOP_ACTIVATION(8, "	停止激活 "),
+    STOP_ACTIVATION_CONFIRMATION(9, "停止激活确认"),
+    ACTIVATE_TERMINATION(10, "激活终止 	"),
+    FILE_TRANSFER(13, "文件传输	"),
+    ANSWER_STATION_CALL(20, "响应站召唤(总召唤)"),
+    UNKNOWN_TYPE(44, "未知的类型标识"),
+    UNKNOWN_REASON_TRANSMISSION(45, "未知的传送原因"),
+    UNKNOWN_ASDU(46, "未知的应用服务数据单元公共地址"),
+    UNKNOWN_ADDRESS(47, "未知的信息对象地址"),
+    RC_STATUS_ERROR(48, "遥控执行软压板状态错误"),
+    RC_TIME_ERROR(49, "遥控执行时间戳错误"),
+    RC_SIGNATURE_ERROR(50, "遥控执行数字签名认证错误");
+    private int code;
+    private String describe;
+
+    TransferReason(int code, String describe) {
+        this.code = code;
+        this.describe = describe;
+    }
+
+    public static String getDdescribe(int code) throws UnknownTransferReasonException {
+        for (TransferReason value : TransferReason.values()) {
+            if (value.code == code) return value.describe;
+        }
+        throw  new UnknownTransferReasonException();
+    }
+
+    public static TransferReason valueOf(int code) throws UnknownTransferReasonException {
+        for (TransferReason value : TransferReason.values()) {
+            if (value.code == code)
+                return value;
+        }
+        throw  new UnknownTransferReasonException();
+    }
+
+    public static TransferReason valueOf(short code) throws UnknownTransferReasonException {
+        return TransferReason.valueOf((int)code);
+    }
+}

+ 45 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/protocol/TypeIdentifier.java

@@ -0,0 +1,45 @@
+package com.gyee.edge.gddlly.iec104.protocol;
+
+
+import com.gyee.edge.gddlly.exception.UnknownTypeIdentifierException;
+
+/**
+ * 归一化值
+ */
+public enum TypeIdentifier {
+    SINGLE_POINT(1, "不带时标的单点信息"),
+    TWO_POINT(3, "不带时标的双点信息"),
+    NORMALIZED(9, "测量值,归一化值"),
+    SCALE(11, "测量值,标度化值"),
+    SHORT_FLOAT(13, "测量值,短浮点数"),
+    SINGLE_POINT_TIME(30, "带CP56Time2a时标的单点信息"),
+    TWO_POINT_TIME(31, "带CP56Time2a时标的双点信息"),
+    SINGLE_COMMAND(45, "单命令(遥控)"),
+    TWO_COMMAND(46, "双命令(遥控)"),
+    PRESETS_SINGLE_PARAmeter(48, "预置/激活单个参数命令"),
+    READ_SINGLE_PARAMETer(102, "读单个参数命令"),
+    END_OF_INITIALIZATIon(70, "初始化结束"),
+    CALL_COMMAND(100, "召唤命令"),
+    CLOCK_SYNCHRONIZATIon(103, "时钟同步/读取命令"),
+    TEST_COMMAND(104, "测试命令"),
+    RESET_PROCESS(105, "复位进程命令"),
+    READ_MULTIPLE_PARAMETERS(132, "读多个参数命令"),
+    PRESETS_MULTIPLE_PARAMETERS(136, "预置/激活多个参数命令");
+
+
+    private int code;
+    private String describe;
+
+    TypeIdentifier(int code, String describe) {
+        this.code = code;
+        this.describe = describe;
+    }
+
+    public static String getDescribe(int code) throws UnknownTypeIdentifierException {
+        for (com.gyee.edge.gddlly.iec104.protocol.TypeIdentifier value : com.gyee.edge.gddlly.iec104.protocol.TypeIdentifier.values()) {
+            if (value.code == code) return value.describe;
+        }
+
+        throw new UnknownTypeIdentifierException();
+    }
+}

+ 98 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/protocol/TypeIdentifierEnum.java

@@ -0,0 +1,98 @@
+package com.gyee.edge.gddlly.iec104.protocol;
+
+/**
+ * 
+* @ClassName: TypeIdentifierEnum   类型标识
+ */
+public enum TypeIdentifierEnum {
+	
+	/**
+	 * 单点摇信
+	 */
+	onePointTeleindication(0x01),
+	/**
+	 * 双点摇信
+	 */
+	twoPointTeleindication(0x03),
+	/**
+	 * 测量值 归一化值 遥测
+	 */
+	normalizedTelemetry(0x09),
+	/**
+	 * 测量值  标度化值 遥测
+	 */
+	scaledTelemetry(0x0B),
+	/**
+	 * 测量值 短浮点数 遥测   Short floating point
+	 */
+	shortFloatingPointTelemetry(0x0D),
+	M_ME_TF_1(0x24),
+	//M_SP_TB_1(0x30),
+
+	/**
+	 * 摇信带时标 单点
+	 */
+	M_SP_TB_1(0x1E),
+
+	/**
+	 * 摇信带时标 双点
+	 */
+	twoPointTimeTeleindication(0x1F),
+	/**
+	 * 单命令 遥控
+	 */
+	onePointTelecontrol(0x2D),
+	/**
+	 * 双命令遥控
+	 */
+	twoPointTelecontrol(0x2E),
+	/**
+	 * 读单个参数
+	 */
+	readOneParameter(0x66),
+	/**
+	 * 读多个参数
+	 */
+	readMultipleParameter(0x84),
+	/**
+	 * 预置单个参数命令
+	 */
+	prefabActivationOneParameter(0x30),
+	/**
+	 * 预置多个个参数
+	 */
+	prefabActivationMultipleParameter(0x88),
+	/**
+	 * 初始化结束
+	 */
+	initEnd(0x46),
+	/**
+	 * 召唤命令
+	 */
+	generalCall(0x64),
+	/**
+	 * 时钟同步
+	 */
+	timeSynchronization(0x67),
+	/**
+	 * 复位进程
+	 */
+	resetPprocess(0x69);
+
+	private byte value;
+	TypeIdentifierEnum(int value) {
+		this.value = (byte) value;
+	}
+	public byte getValue() {
+		return value;
+	}
+	
+	public static TypeIdentifierEnum getTypeIdentifierEnum(byte value) {
+		for (TypeIdentifierEnum type : TypeIdentifierEnum.values()) {
+			if (type.getValue() == value) {
+				return type;
+			}
+		}
+		return null;
+	}
+}

+ 42 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/iec104/protocol/UControlEnum.java

@@ -0,0 +1,42 @@
+package com.gyee.edge.gddlly.iec104.protocol;
+
+import lombok.Getter;
+
+/**
+ * U帧 基本指令
+ *
+ */
+public enum UControlEnum {
+	/**
+	 * 测试命令
+	 */
+	TESTFR(0x43000000),
+	/**
+	 * 测试确认指令
+	 */
+	TESTFR_YES(0x83000000),
+	/**
+	 * 停止指令
+	 */
+	STOPDT(0x13000000),
+	/**
+	 * 停止确认
+	 */
+	STOPDT_YES(0x23000000),
+	/**
+	 * 启动命令
+	 */
+	STARTDT(0x07000000),
+	/**
+	 * 启动确认命令
+	 */
+	STARTDT_YES(0x0B000000);
+
+	@Getter
+	private  int value;
+
+	UControlEnum(int value) {
+		this.value = value;
+	}
+
+}

+ 111 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/redis/RedisDataService.java

@@ -0,0 +1,111 @@
+package com.gyee.edge.gddlly.redis;
+
+import com.gyee.edge.gddlly.config.Point;
+import com.gyee.edge.gddlly.config.PointService;
+import com.gyee.edge.gddlly.utils.RedisUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import java.util.*;
+
+@Slf4j
+@Service
+public class RedisDataService {
+
+    @Autowired
+    private RedisUtil redisUtil;
+
+    @Autowired
+    private PointService pointService;
+
+    public Map<String, Point> getData(Map<String, Point> map){
+
+        List<String> points = new ArrayList<>(map.keySet());
+
+        try{
+            List<String> data = (List<String>)this.redisUtil.getmAll(points);
+            for (int i = 0; i < points.size(); i++){
+                Point point = map.get(points.get(i));
+                String val = data.get(i);
+                if (point == null || !StringUtils.hasText(val))
+                    continue;
+
+                String[] vls = val.split(":");
+                if (vls.length != 2)
+                    continue;
+
+                String ts = vls[0].trim();
+                String vs = vls[1].trim();
+
+                long time = Long.valueOf(ts.substring(ts.indexOf("\"") + 1, ts.length() - 1));
+                double value = Double.valueOf(vs.substring(1, vs.lastIndexOf("\"")));
+
+                point.setLastUpdateTime(new Date(time));
+                if (point.getValue() != value ) {
+                    point.setConsumed(false);
+                    point.setValue(value);
+                }
+               // point.setConsumed(point.getValue() == value ? true : false);
+
+                //map.replace(points.get(i), point);
+            }
+        } catch (Exception e){
+            log.error(e.getMessage());
+            e.printStackTrace();
+            return map;
+        }
+
+        return map;
+    }
+
+    public boolean readRedisData(int addr,String typeStr){
+        List<String> keys;
+        List<Point>  points;
+        try {
+            if (typeStr.toUpperCase().equals("AI")) {
+                keys = pointService.getAiKeys(addr);
+                points = pointService.getAiList(addr);
+            } else {
+                keys = pointService.getDiKeys(addr);
+                points = pointService.getDiList(addr);
+            }
+
+            if (keys == null || points == null || keys.isEmpty() ||
+                keys.size() != points.size() ) {
+                log.error("地址错误! publicaddr = " + addr);
+                return false;
+            }
+
+            List<Object> vals = redisUtil.hmgetAll(keys);
+            if (vals == null || vals.isEmpty() || vals.size() != keys.size()) {
+                log.error("读取redis错误!publicaddr = " + addr);
+                return false;
+            }
+
+            for (int i = 0; i < keys.size(); i++) {
+                Point point = points.get(i);
+                Map<String, String> mp = (Map<String, String>) vals.get(i);
+                if (mp == null || mp.size() == 0)
+                    continue;
+
+                long time = Long.valueOf(mp.get("timestamp")).longValue() * 1000;
+                double value = Double.valueOf(mp.get("value")).doubleValue();
+
+                if (point.getValue() != value) {
+                    point.setConsumed(false);
+                    point.setValue(value);
+                    point.setLastUpdateTime(new Date(time));
+                }
+            }
+        } catch (Exception e){
+            log.error(e.getMessage());
+            e.printStackTrace();
+            return false;
+        }
+
+        return true;
+    }
+
+}

+ 406 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/utils/ByteUtil.java

@@ -0,0 +1,406 @@
+package com.gyee.edge.gddlly.utils;
+
+import java.io.ByteArrayOutputStream;
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * 
+* @ClassName: ByteUtil  
+* @Description: byte 工具类 
+* @author sun 
+ */
+public class ByteUtil {
+
+    public static byte[] float2Bytes(float value) {
+
+        int tempVal = Float.floatToIntBits(value);
+        byte[] result = new byte[4];
+
+        result[0] = (byte) tempVal;
+        result[1] = (byte) (tempVal >> 8);
+        result[2] = (byte) (tempVal >> 16);
+        result[3] = (byte) (tempVal >> 24);
+
+        return result;
+    }
+
+    public static float bytes2Float(byte[] bytes) {
+
+        return Float.intBitsToFloat((bytes[0] & 0xff) |
+                ((bytes[1] & 0xff) << 8) |
+                ((bytes[2] & 0xff) << 16) |
+                ((bytes[3] & 0xff) << 24));
+
+    }
+
+	/**
+	 * 
+	* @Title: intToByteArray  
+	* @Description: int 转换成 byte数组 
+	* @param @param i
+	* @param @return 
+	* @return byte[]   
+	* @throws
+	 */
+	public static byte[] intToByteArray(int i) {
+        byte[] result = new byte[4];
+        result[0] = (byte) ((i >> 24) & 0xFF);
+        result[1] = (byte) ((i >> 16) & 0xFF);
+        result[2] = (byte) ((i >> 8) & 0xFF);
+        result[3] = (byte) (i & 0xFF);
+        return result;
+    }
+	
+	/**
+	* @Title: shortToByteArray  
+	* @Description: short 转换成 byte[] 
+	* @param @param val
+	* @param @return 
+	* @return byte[]   
+	* @throws
+	 */
+	public static byte[] shortToByteArray(short val) {
+		byte[] b = new byte[2];
+		b[0] = (byte) ((val >> 8) & 0xff);
+		b[1] = (byte) (val & 0xff);
+		return b;
+	}
+	
+	/**
+	 * 
+	* @Title: byteArrayToInt  
+	* @Description: byte[] 转换成 int
+	* @param @param bytes
+	* @param @return 
+	* @return int   
+	* @throws
+	 */
+	public static int byteArrayToInt(byte[] bytes) {
+        int value = 0;
+        for (int i = 0; i < 4; i++) {
+            int shift = (3 - i) * 8;
+            value += (bytes[i] & 0xFF) << shift;
+        }
+        return value;
+    }
+	
+	/**
+	 * 
+	* @Title: byteArrayToShort  
+	* @Description: byte[] 转换成short 
+	* @param @param bytes
+	* @param @return 
+	* @return short   
+	* @throws
+	 */
+	public static short byteArrayToShort(byte[] bytes) {
+        short value = 0;
+        for (int i = 0; i < 2; i++) {
+            int shift = (1 - i) * 8;
+            value += (bytes[i] & 0xFF) << shift;
+        }
+        return value;
+    }
+	
+	
+//	/**
+//	 * 
+//	* @Title: listToBytes  
+//	* @Description: TODO 
+//	* @param @param byteList
+//	* @param @return 
+//	* @return byte[]   
+//	* @throws
+//	 */
+//	public static byte[] listToBytes(List<Byte> byteList) {
+//		byte[] bytes = new byte[byteList.size()];
+//		int index = 0;
+//		for (Byte item : byteList) {
+//			bytes[index++] = item;
+//		}
+//		return bytes;
+//	}
+	
+	/**
+	 * 
+	* @Title: date2HByte  
+	* @Description: 日期转换成 CP56Time2a
+	* @param @param date
+	* @param @return 
+	* @return byte[]   
+	* @throws
+	 */
+    public static byte[] date2Hbyte(Date date) {
+    	ByteArrayOutputStream bOutput = new ByteArrayOutputStream();
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(date);
+        // 毫秒需要转换成两个字节其中 低位在前高位在后 
+        // 先转换成short
+        int millisecond = calendar.get(Calendar.SECOND) * 1000 + calendar.get(Calendar.MILLISECOND);
+        
+        // 默认的高位在前
+        byte[] millisecondByte = intToByteArray(millisecond);
+        bOutput.write((byte) millisecondByte[3]);
+        bOutput.write((byte) millisecondByte[2]);
+        
+        // 分钟 只占6个比特位 需要把前两位置为零 
+        bOutput.write((byte) calendar.get(Calendar.MINUTE));
+        // 小时需要把前三位置零
+        bOutput.write((byte) calendar.get(Calendar.HOUR_OF_DAY));
+        // 星期日的时候 week 是0 
+        int week = calendar.get(Calendar.DAY_OF_WEEK);
+        if (week == Calendar.SUNDAY) {
+        	week = 7;
+        } else {
+        	week--;
+        } 
+        // 前三个字节是 星期 因此需要将星期向左移5位  后五个字节是日期  需要将两个数字相加 相加之前需要先将前三位置零
+        bOutput.write((byte) (week << 5) + (calendar.get(Calendar.DAY_OF_MONTH)));
+        // 前四字节置零
+        bOutput.write((byte) ((byte) calendar.get(Calendar.MONTH) + 1));
+        bOutput.write((byte) (calendar.get(Calendar.YEAR) - 2000));
+        return bOutput.toByteArray();
+    }
+    
+    
+    /**
+	 * 
+	* @Title: date2HByte  
+	* @Description:CP56Time2a转换成  时间
+	* @param @param date
+	* @param @return 
+	* @return byte[]   
+	* @throws
+	 */
+    public static Date  byte2Hdate(byte[] dataByte) {
+        int year = (dataByte[6] & 0x7F) + 2000;
+        int month = dataByte[5] & 0x0F;
+        int day = dataByte[4] & 0x1F;
+        int hour = dataByte[3] & 0x1F;
+        int minute = dataByte[2] & 0x3F;
+        int second = dataByte[1] > 0 ? dataByte[1] : (int) (dataByte[1] & 0xff);
+        int millisecond = dataByte[0] > 0 ? dataByte[0] : (int) (dataByte[0] & 0xff);
+        millisecond = (second << 8) + millisecond;
+        second = millisecond / 1000;
+        millisecond = millisecond % 1000;
+        Calendar calendar = Calendar.getInstance();
+        calendar.set(Calendar.YEAR, year);
+        calendar.set(Calendar.MONTH, month);
+        calendar.set(Calendar.DAY_OF_MONTH, day);
+        calendar.set(Calendar.HOUR_OF_DAY, hour);
+        calendar.set(Calendar.MINUTE, minute);
+        calendar.set(Calendar.SECOND, second);
+        calendar.set(Calendar.MILLISECOND, millisecond);
+        return calendar.getTime();
+    }
+
+	public static String byteArrayToHexString(byte[] array) {
+        return byteArray2HexString(array, Integer.MAX_VALUE, false);
+    }
+
+	public static String byteArray2HexString(byte[] arrBytes, int count, boolean blank) {
+        String ret = "";
+        if (arrBytes == null || arrBytes.length < 1) {
+        	return ret;
+        }
+        if (count > arrBytes.length) {
+        	count = arrBytes.length;
+        }
+        StringBuilder builder = new StringBuilder();
+
+        for (int i = 0; i < count; i++) {
+            ret = Integer.toHexString(arrBytes[i] & 0xFF).toUpperCase();
+            if (ret.length() == 1) {
+            	builder.append("0").append(ret);
+            } else {
+            	builder.append(ret);
+            }
+            if (blank) {
+            	builder.append(" ");
+            }
+        }
+
+        return builder.toString();
+
+    }
+
+    /**
+     * 返回指定位置的数组
+     * @param bytes
+     * @param start 开始位置
+     * @param length  截取长度
+     * @return
+     */
+	public  static byte[] getByte(byte[] bytes, int start, int length) {
+		byte[] ruleByte = new byte[length];
+		int index = 0;
+		while (index < length) {
+			ruleByte[index++] = bytes[start++];
+		}
+		return ruleByte;
+	}
+
+    /**
+     * 将int转为高字节在前,低字节在后的byte数组(大端)
+     * @param n int
+     * @return byte[]
+     */
+    public static byte[] intToByteBig(int n) {
+        byte[] b = new byte[4];
+        b[3] = (byte) (n & 0xff);
+        b[2] = (byte) (n >> 8 & 0xff);
+        b[1] = (byte) (n >> 16 & 0xff);
+        b[0] = (byte) (n >> 24 & 0xff);
+        return b;
+    }
+    /**
+     * 将int转为低字节在前,高字节在后的byte数组(小端)
+     * @param n int
+     * @return byte[]
+     */
+    public static byte[] intToByteLittle(int n) {
+        byte[] b = new byte[4];
+        b[0] = (byte) (n & 0xff);
+        b[1] = (byte) (n >> 8 & 0xff);
+        b[2] = (byte) (n >> 16 & 0xff);
+        b[3] = (byte) (n >> 24 & 0xff);
+        return b;
+    }
+    /**
+     * byte数组到int的转换(小端)
+     * @param bytes
+     * @return
+     */
+    public static int bytes2IntLittle(byte[] bytes )
+    {
+        int int1=bytes[0]&0xff;
+        int int2=(bytes[1]&0xff)<<8;
+        int int3=(bytes[2]&0xff)<<16;
+        int int4=(bytes[3]&0xff)<<24;
+
+        return int1|int2|int3|int4;
+    }
+    /**
+     * byte数组到int的转换(大端)
+     * @param bytes
+     * @return
+     */
+    public static int bytes2IntBig(byte[] bytes )
+    {
+        int int1=bytes[3]&0xff;
+        int int2=(bytes[2]&0xff)<<8;
+        int int3=(bytes[1]&0xff)<<16;
+        int int4=(bytes[0]&0xff)<<24;
+
+        return int1|int2|int3|int4;
+    }
+    /**
+     * 将short转为高字节在前,低字节在后的byte数组(大端)
+     * @param n short
+     * @return byte[]
+     */
+    public static byte[] shortToByteBig(short n) {
+        byte[] b = new byte[2];
+        b[1] = (byte) (n & 0xff);
+        b[0] = (byte) (n >> 8 & 0xff);
+        return b;
+    }
+
+    /**
+     * 将short转为低字节在前,高字节在后的byte数组(小端)
+     * @param n short
+     * @return byte[]
+     */
+    public static byte[] shortToByteLittle(short n) {
+        byte[] b = new byte[2];
+        b[0] = (byte) (n & 0xff);
+        b[1] = (byte) (n >> 8 & 0xff);
+        return b;
+    }
+    /**
+     *  读取小端byte数组为short
+     * @param b
+     * @return
+     */
+    public static short byteToShortLittle(byte[] b) {
+        return (short) (((b[1] << 8) | b[0] & 0xff));
+    }
+    /**
+     *  读取大端byte数组为short
+     * @param b
+     * @return
+     */
+    public static short byteToShortBig(byte[] b) {
+        return (short) (((b[0] << 8) | b[1] & 0xff));
+    }
+    /**
+     * long类型转byte[] (大端)
+     * @param n
+     * @return
+     */
+    public static byte[] longToBytesBig(long n) {
+        byte[] b = new byte[8];
+        b[7] = (byte) (n & 0xff);
+        b[6] = (byte) (n >> 8  & 0xff);
+        b[5] = (byte) (n >> 16 & 0xff);
+        b[4] = (byte) (n >> 24 & 0xff);
+        b[3] = (byte) (n >> 32 & 0xff);
+        b[2] = (byte) (n >> 40 & 0xff);
+        b[1] = (byte) (n >> 48 & 0xff);
+        b[0] = (byte) (n >> 56 & 0xff);
+        return b;
+    }
+    /**
+     * long类型转byte[] (小端)
+     * @param n
+     * @return
+     */
+    public static byte[] longToBytesLittle(long n) {
+        byte[] b = new byte[8];
+        b[0] = (byte) (n & 0xff);
+        b[1] = (byte) (n >> 8  & 0xff);
+        b[2] = (byte) (n >> 16 & 0xff);
+        b[3] = (byte) (n >> 24 & 0xff);
+        b[4] = (byte) (n >> 32 & 0xff);
+        b[5] = (byte) (n >> 40 & 0xff);
+        b[6] = (byte) (n >> 48 & 0xff);
+        b[7] = (byte) (n >> 56 & 0xff);
+        return b;
+    }
+    /**
+     * byte[]转long类型(小端)
+     * @param array
+     * @return
+     */
+    public static long bytesToLongLittle( byte[] array )
+    {
+        return ((((long) array[ 0] & 0xff) << 0)
+                | (((long) array[ 1] & 0xff) << 8)
+                | (((long) array[ 2] & 0xff) << 16)
+                | (((long) array[ 3] & 0xff) << 24)
+                | (((long) array[ 4] & 0xff) << 32)
+                | (((long) array[ 5] & 0xff) << 40)
+                | (((long) array[ 6] & 0xff) << 48)
+                | (((long) array[ 7] & 0xff) << 56));
+    }
+
+    /**
+     * byte[]转long类型(大端)
+     * @param array
+     * @return
+     */
+    public static long bytesToLongBig( byte[] array )
+    {
+        return ((((long) array[ 0] & 0xff) << 56)
+                | (((long) array[ 1] & 0xff) << 48)
+                | (((long) array[ 2] & 0xff) << 40)
+                | (((long) array[ 3] & 0xff) << 32)
+                | (((long) array[ 4] & 0xff) << 24)
+                | (((long) array[ 5] & 0xff) << 16)
+                | (((long) array[ 6] & 0xff) << 8)
+                | (((long) array[ 7] & 0xff) << 0));
+    }
+
+
+}

+ 597 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/utils/RedisUtil.java

@@ -0,0 +1,597 @@
+package com.gyee.edge.gddlly.utils;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.data.redis.connection.RedisConnection;
+import org.springframework.data.redis.core.RedisCallback;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * redisTemplate封装
+ *
+ */
+@Component
+public class RedisUtil {
+
+    @Autowired
+    private RedisTemplate<String, Object> redisTemplate;
+
+    public RedisUtil(RedisTemplate<String, Object> redisTemplate) {
+        this.redisTemplate = redisTemplate;
+    }
+
+
+    /**
+     * 查询所有的key
+     * @param pattern
+     * - * 代表匹配任意字符
+     * - ? 代表匹配一个字符
+     * - [] 代表匹配部分字符,例如[1,3]代表匹配1和3,而[1-10]代表匹配1到10的任意数字。
+     * - x 转移字符,例如要匹配星号,问号需要转义的字符
+     * @return
+     */
+    public Set<String> getKeys(String pattern){
+        return redisTemplate.keys(pattern);
+    }
+
+
+    /**
+     * 指定缓存失效时间
+     * @param key 键
+     * @param time 时间(秒)
+     * @return
+     */
+    public boolean expire(String key,long time){
+        try {
+            if(time>0){
+                redisTemplate.expire(key, time, TimeUnit.SECONDS);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 根据key 获取过期时间
+     * @param key 键 不能为null
+     * @return 时间(秒) 返回0代表为永久有效
+     */
+    public long getExpire(String key){
+        return redisTemplate.getExpire(key,TimeUnit.SECONDS);
+    }
+
+    /**
+     * 判断key是否存在
+     * @param key 键
+     * @return true 存在 false不存在
+     */
+    public boolean hasKey(String key){
+        try {
+            return redisTemplate.hasKey(key);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 删除缓存
+     * @param key 可以传一个值 或多个
+     */
+    @SuppressWarnings("unchecked")
+    public void del(String ... key){
+        if(key!=null&&key.length>0){
+            if(key.length==1){
+                redisTemplate.delete(key[0]);
+            }else{
+                redisTemplate.delete(String.valueOf(CollectionUtils.arrayToList(key)));
+            }
+        }
+    }
+
+    //============================String=============================
+    /**
+     * 普通缓存获取
+     * @param key 键
+     * @return 值
+     */
+    public Object get(String key){
+        return key==null?null:redisTemplate.opsForValue().get(key);
+    }
+
+    /**
+     * 普通缓存获取
+     * @param keys 键
+     * @return 值
+     */
+    public Object getmAll(List<String> keys){
+        return keys==null?null:redisTemplate.opsForValue().multiGet(keys);
+    }
+
+    /**
+     * 普通缓存放入
+     * @param key 键
+     * @param value 值
+     * @return true成功 false失败
+     */
+    public boolean set(String key,Object value) {
+        try {
+            redisTemplate.opsForValue().set(key, value);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 普通缓存放入并设置时间
+     * @param key 键
+     * @param value 值
+     * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
+     * @return true成功 false 失败
+     */
+    public boolean set(String key,Object value,long time){
+        try {
+            if(time>0){
+                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
+            }else{
+                set(key, value);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 递增
+     * @param key 键
+     * @param delta 要增加几(大于0)
+     * @return
+     */
+    public long incr(String key, long delta){
+        if(delta<0){
+            throw new RuntimeException("递增因子必须大于0");
+        }
+        return redisTemplate.opsForValue().increment(key, delta);
+    }
+
+    /**
+     * 递减
+     * @param key 键
+     * @param delta 要减少几(小于0)
+     * @return
+     */
+    public long decr(String key, long delta){
+        if(delta<0){
+            throw new RuntimeException("递减因子必须大于0");
+        }
+        return redisTemplate.opsForValue().increment(key, -delta);
+    }
+
+    //================================Map=================================
+    /**
+     * HashGet
+     * @param key 键 不能为null
+     * @param item 项 不能为null
+     * @return 值
+     */
+    public Object hget(String key,String item){
+        return redisTemplate.opsForHash().get(key, item);
+    }
+
+    /**
+     * 获取hashKey对应的所有键值
+     * @param key 键
+     * @return 对应的多个键值
+     */
+    public Object hmget(String key){
+        return redisTemplate.opsForHash().entries(key);
+    }
+
+    /**
+     * 批量读取 keys
+     * @param keys 键
+     * @return 对应的多个键值
+     */
+    public List<Object> hmgetAll(List<String> keys){
+        List<Object> redisResult = redisTemplate.executePipelined((RedisCallback<Map<String, String>>) connection -> {
+            for (String key : keys) {
+                connection.hGetAll(key.getBytes());
+            }
+            return null;
+        });
+        return redisResult;
+    }
+
+    /**
+     * HashSet
+     * @param key 键
+     * @param map 对应多个键值
+     * @return true 成功 false 失败
+     */
+    public boolean hmset(String key, Map<String,Object> map){
+        try {
+            redisTemplate.opsForHash().putAll(key, map);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * HashSet 并设置时间
+     * @param key 键
+     * @param map 对应多个键值
+     * @param time 时间(秒)
+     * @return true成功 false失败
+     */
+    public boolean hmset(String key, Map<String,Object> map, long time){
+        try {
+            redisTemplate.opsForHash().putAll(key, map);
+            if(time>0){
+                expire(key, time);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 向一张hash表中放入数据,如果不存在将创建
+     * @param key 键
+     * @param item 项
+     * @param value 值
+     * @return true 成功 false失败
+     */
+    public boolean hset(String key,String item,Object value) {
+        try {
+            redisTemplate.opsForHash().put(key, item, value);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 向一张hash表中放入数据,如果不存在将创建
+     * @param key 键
+     * @param item 项
+     * @param value 值
+     * @param time 时间(秒)  注意:如果已存在的hash表有时间,这里将会替换原有的时间
+     * @return true 成功 false失败
+     */
+    public boolean hset(String key,String item,Object value,long time) {
+        try {
+            redisTemplate.opsForHash().put(key, item, value);
+            if(time>0){
+                expire(key, time);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 删除hash表中的值
+     * @param key 键 不能为null
+     * @param item 项 可以使多个 不能为null
+     */
+    public void hdel(String key, Object... item){
+        redisTemplate.opsForHash().delete(key,item);
+    }
+
+    /**
+     * 判断hash表中是否有该项的值
+     * @param key 键 不能为null
+     * @param item 项 不能为null
+     * @return true 存在 false不存在
+     */
+    public boolean hHasKey(String key, String item){
+        return redisTemplate.opsForHash().hasKey(key, item);
+    }
+
+    /**
+     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
+     * @param key 键
+     * @param item 项
+     * @param by 要增加几(大于0)
+     * @return
+     */
+    public double hincr(String key, String item,double by){
+        return redisTemplate.opsForHash().increment(key, item, by);
+    }
+
+    /**
+     * hash递减
+     * @param key 键
+     * @param item 项
+     * @param by 要减少记(小于0)
+     * @return
+     */
+    public double hdecr(String key, String item,double by){
+        return redisTemplate.opsForHash().increment(key, item,-by);
+    }
+
+    //============================set=============================
+    /**
+     * 根据key获取Set中的所有值
+     * @param key 键
+     * @return
+     */
+    public Set<Object> sGet(String key){
+        try {
+            return redisTemplate.opsForSet().members(key);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 根据value从一个set中查询,是否存在
+     * @param key 键
+     * @param value 值
+     * @return true 存在 false不存在
+     */
+    public boolean sHasKey(String key,Object value){
+        try {
+            return redisTemplate.opsForSet().isMember(key, value);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 将数据放入set缓存
+     * @param key 键
+     * @param values 值 可以是多个
+     * @return 成功个数
+     */
+    public long sSet(String key, Object...values) {
+        try {
+            return redisTemplate.opsForSet().add(key, values);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+
+    /**
+     * 将set数据放入缓存
+     * @param key 键
+     * @param time 时间(秒)
+     * @param values 值 可以是多个
+     * @return 成功个数
+     */
+    public long sSetAndTime(String key,long time,Object...values) {
+        try {
+            Long count = redisTemplate.opsForSet().add(key, values);
+            if(time>0) {
+                expire(key, time);
+            }
+            return count;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+
+    /**
+     * 获取set缓存的长度
+     * @param key 键
+     * @return
+     */
+    public long sGetSetSize(String key){
+        try {
+            return redisTemplate.opsForSet().size(key);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+
+    /**
+     * 移除值为value的
+     * @param key 键
+     * @param values 值 可以是多个
+     * @return 移除的个数
+     */
+    public long setRemove(String key, Object ...values) {
+        try {
+            Long count = redisTemplate.opsForSet().remove(key, values);
+            return count;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+    //===============================list=================================
+
+    /**
+     * 获取list缓存的内容
+     * @param key 键
+     * @param start 开始
+     * @param end 结束  0 到 -1代表所有值
+     * @return
+     */
+    public List<Object> lGet(String key, long start, long end){
+        try {
+            return redisTemplate.opsForList().range(key, start, end);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 获取list缓存的长度
+     * @param key 键
+     * @return
+     */
+    public long lGetListSize(String key){
+        try {
+            return redisTemplate.opsForList().size(key);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+
+    /**
+     * 通过索引 获取list中的值
+     * @param key 键
+     * @param index 索引  index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
+     * @return
+     */
+    public Object lGetIndex(String key,long index){
+        try {
+            return redisTemplate.opsForList().index(key, index);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 将list放入缓存
+     * @param key 键
+     * @param value 值
+     * @return
+     */
+    public boolean lSet(String key, Object value) {
+        try {
+            redisTemplate.opsForList().rightPush(key, value);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 将list放入缓存
+     * @param key 键
+     * @param value 值
+     * @param time 时间(秒)
+     * @return
+     */
+    public boolean lSet(String key, Object value, long time) {
+        try {
+            redisTemplate.opsForList().rightPush(key, value);
+            if (time > 0) {
+                expire(key, time);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 将list放入缓存
+     * @param key 键
+     * @param value 值
+     * @return
+     */
+    public boolean lSet(String key, List<Object> value) {
+        try {
+            redisTemplate.opsForList().rightPushAll(key, value);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 将list放入缓存
+     * @param key 键
+     * @param value 值
+     * @param time 时间(秒)
+     * @return
+     */
+    public boolean lSet(String key, List<Object> value, long time) {
+        try {
+            redisTemplate.opsForList().rightPushAll(key, value);
+            if (time > 0) {
+                expire(key, time);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 根据索引修改list中的某条数据
+     * @param key 键
+     * @param index 索引
+     * @param value 值
+     * @return
+     */
+    public boolean lUpdateIndex(String key, long index,Object value) {
+        try {
+            redisTemplate.opsForList().set(key, index, value);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 移除N个值为value
+     * @param key 键
+     * @param count 移除多少个
+     * @param value 值
+     * @return 移除的个数
+     */
+    public long lRemove(String key,long count,Object value) {
+        try {
+            Long remove = redisTemplate.opsForList().remove(key, count, value);
+            return remove;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+
+    /**
+     * 模糊查询获取key值
+     * @param pattern
+     * @return
+     */
+    public Set keys(String pattern){
+        return redisTemplate.keys(pattern);
+    }
+
+    /**
+     * 使用Redis的消息队列
+     * @param channel
+     * @param message 消息内容
+     */
+    public void convertAndSend(String channel, Object message){
+        redisTemplate.convertAndSend(channel,message);
+    }
+}

+ 62 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/utils/SpringContextUtil.java

@@ -0,0 +1,62 @@
+package com.gyee.edge.gddlly.utils;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SpringContextUtil implements ApplicationContextAware {
+
+    /**
+     * 上下文对象实例
+     */
+    private static ApplicationContext applicationContext;
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+        this.applicationContext = applicationContext;
+    }
+
+    /**
+     * 获取applicationContext
+     *
+     * @return
+     */
+    public static ApplicationContext getApplicationContext() {
+        return applicationContext;
+    }
+
+    /**
+     * 通过name获取 Bean.
+     *
+     * @param name
+     * @return
+     */
+    public static Object getBean(String name) {
+        return getApplicationContext().getBean(name);
+    }
+
+    /**
+     * 通过class获取Bean.
+     *
+     * @param clazz
+     * @param <T>
+     * @return
+     */
+    public static <T> T getBean(Class<T> clazz) {
+        return getApplicationContext().getBean(clazz);
+    }
+
+    /**
+     * 通过name,以及Clazz返回指定的Bean
+     *
+     * @param name
+     * @param clazz
+     * @param <T>
+     * @return
+     */
+    public static <T> T getBean(String name, Class<T> clazz) {
+        return getApplicationContext().getBean(name, clazz);
+    }
+}

+ 261 - 0
gdnxfgs/src/main/java/com/gyee/edge/gddlly/utils/Util.java

@@ -0,0 +1,261 @@
+package com.gyee.edge.gddlly.utils;
+
+
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * 工具类
+ *
+ * @author Administrator 2018-04-07
+ */
+
+public class Util {
+    /**
+     * 字节数组转换成String,指定长度转换长度
+     *
+     * @param arrBytes
+     * @param count    转换长度
+     * @param blank    要不要空格(每个byte字节,最是否用一个“ ”隔开)
+     * @return "" | arrBytes换成的字符串(不存在null)
+     */
+    public static String byteArray2HexString(byte[] arrBytes, int count, boolean blank) {
+        String ret = "";
+        if (arrBytes == null || arrBytes.length < 1)
+            return ret;
+        if (count > arrBytes.length)
+            count = arrBytes.length;
+        StringBuilder builder = new StringBuilder();
+
+        for (int i = 0; i < count; i++) {
+            ret = Integer.toHexString(arrBytes[i] & 0xFF).toUpperCase();
+            if (ret.length() == 1)
+                builder.append("0").append(ret);
+            else
+                builder.append(ret);
+            if (blank)
+                builder.append(" ");
+        }
+
+        return builder.toString();
+
+    }
+
+    /**
+     * 将两个ASCII字符合成一个字节; 如:"EF"--> 0xEF
+     *
+     * @param src0 byte
+     * @param src1 byte
+     * @return byte
+     */
+    public static byte uniteBytes(byte src0, byte src1) {
+        byte _b0 = Byte.decode("0x" + new String(new byte[]{src0})).byteValue();
+        _b0 = (byte) (_b0 << 4);
+        byte _b1 = Byte.decode("0x" + new String(new byte[]{src1})).byteValue();
+        byte ret = (byte) (_b0 ^ _b1);
+        return ret;
+    }
+
+    /**
+     * 将字节数组转换成16进制字符串
+     *
+     * @param array 需要转换的字符串(字节间没有分隔符)
+     * @return 转换完成的字符串
+     */
+    public static String byteArrayToHexString(byte[] array) {
+        return byteArray2HexString(array, Integer.MAX_VALUE, false);
+    }
+
+    /**
+     * 时标CP56Time2a解析
+     *
+     * @param b 时标CP56Time2a(长度为7 的int数组)
+     * @return 解析结果
+     */
+    public static String TimeScale(int b[]) {
+
+        String str = "";
+        int year = b[6] & 0x7F;
+        int month = b[5] & 0x0F;
+        int day = b[4] & 0x1F;
+        int week = (b[4] & 0xE0) / 32;
+        int hour = b[3] & 0x1F;
+        int minute = b[2] & 0x3F;
+        int second = (b[1] << 8) + b[0];
+
+        str += "时标CP56Time2a:" + "20" + year + "-"
+                + String.format("%02d", month) + "-"
+                + String.format("%02d", day) + "," + hour + ":" + minute + ":"
+                + second / 1000 + "." + second % 1000;
+        return str + "\n";
+    }
+
+    /**
+     * 16进制表示的字符串转换为字节数组
+     *
+     * @param s 16进制表示的字符串
+     * @return byte[] 字节数组
+     */
+    public static int[] hexStringToIntArray(String s) {
+        int len = s.length();
+        int[] b = new int[len / 2];
+        for (int i = 0; i < len; i += 2) {
+            // 两位一组,表示一个字节,把这样表示的16进制字符串,还原成一个字节
+            b[i / 2] = (int) ((Character.digit(s.charAt(i), 16) << 4) + Character
+                    .digit(s.charAt(i + 1), 16));
+        }
+        return b;
+    }
+
+    /**
+     * int转换成16进制字符串
+     *
+     * @param b 需要转换的int值
+     * @return 16进制的String
+     */
+    public static String toHexString(int b) {
+        String hex = Integer.toHexString(b & 0xFF);
+        if (hex.length() == 1) {
+            hex = '0' + hex;
+        }
+        return "0x" + hex.toUpperCase();
+    }
+
+    /**
+     * 检验CS校验和
+     *
+     * @param vCS 需要检验的部分
+     * @param i   CS校验和
+     * @return 检验结果(字符串格式)
+     */
+    public static String variableCS(int[] vCS, int i) {
+        String str = "";
+        str += "校验和CS=";
+        str += toHexString(i);
+        int sum = 0;
+        for (int j = 0; j < vCS.length; j++) {
+            sum += vCS[j];
+        }
+        if ((sum % 256) == i) {
+            str += "   校验无误!";
+
+        } else {
+            str += "   经校验,报文有误!";
+        }
+        return str;
+
+    }
+
+    /**
+     * 解析地址域
+     *
+     * @param low  第一个地址
+     * @param high 第二个地址
+     * @return
+     */
+    public static String address(int low, int high) {
+
+        String lowString = String.format("%02X", low);
+        String highString = String.format("%02X", high);
+
+        return highString + lowString + "H" + "\n";
+    }
+
+    public static String getAddressStr(int address) {
+        String addressStr = String.format("%04X", address);
+        return addressStr.substring(2, 4) + addressStr.substring(0, 2);
+    }
+
+    public static String getAddressStr3(int address) {
+        String addressStr = String.format("%06X", address);
+        return addressStr.substring(4, 6) + addressStr.substring(2, 4) + addressStr.substring(0, 2);
+    }
+
+    public static String getInfoStr(int info, int length) {
+        StringBuilder builder = new StringBuilder();
+        String infoFormat = "%0" + 2 * length + "X";
+        String infoStr = String.format(infoFormat, info);
+        for (int i = infoStr.length()/2; i >0; i--) {
+            builder.append(infoStr.substring(2*i-2,2*i));
+        }
+        return builder.toString();
+    }
+
+    public static String date2HStr(Date date) {
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(date);
+        StringBuilder builder = new StringBuilder();
+        String milliSecond = String.format("%04X", (calendar.get(Calendar.SECOND) * 1000) + calendar.get(Calendar.MILLISECOND));
+        builder.append(milliSecond.substring(2, 4));
+        builder.append(milliSecond.substring(0, 2));
+        builder.append(String.format("%02X", calendar.get(Calendar.MINUTE) & 0x3F));
+        builder.append(String.format("%02X", calendar.get(Calendar.HOUR_OF_DAY) & 0x1F));
+        int week = calendar.get(Calendar.DAY_OF_WEEK);
+        if (week == Calendar.SUNDAY)
+            week = 7;
+        else week--;
+        builder.append(String.format("%02X", (week << 5) + (calendar.get(Calendar.DAY_OF_MONTH) & 0x1F)));
+        builder.append(String.format("%02X", calendar.get(Calendar.MONTH) + 1));
+        builder.append(String.format("%02X", calendar.get(Calendar.YEAR) - 2000));
+        return builder.toString();
+    }
+
+    public static long add2long(int address) {
+        int addressLong = ((address & 0xff00) >> 8) + (address & 0x00ff);
+        return addressLong % 256;
+    }
+
+    public static long CP56Time2Long(Date dateTime) {
+        long sum = 0;
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(dateTime);
+        StringBuilder builder = new StringBuilder();
+        long milliSecond = (calendar.get(Calendar.SECOND) * 1000) + calendar.get(Calendar.MILLISECOND);
+        sum += ((milliSecond & 0xff00) >> 8) % 256;
+        sum += (milliSecond & 0x00ff) % 256;
+        sum += (calendar.get(Calendar.MINUTE) & 0x3F) % 256;
+        sum += (calendar.get(Calendar.HOUR_OF_DAY) & 0x1F) % 256;
+
+        int week = calendar.get(Calendar.DAY_OF_WEEK);
+        if (week == Calendar.SUNDAY)
+            week = 7;
+        else week--;
+        sum += ((week << 5) + (calendar.get(Calendar.DAY_OF_MONTH) & 0x1F)) % 256;
+        sum += (calendar.get(Calendar.MONTH) + 1) % 256;
+        sum += (calendar.get(Calendar.YEAR) - 2000) % 256;
+        return sum;
+    }
+
+    /**
+     * 获取I格式的104报文的控制域
+     */
+    public static String getInformationTransmitFormat(int sendNumber, int receiveNnumber) {
+        sendNumber = sendNumber << 1;
+        receiveNnumber = receiveNnumber << 1;
+        String s = String.format("%04X", sendNumber);
+        String r = String.format("%04X", receiveNnumber);
+
+        return s.substring(2, 4) + s.substring(0, 2) + r.substring(2, 4) + r.substring(0, 2);
+    }
+
+    /**
+     * 获取S格式的104报文的控制域
+     */
+    public static String getNumberedSupervisoryFunction(int receiveNnumber) {
+        receiveNnumber = receiveNnumber << 1;
+        String r = String.format("%04X", receiveNnumber);
+
+        return "01" + "00" + r.substring(2, 4) + r.substring(0, 2);
+    }
+
+    /**
+     * 获取U格式的104报文的控制域
+     */
+    public static String getUnnumberedControlFunction(boolean tester, boolean stopdt, boolean startdt) {
+        int con = 3;
+        con += (tester ? 2 : 1) << 6;
+        con += (stopdt ? 2 : 1) << 4;
+        con += (startdt ? 2 : 1) << 2;
+        return String.format("%02X", con) + "000000";
+    }
+}

+ 44 - 0
gdnxfgs/src/main/resources/application.yaml

@@ -0,0 +1,44 @@
+spring:
+  application:
+  name: gdnxfgs
+  datasource:
+    driver-class-name: org.sqlite.JDBC
+    #    F:\\colud_ideaspace\\edge\\gateway\\src\\main\\resources\\myDb
+    #url: jdbc:sqlite::resource:gddlly.db
+    url: jdbc:sqlite::resource:gdnxzs.sqlite
+    username:
+    password:
+
+
+#  redis:
+#    database: 0
+#    cluster:
+#      nodes: 127.0.0.1:6380,127.0.0.1:6381,127.0.0.1:6382,127.0.0.1:6383,127.0.0.1:6384,127.0.0.1:6385 #集群节点
+#    password: gdnxfd@123     #密码
+#    lettuce:
+#      pool:
+#        max-active: 40  #连接池最大连接数
+#        max-idle: 8     #连接池中最大空闲连接数
+#        max-wait: -1ms  #连接池最大等待阻塞时间
+#        min-idle: 10     #连接池中最小空闲数
+#    timeout: 10000      #访问超时时间
+
+  redis:
+    database: 0
+    host: 192.168.1.84
+    password:
+    pool:
+      maxTotal: 20
+      maxIdle: 20
+      maxwait: 600000
+      minIdle: 10
+    port: 6379
+    timeout: 600000
+
+iec104:
+  port: 2404
+  fullPushInterval: 60000
+  changedPushInterval: 10000
+  frameAiMax: 16
+  frameDiMax: 22
+

+ 7 - 0
gdnxfgs/src/main/resources/banner.txt

@@ -0,0 +1,7 @@
+ ######   ########  ########  ##       ##       ##    ##
+##    ##  ##     ## ##     ## ##       ##        ##  ##
+##        ##     ## ##     ## ##       ##         ####
+##   #### ##     ## ##     ## ##       ##          ##
+##    ##  ##     ## ##     ## ##       ##          ##
+##    ##  ##     ## ##     ## ##       ##          ##
+ ######   ########  ########  ######## ########    ##

BIN
gdnxfgs/src/main/resources/gdnxzs.sqlite


+ 1 - 0
settings.gradle

@@ -6,3 +6,4 @@ include "gateway"
 include "bridge"
 include "loader"
 include "gddlly"
+include "gdnxfgs"