onenet物聯網平臺使用
OneNET平臺提供裝置全生命週期管理相關工具,幫助個人、企業快速實現大規模裝置的雲端管理;開放第三方API介面,推進個性化應用系統構建;提供定製化“和物”APP,加速個性化智慧應用生成。註冊地址為https://open.iot.10086.cn/,使用者可以根據其中的官方文件註冊賬號,建立裝置應用。
本教程使用stm32系列晶片,完成硬體與onenet物聯網平臺進行互動。 stm32外接Enc28j60網絡卡晶片,該晶片內建乙太網mac,phy層。使用該晶片完成網路傳輸,需要在該晶片上搭建一個軟體TCP/IP協議棧,而UIP協議棧是一個很好的選擇。在stm32上移植該協議棧,就可以把stm32採集到的感測器資料上傳到onenet雲平臺。onenet平臺支援多種協議,比如Http,MQTT,EDP等協議,而通過HTTP方式上傳至雲平臺是一種常見而且簡單的方式,只需要把stm32作為HTTP客戶端,通過HTTP的一些標準請求,比如Get,Post等,就可以把資料上傳到雲端,或者從雲端獲取資料。 RestFul API 基於HTTP 協議(詳見//
HTTP協議
HTTP是一個基於TCP/IP通訊協議來傳遞資料,HTTP連線報文分為請求和響應兩種,一次請求訊息包含請求行,請求頭部,空行和請求資料四部分組成。
- 請求行
用來說明請求型別,要訪問的資源目錄以及HTTP使用版本,HTTP請求型別包含GET,POST等,它們的具體差別可以檢視相關文件
- 請求頭部
接著請求行之後的部分,包含若干屬性值,比如指定主機HOST,和onenet互動的API
- 空行
空行位於報文首部和報文主體之間。
- 請求資料
對於不同的請求過程,得到的請求資料不一樣,比如HTML,JSON等.
OneNet HTTP API按照RESTful的方式向外提供服務,其資源模型中包含的資源種類有:使用者、裝置(device)、資料流(datastream)、資料點(datapoint)、觸發器(trigger)、API key、命令等。
- 資料點(Datapoint)
即一個數據流中的一個具體的資料值。資料點採用“Key-Value”的方式儲存。其中Key的組成包括裝置ID、資料流ID、時間等資訊,value部分可以為任何資料物件,如整數、字串或者JSON資料型別。
- 資料流(Datastream)
一個數據流可以理解為一類資料,如感測器之溫度、位置之經緯度,空氣之溼度等。使用者可以自定義資料流名稱,即資料流ID;一個裝置可以新增多個數據流。
根據onenet官方文件,使用onenet上傳資料的POST請求如下所示,請求頭部需要指定三個屬性,分別為api-key,Host,Content-Length。Content-Length為請求資料的位元組大小,在報文首部和報文主體之間還存在空行。使用restful上傳的資料格式為json,請求資源的目錄為 /devices/509226975/datapoints,其中509226975為建立的裝置ID。
POST /devices/509226975/datapoints HTTP/1.1
api-key:aH2orrDNlT1gKXrdurQBoPbm5SI=
Host:api.heclouds.com
Content-Length:63
{"datastreams":[{"id":"sys_time","datapoints":[{"value":50}]}]}
先使用上面的POST請求內容,通過網路除錯組手上傳到onenet伺服器,由以下的截圖得出,資料上傳之後,伺服器就會給客戶端回覆響應的響應。HTTP響應也由狀態行、訊息報頭、空行和響應正文四個部分組成,其中響應的第一行為狀態行,有HTTP協議版本號(HTTP/1.1),狀態碼(200),狀態訊息(OK)。其中第二行和第三行為訊息報頭,用來說明客戶端要使用的附加資訊,Date:生成響應的日期和時間;Content-Type:指定了MIME型別的json,Content-Length為效能感應正文的大小。第三部分為空行,訊息報頭後面的空行是必須的。第四部分為響應正文,伺服器返回給客戶端的文字資訊,圖中是返回得到的json資料。
curl是利用URL語法在命令列方式下工作的開原始檔傳輸工具,在ubuntu主機上可以通過apt-get方式安裝,使用它可以使用命令列的方式,完成一些HTTP請求。在請求行中指定http方法,使用--request GET或--request POST來指定,請求頭部使用--header "請求頭內容" 來指定,報文內容使用--data "請求內容"來指定。ubuntu上通過curl工具來向onenet請求獲取資料命令如下所示:
//向裝置513591948查詢資料
curl --request GET --header "api-key: oueSPcHZ0YdDypDfxY56ynJs=4o=" http://api.heclouds.com/devices/513591948/datastreams
//向裝置509226975上傳資料
curl --request POST --data "{\"datastreams\":[{\"id\":\"sys_time\",\"datapoints\":[{\"value\":888}]}]}" --header "api-key: aH2orrDNlT1gKXrdurQBoPbm5SI=" http://api.heclouds.com/devices/509226975/datapoints
stm32上傳溫度資料給onenet,使用stm32開啟一個定時器,在定時器中將傳送標誌位置位,在UIp狀態機空閒時不斷判斷該標誌位是否置位,如果置位就將採集到的溫度資料打包成上述講解的HTTP報文並通過POST上傳到onenet,然後使用者在瀏覽器就可以看到相關資料。相關核心程式碼如下所示:
void onenet_send()
{
float value=get_temperature();//獲取stm32內部溫度感測器資料
//打包請求資料
sprintf(http_content,"{\"datastreams\":[{\"id\":\"temp\",\"datapoints\":[{\"value\":%3.1f}]}]}",value);
//請求行
sprintf(http_header,"POST %s HTTP/1.1\r\n",remote_path);
strcpy(http_request,http_header);
//請求頭部的屬性
sprintf(http_attribute,"Host:%s\r\n",remote_server);
strcat(http_request,http_attribute);
memset(http_attribute,0,sizeof(http_attribute));
//新增請求頭部的屬性
sprintf(http_attribute,"api-key:%s\r\n",apikey);
strcat(http_request,http_attribute);
memset(http_attribute,0,sizeof(http_attribute));
//新增請求頭部的屬性
sprintf(http_attribute,"Content-Length:%d\r\n",strlen(http_content));
strcat(http_request,http_attribute);
memset(http_attribute,0,sizeof(http_attribute));
strcat(http_request,"\r\n");
strcat(http_request,http_content);
uip_send(http_request,sizeof(http_request));
}
//uip回撥介面
void tcp_client_appcall()
{
if(uip_connected())//已經連線上伺服器?
{
uip_sock_flag =CLIENT_CONNECT;
uip_log("tcp_client connected!\r\n");//
return;
}
if(uip_aborted())//意外終止?
{
//uip_abort();
uip_sock_flag =CLIENT_DISCONNECT;
uip_log("tcp_client aborted!\r\n");//´òÓ¡log
tcp_client_reconnect();
}
if(uip_timedout())//超時?
{
uip_sock_flag =CLIENT_TIMEOUT;
uip_log("tcp_client timeout!\r\n");//
tcp_client_reconnect();
}
if(uip_acked())//確認發出資料?
{
uip_ack_flag=1;
uip_log("tcp_client acked!\r\n");//±íʾ³É¹¦·¢ËÍ
}
if(uip_newdata())//遠端主機已經發出資料
{
if(uip_ack_flag)
{
uip_log("*************tcp_client recv data!*********\r\n");//±íʾ³É¹¦·¢ËÍ
printf("\r\n%s\r\n",uip_appdata);
}
}
if(uip_rexmit())//重發?
{
uip_log("tcp_rexmit\r\n");
onenet_send();
return;
}
if(uip_poll())//應用程式迴圈執行
{
uip_log("uip_poll\r\n");
// if(uip_sock_flag!=CLIENT_CONNECT)
// {
// uip_abort();
// }
if(uip_send_flag)//判斷髮送標誌位是否置位?
{
uip_send_flag=0;
onenet_send();
}
}
if(uip_closed())//由於onenet上傳一次資料,連線會自動關閉 關閉後就重連
{
uip_ack_flag=0;
uip_sock_flag=CLIENT_DISCONNECT;
uip_log("tcp_client closed! reconnect!\r\n");//´òÓ¡log
tcp_client_reconnect();
return;
}
}
//定時器中斷服務函式
void TIM3_IRQHandler(void)
{
static u8 tcnt;
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //
{
GPIOE->ODR ^= GPIO_Pin_8;
if(uip_sock_flag == CLIENT_CONNECT)//連線是否還存在?
{
uip_send_flag=1;
}
}
TIM_ClearITPendingBit(TIM3, TIM_IT_Update ); //
}
開啟瀏覽器,進入onenet中的裝置列表,進入此裝置的裝置流展示的選項,就可以觀察到溫度資料已經上傳到雲平臺上了。
以上是通過一個POST請求將上傳上傳到雲平臺的例子,首先在網路除錯助手中,通過一個get請求查詢資料,使用報文如下:
GET /devices/513591948/datastreams HTTP/1.1
Host:api.heclouds.com
api-key:oueSPcHZ0YdDypDfxY56ynJs=4o=
下面將使用stm32通過get請求不斷查詢伺服器的資料,來控制板載LED燈的量滅,在使用過程中開啟一個定時器每隔一段時間通過一個Get請求,查詢其中的資料,然後在uip狀態機的接受過程中接收伺服器響應的資料,得到資料就解析其中的響應報文,然後解析json中的資料包,然後得到開關量,控制LED的亮滅
//傳送一個GET請求
void onenet_send()
{
//打包請求資料
sprintf(http_header,"GET %s HTTP/1.1\r\n",remote_path);
strcpy(http_request,http_header);
//請求行
sprintf(http_attribute,"Host:%s\r\n",remote_server);
strcat(http_request,http_attribute);
memset(http_attribute,0,sizeof(http_attribute));
//請求行新增屬性
sprintf(http_attribute,"api-key:%s\r\n",apikey);
strcat(http_request,http_attribute);
memset(http_attribute,0,sizeof(http_attribute));
strcat(http_request,"\r\n");
uip_send(http_request,sizeof(http_request));
}
//uip回撥介面
void tcp_client_appcall()
{
if(uip_connected())//已經連線上伺服器?
{
uip_sock_flag =CLIENT_CONNECT;
uip_log("tcp_client connected!\r\n");//±íʾÁ¬½Ó³É¹¦
return;
}
if(uip_aborted())//意外終止?
{
//uip_abort();
uip_sock_flag =CLIENT_DISCONNECT;
uip_log("tcp_client aborted!\r\n");//´òÓ¡log
tcp_client_reconnect();
//uip_resolv_connect(remote_server,80);
}
if(uip_timedout())//超時?
{
uip_sock_flag =CLIENT_TIMEOUT;
uip_log("tcp_client timeout!\r\n");//´òÓ¡log
tcp_client_reconnect();
//uip_resolv_connect(remote_server,80);
return;
}
if(uip_acked())//確認已經發出資料?
{
uip_ack_flag=1;
uip_log("tcp_client acked!\r\n");//±íʾ³É¹¦·¢ËÍ
}
if(uip_newdata())//遠端主機已經發出資料
{
if(uip_ack_flag)
{
uip_ack_flag=0;
uip_log("*************tcp_client recv data!*********\r\n");//±íʾ³É¹¦·¢ËÍ
len_temp=uip_datalen();
memcpy(tcp_client_databuf,uip_appdata,len_temp);
printf("\r\n%s\r\n",uip_appdata);
value_info=(u8 *)strstr(tcp_client_databuf,"\"current_value\"");//擷取"\"current_value\""開頭的子串
if(value_info!=NULL)
{
len_temp=strlen("\"current_value\":");
status=*(value_info+len_temp);
printf("\r\n%c\r\n",status);
if(status =='0')
{
printf("switch close!\r\n");
// GPIOE->ODR |= GPIO_Pin_8;
GPIOE->BRR = GPIO_Pin_8;
}
else
{
printf("switch open!\r\n");
//GPIOE->ODR &= ~GPIO_Pin_8;
GPIOE->BSRR = GPIO_Pin_8;
}
}
memset(tcp_client_databuf,0,sizeof(tcp_client_databuf));
}
}
if(uip_rexmit())//重發
{
uip_log("tcp_rexmit\r\n");
onenet_send();
return;
}
if(uip_poll())//應用程式迴圈執行
{
//uip_log("uip_poll\r\n");
if(uip_sock_flag!=CLIENT_CONNECT)
{
uip_abort();
}
if(uip_send_flag)//傳送標誌位是否置位
{
uip_send_flag=0;
onenet_send();
}
return;
}
if(uip_closed())//由於onenet上傳一次資料,連線會自動關閉 關閉後就重連
{
uip_ack_flag=0;
uip_sock_flag=CLIENT_DISCONNECT;
uip_log("tcp_client closed! reconnect!\r\n");//´òÓ¡log
tcp_client_reconnect();
return;
}
}
//定時器3中斷服務函式
void TIM3_IRQHandler(void)
{
static u8 tcnt;
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
{
if(uip_sock_flag == CLIENT_CONNECT)//還存在連線
{
uip_send_flag=1;
}
}
TIM_ClearITPendingBit(TIM3, TIM_IT_Update ); //
}
在瀏覽器中進入onenet中的應用管理,開啟該應用,按下ON/OFF按鈕,就可以控制板載LED燈的亮滅