华为云编解码插件教程 一、 环境搭建 JDK 1.8以上
eclipse
Maven
具体说明https://support.huaweicloud.com/devg-IoT/iot_02_4020.html
二、 Profile说明 Profile实际上是一系列关于设备模型的描述文件,每个文件都使用JSON格式(键值对)。
Profile中首先需要说明设备的基本信息,包括厂商ID,厂商名称,设备类型,接入协议,以及设备可以提供的哪些服务等;其次,profile中要针对每一项服务,用一个独立的文件进行详细描述。服务,可以理解为是对设备消息(上下行)功能的一个分类,一个服务就代表一类功能;每个服务下包含若干属性和命令,每个属性对应上报消息中的某一个数据,每个命令字段则对应下行消息中的某些字段。
可选在线开发,离线开发
官方说明https://support.huaweicloud.com/devg-IoT/iot_02_9991.html
三、 编解码插件编写 从华为资源中心下载编解码插件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个:
1、源文件说明 a) ProtocolAdapterImpl.java 可以理解为是插件的入口文件,对外提供调用接口。该文件只需要修改两个字符串的定义即可:
1 2 3 4 5 6 7 // 厂商名称 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 下都要修改。在本例中,需要修改为:
1 2 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. 导入工程后是有错误的。 这是因为我们在第2节中将文件路径修改了,与代码里面的包路径不一致引起的。解决方法为:依次打开源文件,将第一行的
1 2 3 4 5 package com.Huawei.NBIoTDevice.WaterMeter; 修改为 package com.ThirdParty.MyModel.MyType;
打开OSGI_INF目录下的CodeProvideHandler.xml 文件:
CodeProvideHandler.xml路径
打开后,文件内容如下图所示:
CodeProvideHandler.xml内容
将Name 、 Class* 内的路径也修改为对应的包路径:
CodeProvideHandler.xml修改
5、代码实现 1)修改ProtocolAdatpterImpl.java文件 在文件中找到如下两行:
1 2 3 4 // 厂商名称 private static final String MANU_FACTURERID = "Huawei"; // 设备型号 private static final String MODEL = "NBIoTDevice";
将MANU_FACTURERID 和 MODEL 定义修改为profile中定义的厂商ID和设备型号,本例中需要修改为:
1 2 3 4 // 厂商名称 private static final String MANU_FACTURERID = "ThirdParty"; // 设备型号 private static final String MODEL = "MyModel";
2)解码实现 解码,是将NB模组上报的二进制码流按格式解析出对应字段的过程。解码的代码在ReportProcess.java 文件中。
第一个函数:public ReportProcess(byte [] binaryData) 入参 byte[] binaryData就是NB模组上报的二进制码流。解码得到数据存储在成员变量当中。本例中的代码实现如下:
a.先看官方提供的例子:
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 public ReportProcess (byte [] binaryData) { if (binaryData[2 ] == bDeviceReq) { msgType = "deviceReq" ; hasMore = binaryData[3 ]; brightness = binaryData[4 ]; 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 ; temperature = (int ) binaryData[11 ] & 0xFF - 128 ; } } public ObjectNode toJsonNode () { try { ObjectMapper mapper = new ObjectMapper(); ObjectNode root = mapper.createObjectNode(); root.put("msgType" , this .msgType); if (this .msgType.equals("deviceReq" )) { root.put("hasMore" , this .hasMore); ArrayNode arrynode = mapper.createArrayNode(); ObjectNode brightNode = mapper.createObjectNode(); brightNode.put("serviceId" , "Brightness" ); ObjectNode brightData = mapper.createObjectNode(); brightData.put("brightness" , this .brightness); brightNode.put("serviceData" , brightData); arrynode.add(brightNode); 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); ObjectNode temperatureNode = mapper.createObjectNode(); temperatureNode.put("serviceId" , "Temperature" ); ObjectNode temperatureData = mapper.createObjectNode(); temperatureData.put("temperature" , this .temperature); temperatureNode.put("serviceData" , temperatureData); arrynode.add(temperatureNode); 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); 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); if (isContainMid) { root.put("mid" , this .mid); } ObjectNode body = mapper.createObjectNode(); body.put("result" , 0 ); root.put("body" , body); } return root; } catch (Exception e) { e.printStackTrace(); return null ; } } }
b.太长了,看我写的简陋的例子
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 public ReportProcess (byte [] binaryData) { msgType = "deviceReq" ; light=0 ; for (int i=0 ;i<5 ;i++) { if (binaryData[i]==20 ) { binaryData[i]=00 ; } else { binaryData[i]=(byte ) ((int )binaryData[i]&0x0f ); } } light =(int ) (((int )binaryData[0 ]*10000 )+((int )binaryData[1 ]*1000 )+((int )binaryData[2 ]*100 )+((int )binaryData[3 ]*10 )+((int )binaryData[4 ])); } public ObjectNode toJsonNode () { try { ObjectMapper mapper = new ObjectMapper(); ObjectNode root = mapper.createObjectNode(); root.put("msgType" , "deviceReq" ); ArrayNode arrynode = mapper.createArrayNode(); ObjectNode serviceNode = mapper.createObjectNode(); ObjectNode serviceDataNode = mapper.createObjectNode(); serviceDataNode.put("Light" , this .light); serviceNode.put("serviceId" , "Light" ); serviceNode.set("serviceData" , serviceDataNode); arrynode.add(serviceNode); root.set("data" , arrynode); return root; } catch (Exception e) { e.printStackTrace(); return null ; } }
3)编码实现 修改CmdProcess.java中的代码,实现插件对下发命令和上报数据响应的编码能力。
a.同样,先看官方提供的例子:
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 public CmdProcess (ObjectNode input) { try { this .msgType = input.get("msgType" ).asText(); if (msgType.equals("cloudRsp" )) { this .errcode = input.get("errcode" ).asInt(); this .hasMore = input.get("hasMore" ).asInt(); } else { 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" )) { 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); 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; } } 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) { e.printStackTrace(); return null ; } } }
b.我写的简陋的例子
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 public byte [] toByte() { try { if (this .msgType.equals("cloudReq" )) { if (this .cmd.equals("Control" )) { byte [] downData = new byte [4 ]; String Data = paras.get("LED" ).asText(); downData = Data.getBytes(); 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 downData; } } 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) { e.printStackTrace(); return null ; } }
6. 编解码插件打包 (以官网代码为例) 插件变成完成后,需要使用Maven打包成jar包,并制作成插件包。
Maven打包
打开DOS窗口,进入“pom.xml”所在的目录。
输入maven打包命令:mvn package。
DOS窗口中显示“BUILD SUCCESS”后,打开与“pom.xml”目录同级的target文件夹,获取打包好的jar包。
jar包命名规范为:设备类型-厂商ID-设备型号-版本.jar,例如:WaterMeter-Huawei-NBIoTDevice-version.jar。
com目录存放的是class文件。
META-INF下存放的是OSGI框架下的jar的描述文件(根据pom.xml配置生成的)。
OSGI-INF下存放的是服务配置文件,把编解码注册为服务,供平台调用(只能有一个xml文件)。
其他jar是编解码引用到的jar包。
制作插件包
新建文件夹命名为“package”,包含一个“preload/”子文件夹。
将打包好的jar包放到“preload/”文件夹。
在“package”文件夹中,新建“package-info.json”文件。该文件的字段说明和模板如下:
说明:
“package-info.json”需要以UTF-8无BOM格式编码。仅支持英文字符。
字段名
字段描述
是否必填
specVersion
描述文件版本号,填写固定值:”1.0”。
是
fileName
软件包文件名,填写固定值:”codec-demo”
是
version
软件包版本号。描述package.zip的版本,请与下面的bundleVersion取值保持一致。
是
deviceType
设备类型,与Profile文件中的定义保持一致。
是
manufacturerName
制造商名称,与Profile文件中的定义保持一致,否则无法上传到平台。
是
model
产品型号,与Profile文件中的定义保持一致。
是
platform
平台类型,本插件包运行的物联网平台的操作系统,填写固定值:”linux”。
是
packageType
软件包类型,该字段用来描述本插件最终部署的平台模块,填写固定值:”CIGPlugin”。
是
date
出包时间,格式为:”yyyy-MM-dd HH-mm-ss”,如”2017-05-06 20:48:59”。
否
description
对软件包的自定义描述。
否
ignoreList
忽略列表,默认为空值。
是
bundles
一组bundle的描述信息。说明: bundle就是压缩包中的jar包,只需要写一个bundle。
是
字段名
字段描述
是否必填
bundleName
插件名称,和上文中pom.xml的Bundle-SymbolicName保持一致。
是
bundleVersion
插件版本,与上面的version取值保持一致。
是
priority
插件优先级,可赋值默认值:5。
是
fileName
插件jar的文件名称。
是
bundleDesc
插件描述,用来介绍bundle功能。
是
versionDesc
插件版本描述,用来介绍版本更迭时的功能特性。
是
package-info.json 文件模板:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 { "specVersion" :"1.0" , "fileName" :"codec-demo" , "version" :"1.0.0" , "deviceType" :"WaterMeter" , "manufacturerName" :"Huawei" , "model" :"NBIoTDevice" , "description" :"codec" , "platform" :"linux" , "packageType" :"CIGPlugin" , "date" :"2017-02-06 12:16:59" , "ignoreList" :[], "bundles" :[ { "bundleName" : "WaterMeter-Huawei-NBIoTDevice" , "bundleVersion" : "1.0.0" , "priority" :5 , "fileName" : "WaterMeter-Huawei-NBIoTDevice-1.0.0.jar" , "bundleDesc" :"" , "versionDesc" :"" }] }
选中“package”文件夹中的全部文件,打包成zip格式(“package.zip”)。
说明:
“package.zip”中不能包含“package”这层目录。
7.编解码插件质检 编解码插件的质检用于检验编解码是否可以正常使用。
获取编解码插件检测工具 。
将检测工具“pluginDetector.jar”、Profile文件的“devicetype-capability.json”和需要检测的编解码插件包“package.zip”和tool文件夹放在同一个目录下。
比较简单,具体参照 官方教程
8.编解码插件包离线签名 具体参照 [官方教程](https://support.huaweicloud.com/devg-IoT/iot_02_4020.html)
四、 附上我的Code https://github.com/Xbean1028/NB-IOT
第一次学习,请多指教
Ursprünglicher Autor: Bean
Ursprünglicher Link: http://yoursite.com/2019/09/29/华为云离线编解码插件教程/
Copyright-Erklärung: Bitte geben Sie die Quelle des Nachdrucks an.