更新时间:2022-02-21 GMT+08:00

实现样例讲解

在编解码插件的DEMO工程(点击获取)中,提供了编解码插件样例,样例工程结构如下图所示。

图1 样例工程结构图

本工程是一个Maven工程,开发者可在此样例工程的基础上修改如下部分,适配成自己需要的编解码插件:

说明:

加密算法请使用JDK自带的加密算法,JDK支持的加密算法,详见附录:JDK支持的加密算法

  • Maven的配置文件

    在pom.xml文件中,根据命令规范,修改编解码插件的名字。

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    <groupId>com.thrid.party</groupId>
    <!-- 请修改为你的编解码插件的名字,命名规范:设备类型-厂商ID-设备型号,例如:WaterMeter -Huawei-NBIoTDevice -->
    <artifactId>WaterMeter-Huawei-NBIoTDevice</artifactId>
    <version>1.0.0</version>
    <!-- 请检查这里的值为bundle,不能为jar -->
    <packaging>bundle</packaging>
    
    <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <junit.version>4.11</junit.version>
    <fasterxml.jackson.version>2.7.4</fasterxml.jackson.version>
    <felix.maven.plugin.version>2.5.4.fixed2</felix.maven.plugin.version>
    <json.lib.version>2.4</json.lib.version>
    <m2m.cig.version>1.3.1</m2m.cig.version>
    <slf4j.api.version>1.7.6</slf4j.api.version>
    </properties>
    
    <dependencies>
    <!-- 单元测试使用 -->
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>${junit.version}</version>
    </dependency>
    <!-- 日志使用 -->
    <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>${slf4j.api.version}</version>
    </dependency>
    <!-- 转换JSON使用,必须 -->
    <dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>${fasterxml.jackson.version}</version>
    </dependency>
    <!-- Huawei提供的编解码接口,必须 -->
    <!-- systemPath请替换成你本地的目录 \codecDemo\lib\com.huawei.m2m.cig.tup-1.3.1.jar -->
    <dependency>
    <groupId>com.huawei</groupId>
    <artifactId>protocal-jar</artifactId>
    <version>1.3.1</version>
    <scope>system</scope>
    <systemPath>${basedir}/lib/com.huawei.m2m.cig.tup-1.3.1.jar</systemPath>
    </dependency>
    
    <!-- 本例中数据转换使用到的jar,你用到的jar请写到这里,记得把artifactId填入后面的Embed-Dependency -->
    <dependency>
    <groupId>net.sf.json-lib</groupId>
    <artifactId>json-lib</artifactId>
    <version>2.4</version>
    <classifier>jdk15</classifier>
    </dependency>
    
    </dependencies>
    <build>
    <plugins>
    <!-- 编码需要使用JDK1.8版本 -->
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
    <source>1.8</source>
    <target>1.8</target>
    </configuration>
    </plugin>
    <!-- OSGI规范打包配置 -->
    <plugin>
    <groupId>org.apache.felix</groupId>
    <artifactId>maven-bundle-plugin</artifactId>
    <version>${felix.maven.plugin.version}</version>
    <extensions>true</extensions>
    <configuration>
    <buildDirectory>./target</buildDirectory>
    <archive>
    <addMavenDescriptor>false</addMavenDescriptor>
    </archive>
    <instructions>
    <Bundle-RequiredExecutionEnvironment>J2SE-1.5</Bundle-RequiredExecutionEnvironment>
    <Bundle-Activator></Bundle-Activator>
    <Service-Component>OSGI-INF/*</Service-Component>
    <!-- 请修改为你的编解码插件的名字,命名规范:设备类型-厂商ID-设备型号,例如:WaterMeter-Huawei-NBIoTDevice -->
    <Bundle-SymbolicName>WaterMeter-Huawei-NBIoTDevice</Bundle-SymbolicName>
    <Export-Package></Export-Package>
    <!-- 把代码中import的package都引入进来,使用逗号分隔。【有两种jar不需要的填到Import-Package,否则插件无法启动,这两种jar分别为:①java.**开头的包;②引用的是Embed-Dependency里面的jar】 -->
    <Import-Package>
    org.slf4j,
    org.slf4j.spi,
    org.apache.log4j.spi,
    com.huawei.m2m.cig.tup.modules.protocol_adapter,
    com.fasterxml.jackson.databind,
    com.fasterxml.jackson.databind.node
    </Import-Package>
    <!-- 除junit,slf4j-api,jackson-databind,protocal-jar, 其他所有的依赖包,必须把包对应的artifactId填入Embed-Dependency。artifactId之间以逗号分隔。Maven打包时你的依赖包需要打入到你的jar内。 -->
    <Embed-Dependency>
    json-lib
    </Embed-Dependency>
    </instructions>
    </configuration>
    <executions>
    <execution>
    <id>generate-resource</id>
    <goals>
    <goal>manifest</goal>
    </goals>
    </execution>
    </executions>
    </plugin>
    </plugins>
    </build>
    </project>
  • 编解码代码实现
    • 在ProtocolAdapterImpl.java中,修改厂商ID(MANU_FACTURERID)和设备型号(MODEL)的取值。IoT平台通过厂商ID和设备型号将编解码插件和Profile文件进行关联。
      1
      2
      3
      4
      5
      private static final Logger logger = LoggerFactory.getLogger(ProtocolAdapterImpl.class);
      // 厂商名称
      private static final String MANU_FACTURERID = "Huawei";
      // 设备型号
      private static final String MODEL = "NBIoTDevice";
      
    • 修改CmdProcess.java中的代码,实现插件对下发命令和上报数据响应的编码能力。
        1
        2
        3
        4
        5
        6
        7
        8
        9
       10
       11
       12
       13
       14
       15
       16
       17
       18
       19
       20
       21
       22
       23
       24
       25
       26
       27
       28
       29
       30
       31
       32
       33
       34
       35
       36
       37
       38
       39
       40
       41
       42
       43
       44
       45
       46
       47
       48
       49
       50
       51
       52
       53
       54
       55
       56
       57
       58
       59
       60
       61
       62
       63
       64
       65
       66
       67
       68
       69
       70
       71
       72
       73
       74
       75
       76
       77
       78
       79
       80
       81
       82
       83
       84
       85
       86
       87
       88
       89
       90
       91
       92
       93
       94
       95
       96
       97
       98
       99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      package com.Huawei.NBIoTDevice.WaterMeter;
      
      import com.fasterxml.jackson.databind.JsonNode;
      import com.fasterxml.jackson.databind.node.ObjectNode;
      
      public class CmdProcess {
      
          //private String identifier = "123";
          private String msgType = "deviceReq";
          private String serviceId = "Brightness";
          private String cmd = "SET_DEVICE_LEVEL";
          private int hasMore = 0;
          private int errcode = 0;
          private int mid = 0;
          private JsonNode paras;
      
      
          public CmdProcess() {
          }
      
          public CmdProcess(ObjectNode input) {
      
              try {
                  // this.identifier = input.get("identifier").asText();
                  this.msgType = input.get("msgType").asText();
                  /*
                  平台收到设备上报消息,编码ACK
                  {
                      "identifier":"0",
                      "msgType":"cloudRsp",
                      "request": ***,//设备上报的码流
                      "errcode":0,
                      "hasMore":0
                  }
                  * */
                  if (msgType.equals("cloudRsp")) {
                      //在此组装ACK的值
                      this.errcode = input.get("errcode").asInt();
                      this.hasMore = input.get("hasMore").asInt();
                  } else {
                  /*
                  平台下发命令到设备,输入
                  {
                      "identifier":0,
                      "msgType":"cloudReq",
                      "serviceId":"WaterMeter",
                      "cmd":"SET_DEVICE_LEVEL",
                      "paras":{"value":"20"},
                      "hasMore":0
      
                  }
                  * */
                      //此处需要考虑兼容性,如果没有传mId,则不对其进行编码
                      if (input.get("mid") != null) {
                          this.mid = input.get("mid").intValue();
                      }
                      this.cmd = input.get("cmd").asText();
                      this.paras = input.get("paras");
                      this.hasMore = input.get("hasMore").asInt();
                  }
      
              } catch (Exception e) {
                  e.printStackTrace();
              }
      
          }
      
          public byte[] toByte() {
              try {
                  if (this.msgType.equals("cloudReq")) {
                      /*
                      应用服务器下发的控制命令,本例只有一条控制命令:SET_DEVICE_LEVEL
                      如果有其他控制命令,增加判断即可。
                      * */
                      if (this.cmd.equals("SET_DEVICE_LEVEL")) {
                          int brightlevel = paras.get("value").asInt();
                          byte[] byteRead = new byte[5];
                          ByteBufUtils buf = new ByteBufUtils(byteRead);
                          buf.writeByte((byte) 0xAA);
                          buf.writeByte((byte) 0x72);
                          buf.writeByte((byte) brightlevel);
      
                          //此处需要考虑兼容性,如果没有传mId,则不对其进行编码
                          if (Utilty.getInstance().isValidofMid(mid)) {
                              byte[] byteMid = new byte[2];
                              byteMid = Utilty.getInstance().int2Bytes(mid, 2);
                              buf.writeByte(byteMid[0]);
                              buf.writeByte(byteMid[1]);
                          }
      
                          return byteRead;
                      }
                  }
      
                  /*
                  平台收到设备的上报数据,根据需要编码ACK,对设备进行响应,如果此处返回null,表示不需要对设备响应。
                  * */
                  else if (this.msgType.equals("cloudRsp")) {
                      byte[] ack = new byte[4];
                      ByteBufUtils buf = new ByteBufUtils(ack);
                      buf.writeByte((byte) 0xAA);
                      buf.writeByte((byte) 0xAA);
                      buf.writeByte((byte) this.errcode);
                      buf.writeByte((byte) this.hasMore)
                      return ack;
                  }
                  return null;
              } catch (Exception e) {
                  // TODO: handle exception
                  e.printStackTrace();
                  return null;
              }
          }
      
      }
      
    • 修改ReportProcess.java中的代码,实现插件对设备上报数据和命令执行结果的解码能力。
        1
        2
        3
        4
        5
        6
        7
        8
        9
       10
       11
       12
       13
       14
       15
       16
       17
       18
       19
       20
       21
       22
       23
       24
       25
       26
       27
       28
       29
       30
       31
       32
       33
       34
       35
       36
       37
       38
       39
       40
       41
       42
       43
       44
       45
       46
       47
       48
       49
       50
       51
       52
       53
       54
       55
       56
       57
       58
       59
       60
       61
       62
       63
       64
       65
       66
       67
       68
       69
       70
       71
       72
       73
       74
       75
       76
       77
       78
       79
       80
       81
       82
       83
       84
       85
       86
       87
       88
       89
       90
       91
       92
       93
       94
       95
       96
       97
       98
       99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      128
      129
      130
      131
      132
      133
      134
      135
      136
      137
      138
      139
      140
      141
      142
      143
      144
      145
      146
      147
      148
      149
      150
      151
      152
      153
      154
      155
      156
      157
      158
      159
      160
      161
      162
      163
      164
      165
      166
      167
      168
      169
      170
      171
      172
      173
      174
      175
      176
      177
      178
      179
      180
      181
      182
      183
      package com.Huawei.NBIoTDevice.WaterMeter;
      
      import com.fasterxml.jackson.databind.ObjectMapper;
      import com.fasterxml.jackson.databind.node.ArrayNode;
      import com.fasterxml.jackson.databind.node.ObjectNode;
      
      public class ReportProcess {
          //private String identifier;
      
          private String msgType = "deviceReq";
          private int hasMore = 0;
          private int errcode = 0;
          private byte bDeviceReq = 0x00;
          private byte bDeviceRsp = 0x01;
      
          //serviceId=Brightness字段
          private int brightness = 0;
      
          //serviceId=Electricity字段
          private double voltage = 0.0;
          private int current = 0;
          private double frequency = 0.0;
          private double powerfactor = 0.0;
      
          //serviceId=Temperature字段
          private int temperature = 0;
      
          private byte noMid = 0x00;
          private byte hasMid = 0x01;
          private boolean isContainMid = false;
          private int mid = 0;
      
          /**
           * @param binaryData 设备发送给平台coap报文的payload部分
           *                   本例入参:AA 72 00 00 32 08 8D 03 20 62 33 99
           *                   byte[0]--byte[1]:  AA 72 命令头
           *                   byte[2]:   00 mstType 00表示设备上报数据deviceReq
           *                   byte[3]:   00 hasMore  0表示没有后续数据,1表示有后续数据,不带按照0处理
           *                   byte[4]--byte[11]:服务数据,根据需要解析//如果是deviceRsp,byte[4]表示是否携带mid, byte[5]--byte[6]表示短命令Id
           * @return
           */
          public ReportProcess(byte[] binaryData) {
              // identifier参数可以根据入参的码流获得,本例指定默认值123
              // identifier = "123";
      
              /*
              如果是设备上报数据,返回格式为
              {
                  "identifier":"123",
                  "msgType":"deviceReq",
                  "hasMore":0,
                  "data":[{"serviceId":"Brightness",
                            "serviceData":{"brightness":50},
                            {
                            "serviceId":"Electricity",
                            "serviceData":{"voltage":218.9,"current":800,"frequency":50.1,"powerfactor":0.98},
                            {
                            "serviceId":"Temperature",
                            "serviceData":{"temperature":25},
                            ]
      	    }
      	    */
              if (binaryData[2] == bDeviceReq) {
                  msgType = "deviceReq";
                  hasMore = binaryData[3];
      
                  //serviceId=Brightness 数据解析
                  brightness = binaryData[4];
      
                  //serviceId=Electricity 数据解析
                  voltage = (double) (((binaryData[5] << 8) + (binaryData[6] & 0xFF)) * 0.1f);
                  current = (binaryData[7] << 8) + binaryData[8];
                  powerfactor = (double) (binaryData[9] * 0.01);
                  frequency = (double) binaryData[10] * 0.1f + 45;
      
                  //serviceId=Temperature 数据解析
                  temperature = (int) binaryData[11] & 0xFF - 128;
              }
              /*
              如果是设备对平台命令的应答,返回格式为:
             {
                  "identifier":"123",
                  "msgType":"deviceRsp",
                  "errcode":0,
                  "body" :{****} 特别注意该body体为一层json结构。
              }
      	    */
              else if (binaryData[2] == bDeviceRsp) {
                  msgType = "deviceRsp";
                  errcode = binaryData[3];
                  //此处需要考虑兼容性,如果没有传mId,则不对其进行解码
                  if (binaryData[4] == hasMid) {
                      mid = Utilty.getInstance().bytes2Int(binaryData, 5, 2);
                      if (Utilty.getInstance().isValidofMid(mid)) {
                          isContainMid = true;
                      }
      
                  }
              } else {
                  return;
              }
      
      
          }
      
          public ObjectNode toJsonNode() {
              try {
                  //组装body体
                  ObjectMapper mapper = new ObjectMapper();
                  ObjectNode root = mapper.createObjectNode();
      
                  // root.put("identifier", this.identifier);
                  root.put("msgType", this.msgType);
      
                  //根据msgType字段组装消息体
                  if (this.msgType.equals("deviceReq")) {
                      root.put("hasMore", this.hasMore);
                      ArrayNode arrynode = mapper.createArrayNode();
      
                      //serviceId=Brightness 数据组装
                      ObjectNode brightNode = mapper.createObjectNode();
                      brightNode.put("serviceId", "Brightness");
                      ObjectNode brightData = mapper.createObjectNode();
                      brightData.put("brightness", this.brightness);
                      brightNode.put("serviceData", brightData);
                      arrynode.add(brightNode);
                      //serviceId=Electricity 数据组装
                      ObjectNode electricityNode = mapper.createObjectNode();
                      electricityNode.put("serviceId", "Electricity");
                      ObjectNode electricityData = mapper.createObjectNode();
                      electricityData.put("voltage", this.voltage);
                      electricityData.put("current", this.current);
                      electricityData.put("frequency", this.frequency);
                      electricityData.put("powerfactor", this.powerfactor);
                      electricityNode.put("serviceData", electricityData);
                      arrynode.add(electricityNode);
                      //serviceId=Temperature 数据组装
                      ObjectNode temperatureNode = mapper.createObjectNode();
                      temperatureNode.put("serviceId", "Temperature");
                      ObjectNode temperatureData = mapper.createObjectNode();
                      temperatureData.put("temperature", this.temperature);
                      temperatureNode.put("serviceData", temperatureData);
                      arrynode.add(temperatureNode);
      
                      //serviceId=Connectivity 数据组装
                      ObjectNode ConnectivityNode = mapper.createObjectNode();
                      ConnectivityNode.put("serviceId", "Connectivity");
                      ObjectNode  ConnectivityData = mapper.createObjectNode();
                      ConnectivityData.put("signalStrength", 5);
                      ConnectivityData.put("linkQuality", 10);
                      ConnectivityData.put("cellId", 9);
                      ConnectivityNode.put("serviceData", ConnectivityData);
                      arrynode.add(ConnectivityNode);
      
                      //serviceId=battery 数据组装
                      ObjectNode batteryNode = mapper.createObjectNode();
                      batteryNode.put("serviceId", "battery");
                      ObjectNode batteryData = mapper.createObjectNode();
                      batteryData.put("batteryVoltage", 25);
                      batteryData.put("battervLevel", 12);
                      batteryNode.put("serviceData", batteryData);
                      arrynode.add(batteryNode);
      
                      root.put("data", arrynode);
      
                  } else {
                      root.put("errcode", this.errcode);
                      //此处需要考虑兼容性,如果没有传mid,则不对其进行解码
                      if (isContainMid) {
                          root.put("mid", this.mid);//mid
                      }
                      //组装body体,只能为ObjectNode对象
                      ObjectNode body = mapper.createObjectNode();
                      body.put("result", 0);
                      root.put("body", body);
                  }
                  return root;
              } catch (Exception e) {
                  e.printStackTrace();
                  return null;
              }
          }
      }