監測現場實時資料上傳到中移物聯網OneNet雲平臺
OneNet,中移物聯網雲平臺,是由中國移動打造的PaaS物聯網開放平臺。平臺能夠幫助開發者輕鬆實現裝置接入與裝置連線,快速完成產品開發部署,為智慧硬體、智慧家居產品提供完善的物聯網解決方案(摘自360百科)。實際監測環境中,平臺一般是前端監測裝置和後臺軟體產品的一個橋樑,現場裝置資料通過RTU(資料傳輸單元)並以平臺可以接受的MQTT協議,上傳到OneNet雲平臺;後臺軟體可以在該平臺上獲取相關資料進行解算、顯示。這是主流的使用方法,現在很多RTU都支援將資料接入到OneNet平臺,網上也有很多相關的介紹。但是現在,由於前端資料經過RTU只是原始資料,底層難以實現資料解算,而後臺軟體開發方又需要初步解算過的資料。
因此我們想了一個方案,通過我們自己的伺服器中轉,在本地伺服器進行資料解算之後,按照軟體開發方要求的資料格式和他們給的設備註冊號等資訊上傳到OneNet雲平臺。為了方便,我們在做測試的時候,自己先在OneNet雲平臺上註冊了賬號,有了自己的開發者中心。這樣建立的產品、註冊的裝置、上傳的資料都能看得見,方便觀察效果。整個資料上傳的測試過程包括以下幾個步驟:
1)首先是要有一個自己的開發者中心。
1.1 註冊賬號
1.2 建立產品
這兩步在OneNet的這個開發文件中解釋得很清楚:https://open.iot.10086.cn/doc/art243.html#66
現在我們在雲平臺上有了自己的產品,類似於有了一個數據上傳中心。在開發者中心首頁,可以看到自己的產品列表,我這裡有 兩個產品,用於測試的產品僅僅是test1這個產品,建立的時候我選擇的是MQTT協議,這個可以根據需求選擇,上文連結中的 文件也有簡單介紹。我將會把資料上傳到test1產品下面。
建立產品之後,系統會生成一個產品ID、APIKey和正式環境註冊碼,正式環境註冊碼將在後面註冊裝置的時候用到。
1.3 註冊裝置
一般都是指現場RTU接入的裝置,我們現場有5臺獨立裝置。在這裡我們模擬現場,在平臺上註冊了5臺裝置。但實際上,資料卻是從我們自己本地的伺服器上傳過去的,相當於本地伺服器上的資料上傳程式充當了5臺虛擬裝置,往雲平臺推送資料; 也可以理解為程式僅僅是一箇中轉過程。本文的重點就在於如何通過軟體程式語言將資料推送至雲平臺。
測試的時候註冊裝置很簡單,也是通過在自己的產品test1下面手動建立(後面會講到在沒有條件手動建立的時候,如何動態建立):
輸入裝置名稱和鑑權資訊即可,一般這兩項客戶方都會要求按照一定的格式命名,最終將註冊後的引數上報過去。在實際操作過程中,我們就遇到了命名不符合要求的情況,重新命名註冊後,系統返回的裝置ID和APIKey也會改變,還是挺麻煩的。這裡測試就不用管那麼多,注意一下系統返回的裝置ID
以上,我們以註冊的名為LGLK_ZSD1800324的裝置為例,系統返回裝置ID和APIKey,後續會用到。可以看到,剛註冊的裝置資料展示還是0,沒有傳入任何資料:
至此,我們已經有了上傳資料的一切條件,要用到的就是裝置ID和APIKey,要得到這兩項,前面的步驟一個都省不掉。另外,使用者在新的裝置中,不需要提前建立資料流,因為當用戶使用協議上傳資料點的時候,平臺會自動建立資料流,採用這樣的方式就能省去資料流建立工作。
以上,我們就有了基於OneNet雲平臺的以下條件:
產品(含產品ID、產品ID、APIKey、正式環境註冊碼)——>產品下的裝置(含裝置ID、APIKey)——>裝置下的資料流datastream(可選,一般不推薦手動建立)。
2)終於到了資料上傳軟體這一塊了。因為以前接觸過TCP通訊,這次在編寫C++軟體程式的時候,我還是選擇了C++中的SOCKET通訊,向雲平臺傳送HTTP協議的資料上傳報文。其實編寫資料上傳軟體,軟體是client端,負責傳送資料並不難,抓住socket TCP通訊client端的主要步驟即可(這些內容在網上也有很多介紹):
1、建立一個socket,用函式socket();
2、設定socket屬性,用函式setsockopt();* 可選
3、繫結IP地址、埠等資訊到socket上,用函式bind();* 可選
4、設定要連線的對方的IP地址和埠等屬性;
5、連線伺服器,用函式connect();
6、收發資料,用函式send()和recv(),或者read()和write();
7、關閉網路連線;
這些步驟要一步步落實,第4步“設定要連線的對方的IP地址和埠等屬性”,設定的IP地址是OneNet公開協議接入指南http接入要連線到IP 183.230.40.34,埠80。使用Send()函式傳送到指定上述地址的時候,要注意資料格式一定是按照規定的格式。
我用到的資料上傳報文如下:
- POST /devices/29866262/datapoints?type=3 HTTP/1.1
- api-key: k3EQjLNqBKUej=IR9I7c6tLsZkM=
- Host:api.heclouds.com
- connection: close
- Content-Length:65
- {"datastreams":[{"id":"temperature","datapoints":[{"value":0}]}]}
以上報文需要注意的是:第一行的裝置ID29866262替換成你自己的裝置ID;第二行裝置的api-key替換成自己的裝置api-key;第4行這個引數決定了你傳送完這段報文之後是保持TCP繼續連線還是斷開連線。我第一次就是按照預設的close,結果資料只能上傳一次,後來發現這個的時候,才醒悟過來,就把這行程式碼去掉了。就是說傳送完一次資料之後,繼續保持連線,因為實時資料會源源不斷地上傳,必須保持連線。第5行指的是第7行資料的長度,這個不允許出錯,但是也不需要一個個數,在word文件裡可以計數
以防數錯。而且在C++程式碼裡,是有strlen()這麼一個函式,可以輸出char*陣列的字元數的。這樣就能保證精確度,這個長度錯了,上傳的報文雲端也是沒法解析的,會報錯。還有就是第5行之後有一個空行,在C++程式碼裡換行\r\n就要加上兩個了,切記。
這段資料上報成功,系統裝置下面就會自動建立temperature資料流,值為0。我先是用的TCP網路除錯助手做的實驗。網路除錯助手實際上就是用TCP的SOCKET通訊完成資料上傳和接收的。我們可以看到雲平臺返回{“errno":0;“error":"succ"},說明資料上傳成功,並且自動建立了資料流:
裝置資料狀態:
資料內容:
以上只是用TCP網路除錯助手做了個測試,資料上傳成功,說明資料報文是沒有問題的。後面就是用程式碼實現了,直接上程式碼:
//相關變數定義 //1、TCP連線地址和埠號 char* host = "183.230.40.34"; int port = 80; char* addr = "api.heclouds.com"; //2、上傳資料post HTTP報文 char* pHttpPost = "POST %s HTTP/1.1\r\n" "api-key:%s\r\n" "Host: %s\r\n" "Content-Length: %d\r\n\r\n" //兩個“\r\n” "%s"; char* URL= "http://api.heclouds.com/devices/29866262/datapoints?type=3";//給裝置ID為29866262的裝置上傳Type=3格式的資料 char* msg = "{\"datastreams\":[{\"id\":\"001_1\",\"datapoints\":[{\"value\":%d}]}]}";//資料格式,Type=3 //3、裝置的APIKey char* api_key = "k3EQjLNqBKUej=IR9I7c6tLsZkM="; //定義了一個ClientNet類的變數client,在該類內完成和雲平臺的連線、傳送資料等函式 client.Connect(port, host); //連線到雲平臺 //資料上傳 char strTemp[BufferLen]; sprintf(strTemp, "%s", URL);//把char*的URL引數改為char型陣列格式 char strHttpPost[BufferLen];//儲存上傳資料的最終報文 char rainArray[BufferLen];// char msgArray[BufferLen]; sprintf(msgArray, msg0, RealRainfall*0.2);//實時資料以msg0的格式傳給msgArray sprintf(strHttpPost, pHttpPost, strTemp, api_key, addr, strlen(msgArray), msgArray);//生成最終的HTTP報文 //傳送資料報文 client.SendMsg(strHttpPost, strlen(strHttpPost)); client.Close();//關閉連線
以上擷取的是程式碼片段,其中涉及到的ClienNet類的構建,詳見附件。至此,基本完成軟體和雲平臺的連線、資料上傳工作。