如果设备无法直接和云端传输JSON格式的数据,则需要设备通过二进制格式将数据透传到云端,由云端运行解析脚本将透传的数据转换成标准ICA格式的JSON数据。

功能介绍

目前解析脚本通过JavaScript开发。设备和脚本的数据协议格式支持标准和自定义两种方式。

  • 使用标准协议开发的设备可以直接使用云端自动生成的脚本。
  • 如果协议自定义则需要开发者自行开发JS脚本。

脚本需要支持以下两个方法即可和云端进行通信。

  • ICA协议数据转二进制数据(protocolToRawData)
  • 二进制数据转ICA协议数据(rawDataToProtocol)
通信原理

目前脚本仅支持符合ECMAScript 5.1的JavaScript语法。上传数据的流程如下。

  1. 设备上报透传数据。
  2. 云端通过脚本解析设备上报的数据,将数据转换为IoT平台标准格式的数据。
  3. 使用转换后的数据进行业务处理。
  4. 通过脚本解析云端返回的结果。
  5. 将解析后的返回结果推送给设备。
脚本上传数据流程

透传协议原理介绍

为了让开发者免去脚本的开发,以及考虑减轻MCU的运算,我们制定了一套简易的数据协议,核心数据传输采用TLV格式。

数据格式
  • 数据传输统一使用大端(即高字节在前,低字节在后)字节序。
  • 为保证传输可靠性,通信需要实现应答、超时及重传机制。
说明 本文档对通信方式和物理参数不做要求(例如UART通信),需要指定如下参数。
  • 波特率:115200
  • 数据位:8
  • 奇偶校验:无
  • 停止位:1
  • 协议帧类型定义协议帧类型
  • payload格式定义payload格式
    • method:操作的方法,定义如下。method定义
    • id:帧标识符,用于区分不同的请求。回复帧与请求帧的id必须相同,表示对该帧的回复。
    • data:数据域,具体格式根据method来确定。
      • Get方法的data域格式Get方法

        attrid即云端需要读取属性的ID,通过编号表示。

      • Set/Report方法的data域格式Set/Report方法

        协议中将类型、属性进行编号表示。len非必须,仅在类型为数组和文本(text)的情况下需要,表示长度。

      • Service/Event方法的data域格式Service/Event方法

        对服务和事件进行编号传输,ID表示服务或者事件的编号,parameters表示服务或者事件携带的参数。

      • Ack方法的data域格式ACK方法errcode表示错误码。

操作步骤

  1. 登录生活物联网控制台
  2. 选择项目名称,创建产品,且产品的数据格式设置为透传/自定义。详细参见创建产品
  3. 定义产品功能,详细参见功能概述
  4. 进入产品的设备调试页面。
  5. 数据解析中单击编辑脚本
    数据解析
  6. 编辑脚本。
    编辑脚本

    以某一个测试产品为例,假设产品有三个属性prop_floatprop_int16prop_bool

    说明 模拟时需要注意产品定义的属性的读写属性,否则将会产生错误。
    var COMMAND_REPORT = 0x00; //属性上报
    var COMMAND_SET = 0x01; //属性设置
    var COMMAND_REPORT_REPLY = 0x02; //上报数据返回结果
    var COMMAND_SET_REPLY = 0x03; //属性设置设备返回结果
    var COMMAD_UNKOWN = 0xff;    //未知的命令
    var ALINK_PROP_REPORT_METHOD = 'thing.event.property.post'; //标准ALink JSON格式topic, 设备上传属性数据到云端
    var ALINK_PROP_SET_METHOD = 'thing.service.property.set'; //标准ALink JSON格式topic, 云端下发属性控制指令到设备端
    var ALINK_PROP_SET_REPLY_METHOD = 'thing.service.property.set'; //标准ALink JSON格式topic, 设备上报属性设置的结果到云端
    /*
    示例数据:
    设备上报数据
    传入参数 ->
        0x000000000100320100000000
    输出结果 ->
        {"method":"thing.event.property.post","id":"1","params":{"prop_float":0,"prop_int16":50,"prop_bool":1},"version":"1.0"}
    
    属性设置的返回结果
    传入参数 ->
        0x0300223344c8
    输出结果 ->
        {"code":"200","data":{},"id":"2241348","version":"1.0"}
    */
    function rawDataToProtocol(bytes) {
        var uint8Array = new Uint8Array(bytes.length);
        for (var i = 0; i < bytes.length; i++) {
            uint8Array[i] = bytes[i] & 0xff;
        }
        var dataView = new DataView(uint8Array.buffer, 0);
        var jsonMap = new Object();
        var fHead = uint8Array[0]; // command
        if (fHead == COMMAND_REPORT) {
            jsonMap['method'] = ALINK_PROP_REPORT_METHOD; //ALink JSON格式 - 属性上报topic
            jsonMap['version'] = '1.0'; //ALink JSON格式 - 协议版本号固定字段
            jsonMap['id'] = '' + dataView.getInt32(1); //ALink JSON格式 - 标示该次请求id值
            var params = {};
            params['prop_int16'] = dataView.getInt16(5); //对应产品属性中 prop_int16
            params['prop_bool'] = uint8Array[7]; //对应产品属性中 prop_bool
            params['prop_float'] = dataView.getFloat32(8); //对应产品属性中 prop_float
            jsonMap['params'] = params; //ALink JSON格式 - params标准字段
        } else if(fHead == COMMAND_SET_REPLY) {
            jsonMap['version'] = '1.0'; //ALink JSON格式 - 协议版本号固定字段
            jsonMap['id'] = '' + dataView.getInt32(1); //ALink JSON格式 - 标示该次请求id值
            jsonMap['code'] = ''+ dataView.getUint8(5);
            jsonMap['data'] = {};
        }
    
        return jsonMap;
    }
    /*
    示例数据:
    属性设置
    传入参数 ->
        {"method":"thing.service.property.set","id":"12345","version":"1.0","params":{"prop_float":123.452, "prop_int16":333, "prop_bool":1}}
    输出结果 ->
        0x0100003039014d0142f6e76d
    
    设备上报的返回结果
    传入数据 ->
        {"method":"thing.event.property.post","id":"12345","version":"1.0","code":200,"data":{}}
    输出结果 ->
        0x0200003039c8
    */
    function protocolToRawData(json) {
        var method = json['method'];
        var id = json['id'];
        var version = json['version'];
        var payloadArray = [];
        if (method == ALINK_PROP_SET_METHOD) // 属性设置
        {
            var params = json['params'];
            var prop_float = params['prop_float'];
            var prop_int16 = params['prop_int16'];
            var prop_bool = params['prop_bool'];
            //按照自定义协议格式拼接 rawData
            payloadArray = payloadArray.concat(buffer_uint8(COMMAND_SET)); // command字段
            payloadArray = payloadArray.concat(buffer_int32(parseInt(id))); // ALink JSON格式 'id'
            payloadArray = payloadArray.concat(buffer_int16(prop_int16)); // 属性'prop_int16'的值
            payloadArray = payloadArray.concat(buffer_uint8(prop_bool)); // 属性'prop_bool'的值
            payloadArray = payloadArray.concat(buffer_float32(prop_float)); // 属性'prop_float'的值
        } else if (method ==  ALINK_PROP_REPORT_METHOD) { //设备上报数据返回结果
            var code = json['code'];
            payloadArray = payloadArray.concat(buffer_uint8(COMMAND_REPORT_REPLY)); //command字段
            payloadArray = payloadArray.concat(buffer_int32(parseInt(id))); // ALink JSON格式 'id'
            payloadArray = payloadArray.concat(buffer_uint8(code));
        } else { //未知命令,对于有些命令不做处理
            var code = json['code'];
            payloadArray = payloadArray.concat(buffer_uint8(COMMAD_UNKOWN)); //command字段
            payloadArray = payloadArray.concat(buffer_int32(parseInt(id))); // ALink JSON格式 'id'
            payloadArray = payloadArray.concat(buffer_uint8(code));
        }
        return payloadArray;
    }
    //以下是部分辅助函数
    function buffer_uint8(value) {
        var uint8Array = new Uint8Array(1);
        var dv = new DataView(uint8Array.buffer, 0);
        dv.setUint8(0, value);
        return [].slice.call(uint8Array);
    }
    function buffer_int16(value) {
        var uint8Array = new Uint8Array(2);
        var dv = new DataView(uint8Array.buffer, 0);
        dv.setInt16(0, value);
        return [].slice.call(uint8Array);
    }
    function buffer_int32(value) {
        var uint8Array = new Uint8Array(4);
        var dv = new DataView(uint8Array.buffer, 0);
        dv.setInt32(0, value);
        return [].slice.call(uint8Array);
    }
    function buffer_float32(value) {
        var uint8Array = new Uint8Array(4);
        var dv = new DataView(uint8Array.buffer, 0);
        dv.setFloat32(0, value);
        return [].slice.call(uint8Array);
    }
  7. (可选)本地调试脚本。

    为了方便开发及调试脚本,可将脚本放在本地环境中进行调用。本地调试脚本仅用于本地测试。

    // rawDataToProtocol和protocolToRawData的实现放在这里
    
    // Test Demo
    function Test()
    {
        //0x001232013fa00000
        var rawdata_report_prop = new Buffer([
            0x00, //固定command头, 0代表是上报属性
            0x00, 0x22, 0x33, 0x44, //对应id字段, 标记请求的序号
            0x12, 0x32, //两字节 int16, 对应属性 prop_int16
            0x01, //一字节 bool, 对应属性 prop_bool
            0x3f, 0xa0, 0x00, 0x00 //四字节 float, 对应属性 prop_float
        ]);
    
        rawDataToProtocol(rawdata_report_prop);
    
        var setString = new String('{"method":"thing.service.property.set","id":"12345","version":"1.0","params":{"prop_float":123.452, "prop_int16":333, "prop_bool":1}}');
        protocolToRawData(JSON.parse(setString));
    }
    
    Test();
  8. 模拟数据调试。
    • 模拟设备上报数据

      模拟类型选择设备上报数据,填写测试数据。控制台中模拟输入的数据为设备上报数据的十六进制格式数据。

      0x00002233441232013fa00000

      单击运行,查看上报数据输出结果。

      {
          "method": "thing.event.property.post", 
          "id": "2241348", 
          "params": {
              "prop_float": 1.25, 
              "prop_int16": 4658, 
              "prop_bool": 1
          }, 
          "version": "1.0"
      }
    • 设备上报数据返回结果

      模拟类型选择设备接收数据,填写测试数据。

      {
        "id": "12345",
        "version": "1.0",
        "code": 200,
        "method": "thing.event.property.post",
        "data": {}
      }

      单击运行,查看接收数据输出结果,输出结果为脚本转换结果的十六进制格式数据。

      0x0100003039014d0142f6e76d
    • 模拟属性设置设备返回结果

      模拟属性设置设备返回属性设置结果,填写测试数据。

      0x0300223344c8

      单击运行,查看设备上报的数据。

      {
        "code": "200",
        "data": {},
        "id": "2241348",
        "version": "1.0"
      }