一個(gè)超火超給力的STM32開(kāi)源疫情監(jiān)控項(xiàng)目
目前全世界依然都在共同努力抗擊疫情,雖然我們不能成為一線的白衣天使,但我們總可以為這個(gè)世界做點(diǎn)什么,比如做一個(gè)疫情監(jiān)控?cái)[件,每天早晨起床第一件事就是看今天的疫情情況,時(shí)刻提醒自己做好必要的防護(hù)措施,養(yǎng)成這樣一個(gè)好習(xí)慣,圈友王總的這個(gè)項(xiàng)目一出,可謂是震撼全場(chǎng)呀!讓我們來(lái)一起欣賞和學(xué)習(xí)一下這個(gè),如下圖,以下是我在基于野火F103的開(kāi)發(fā)板上將這個(gè)項(xiàng)目移植了過(guò)來(lái),大體框架已經(jīng)有了,但是獲取疫情的API還有問(wèn)題,目測(cè)是ESP8266指令不兼容SSL,回去更新下ESP8266固件再試試。
文章目錄
-
前言 -
開(kāi)發(fā)板的選擇 -
獲取疫情數(shù)據(jù)API接口 -
ESP8266發(fā)送HTTPS請(qǐng)求 -
LCD顯示 -
代碼下載 -
在其他MCU上的實(shí)現(xiàn)
前言
2020,新冠肺炎疫情在全球蔓延,國(guó)內(nèi)得到了有效的控制,最近國(guó)內(nèi)部分地區(qū)的疫情形勢(shì)又緊張起來(lái)。
不知道大家是否了解我之前做的一個(gè)新冠肺炎疫情監(jiān)控平臺(tái),基于跨平臺(tái)Qt實(shí)現(xiàn),從桌面Qt,到嵌入式Qt,相關(guān)文章:
基于桌面Qt環(huán)境的疫情監(jiān)控平臺(tái)開(kāi)發(fā)筆記:
[開(kāi)源]基于桌面Qt的肺炎疫情監(jiān)控平臺(tái)
[開(kāi)源]基于桌面Qt的肺炎疫情監(jiān)控平臺(tái)1.1版本
基于嵌入式Qt環(huán)境的疫情監(jiān)控平臺(tái)開(kāi)發(fā)筆記:
[開(kāi)源]我用STM32MP1做了個(gè)疫情監(jiān)控平臺(tái)1—交叉編譯環(huán)境搭建
[開(kāi)源]我用STM32MP1做了個(gè)疫情監(jiān)控平臺(tái)2—Qt環(huán)境搭建
[開(kāi)源]我用STM32MP1做了個(gè)疫情監(jiān)控平臺(tái)3—疫情監(jiān)控平臺(tái)實(shí)現(xiàn)
[開(kāi)源]我用STM32MP1做了個(gè)疫情監(jiān)控平臺(tái)4—功能完善界面重新設(shè)計(jì)
作為疫情監(jiān)控三部曲:桌面PC > 嵌入式ARM Linux > MCU。在前面兩個(gè)平臺(tái)上實(shí)現(xiàn)之后,就想著在內(nèi)存和性能都比較有限的MCU上實(shí)現(xiàn),比如STM32F103,但一直都沒(méi)有找到一個(gè)合適的API接口,直到最近發(fā)現(xiàn)了一個(gè)數(shù)據(jù)量比較小,連接比較穩(wěn)定的API。
于是,設(shè)計(jì)了這個(gè)基于STM32 MCU的疫情監(jiān)控平臺(tái),STM32通過(guò)串口和ESP8266進(jìn)行AT指令交互,連接互聯(lián)網(wǎng)獲取最新的疫情數(shù)據(jù),并顯示在LCD顯示屏上,可以直觀方便的了解到最新的疫情數(shù)據(jù)信息。
最終效果如下:
開(kāi)發(fā)板的選擇
開(kāi)發(fā)板用的是我在大四時(shí)自己設(shè)計(jì)的STM32開(kāi)發(fā)板——NiceDay,基于STM32F103RET主控。前幾天看大佬說(shuō)有學(xué)生在大一就自己畫(huà)板打樣了,我感到自愧不如啊!
這是我設(shè)計(jì)的第二塊板子(第一塊是畢業(yè)設(shè)計(jì)兩輪平衡車(chē)主板),是在大四快畢業(yè)時(shí),畢設(shè)實(shí)物和論文完成之后還有點(diǎn)時(shí)間,就設(shè)計(jì)了這款板子,最開(kāi)始是準(zhǔn)備做桌面天氣時(shí)鐘的。
如果你在百度上搜索:ESP8266 關(guān)鍵字,其中就有我當(dāng)時(shí)的一個(gè)回答。
好了,言歸正傳,換個(gè)API就是疫情監(jiān)控平臺(tái)了:
獲取疫情數(shù)據(jù)API接口
2020新冠疫情的爆發(fā),各大互聯(lián)網(wǎng)IT公司和個(gè)人都開(kāi)發(fā)了實(shí)時(shí)疫情地圖平臺(tái),騰訊新聞、丁香園、網(wǎng)易、新浪等等,這些數(shù)據(jù)大小都在幾百KB,對(duì)于PC和嵌入式Linux來(lái)說(shuō),不用在意數(shù)據(jù)量的大小,但是對(duì)于存儲(chǔ)非常有限的MCU來(lái)說(shuō),數(shù)據(jù)量的大小是不得不考慮的一個(gè)問(wèn)題,而且對(duì)于ESP8266來(lái)說(shuō),AT指令的方式,SSL緩存最大只有4096個(gè)字節(jié)的緩存!
經(jīng)過(guò)網(wǎng)上一番搜索,找到了幾個(gè)數(shù)據(jù)量小的API,但是有的接口連接不穩(wěn)定,剛連上就掉線了,最后終于找到了一個(gè)連接穩(wěn)定,數(shù)據(jù)量小,數(shù)據(jù)齊全的接口:https://lab.isaaclin.cn/nCoV/zh
這是一位國(guó)人使用服務(wù)器爬蟲(chóng)獲取了丁香園的數(shù)據(jù),然后開(kāi)放了API接口供大家免費(fèi)使用,目前已經(jīng)被調(diào)用了2千萬(wàn)次,這個(gè)網(wǎng)站還包括了多個(gè)接口,我只使用到了其中的疫情數(shù)據(jù)這一個(gè)接口:https://lab.isaaclin.cn/nCoV/api/overall
,數(shù)據(jù)量大概為1300個(gè)字節(jié)。
JSON數(shù)據(jù)內(nèi)容如下:
為了能使用ESP8266獲取這個(gè)API返回的內(nèi)容,我們還需要知道以下信息:TCP連接類(lèi)型,端口號(hào),API地址。
我們?cè)跒g覽器中按F12,打開(kāi)開(kāi)發(fā)者模式,在地址欄輸入https://lab.isaaclin.cn/nCoV/api/overall
這個(gè)接口地址,可以很容易的獲取到我們想要的信息:
服務(wù)器地址:47.102.117.253
端口號(hào):443
API地址:https://lab.isaaclin.cn/nCoV/api/overall
關(guān)于端口號(hào),如果API地址是http開(kāi)頭的,一般是選擇TCP連接類(lèi)型,80端口;如果是https開(kāi)頭的,一般是選擇SSL連接類(lèi)型,443端口。這個(gè)信息在后面會(huì)用到。
ESP8266發(fā)送HTTPS請(qǐng)求
WiFi模塊選擇的是樂(lè)鑫的ESP8266-01S模組,支持AP、Station和AP&Station混合模式。
在進(jìn)行正式的開(kāi)發(fā)之前,我們先測(cè)試一下使用串口模塊連接ESP8266,直接發(fā)送AT指令的方式來(lái)獲取疫情數(shù)據(jù)。
整體流程是:配置工作模式 > 連接WiFi > 與服務(wù)器建立SSL連接 > 發(fā)送GET請(qǐng)求獲取數(shù)據(jù)
0.為了確保模塊保持初始狀態(tài),在進(jìn)行配置之前,先讓模塊恢復(fù)出廠設(shè)置:AT+RESTORE
AT+RESTORE
ets Jan 8 2013,rst cause:2, boot mode:(3,7)
2nd boot version : 1.5
SPI Speed : 40MHz
SPI Mode : DIO
SPI Flash Size & Map: 8Mbit(512KB+512KB)
jump to run user1 @ 1000
ready
獲取AT固件版本信息:AT+GMR
AT+GMR
AT version:1.2.0.0(Jul 1 2016 20:04:45)
SDK version:1.5.4.1(39cb9a32)
Ai-Thinker Technology Co. Ltd.
Dec 2 2016 14:21:16
OK
有的AT固件版本不支持HTTPS連接。最新版本的AT固件是支持HTTPS連接的,下載地址:https://docs.ai-thinker.com/_media/esp8266/ai-thinker_esp8266_at_firmware_dout_v1.5.4.1-a_20171130.rar
1.WiFi模塊設(shè)置為Station模式:AT+CWMODE=1
2.配網(wǎng),連接WiFi:AT+CWJAP="ssid","password"
AT+CWMODE=1
OK
AT+CWJAP="stm32_2019_ncov","www.wangchaochao.top"
WIFI CONNECTED
WIFI GOT IP
OK
3.設(shè)置單連接模式:AT+CIPMUX=0
4.設(shè)置SSL連接大?。?code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;max-width: 100%;overflow-wrap: break-word;font-size: inherit;line-height: inherit;border-radius: 4px;color: rgb(233, 105, 0);background: rgb(248, 248, 248);box-sizing: border-box !important;">AT+CIPSSLSIZE=4096
5.與服務(wù)器建立HTTPS/SSL連接:AT+CIPSTART="SSL","47.102.117.253",443
6.設(shè)置為透?jìng)髂J剑?code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;max-width: 100%;overflow-wrap: break-word;font-size: inherit;line-height: inherit;border-radius: 4px;color: rgb(233, 105, 0);background: rgb(248, 248, 248);box-sizing: border-box !important;">AT+CIPMODE=1
7.啟動(dòng)透?jìng)鳎?code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;max-width: 100%;overflow-wrap: break-word;font-size: inherit;line-height: inherit;border-radius: 4px;color: rgb(233, 105, 0);background: rgb(248, 248, 248);box-sizing: border-box !important;">AT+CIPSEND
8.發(fā)送GET HTTPS請(qǐng)求:GET https://lab.isaaclin.cn/nCoV/api/overall
如果以上都配置正確,會(huì)收到服務(wù)器返回的數(shù)據(jù),也就是我們的想要的疫情數(shù)據(jù)。
如果SSL連接不斷開(kāi),一直在透?jìng)髂J剑涂梢悦扛粢欢螘r(shí)間GET一次API,這樣就可以獲取到最新的疫情數(shù)據(jù)了。
經(jīng)過(guò)多次GET請(qǐng)求測(cè)試發(fā)現(xiàn),連接還比較穩(wěn)定,沒(méi)有出現(xiàn)掉線的情況,但是由于API的訪問(wèn)限制,不要太頻繁的發(fā)送GET請(qǐng)求,否則可能會(huì)被API開(kāi)發(fā)者把IP封掉。
當(dāng)然,如果連接斷開(kāi),就要重新執(zhí)行建立SSL連接,設(shè)置透?jìng)髂J剑_(kāi)始透?jìng)鬟@幾個(gè)操作。如果要主動(dòng)斷開(kāi)SSL連接,可以先發(fā)送不帶回車(chē)換行的+++
退出透?jìng)?,然后使?code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;max-width: 100%;overflow-wrap: break-word;font-size: inherit;line-height: inherit;border-radius: 4px;color: rgb(233, 105, 0);background: rgb(248, 248, 248);box-sizing: border-box !important;">AT+CIPCLOSE關(guān)閉SSL連接。
單獨(dú)的AT指令測(cè)試沒(méi)問(wèn)題,那我們就可以使用MCU的串口來(lái)自動(dòng)完成和ESP8266的AT指令交互了。
JSON數(shù)據(jù)的解析
數(shù)據(jù)是JSON格式的,解析庫(kù)使用的是開(kāi)源小巧的cJSON庫(kù),只有兩個(gè)文件,使用起來(lái)非常方便。
在進(jìn)行解析之前,先來(lái)分析一下JSON原始數(shù)據(jù)的格式:results
鍵的值是一個(gè)數(shù)組,數(shù)組只有一個(gè)JSON對(duì)象,獲取這個(gè)對(duì)象對(duì)應(yīng)鍵的值可以獲取到國(guó)內(nèi)現(xiàn)存和新增確診人數(shù)、累計(jì)和新增死亡人數(shù),累計(jì)和新增治愈人數(shù)等數(shù)據(jù)。
全球疫情數(shù)據(jù)保存在globalStatistics
鍵里,它的值是一個(gè)JSON對(duì)象,對(duì)象僅包含簡(jiǎn)單的鍵值對(duì),這些鍵的值,就是全球疫情數(shù)據(jù),其中updateTime
鍵的值是更新時(shí)間,這是毫秒級(jí)UNIX時(shí)間戳,可以轉(zhuǎn)換為標(biāo)準(zhǔn)北京時(shí)間。
{
"results": [{
"currentConfirmedCount": 509,
"currentConfirmedIncr": 16,
"confirmedCount": 85172,
"confirmedIncr": 24,
"suspectedCount": 1899,
"suspectedIncr": 4,
"curedCount": 80015,
"curedIncr": 8,
"deadCount": 4648,
"deadIncr": 0,
"seriousCount": 106,
"seriousIncr": 9,
"globalStatistics": {
"currentConfirmedCount": 4589839,
"confirmedCount": 9746927,
"curedCount": 4663778,
"deadCount": 493310,
"currentConfirmedIncr": 281,
"confirmedIncr": 711,
"curedIncr": 424,
"deadIncr": 6
},
"updateTime": 1593227489355
}],
"success": true
}
先定義了結(jié)構(gòu)體ncov_data,用于存儲(chǔ)國(guó)內(nèi)和全球疫情數(shù)據(jù):
struct ncov_data{
long currentConfirmedCount;
long currentConfirmedIncr;
long confirmedCount;
long confirmedIncr;
long curedCount;
long curedIncr;
long seriousCount;
long seriousIncr;
long deadCount;
long deadIncr;
char updateTime[20];
};
對(duì)應(yīng)的解析函數(shù):
uint8_t parse_ncov_data(void)
{
int ret = 0;
cJSON *root, *result_arr;
cJSON *result, *global;
time_t updateTime;
struct tm *time;
//root = cJSON_Parse((const char *)str); //創(chuàng)建JSON解析對(duì)象,返回JSON格式是否正確
printf("接收到的數(shù)據(jù):%d\r\r\n", strlen((const char*)USART2_RX_BUF)); //JSON原始數(shù)據(jù)
root = cJSON_Parse((const char*)USART2_RX_BUF);
if (root != 0)
{
printf("JSON format ok, start parse!!!\r\n");
result_arr = cJSON_GetObjectItem(root, "results");
if(result_arr->type == cJSON_Array)
{
printf("result is array\r\n");
result = cJSON_GetArrayItem(result_arr, 0);
if(result->type == cJSON_Object)
{
printf("result_arr[0] is object\r\n");
/* china data parse */
dataChina.currentConfirmedCount = cJSON_GetObjectItem(result, "currentConfirmedCount")->valueint;
dataChina.currentConfirmedIncr = cJSON_GetObjectItem(result, "currentConfirmedIncr")->valueint;
dataChina.confirmedCount = cJSON_GetObjectItem(result, "confirmedCount")->valueint;
dataChina.confirmedIncr = cJSON_GetObjectItem(result, "confirmedIncr")->valueint;
dataChina.curedCount = cJSON_GetObjectItem(result, "curedCount")->valueint;
dataChina.curedIncr = cJSON_GetObjectItem(result, "curedIncr")->valueint;
dataChina.deadCount = cJSON_GetObjectItem(result, "deadCount")->valueint;
dataChina.deadIncr = cJSON_GetObjectItem(result, "deadIncr")->valueint;
printf("------------國(guó)內(nèi)疫情-------------\r\n");
printf("現(xiàn)存確診: %5d, 較昨日:%3d\r\n", dataChina.currentConfirmedCount, dataChina.currentConfirmedIncr);
printf("累計(jì)確診: %5d, 較昨日:%3d\r\n", dataChina.confirmedCount, dataChina.confirmedIncr);
printf("累計(jì)治愈: %5d, 較昨日:%3d\r\n", dataChina.curedCount, dataChina.curedIncr);
printf("累計(jì)死亡: %5d, 較昨日:%3d\r\n", dataChina.deadCount, dataChina.deadIncr);
printf("現(xiàn)存無(wú)癥狀: %5d, 較昨日:%3d\r\n\r\n", dataChina.seriousCount, dataChina.seriousIncr);
global = cJSON_GetObjectItem(result, "globalStatistics");
if(global->type == cJSON_Object)
{
dataGlobal.currentConfirmedCount = cJSON_GetObjectItem(global, "currentConfirmedCount")->valueint;
dataGlobal.currentConfirmedIncr = cJSON_GetObjectItem(global, "currentConfirmedIncr")->valueint;
dataGlobal.confirmedCount = cJSON_GetObjectItem(global, "confirmedCount")->valueint;
dataGlobal.confirmedIncr = cJSON_GetObjectItem(global, "confirmedIncr")->valueint;
dataGlobal.curedCount = cJSON_GetObjectItem(global, "curedCount")->valueint;
dataGlobal.curedIncr = cJSON_GetObjectItem(global, "curedIncr")->valueint;
dataGlobal.deadCount = cJSON_GetObjectItem(global, "deadCount")->valueint;
dataGlobal.deadIncr = cJSON_GetObjectItem(global, "deadIncr")->valueint;
printf("\r\n**********global ncov data**********\r\n");
printf("------------全球疫情-------------\r\n");
printf("現(xiàn)存確診: %8d, 較昨日:%5d\r\n", dataGlobal.currentConfirmedCount, dataGlobal.currentConfirmedIncr);
printf("累計(jì)確診: %8d, 較昨日:%5d\r\n", dataGlobal.confirmedCount, dataGlobal.confirmedIncr);
printf("累計(jì)死亡: %8d, 較昨日:%5d\r\n", dataGlobal.deadCount, dataGlobal.deadIncr);
printf("累計(jì)治愈: %8d, 較昨日:%5d\r\n\r\n", dataGlobal.curedCount, dataGlobal.curedIncr);
} else return 1;
/* 毫秒級(jí)時(shí)間戳轉(zhuǎn)字符串 */
updateTime = (time_t )(cJSON_GetObjectItem(result, "updateTime")->valuedouble / 1000);
updateTime += 8 * 60 * 60; /* UTC8校正 */
time = localtime(&updateTime);
/* 格式化時(shí)間 */
strftime(dataChina.updateTime, 20, "%m-%d %H:%M", time);
printf("更新于:%s\r\n", dataChina.updateTime);/* 06-24 11:21 */
} else return 1;
} else return 1;
printf("\r\nparse complete \r\n");
gui_show_ncov_data(dataChina, dataGlobal);
}
else
{
printf("JSON format error:%s\r\n", cJSON_GetErrorPtr()); //輸出json格式錯(cuò)誤信息
return 1;
}
cJSON_Delete(root);
return ret;
}
在調(diào)用cJSON_Parse()之后,一定要調(diào)用cJSON_Delete()釋放內(nèi)存,否則會(huì)造成內(nèi)存泄露。
如果解析失敗,可以把啟動(dòng)文件里的堆棧大小設(shè)置大一點(diǎn):
LCD顯示
液晶屏使用的是3.2寸 LCD,IL9341驅(qū)動(dòng)芯片,320*240分辨率,16位并口。由于屏幕分辨率比較低,可顯示的內(nèi)容有限,所以只是顯示了最基本的幾個(gè)疫情數(shù)據(jù)。為了減小程序大小,GUI只實(shí)現(xiàn)了基本的畫(huà)點(diǎn),畫(huà)線函數(shù),字符的顯示,采用的是部分字符取模,只對(duì)程序中用到的漢字和字符進(jìn)行取模。
為了增強(qiáng)可移植性,程序中并沒(méi)有使用外置SPI Flash存儲(chǔ)整個(gè)字庫(kù),下面是顯示效果:
這個(gè)是裸機(jī)版本的,基于RT-Thread RTOS也已經(jīng)實(shí)現(xiàn),敬請(qǐng)期待!
待優(yōu)化和調(diào)整
目前只使用到了一個(gè)API接口,當(dāng)然,這個(gè)平臺(tái)也提供其他接口可供使用:
最新的疫情新聞
https://lab.isaaclin.cn//nCoV/api/news
最新的各省市疫情數(shù)據(jù)
https://lab.isaaclin.cn//nCoV/api/area?latest=1&province=%E5%8C%97%E4%BA%AC%E5%B8%82
最新的辟謠信息
https://lab.isaaclin.cn//nCoV/api/rumors
代碼下載
代碼已經(jīng)開(kāi)源,地址在文末,歡迎大家參與,豐富這個(gè)小項(xiàng)目的功能!
GitHub開(kāi)源地址:https://github.com/whik/stm32_2019_ncov
或者關(guān)注我的公眾號(hào):電子電路開(kāi)發(fā)學(xué)習(xí)(ID: MCU149),在后臺(tái)回復(fù)【STM32疫情監(jiān)控】,我會(huì)把工程下載鏈接發(fā)送給你。
如果你手上的硬件和我的一樣,只需要修改工程中的USER\config.h
文件中的WiFi信息,就可以直接使用了。
在其他MCU上的實(shí)現(xiàn)
其實(shí),我也在其他廠商的MCU上實(shí)現(xiàn)了,比如國(guó)產(chǎn)的靈動(dòng)MM32,富芮坤FR8106H等,實(shí)現(xiàn)過(guò)程都是大同小異,對(duì)于在這兩款MCU上的實(shí)現(xiàn)感興趣的朋友,可以到我的博客查看詳細(xì)的開(kāi)發(fā)筆記:
基于靈動(dòng)MM32的新冠肺炎疫情數(shù)據(jù)實(shí)時(shí)監(jiān)控平臺(tái)
www.wangchaochao.top/2020/06/27/mm32-2019-ncov/基于FR8016H+ESP8266的新冠肺炎疫情監(jiān)控平臺(tái)
www.wangchaochao.top/2020/07/12/Novel-coronavirus-pneumonia-surveillance-platform-based-on-FR8016H-ESP8266/
推薦閱讀
JSON格式簡(jiǎn)介
使用cJSON庫(kù)解析和構(gòu)建JSON字符串
[開(kāi)源]基于桌面Qt的肺炎疫情監(jiān)控平臺(tái)
[開(kāi)源]基于桌面Qt的肺炎疫情監(jiān)控平臺(tái)1.1版本
[開(kāi)源]我用STM32MP1做了個(gè)疫情監(jiān)控平臺(tái)1—交叉編譯環(huán)境搭建
[開(kāi)源]我用STM32MP1做了個(gè)疫情監(jiān)控平臺(tái)2—Qt環(huán)境搭建
[開(kāi)源]我用STM32MP1做了個(gè)疫情監(jiān)控平臺(tái)3—疫情監(jiān)控平臺(tái)實(shí)現(xiàn)
[開(kāi)源]我用STM32MP1做了個(gè)疫情監(jiān)控平臺(tái)4—功能完善界面重新設(shè)計(jì)
我的博客:www.wangchaochao.top
我的公眾號(hào):mcu149
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問(wèn)題,請(qǐng)聯(lián)系我們,謝謝!