透传/编辑脚本
如果设备无法直接和云端传输JSON格式的数据,则需要设备通过二进制格式将数据透传到云端,由云端运行解析脚本将透传的数据转换成标准ICA格式的JSON数据。
前提条件
已创建产品,且产品的数据格式设置为透传/自定义。否则在产品-设备调试页面不显示该内容。透传解析脚本功能介绍
目前解析脚本通过JavaScript开发。设备和脚本的数据协议格式支持标准和自定义两种方式。
- 使用标准协议开发的设备可以直接使用云端自动生成的脚本。
- 如果协议自定义则需要开发者自行开发JS脚本。
脚本需要支持以下两个方法即可和云端进行通信。
- ICA协议数据转二进制数据(protocolToRawData)
- 二进制数据转ICA协议数据(rawDataToProtocol)
操作步骤
- 进入产品-设备调试页面。
- 在数据解析中单击编辑脚本。
- 编辑脚本,并可以模拟数据调试。
脚本定义
目前脚本仅支持符合ECMAScript 5.1的JavaScript语法。
脚本上传数据的流程如下。
- 设备上报透传数据。
- 云端先对设备上报的数据,通过脚本进行解析转换为IoT平台标准数据格式。
- 使用转换后的数据进行业务处理。
- 对于云端返回的结果,通过脚本进行解析。
- 推送转换后的返回结果给设备。
脚本编写
- 产品定义
以某一个测试产品为例,假设产品有三个属性prop_float,prop_int16,prop_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格式。
- 数据传输统一使用大端(即高字节在前,低字节在后)字节序
- 为保证传输可靠性,通信需要实现应答、超时及重传机制
- 协议帧类型定义
- payload格式定义
- method:操作的方法,定义如下。
- id:帧标识符,用于区分不同的请求。回复帧与请求帧的id必须相同,表示对该帧的回复。
- data:数据域,具体格式根据method来确定。
- Get方法的data域格式
attrid即云端需要读取属性的ID,通过编号表示。
- Set/Report方法的data域格式
协议中将类型、属性进行编号表示。len非必须,仅在类型为数组和文本(text)的情况下需要,表示长度。
- Service/Event方法的data域格式
对服务和事件进行编号传输,ID表示服务或者事件的编号,parameters表示服务或者事件携带的参数。
- Ack方法的data域格式errcode表示错误码。
- Get方法的data域格式
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.h和thing.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);
- SDK初始化函数:
- 产品功能相关接口
/ 属性值范围定义 / #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)