如果設備無法直接和云端傳輸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)