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

前提条件

已创建产品,且产品的数据格式设置为透传/自定义。否则在产品-设备调试页面不显示该内容。

透传解析脚本功能介绍

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

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

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

  • ICA协议数据转二进制数据(protocolToRawData)
  • 二进制数据转ICA协议数据(rawDataToProtocol)

通信原理

操作步骤

  1. 进入产品-设备调试页面。
  2. 数据解析中单击编辑脚本

    数据解析
  3. 编辑脚本,并可以模拟数据调试。

    编辑脚本

脚本定义

目前脚本仅支持符合ECMAScript 5.1的JavaScript语法。

脚本上传数据的流程如下。


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

脚本编写

  • 产品定义

    以某一个测试产品为例,假设产品有三个属性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);
    }

控制台模拟数据调试

说明 模拟时需要注意产品定义的属性的读写属性,否则将会产生错误。
  • 模拟设备上报数据

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

    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"
    }

本地调试脚本

仅用于本地测试,控制台请使用控制台模拟数据调试,为了方便开发及调试脚本,可将脚本放在本地环境中进行调用,参考如下。

// 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();

简易数据透传协议

为了让开发者免去脚本的开发,以及考虑减轻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表示错误码。

MCU SDK

针对上述提到的二进制的标准协议,我们提供了MCUSDK实现了协议的封装。此外,我们还会根据开发者的产品功能定义在MCU SDK中生成与之对应的代码和上报、接收处理逻辑。开发者使用MCUSDK开发就不用实现通讯协议和产品功能的定义,直接按照提供的API接口调用以及添加自己的业务逻辑即可。

例如,从云端下发一个关灯(对应属性标识Switch)的请求,需要在开发者在特定的API内部实现Switch的处理,MCU SDK默认实现了对云端的回复。设备本地灯的开关状态变化,开发者的程序识别到后,调用修改Switch属性的API后,MCU SDK会将变化上报到云端。开发者就只需要关注设备业务功能的开发即可。

目前MCU SDK支持如下几种芯片型号生成对应开发工程,开发者可以直接基于此工程直接开发自己的应用。如果选择其他平台,我们会提供SDK和简单的示例demo,开发者可以在Linux进行编译运行。

  • STM8S207

    开发IDE使用IAR for STM8(EWSTM8)。

  • STM32L053R8

    开发IDE使用Keil MDK5。

  • 其他平台

    仅提供简单的示例。可以在Linux中编译运行。

MCU SDK的核心代码位于sdk-core目录,目录结构如下所示,包括了头文件目录inc和源代码目录src


目录结构

头文件说明如下。

  • common.h

    SDK共用的头文件,包括了类型定义、SDK全局对象定义以及公共的API。需要开发者关注。

  • platform.h

    定义了需要开发者实现或者处理的函数。需要开发者关注。

  • protocol.h

    定义了和云端通讯协议的使用的接口。开发者可以不用关注。

  • thing.h

    包含了产品功能(属性、服务、事件)相关定义。需要开发者关注。

源文件说明如下。

  • common.c

    SDK公共代码的实现。

  • protocol.c

    和云端通信协议的定义及接口实现。

  • thing.c

    产品功能相关的接口实现。

thing.hthing.c部分代码会根据产品的TSL自动生成。

API说明

需要用户调用的接口如下。
  • 公共接口
    • SDK初始化函数:void boneSdkInit(void)
    • SDK运行函数,在while中调用:void boneSdkRun(void)
    • 接收串口的字节数据,在串口中断服务程序中调用:Int32_t boneRcvFromUart(Uint8_t *data, Uint16_t length);
    • 系统运行时间计算(定时1ms调用,暂时可以不用实现):void boneSystimeInc(void);
  • 产品功能相关接口
    / 属性值范围定义 /
    #define  ATTR_XXX
    
    / 属性值的设置和获取,AttrName表示的是属性名称,实际的接口名称和属性标识对应,AttrVal表示的是属性的值, ValueType表示的是属性的类型 /
    void boneSet_AttrName(AttrVal);
    ValueType boneGet_AttrName(void);
    
    / 事件上报,EventName表示的是事件名称,实际的接口名称和事件标识对应,Params表示事件的输出参数 /
    void boneEvntPost_EventName(Params);
    说明 以上接口及参数定义的只是一个模版,具体的API要视自己定义的产品功能而定。以下是示例产品自动生成的API。
    • 属性值范围定义
      #define RANG_PROPINT8_R_MIN   -100
      #define RANG_PROPINT8_R_MAX   100
    • 属性值的设置和获取
      void boneSet_PropInt8_r(Int8_t data);
      Int8_t boneGet_PropInt8_r(void);
      
      void boneSet_PropUint8_r(Uint8_t data);
      Uint8_t boneGet_PropUint8_r(void);
    • 事件上报
      void boneEvntPost_EventInfo(eo_EventInfo_t *arg);
      
      void boneEvntPost_EventAlarm(void);
  • 需要用户实现的接口
    / 串口发送协议数据 /
    Int32_t boneUartSend(Uint8_t *buffer, Uint16_t length);
    
    /* 锁相关接口,无os可空实现 */
    void *boneMutexCreate(void);
    void boneMutexDestroy(void *mutex);
    void boneMutexLock(void *mutex);
    void boneMutexUnlock(void *mutex);
  • 需要用户添加处理方法的接口
    /* 属性变化处理函数 */
    void bonePropChangeHandler(Int32_t index);
    
    /* 服务处理函数,如果没有服务可忽略。接口名称中的ServiceName表示的是服务名称,实际的接口名称和服务标识对应 */
    static Int32_t boneServCall_ServiceName(Uint32_t id, Uint8_t *data, Uint16_t length)

    示例如下。

    static Int32_t boneServCall_SrvAsync1(Uint32_t id, Uint8_t *data, Uint16_t length);
    
    static Int32_t boneServCall_SrvAsync2(Uint32_t id)