
# NB-IoT（CoAP）- 编解码插件样例
本文以烟感设备为例，介绍如何使用JavaScript语言开发一个支持CoAP协议的设备进行属性上报和命令下发编解码的FunctionGraph函数插件。并介绍从二进制数据到JSON数据格式的转换及其调试方法。
#### 烟感设备样例
**场景说明**
有一款烟感设备，具有如下特征：
- 具有烟雾报警功能（火灾等级）和温度上报功能。
- 支持远程控制命令，可远程打开报警功能。比如火灾现场温度，远程打开烟雾报警，提醒住户疏散。
- 该款烟感设备，设备能力比较弱，无法按照设备接口定义的JSON格式上报数据，只能上报简单的二进制数据。
**产品模型定义**
在烟感产品的开发空间，完成产品模型定义。
- level：火灾级别，用于表示火灾的严重程度。
- temperature：温度，用于表示火灾现场温度。
- SET_ALARM：打开或关闭告警命令，value=0表示关闭，value=1表示打开。响应命令result用于上报修改后告警值。
  图1模型定义-smokedetector   
  ![](https://support.huaweicloud.com/devg-iothub/figure/zh-cn_image_0000001950352374.png "点击放大") 
 
#### 编解码插件开发
1. 在烟感产品的详情页面，选择"插件开发"，单击"FunctionGraph开发"，单击"创建函数"，若是第一次使用，需要进行访问授权。
   
   图2FunctionGraph-创建FunctionGraph函数   
   ![](https://support.huaweicloud.com/devg-iothub/figure/zh-cn_image_0000002221626306.png "点击放大")
   
   
2. 进入FunctionGraph控制台后，单击"创建函数"。在弹出的界面中，选择"创建空白函数"，自定义填入函数名称，运行时选择"Node.js 16.17"。 
   图3函数列表-创建函数   
   ![](https://support.huaweicloud.com/devg-iothub/figure/zh-cn_image_0000001970149349.png "点击放大")
   图4创建函数-参数信息
   ![](https://support.huaweicloud.com/devg-iothub/figure/zh-cn_image_0000001970160949.png "点击放大")
   
   
3. 编写脚本，实现二进制数据到JSON数据的转换。脚本需要实现如下两个方法： 
   - decode：将设备上报的二进制数据转换为平台产品模型中定义的JSON格式。具体的JSON格式要求见：[数据解码格式定义](https://support.huaweicloud.com/devg-iothub/iot_02_0011.html#ZH-CN_TOPIC_0000002232531577__p2032543921611) 。
   
   - encode：平台有下行数据发送给设备时，将平台的JSON格式数据转换为设备支持的二进制格式。平台的JSON格式见：[数据编码格式定义](https://support.huaweicloud.com/devg-iothub/iot_02_0011.html#ZH-CN_TOPIC_0000002232531577__p1540412617152)。
   
   
   针对当前烟感产品实现的JavaScript样例如下，将代码复制到Project工程中。
   ```
   //上行消息类型
   var MSG_TYPE_PROPERTIES_REPORT = 'properties_report'; //设备属性上报
   var MSG_TYPE_COMMAND_RSP = 'command_response'; //设备返回命令响应
   //下行消息类型
   var MSG_TYPE_COMMANDS = 'commands'; //平台命令下发
   var MSG_TYPE_PROPERTIES_REPORT_REPLY = 'properties_report_reply'; //设备属性上报的响应消息
   //消息类型列表
   var MSG_TYPE_LIST = {
       0: MSG_TYPE_PROPERTIES_REPORT,          //码流中0字节标识为设备属性上报
       1: MSG_TYPE_PROPERTIES_REPORT_REPLY,    //码流中1字节标识为设备属性上报的响应消息
       2: MSG_TYPE_COMMANDS,                   //码流中2字节标识为平台命令下发
       3: MSG_TYPE_COMMAND_RSP                 //码流中3字节标识为设备返回命令响应
   };
   // FunctionGraph入口函数
   exports.handler = async (event, context) => {
       const codecType = event.codecType;
       const message = JSON.parse(event.message);
       console.log("input Data:", event);
       if (codecType === "decode") {
           // 解码操作
           return decode(message.payload);
       } else if (codecType === "encode") {
           // 编码操作
           return encode(message);
       }
   }
   /*
   示例：烟感设备上报属性和回复命令响应时，携带的是二进制码流，通过javascript脚本将二进制码流数据解码为符合产品模型定义的json格式数据
   传入参数：
     payload:[0x00, 0x00, 0x50, 0x00, 0x5a]
     其中payload[0]代表数据类型，0x00代表属性上报；payload[1]与payload[2]代表属性level的值；payload[3]与payload[4]代表属性temperature的值,payload[3]为小数点前的值，payload[4]为小数点后的值。
   输出结果:
     {"msg_type":"properties_report","services":[{"service_id":"smokedetector","properties":{"level":80,"temperature":90}}]}
   传入参数:
     payload: [0x03, 0x01, 0x00, 0x00, 0x01]
     其中payload[0]代表数据类型，0x03代表设备返回命令响应；payload[1]表示request_id的值，用于标识是否为某一条信息；payload[2]与代表命令设置是否成功，为0则成功；payload[3]与payload[4]代表命令响应"value"的值。
   输出结果:
     {"msg_type":"command_response","request_id":1,"result_code":0,"paras":{"value":1}}
   */
   function decode(payload) {
       // 这里可以实现具体的解码逻辑
       var binaryString = atob(payload);
       const byteArray = new Uint8Array(binaryString.length);
       for (let i = 0; i < binaryString.length; i++) {
           byteArray[i] = binaryString.charCodeAt(i);
       }
       /* byteArray 为解码后，设备上报的二进制数据，客户可以在此处查看上报的数据是否解析正确 */
       var returnData;
       var messageId = byteArray[0];
       if (MSG_TYPE_LIST[messageId] == MSG_TYPE_PROPERTIES_REPORT) {
           returnData = decodePropertiesReport(byteArray);
       } else if (MSG_TYPE_LIST[messageId] == MSG_TYPE_COMMAND_RSP) {
           returnData = decodeCommandRsp(byteArray);
       }
       return returnData;
   }
   /*
   示例数据：
   命令下发时，通过javascript的encode方法将平台JSON格式的数据，编码为二进制码流
   传入参数 ->
       {"msg_type":"commands","request_id":1,"command_name":"SET_ALARM","service_id":"smokedetector","paras":{"value":1}}
   输出结果 ->
       [0x02, 0x00, 0x00, 0x00, 0x01] 
       其中payload[0]代表数据类型，0x02代表平台命令下发；payload[1]代表命令ID；payload[2]用于标识command_name，当command_name是SET_ALARM时，payload[2] = 0x00；payload[3]与payload[4]代表命令下发value的值。
   示例数据：设备上报属性时返回响应消息，通过javascript的encode方法将平台JSON格式的数据，编码为二进制码流
   传入参数 ->
       {"msg_type":"properties_report_reply","request":"000050005a","result_code":0}
   输出结果 ->
       [0x01, 0x00]
       其中payload[0]代表数据类型，0x01为设备属性上报的响应消息；payload[1]代表设备响应结果，为0x00时成功。
   */
   function encode(data) {
       var msgType = data.msg_type;
       let payload = [];
       var status = 200;
       // 命令下发
       if (msgType == MSG_TYPE_COMMANDS) { 
           payload[0] = 0x02; // 标识命令下发
           payload[1] = data.request_id & 0xFF; // 命令ID
           if (data.command_name == 'SET_ALARM') {
               payload[2] = 0x00; // 标识命令名称
           }
           // 设置命令属性值
           payload[3] = (data.paras.value >> 8) & 0xFF;
           payload[4] = data.paras.value & 0xFF;
       } else if (msgType == MSG_TYPE_PROPERTIES_REPORT_REPLY) { 
           // 设备属性上报的响应消息
           payload[0] = 0x01; // 标识属性上报的响应消息
           if (0 == data.result_code) {
               payload[1] = 0x00; // 标识属性上报消息处理成功
           } else { 
               payload[1] = 0x01;
               status = 401;
           }
       }
       return outputData(status, { "payload": payload });
   }
   // 属性上报 -- 上行
   function decodePropertiesReport(byteArray) { 
       //设置serviceId参数值，该参数值对应产品模型中的服务类型smokedetector
       var serviceId = 'smokedetector';
       var level = byteArray[1] * Math.pow(2, 8) + byteArray[2];
       var status = 200;
       var jsonObj;
       if (byteArray.length < 4) {
           jsonObj = {
               "msg_type": "ERR", "message":"decodePropertiesReport byte length < 5."
           };
           status = 402;
       }
       // 获取第4位和第5位的值
       const integerPart = byteArray[3]; // 第4位
       const decimalPart = byteArray[4]; // 第5位
       // 组合成小数
       const temperature = parseFloat(integerPart + '.' + decimalPart);
       jsonObj = {
           "msg_type": MSG_TYPE_PROPERTIES_REPORT, "services":
               [{ "service_id": serviceId, "properties": { "level": level, "temperature": temperature } }]
       };
       return outputData(status, jsonObj);
   }
   // 命令响应 -- 上行
   function decodeCommandRsp(byteArray) {
       var requestId = byteArray[1];
       var result_code = byteArray[2]; //从二进制码流中获取命令执行结果
       var value = byteArray[3] * Math.pow(2, 8) + byteArray[4]; //从二进制码流中获取命令执行结果返回值
       //转换为命令响应的JSON格式
       jsonObj = {
           'msg_type': MSG_TYPE_COMMAND_RSP, 'request_id': requestId,
           'result_code': result_code, 'paras': { 'value': value }
       };
       return outputData(200, jsonObj);
   }
   function outputData(status, body) { 
       
       const output =
       {
           'status': status,
           'message': JSON.stringify(body),
       }
       console.log("output Data:", output);
       return output;
   }
   ```
   图5FunctionGraph-复制代码到Project工程CoAP   
   ![](https://support.huaweicloud.com/devg-iothub/figure/zh-cn_image_0000002256730441.png "点击放大")
   
   
4. 在线调试脚本。脚本编辑完成后，在FunctionGraph函数中单击"配置测试事件"，选择空白模板，输入模拟数据，单击"创建"。配置好测试事件后，单击"测试"，即可得到函数返回结果及其日志。 
   假设输入以下模拟数据：其中payload为设备侧上报的二进制：0x00, 0x00, 0x50, 0x00,0x5a。"AABQAFo="为被平台Base64编码后的值。
   ```
   {
       "codecType": "decode",
       "message": "{\"topic\": null,\"payload\": \"AABQAFo=\"}"
   }
   ```
   图6FunctionGraph-添加测试事件   
   ![](https://support.huaweicloud.com/devg-iothub/figure/zh-cn_image_0000002221813760.png "点击放大")
   图7FunctionGraph-测试结果CoAP   
   ![](https://support.huaweicloud.com/devg-iothub/figure/zh-cn_image_0000002476701272.png "点击放大")
   
   
5. 调试成功后，在[1]的"目标函数"中选择您创建的FunctionGraph函数，并单击"部署"。
   
   图8FunctionGraph开发-部署插件   
   ![](https://support.huaweicloud.com/devg-iothub/figure/zh-cn_image_0000002256705825.png "点击放大")
   
   
 
