国内最强悍的工作流平台,工作流平台功能有哪些?以及该如何选择?
694
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
四、 插件打包
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小时内删除侵权内容。