手把手教你利用鴻蒙OS實現智慧家居·LOT上雲項目
一、前言
今天使用鴻蒙OS,做一個LOT上雲的智慧家居項目。我們想實現的場景是這樣的:雲端WEB有一個控制界面,能夠操控家房間里的燈和風扇,同時將房間里溫度、濕度、光強實時顯示出來。
二、案例思路
先講一下大致的思路,可以分為兩個部分:先配置雲服務器,再編寫底層MCU的業務代碼,實現數據采集與聯網上報。邏輯上沒有復雜的東西,但貴在走通整個流程。和普通RTOS上雲的方案差不多,具體差異在軟件方面。
1、准備工作
雲服務器的配置,不算復雜,主要是前端的處理和顯示,可以先在服務器調通,再根據雲服務提供的API,進行訪問。
硬件環境使用小熊派·鴻蒙季開發板和E53_IA1擴展板(有溫濕度光強傳感器和電機)。
具體如何創建工程,可以參考我的上一篇文章,這里采用Windows環境下的開發方式。無論是基於HPM還是Docker環境獲取鴻蒙源碼創建工程,都很簡單。
這個Demo,我們將會用到鴻蒙OS的內核子系統和驅動子系統。內核子系統主要使用線程相關的API(基於CMSIS-2.0)和網絡服務相關的API(socket);驅動子系統主要調用底層的GPIO和硬件I2C,控制外部設備。
在內核子系統和驅動子系統上,我們還需要一個組件(軟件包),物聯網通信協議MQTT,利用它進行上雲服務。
列一下主要的資源和工具:
硬件:
- 小熊派 · 鴻蒙季開發板
- E53_IA1擴展板
源碼:
- Hi3861開發板的源碼,來源Hb,適用於windows環境
IDE
- vscode(IDE平台)
- DevEco Device Tool (IDE組件,可選)
- RaiDrive
本地環境:
- windows10 64位
- ubuntu18.04
雲環境:
- HUAWEI-LoTCloud(雲服務器平台)
- CloudIDE(可選,用於在線調試API接口)
下面,跟着我具體的操作,一步一步實現整個方案,內容比較多,務必提前裝好環境,可以先看看前面的文章,把環境搭建起來。
2. 雲端操作
先講雲服務器這里。為了方便驗證,我們首選華為雲服務器(騰訊雲、阿里雲也可,原理大同小異)。
操作流程大致如下:
設備接入華為雲平台之前,需要在平台注冊用,已注冊過的可忽略這一步。華為雲地址:https://www.huaweicloud.com/
登陸以后,在華為雲首頁單擊控制台
,進入產品控制終端,這里包含了各種雲服務的產品。
選擇雲服務器的地點為華為-北京四
。
點擊左側的 服務器
,找到物聯網
,選擇設備接入IoTDA
並立即使用。或者在搜索輸入 設備接入IoTDA
跳轉過去。下次選擇這個服務時,直接點擊搜索欄下的最近訪問的服務,就能快速進入相應的服務當中,非常方便。
點擊產品
,選擇創建產品
,填寫產品信息。「所屬資源空間」選擇默認,「產品名稱」這里填寫一個Smart_House(根據自己喜好寫一個),「協議類型」選擇MQTT就好,「數據格式」為JSON,「廠商名稱」填寫一口Linux,「設備類型」填寫senser。點擊確定,完成產品的創建。
創建完畢,彈出產品創建成功的窗口消息。
點擊產品列表的「查看」,進行設備的相關操作。
定義一個服務模型,「服務ID」隨便起名字,這里填入Agriculture,「服務類型」填入senser。點擊確定,完成服務的添加。
接下來為服務設置屬性和命令,這里規定了數據通信的基本格式。
點擊「添加屬性」,以溫度為例,「屬性名稱」填寫Temperature,「屬性描述」填寫溫度,「數據類型」為整型,「訪問權限」為可讀,剩下的默認即可。其中「屬性名稱」的內容,要與后面我們在MCU中發送的信息保持一致,這里先提一句。
與溫度類似,我們依次填寫如下內容,不同的是燈和電機,兩個的「數據類型」是字符串,「長度」為3。下圖列舉了燈的屬性和其他的設備屬性總覽。
接着添加服務命令,點擊「添加命令」,依次輸入「命令名稱」,再點擊「新增輸入參數」。
新增輸入參數和服務屬性差不多,這里是字符串的數據類型,輸入枚舉值,用英文逗號做分割。
我們來看一下所有的屬性和命令,差不多就這樣:
我們往下進行,點擊「設備」,
選擇「注冊設備」填寫設備屬性,
「所屬資源空間」選擇默認賬戶的即可,
「所屬產品」選擇上面自己創建的產品,
「設備標識碼」填寫senser,
「設備名稱」填寫house,其他保持默認,
點擊確定完成創建。
設備創建成功以后,有兩個重要信息需要保存,分別是設備ID和設備密鑰。
設備ID: 60cdaf505f880902bcaa161c_senser
設備密鑰: 4a423f69b41806de0d8ed77e145534e7
接着我們利用獲取的密鑰,生成直連MQTT所需的ClentID,通過這個鏈接跳轉:https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/
https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/
以上,我們雲服務器的配置,先到此為止,接下來就是MCU終端上的軟件編寫。在我們完成軟件編寫以后,在進行兩邊的聯調測試。
三、軟件編寫
我們使用的鴻蒙OS源碼,已經包含了MQTT等常用的模塊,在示例工程中可以方便的查找。這里先簡單講一下目錄結構,熟悉一下整個鴻蒙OS在源碼框架上的細節。
這里使用的鴻蒙源碼工程,由HPM包管理器獲取,具體源碼結構如下:
我們列一張表,看看每一個文件夾具體承擔了哪些職能:
文件名稱 | 描述 |
---|---|
applications | BearPi-HM_Nano開發板應用案例 |
base | 系統的基礎服務,主要使用DFX子系統、啟動文件、硬件適配接口等 |
kernel | 內核子系統 |
ohos_bundles | 廠家提供的一些組件和服務 |
third_party | 第三方組件 |
foundation | 系統服務框架子系統、WAN開發 |
headers | 存放main頭文件 |
src | 存放 main源文件 |
utils | 公共基礎庫 |
test | XTS認證子系統 |
vendor | 硬件抽象層 |
build | 編譯構建子系統 |
out | 存放編譯文件 |
bin | 存放二進制文件 |
適合本文項目的代碼示例,在applications文件夾,具體目錄為:applications\BearPi\BearPi-HM_Nano\sample\D6_iot_cloud_oc
。這里列一下目錄文件結構:
文件名稱 | 描述 |
---|---|
E53_IA1.c | 擴展板驅動 |
oc_mqtt_profile_package.c | 打包和配置MQTT數據 |
oc_mqtt.c | MQTT連接服務 |
wifi_connet.c | wifi連接服務 |
iot_cloud_oc_sample.c | 業務邏輯代碼 |
我們主要要用到的API如下,具體實現的細節,可以到源文件里面去閱讀。可以分為初始化和數據上傳兩個部分。
1. 初始化
1)設備信息
void device_info_init(char *client_id, char * username, char *password);
設置設備信息,在調用oc_mqtt_init()前要先設置設備信息
參數 | 描述 |
---|---|
無 | 無 |
返回 | 描述 |
0 | 成功 |
-1 | 獲得設備信息失敗 |
-2 | mqtt 客戶端初始化失敗 |
2)華為IoT平台 初始化
int oc_mqtt_init(void);
華為IoT平台初始化函數,需要在使用 華為IoT平台 功能前調用。
參數 | 描述 |
---|---|
無 | 無 |
返回 | 描述 |
0 | 成功 |
-1 | 獲得設備信息失敗 |
-2 | mqtt 客戶端初始化失敗 |
3)設置命令響應函數
void oc_set_cmd_rsp_cb(void(*cmd_rsp_cb)(uint8_t *recv_data, size_t recv_size, uint8_t **resp_data, size_t *resp_size));
設置命令響應回調函數。
參數 | 描述 |
---|---|
recv_data | 接收到的數據 |
recv_size | 數據的長度 |
resp_data | 響應數據 |
resp_size | 響應數據的長度 |
返回 | 描述 |
無 | 無 |
2. 數據上傳
1)設備消息上報
int oc_mqtt_profile_msgup(char *deviceid,oc_mqtt_profile_msgup_t *payload);
是指設備無法按照產品模型中定義的屬性格式進行數據上報時,可調用此接口將設備的自定義數據上報給平台,平台將設備上報的消息轉發給應用服務器或華為雲其他雲服務上進行存儲和處理。
參數 | 描述 |
---|---|
deviceid | 設備id |
payload | 要上傳的消息 |
返回 | 描述 |
0 | 上傳成功 |
1 | 上傳失敗 |
2)設備上報屬性數據
int oc_mqtt_profile_propertyreport(char *deviceid,oc_mqtt_profile_service_t *payload);
用於設備按產品模型中定義的格式將屬性數據上報給平台。
參數 | 描述 |
---|---|
deviceid | 設備id |
payload | 要上傳的消息 |
返回 | 描述 |
0 | 上傳成功 |
1 | 上傳失敗 |
屬性上報和消息上報的區別,請查看消息通信說明
3)網關批量上報屬性數據
int oc_mqtt_profile_gwpropertyreport(char *deviceid,oc_mqtt_profile_device_t *payload);
用於批量設備上報屬性數據給平台。網關設備可以用此接口同時上報多個子設備的屬性數據。
參數 | 描述 |
---|---|
deviceid | 設備id |
payload | 要上傳的消息 |
返回 | 描述 |
0 | 上傳成功 |
1 | 上傳失敗 |
4)屬性設置的響應結果
int oc_mqtt_profile_propertysetresp(char *deviceid,oc_mqtt_profile_propertysetresp_t *payload);
參數 | 描述 |
---|---|
deviceid | 設備id |
payload | 消息 |
返回 | 描述 |
0 | 上傳成功 |
1 | 上傳失敗 |
5)屬性查詢響應結果
int oc_mqtt_profile_propertygetresp(char *deviceid,oc_mqtt_profile_propertygetresp_t *payload);
參數 | 描述 |
---|---|
deviceid | 設備id |
payload | 消息 |
返回 | 描述 |
0 | 上傳成功 |
1 | 上傳失敗 |
6)將命令的執行結果返回給平台
int oc_mqtt_profile_cmdresp(char *deviceid,oc_mqtt_profile_cmdresp_t *payload);
平台下發命令后,需要設備及時將命令的執行結果返回給平台,如果設備沒回響應,平台會認為命令執行超時。
參數 | 描述 |
---|---|
deviceid | 設備id |
payload | 要上傳的消息 |
返回 | 描述 |
0 | 上傳成功 |
1 | 上傳失敗 |
3. 編寫業務邏輯
1)連接平台
准備好上文我們獲取的連接信息(ClientId、Username、Password),一個可以上網的WIFI(賬戶和密碼),注意不可以用5G頻段。
#define CLIENT_ID "60cdaf505f880902bcaa161c_senser_0_0_2021062002"
#define USERNAME "60cdaf505f880902bcaa161c_senser"
#define PASSWORD "e7f839333a8d3618a975e2626df1462f67202f3f4103080fe8d6f05df0fa7ce3"
WifiConnect("TP-LINK_65A8","0987654321");
device_info_init(CLIENT_ID,USERNAME,PASSWORD);
oc_mqtt_init();
oc_set_cmd_rsp_cb(oc_cmd_rsp_cb);
2)推送數據
當需要上傳數據時,需要先拼裝數據,然后通過oc_mqtt_profile_propertyreport上報數據。代碼示例如下:
/**
* @brief 處理上報的數據。
* @details Process the reported data.
* @param[in] report 需要上報的數據。The data to be reported.
* @return None
***/
static void deal_report_msg(report_t *report)
{
/** 定義服務ID句柄 */
oc_mqtt_profile_service_t service;
/** 定義溫度的上報數據句柄 */
oc_mqtt_profile_kv_t temperature;
/** 定義濕度的上報數據句柄 */
oc_mqtt_profile_kv_t humidity;
/** 定義亮度的上報數據句柄 */
oc_mqtt_profile_kv_t luminance;
/** 定義電燈的上報數據句柄 */
oc_mqtt_profile_kv_t led;
/** 定義電機的上報數據句柄 */
oc_mqtt_profile_kv_t motor;
/** 初始化要上報的服務ID數據 */
service.event_time = NULL;
service.service_id = "Agriculture";
service.service_property = &temperature;
service.nxt = NULL;
/** 初始化要上報的溫度數據 */
temperature.key = "Temperature";
temperature.value = &report->temp;
temperature.type = EN_OC_MQTT_PROFILE_VALUE_INT;
temperature.nxt = &humidity;
/** 初始化要上報的濕度數據 */
humidity.key = "Humidity";
humidity.value = &report->hum;
humidity.type = EN_OC_MQTT_PROFILE_VALUE_INT;
humidity.nxt = &luminance;
/** 初始化要上報的亮度數據 */
luminance.key = "Luminance";
luminance.value = &report->lum;
luminance.type = EN_OC_MQTT_PROFILE_VALUE_INT;
luminance.nxt = &led;
/** 初始化要上報的電燈數據 */
led.key = "LightStatus";
led.value = g_app_cb.led?"ON":"OFF";
led.type = EN_OC_MQTT_PROFILE_VALUE_STRING;
led.nxt = &motor;
/** 初始化要上報的電機數據 */
motor.key = "MotorStatus";
motor.value = g_app_cb.motor?"ON":"OFF";
motor.type = EN_OC_MQTT_PROFILE_VALUE_STRING;
motor.nxt = NULL;
/** 將屬性數據上報給平台 */
oc_mqtt_profile_propertyreport(USERNAME,&service);
return;
}
3)命令接收
華為IoT平台支持下發命令,命令是用戶自定義的。接收到命令后會將命令數據發送到隊列中,task_main_entry函數中讀取隊列數據並調用deal_cmd_msg函數進行處理,代碼示例如下:
/**
* @brief 將命令數據發送到隊列。
* @details Send command data to the queue.
* @param[in] recv_data 接收的數據
* @param[in] recv_size 接收數據的大小
* @param[in] resp_data 接收的上報數據
* @param[in] resp_size 接收的上報數據的大小
* @return None
***/
void oc_cmd_rsp_cb(uint8_t *recv_data, size_t recv_size, uint8_t **resp_data, size_t *resp_size)
{
app_msg_t *app_msg;
int ret = 0;
app_msg = malloc(sizeof(app_msg_t));
app_msg->msg_type = en_msg_cmd;
app_msg->msg.cmd.payload = (char *)recv_data;
printf("recv data is %.*s\n", recv_size, recv_data);
/** 送入隊列 */
ret = osMessageQueuePut(mid_MsgQueue,&app_msg,0U, 0U);
if(ret != 0){
free(recv_data);
}
*resp_data = NULL;
*resp_size = 0;
}
/**
* @brief 線程入口,讀取隊列數據並處理。
* @details Thread entry, read queue data and process.
* @param[in] None
* @return None
***/
static int task_main_entry( void )
{
app_msg_t *app_msg;
/** 連接WIFI */
WifiConnect("TP-LINK_65A8","0987654321");
/** 注冊設備的連接信息*/
device_info_init(CLIENT_ID,USERNAME,PASSWORD);
/** 初始化MQTT*/
oc_mqtt_init();
oc_set_cmd_rsp_cb(oc_cmd_rsp_cb);
while(1){
app_msg = NULL;
(void)osMessageQueueGet(mid_MsgQueue,(void **)&app_msg,NULL, 0U);
if(NULL != app_msg){
switch(app_msg->msg_type){
case en_msg_cmd:
deal_cmd_msg(&app_msg->msg.cmd);
break;
case en_msg_report:
deal_report_msg(&app_msg->msg.report);
break;
default:
break;
}
free(app_msg);
}
}
return 0;
}
/**
* @brief 解析命令,並給出處理的結果。
* @details Thread entry, read queue data and process.
* @param[in] cmd 命令。
* @return None
***/
static void deal_cmd_msg(cmd_t *cmd)
{
cJSON *obj_root;
cJSON *obj_cmdname;
cJSON *obj_paras;
cJSON *obj_para;
int cmdret = 1;
oc_mqtt_profile_cmdresp_t cmdresp;
obj_root = cJSON_Parse(cmd->payload);
if(NULL == obj_root){
goto EXIT_JSONPARSE;
}
obj_cmdname = cJSON_GetObjectItem(obj_root,"command_name");
if(NULL == obj_cmdname){
goto EXIT_CMDOBJ;
}
if(0 == strcmp(cJSON_GetStringValue(obj_cmdname),"Agriculture_Control_light")){
obj_paras = cJSON_GetObjectItem(obj_root,"paras");
if(NULL == obj_paras){
goto EXIT_OBJPARAS;
}
obj_para = cJSON_GetObjectItem(obj_paras,"light");
if(NULL == obj_para){
goto EXIT_OBJPARA;
}
///< operate the LED here
if(0 == strcmp(cJSON_GetStringValue(obj_para),"ON")){
g_app_cb.led = 1;
Light_StatusSet(ON);
printf("Light On!");
}
else{
g_app_cb.led = 0;
Light_StatusSet(OFF);
printf("Light Off!");
}
cmdret = 0;
}
else if(0 == strcmp(cJSON_GetStringValue(obj_cmdname),"Agriculture_Control_Motor")){
obj_paras = cJSON_GetObjectItem(obj_root,"paras");
if(NULL == obj_paras){
goto EXIT_OBJPARAS;
}
obj_para = cJSON_GetObjectItem(obj_paras,"motor");
if(NULL == obj_para){
goto EXIT_OBJPARA;
}
///< operate the Motor here
if(0 == strcmp(cJSON_GetStringValue(obj_para),"ON")){
g_app_cb.motor = 1;
Motor_StatusSet(ON);
printf("Motor On!");
}
else{
g_app_cb.motor = 0;
Motor_StatusSet(OFF);
printf("Motor Off!");
}
cmdret = 0;
}
EXIT_OBJPARA:
EXIT_OBJPARAS:
EXIT_CMDOBJ:
cJSON_Delete(obj_root);
EXIT_JSONPARSE:
///< do the response
cmdresp.paras = NULL;
cmdresp.request_id = cmd->request_id;
cmdresp.ret_code = cmdret;
cmdresp.ret_name = NULL;
(void)oc_mqtt_profile_cmdresp(NULL,&cmdresp);
return;
}
4. 編譯調試
修改 applications\sample\BearPi\BearPi-HM_Nano
路徑下 BUILD.gn 文件,指定 oc_mqtt
參與編譯。
#"D1_iot_wifi_sta:wifi_sta",
#"D2_iot_wifi_sta_connect:wifi_sta_connect",
#"D3_iot_udp_client:udp_client",
#"D4_iot_tcp_server:tcp_server",
#"D5_iot_mqtt:iot_mqtt",
"D6_iot_cloud_oc:oc_mqtt",
#"D7_iot_cloud_onenet:onenet_mqtt",
示例代碼編譯燒錄代碼后,按下開發板的RESET按鍵,通過串口助手查看日志,會打印溫濕度及光照強度信息。
sdk ver:Hi3861V100R001C00SPC025 2020-09-03 18:10:00
FileSystem mount ok.
wifi init success!
00 00:00:00 0 68 D 0/HIVIEW: hilog init success.
00 00:00:00 0 68 D 0/HIVIEW: log limit init success.
00 00:00:00 0 68 I 1/SAMGR: Bootstrap core services(count:3).
00 00:00:00 0 68 I 1/SAMGR: Init service:0x4b8040 TaskPool:0xfa9a4
00 00:00:00 0 68 I 1/SAMGR: Init service:0x4b8064 TaskPool:0xfb014
00 00:00:00 0 68 I 1/SAMGR: Init service:0x4b81c8 TaskPool:0xfb1d4
00 00:00:00 0 100 I 1/SAMGR: Init service 0x4b8064 <time: 0ms> success!
00 00:00:00 0 0 I 1/SAMGR: Init service 0x4b8040 <time: 0ms> success!
00 00:00:00 0 200 D 0/HIVIEW: hiview init success.
00 00:00:00 0 200 I 1/SAMGR: Init service 0x4b81c8 <time: 0ms> success!
00 00:00:00 0 200 I 1/SAMGR: Initialized all core system services!
00 00:00:00 0 0 I 1/SAMGR: Bootstrap system and application services(count:0).
00 00:00:00 0 0 I 1/SAMGR: Initialized all system and application services!
00 00:00:00 0 0 I 1/SAMGR: Bootstrap dynamic registered services(count:0).
SENSOR:lum:107.50 temp:33.34 hum:63.95
<--System Init-->
<--Wifi Init-->
register wifi event succeed!
callback function for wifi scan:0, 0
+NOTICE:SCANFINISH
callback function for wifi scan:1, 24
WaitSacnResult:wait success[1]s
********************
no:001, ssid:養只狗叫瑞邦 , rssi: -53
no:002, ssid:電信302 , rssi: -63
no:003, ssid:412 , rssi: -64
no:004, ssid:DIRECT-IXLAPTOP-O3K3OKASmsUK , rssi: -69
...
********************
Select: 2 wireless, Waiting...
+NOTICE:CONNECTED
SENSOR:lum:67.50 temp:33.17 hum:68.33
WaitConnectResult:wait success[1]s
WiFi connect succeed!
begain to dhcp
<-- DHCP state:Inprogress -->
<-- DHCP state:Inprogress -->
<-- DHCP state:OK -->
server :
server_id : 192.168.1.1
mask : 255.255.255.0, 1
gw : 192.168.1.1
T0 : 7200
T1 : 3600
T2 : 6300
clients <1> :
mac_idx mac addr state lease tries rto
0 e81131641696 192.168.1.123 10 0 1 3
SENSOR:lum:79.17 temp:32.77 hum:60.45
SENSOR:lum:38.33 temp:32.51 hum:52.88
SENSOR:lum:42.50 temp:32.30 hum:50.59
SENSOR:lum:42.50 temp:32.11 hum:49.73
SENSOR:lum:40.00 temp:31.91 hum:49.74
SENSOR:lum:41.67 temp:31.75 hum:49.96
回到華為雲平台,平台上的設備顯示為在線狀態
點擊設備右側的“查看”,進入設備詳情頁面,可看到上報的數據
在華為雲平台設備詳情頁,單擊“命令”,選擇同步命令下發,選中創建的命令屬性,單擊“確定”,即可發送下發命令控制設備。
看一下現象:串口打印雲端接收的數據,並執行點燈的指令。
5. 調試華為雲API
點擊「API檢索和調試」,進入API調測界面。
目前開放有Java、python、node.js、php等,可以根據個人的需求,構建前端。這里我們先調試API,選擇一個設備命令,按照圖示操作。
注意Body里面的參數,與我們上文產品的屬性是一樣的,其中paras的參數,填寫要符合圖片給出的規范,也就是JSON的格式。
最后點擊調式,給出調試結果,我們的開發板上,燈也被點亮!
四、總結
- 雲端的操作,要注意和終端軟件編寫的信息相同,一個是MQTT的連接信息不能出錯,還有就是注意名稱之間的大小寫要相同;
- 終端MCU軟件的編寫,注意分層設計,先寫好各自的功能模塊,最后再實現相關的業務邏輯;
- 注意調測,利用好串口和雲端MQTT信息跟蹤服務;
- 整體走下來,工作量還是蠻大的,需要注意的地方有很多,所以要特別細心。
- 源代碼后台回復
鴻蒙
獲取,工程文件可以參考上個文章獲取。