透传-编辑脚本语法说明
更新时间:2019-06-21 13:48:35
解析脚本
透传解析脚本功能介绍
由于低配置且资源受限或者对网络流量有要求的设备不适合直接和云端传输Json格式的数据,因此设备通过二进制格式将数据透传到云端,由云端运行一段解析脚本将透传的数据转换成标准ICA格式的JSON数据。
目前解析脚本通过JavaScript开发。 设备和脚本的数据协议格式支持标准和自定义两种方式。
使用标准协议开发的设备可以直接使用云端自动生成的脚本,若协议自定义则需要开发者自行开发JS脚本。
脚本需要支持以下两个方法即可和云端上下行通讯:(1) ICA协议数据转二进制数据(protocolToRawData) (2)二进制数据转ICA协议数据(rawDataToProtocol)。
脚本定义
语言定义
目前脚本仅支持符合ECMAScript 5.1的JavaScript语法。
脚本上传数据流程
- 设备上报透传数据
- 云端先对设备上报的数据,通过脚本进行解析转换为IoT平台标准数据格式
- 使用转换后的数据进行业务处理
- 对于云端返回的结果,通过脚本进行解析
- 推送转换后的返回结果给设备
脚本的格式
/**
* 将Alink协议的数据转换为设备能识别的二进制格式数据, IoT平台给设备下发数据时调用
* 入参:jsonObj 对象 不能为空
* 出参:rawData byte[]数组 不能为空
*
*/
function protocolToRawData(jsonObj) {
return rawdata;
}
/**
* 将二进制格式的数据转换为Alink协议的数据, 设备上报数据到云端时调用
* 入参:rawData byte[]数组 不能为空
* 出参:jsonObj 对象 不能为空
*/
function rawDataToProtocol(rawData) {
return jsonObj;
}
脚本编写
产品定义
我们已一个测试产品为例,假设产品有三个属性prop_float, prop_int16, prop_bool。
标识符 | 数据类型 |
---|---|
prop_float | 浮点单精度 float |
prop_int16 | 整数型 int32 |
prop_bool | 布尔型 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);
}
控制台模拟数据调试
⚠️ 模拟时需要注意产品定义的属性的读写属性,否则将会产生错误
模拟设备上报数据
模拟类型选择设备上报数据,填写测试数据, 控制台中模拟输入的数据为设备上报数据的16进制格式数据:
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": {}
}
点击运行,查看接收数据输出结果,输出结果为脚本转换后结果的16进制格式:
0x0200003039c8
模拟设备接收数据
模拟类型选择设备接收数据,填写测试数据:
{
"method": "thing.service.property.set",
"id": "12345",
"version": "1.0",
"params": {
"prop_float": 123.452,
"prop_int16": 333,
"prop_bool": 1
}
}
点击运行,查看接收数据输出结果,输出结果为脚本转换后结果的16进制格式:
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格式定义
各字段说明如下:
method
操作的方法。定义如下:
id
帧标识符,用于区分不同的请求。回复帧与请求帧的id必须相同,表示对该帧的回复。
data
数据域,具体格式根据method来确定。
- Get方法的data域格式
说明:attrid即云端需要读取属性的ID,通过编号表示。
- Set/Report方法的data域格式
说明:
(1)协议中将类型、属性进行编号表示。
(2)len非必须,仅在类型为数组和文本(text)的情况下需要,表示长度。
- Service/Event方法的data域格式
说明:对服务和事件进行编号传输,ID表示服务或者事件的编号,parameters表示服务或者事件携带的参数。
- Ack方法的data域格式
说明:errcode表示错误码。
上述提到的属性、类型、服务、事件的编号是通过定义的产品TSL自动生成的,具体可以参考自动生成的脚本或者MCUSDK。
MCU SDK
针对上述提到的二进制的标准协议,我们提供了MCUSDK实现了协议的封装。
此外,我们还会根据开发者的产品功能定义在MCU SDK中生成与之对应的代码和上报、接收处理逻辑。开发者使用MCUSDK开发就不用实现通讯协议和产品功能的定义,直接按照提供的API接口调用以及添加自己的业务逻辑即可。
例如,从云端下发一个关灯(对应属性标识Switch)的请求,需要在开发者在特定的API内部实现Switch的处理,MCUSDK默认实现了对云端的回复;例如,设备本地灯的开关状态变化,开发者的程序识别到后调用修改Switch属性的API后,MCUSDK会将变化上报到云端。通过这样开发者就只需要关注设备业务功能的开发即可。
目前MCUSDK支持如下几种芯片型号生成对应开发工程,开发者可以直接基于此工程直接开发自己的应用。
如果选择其他平台,我们会提供sdk和简单的示例demo,开发者可以在Linux进行编译运行。
STM8S207
开发IDE使用IAR for STM8(EWSTM8)。STM32L053R8
开发IDE使用Keil MDK5。其他平台
仅提供简单的示例。可以在Linux中编译运行。
目录结构
MCUSDK的核心代码位于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);
产品功能相关接口
/ 属性值范围定义 /
#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
属性值的设置和获取
```c
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);
- 事件上报
```c
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)