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

llb90 发表于 2019/09/16 11:11:35 2019/09/16
【摘要】 本文是编解码插件离线教程的第二篇,也是最后一篇,主要从零开始详细讲解下工程的建立、代码的编写过程。

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

三、    插件编写

1、源文件说明

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

1.png

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

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

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

插件源码文件有5个:

1.png

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工程,如下图所示:

1.png

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

1.png

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

package com.Huawei.NBIoTDevice.WaterMeter;

修改为:

package com.ThirdParty.MyModel.MyType;

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

1.png


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

1.png

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

1.png

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数组,将第四个字节往后的内容拷贝至该数组内,得到upDataSystem.arraycopy JDK提供的数组拷贝函数第一个参数是源数组,第二个参数是偏移,表示从源数组的第几个字节开始拷贝,第三个参数是目的数组,第四个参数是目的数组的偏移,第5个参数表示拷贝的长度。


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

1.png

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数据格式是:

1.png

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

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

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

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

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

因此,编码的思路是:

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

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

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

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

6、生成Jar

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

image.png

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

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


四、    插件打包

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

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

{
       "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和参考文档

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区),文章链接,文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:cloudbbs@huaweicloud.com进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。