【OC平台】编解码插件离线开发教程【二】

网友投稿 672 2022-05-30

本文是编解码插件离线教程的第二篇,也是最后一篇,主要从零开始详细讲解下工程的建立、代码的编写过程。

三、    插件编写

1、源文件说明

从华为资源中心下载编解码插件Demo(下载地址:https://developer.obs.cn-north-4.myhuaweicloud.com/manage/tool/CodecDemo/CodecDemo.zip),并解压到本地。文件结构如下图所示:

源文件在src文件夹下;编译生成的插件包在target文件夹下。src 文件夹包含 main 、test 两个子文件夹,main下存放源码,test下是单元测试代码。

官网下载的Demo中,源码的路径是:src\main\java\com\Huawei\NBIoTDevice\WaterMeter ;

单元测试代码的路径是:src\test\java\com\Huawei\NBIoTDevice\WaterMeter。

插件源码文件有5个:

a)    ProtocolAdapterImpl.java 可以理解为是插件的入口文件,对外提供调用接口。该文件只需要修改两个字符串的定义即可:

// 厂商名称 private static final String MANU_FACTURERID = "Huawei"; // 设备型号 private static final String MODEL = "NBIoTDevice";

修改为profile当中定义的厂商ID和设备型号。

b)    CmdProcess.java 实现下行命令的编码工作,将从收到的服务器报文中提取出命令字段对应的内容,并将其转换成字节流。

需要实现的函数是:  public byte[] toByte()。

c)    ReportProcess.java 实现将收到的二进制码流按照格式解码出对应profile中的属性值,并生成JSON格式。

需要实现的函数是:

public ReportProcess(byte[] binaryData),根据二进制码流的格式,从中取出对应字节,转换成profile中对应属性的值。

public ObjectNode toJsonNode(),将解码出来的属性值封装成JSON格式。

d) ByteBufUtils.java 和 Utilty.java 文件封装了一些公共方法,不用做修改。也不会使用到。

2、修改文件路径(包名)

插件包名的要求是:com.厂商名称.设备型号.设备类型。因此下载下来的代码,要根据自己的设备修改下文件路径。即将Huawei文件夹重命名为profile中定义的厂商名称,NBIoTDevice文件夹重命名为profile中定义的设备型号,WaterMeter文件夹重命名为profile中定义的设备类型。注意:src\main 和src\test 下都要修改。在本例中,需要修改为:

src\main\java\com\ThirdParty\MyModel\MyTyp, src\test\java\com\ThirdParty\MyModel\MyType

3、修改pom.xml

打开pom.xml文件,修改第7行“artifactId”和第88行“Bundle-SymbolicName”的值为:设备类型-厂商ID-设备型号。在本例中,需要修改为:MyType-ThirdParty-MyModel。注:根据自己的实际情况该写即可。

4、导入工程

打开eclipse,点击file->import,在弹出窗口中选择maven工程,如下图所示:

之后在弹出的窗口中,点击Browse,选择工程路径(pom.xml文件所在路径)。工程导入后如下图所示:

从上图中可以看到首次导入工程后是有错误的。这是因为我们在第2节中将文件路径修改了,与代码里面的包路径不一致引起的。解决方法为:依次打开源文件,将第一行的

package com.Huawei.NBIoTDevice.WaterMeter;

修改为:

package com.ThirdParty.MyModel.MyType;

接着,打开OSGI_INF目录下的CodeProvideHandler.xml 文件:

打开后,文件内容如下图所示:

将Name 、 Class* 内的路径也修改为对应的包路径:

5、代码实现

第1节中说明了各个源文件要修改的地方,本节中具体讲解实现的方法。

1) 修改ProtocolAdatpterImpl.java文件

在文件中找到如下两行:

// 厂商名称                 private static final String MANU_FACTURERID = "Huawei";         // 设备型号         private static final String MODEL = "NBIoTDevice";

将MANU_FACTURERID 和 MODEL定义修改为profile中定义的厂商ID和设备型号,本例中需要修改为:

// 厂商名称     private static final String MANU_FACTURERID = "ThirdParty";     // 设备型号     private static final String MODEL = "MyModel";

2) 解码实现

解码,是将NB模组上报的二进制码流按格式解析出对应字段的过程。解码的代码在ReportProcess.java 文件中。

第一个函数:public ReportProcess(byte[] binaryData) 入参 byte[] binaryData就是NB模组上报的二进制码流。解码得到数据存储在成员变量当中。本例中的代码实现如下:

public ReportProcess(byte[] binaryData) { /* 设备上报数据格式为:前两个字节表示batteryLevel,大端;  * 第三个字节表示后续字节长度;  * 第四个字节到最后表示不定长负载数据,其长度由第三个字节的值表示  * 因此,数据长度的合法值是: binaryData[2] + 3  * BatteryLevel和upData是定义的成员变量.用来存储解码得到的数据。  */     int tempVal;     //长度校验,不合法则直接退出     if(binaryData.length < 3)     {     return;     }     tempVal = binaryData[2] & 0xFF;     if(binaryData.length < tempVal + 3)     {     return;     }     //计算batteryLevel,将前两个字节拼起来     tempVal = ((binaryData[0] << 8)&0xff00) + (binaryData[1] & 0xFF);     this.BatteryLevel = tempVal;     //取出不定长负载,存入upData字段     tempVal = binaryData[2] & 0xFF;     byte[] payload = new byte[tempVal];     System.arraycopy(binaryData, 3, payload, 0, tempVal);         this.upData = payload;     }

NB上报二进制数据的格式为:前两个字节表示batteryLevel,大端,整型;第三个字节表示后边还有多少字节;第四个字节往后表示不定长字段upData。因此,解码的思路便是:

a)    首先判断数据长度是否合法,至少应为3个字节,对应第37行代码;数据长度应不小于第3个字节的值加上3,对应第42行代码。该部分代码属于保护性代码。

b)    将前两个字节拼成一个16位的整型数据,表示batteryLevel

c)    根据第三个字节的值,创建一个Byte数组,将第四个字节往后的内容拷贝至该数组内,得到upData。System.arraycopy 是JDK提供的数组拷贝函数: 第一个参数是源数组,第二个参数是偏移,表示从源数组的第几个字节开始拷贝,第三个参数是目的数组,第四个参数是目的数组的偏移,第5个参数表示拷贝的长度。

第二个函数:public ObjectNode toJsonNode() 返回一个ObjectNode对象(JSON)。该函数的功能,是将解码后得到的数据,按照规定格式填入一个JSON对象中。本例中,生成的JSON对象的内容格式如下图所示:

JSON对象的内容格式要求是:"msgType":  "deviceReq",  表示设备上报数据,固定不动;“data”:数组对象,数组中的每个元素分别对应profile中的一个服务;“serviceID”的值是profile中定义的服务名称;“serviceData”的值是该服务下所有的属性值。(本例中,profile定义了两个服务,Battery服务中有一个BatteryLevel属性;Transmission服务中有一个upData属性)。由图13的“upData”的值可以看出,数组类型的值,需要将二进制流转成base64编码的格式。

该函数的代码实现如下所示:

public ObjectNode toJsonNode() {         try {             //组装body体             ObjectMapper mapper = new ObjectMapper();             ObjectNode root = mapper.createObjectNode();             // root.put("identifier", this.identifier);             root.put("msgType", "deviceReq");             ArrayNode arrynode = mapper.createArrayNode();             //serviceId=Battery 数据组装             ObjectNode serviceNode = mapper.createObjectNode();             ObjectNode serviceDataNode = mapper.createObjectNode();             serviceDataNode.put("BatteryLevel", this.BatteryLevel);             serviceNode.put("serviceId", "Battery");             serviceNode.set("serviceData", serviceDataNode);             arrynode.add(serviceNode);             //serviceId=Transmission 数据组装             serviceNode = mapper.createObjectNode();             serviceDataNode = mapper.createObjectNode();             serviceDataNode.put("upData", this.upData);             serviceNode.put("serviceId", "Transmission");             serviceNode.set("serviceData", serviceDataNode);             arrynode.add(serviceNode);             root.set("data", arrynode);             return root;         } catch (Exception e) {             e.printStackTrace();             return null;         }     }

该函数代码比较简单,主要是用到了 ObjectMapper 这个类,该类提供了JAVA中操作JSON数据的方法,可对照上图中上报数据格式,仔细理解该部分代码。

3)  编码实现

编码,是将IoT平台收到的服务器下行数据(服务器下行数据是http或者https协议),从中提取出下行字段,并将其拼成二进制码流。编码部分的代码在 CmdProcess.java 文件中。需要实现的函数是:

public byte[] toByte()

本例中,该函数的实现代码如下图所示:

public byte[] toByte() {         try {             if (this.msgType.equals("cloudReq")) {             /*              * msgType == cloudReq 表示应用服务器下发的控制命令              * 本例只有一条控制命令:CLOUDREQ(profile中定义的下行命令名称)              * 如果有其他控制命令,增加判断即可。              * 命令有两个参数:cmdType,一个字节,downData,不定长数组              * 下行的二进制数据数据格式是: cmdType + downData                  */                 if (this.cmd.equals("CLOUDREQ")) {                     byte cmdType = (byte)paras.get("cmdType").asInt();                     byte[] downData = paras.get("downData").binaryValue();                                          byte[] byteRead = new byte[downData.length+1];                     byteRead[0] = cmdType;                     System.arraycopy(downData,0,byteRead,1,downData.length);                     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;         }     }

说明: 从服务器下行命令的JSON数据格式是:

该格式由平台定义,其中:

"msgType": "cloudReq", 固定值,表示服务器下行命令;

"serviceId",profile中对应的服务,本例中是"Transmission",

"cmd",profile中定义的下行命令,本例中是"CLOUDREQ",

"paras",profile中定义的下行命令的各个字段,本例中是cmdType和downData两个字段;图16中,cmdType的值是2,downData是一个不定长数组,base64编码格式。

因此,编码的思路是:

a)    判断下行命令是否是profile中定义的。

b)    获取cmdType字段的值,该值占一个字节。

c)    获取downData的值,该值是base64编码形式,需转成二进制码流

d) 将两个字段的值拼接成一个二进制数组,并返回。

6、生成Jar包

经过前面的工作后,代码就已经准备好了,接下来是生成JAR包。在DOS窗口中进入pom.xml文件所在路径,执行 mvn package 命令,最后弹出如下图所示的结果,则表明生成Jar包成功。如果有错误,则根据提示再去修改代码,然后重新执行 mvn package。

在工程目录的target文件夹下,存放生成的JAR包“MyType-ThirdParty-MyModel-1.0.0.jar”。JAR包的命名规则是:

设备类型-厂商ID-设备型号-版本号.jar

【OC平台】编解码插件离线开发教程【二】

四、    插件打包

1、        新建package文件,包含一个“preload”子文件夹,将上一章中生成的JAR包拷贝至preload文件夹下。

2、        在package文件夹中新建“package-info.json”文件(文本格式)。打开该文件,以UTF-8无BOM格式编辑,将以下大括号内容拷入该文件中并保存。

{        "specVersion": "1.0",        "fileName": "package.zip",        "version": "1.0.0",        "deviceType": "MyType",        "manufacturerName": "ThirdParty",        "model": "MyModel",        "description": "CIG codec plugin auto-generated by sps.",        "platform": "linux",        "packageType": "CIGPlugin",        "date": "Tue Nov 27 07:55:49 GMT 2018",        "ignoreList": [],        "bundles": [{               "bundleName": "MyType-ThirdParty-MyModel",               "bundleVersion": "1.0.0",               "priority": 5,               "fileName": "MyType-ThirdParty-MyModel-1.0.0.jar",               "bundleDesc": "",               "versionDesc": ""        }] }

注: 在移植到别的项目中的时候,该文件需要修改的地方有:

"deviceType",需根据实际的profile填写设备类型

"manufacturerName",需根据实际的profile填写厂商名称

"model",需根据实际的profile填写设备型号

"bundleName",根据实际的profile填写,设备型号-厂商ID-设备类型

"fileName",jar包的名称

3、        选中"package"文件夹中的全部文件,打包成zip格式。(“package.zip”,该压缩包内不能包含“package”目录)

说明:本章内容可参考 “华为IoT平台NB-IoT设备集成开发指南.pdf” 6.5.4.2.3章节的“制作插件包”部分的内容。

package.zip 文件即为制作好的编解码插件包。可在平台上传该文件部署即可。

后续的工作还有插件质检、签名,相对就简单很多,也不是必须的过程。本文就不赘述了。

附件中给出插件demo和参考文档

附件: testPlugSourceCode.zip 199.33KB 下载次数:7次

附件: 华为IoT平台NB-IoT设备集成开发指南.pdf 5.91M 下载次数:14次

IoT 编解码插件

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Excel表格如何突显折线关键数据
下一篇:网站访问故障背后发生了什么?华为工程师教你快速应对【全球软件技术大会技术分享】
相关文章