8

基于灵动MM32的新冠肺炎疫情数据实时监控平台

 3 years ago
source link: http://www.wangchaochao.top/2020/06/27/mm32-2019-ncov/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
ARM

基于灵动MM32的新冠肺炎疫情数据实时监控平台

开源小项目

Posted by Wang Chao on June 27, 2020 | view 82 times

2020,新冠肺炎疫情全球爆发,国内得到了有效的控制。最近北京疫情,形势又紧张起来了,我们公司也为了配合防控疫情的要求,由之前的复工复产改为了居家办公。

不知道大家是否了解过,我之前春节假期在家做的的两个初学Qt的实战项目:

由于Qt的跨平台特性,所以在嵌入式Linux上的移植也比较顺利。对于PC和嵌入式ARM来说,疫情数据的获取,数据的图表显示,辟谣信息、疫情新闻的显示,做起来都比较轻松。

作为疫情监控平台三部曲:桌面 > 嵌入式ARM Linux > MCU。在前面两个平台实现之后,就一直想着在内存和性能有限的MCU上实现,但一直都没有找到一个合适的API接口,直到最近发现了一个数据量比较小,连接比较稳定的API。于是,我利用有限的业余时间,设计了这个基于灵动MM32 MCU的疫情监控平台,MM32通过串口和ESP8266进行AT指令交互,连接互联网获取最新的疫情数据,并显示在TFT显示屏上,可以直观方便的了解到最新的疫情数据信息。

疫情监控平台

获取疫情数据API接口

2020新冠疫情的爆发,各大互联网IT公司和个人都开发了实时疫情地图平台,腾讯新闻、丁香园、网易、新浪等等,这些数据大小都在几百KB,对于PC和嵌入式Linux来说,不用在意数据量的大小,但是对于存储非常有限的MCU来说,数据量的大小是不得不考虑的一个问题,而且对于ESP8266来说,AT指令的方式,SLL缓存最大只有4096个字节的缓存!

经过网上一番搜索,找到了几个数据量小的API,但是有的接口连接不稳定,刚连上就掉线了,最后终于找到了一个连接稳定,数据量小,数据齐全的接口:https://lab.isaaclin.cn/nCoV/zh

这是一位国人使用服务器爬虫获取了丁香园的数据,然后开放了API接口供大家免费使用,目前已经被调用了2千万次,这个网站还包括了多个接口,我只使用到了其中的疫情数据这一个接口:https://lab.isaaclin.cn/nCoV/api/overall,数据量大概为1300个字节。

JSON数据内容如下:

json数据格式

为了能使用ESP8266获取这个API返回的内容,我们还需要知道以下信息:TCP连接类型,端口号,API地址。

我们在浏览器中按F12,打开开发者模式,在地址栏输入https://lab.isaaclin.cn/nCoV/api/overall这个接口地址,可以很容易的获取到我们想要的信息:

服务器地址:47.102.117.253
端口号:443
API地址:https://lab.isaaclin.cn/nCoV/api/overall

关于端口号,如果API地址是http开头的,一般是选择TCP连接类型,80端口;如果是https开头的,一般是选择SSL连接类型,443端口。这个信息在后面会用到。

API获取

ESP8266发送HTTPS请求

WiFi模块选择的是乐鑫的ESP8266-01S模组,支持AP、Station和AP&Station混合模式。

ESP8266-01S

在进行正式的开发之前,我们先使用串口模块连接ESP8266,直接发送AT指令的方式来获取疫情数据。

整体流程是:配置工作模式 > 连接WiFi > 与服务器建立SSL连接 > 发送GET请求获取数据

0.为了确保模块保持初始状态,在进行配置之前,先让模块恢复出厂设置: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连接的,下载地址:ESP8266-01S出厂默认 AT 固件

1.WiFi模块设置为Station模式:AT+CWMODE=1

2.配网,连接WiFi:AT+CWJAP="ssid","password"


AT+CWMODE=1

OK
AT+CWJAP="mm32_2019_ncov","www.wangchaochao.top"

WIFI CONNECTED
WIFI GOT IP

OK

3.设置单连接模式:AT+CIPMUX=0

4.设置SSL连接大小:AT+CIPSSLSIZE=4096

5.与服务器建立HTTPS/SSL连接:AT+CIPSTART="SSL","47.102.117.253",443

6.设置为透传模式:AT+CIPMODE=1

7.启动透传:AT+CIPSEND

8.发送GET HTTPS请求:GET https://lab.isaaclin.cn/nCoV/api/overall

串口指令交互

如果以上都配置正确,会收到服务器返回的数据,也就是我们的想要的疫情数据。

如果SSL连接不断开,一直在透传模式,就可以每隔一段时间GET一次API,这样就可以获取到最新的疫情数据了。

经过多次GET请求测试发现,连接还比较稳定,没有出现掉线的情况,但是由于API的访问限制,不要太频繁的发送GET请求,否则可能会被API开发者把IP封掉。

当然,如果连接断开,就要重新执行建立SSL连接,设置透传模式,开始透传这几个操作。如果要主动断开SSL连接,可以先发送不带回车换行的+++退出透传,然后使用AT+CIPCLOSE关闭SSL连接。

单独的AT指令测试没问题,那我们就可以使用MCU的串口来自动完成和ESP8266的AT指令交互了。

MM32获取疫情数据过程

JSON数据的解析

数据是JSON格式的,解析库使用的是开源小巧的cJSON库,只有两个文件,使用起来非常方便。

在进行解析之前,先来分析一下JSON原始数据的格式:results键的值是一个数组,数组只有一个JSON对象,获取这个对象对应键的值可以获取到国内现存和新增确诊人数、累计和新增死亡人数,累计和新增治愈人数等数据。

全球疫情数据保存在globalStatistics键里,它的值是一个JSON对象,对象仅包含简单的键值对,这些键的值,就是全球疫情数据,其中updateTime键的值是更新时间,这是毫秒级UNIX时间戳,可以转换为标准北京时间。


{
	"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
}

先定义了结构体ncov_data,用于存储国内和全球疫情数据:


struct ncov_data{
    int confirmedCount; //累计确诊
    int curedCount;     //累计治愈
    int deadCount;      //累计死亡
    int confirmedIncr;  //新增确诊
    int curedIncr;      //新增治愈
    int deadIncr;        //新增死亡
    char updateTime[20];
};

对应的解析函数:


#include "ncov_data.h"

struct ncov_data dataChina;
struct ncov_data dataGlobal;

uint8_t parse_ncov_data(struct ncov_data *dataChina, struct ncov_data *dataGlobal)
{
    cJSON *root;
    cJSON *results_arr;
    cJSON *results;
    cJSON *globalStatistics;
    
    time_t updateTime;
    struct tm *time;
    
    printf("开始解析疫情数据, 数据长度:%d\r\n", strlen((const char *)UART2_RX_BUF));
	
	/* 申请内存 */
    root = cJSON_Parse((const char *)UART2_RX_BUF);
    if(!root)
    {
        printf("疫情数据格式错误\r\n");
        return 0;
    }
    else
    {
        printf("疫情数据格式正确,开始解析\r\n");

        results_arr = cJSON_GetObjectItem(root, "results");
        if(results_arr)
        {
            results = cJSON_GetArrayItem(results_arr, 0);
            if(results)
            {
                dataChina->confirmedCount = cJSON_GetObjectItem(results, "currentConfirmedCount")->valueint;
                dataChina->confirmedIncr = cJSON_GetObjectItem(results, "currentConfirmedIncr")->valueint;
                dataChina->curedCount = cJSON_GetObjectItem(results, "curedCount")->valueint;
                dataChina->curedIncr = cJSON_GetObjectItem(results, "curedIncr")->valueint;
                dataChina->deadCount = cJSON_GetObjectItem(results, "deadCount")->valueint;
                dataChina->deadIncr = cJSON_GetObjectItem(results, "deadIncr")->valueint;

                printf("------------国内疫情-------------\r\n");
                printf("现存确诊: %-7d, 较昨日:%-5d\r\n", 
                    dataChina->confirmedCount, dataChina->confirmedIncr);
                printf("累计治愈: %-7d, 较昨日:%-5d\r\n", 
                    dataChina->curedCount, dataChina->curedIncr);
                printf("累计死亡: %-7d, 较昨日:%-5d\r\n", 
                    dataChina->deadCount, dataChina->deadIncr);

                globalStatistics = cJSON_GetObjectItem(results, "globalStatistics");
                if(globalStatistics)
                {
                    dataGlobal->confirmedCount = cJSON_GetObjectItem(globalStatistics, "currentConfirmedCount")->valueint;
                    dataGlobal->confirmedIncr = cJSON_GetObjectItem(globalStatistics, "currentConfirmedIncr")->valueint;
                    dataGlobal->curedCount = cJSON_GetObjectItem(globalStatistics, "curedCount")->valueint;
                    dataGlobal->curedIncr = cJSON_GetObjectItem(globalStatistics, "curedIncr")->valueint;
                    dataGlobal->deadCount = cJSON_GetObjectItem(globalStatistics, "deadCount")->valueint;
                    dataGlobal->deadIncr = cJSON_GetObjectItem(globalStatistics, "deadIncr")->valueint;

                    printf("------------全球疫情-------------\r\n");
                    printf("现存确诊: %-7d, 较昨日:%-5d\r\n", 
                        dataGlobal->confirmedCount, dataGlobal->confirmedIncr);
                    printf("累计治愈: %-7d, 较昨日:%-5d\r\n",
                        dataGlobal->deadCount, dataGlobal->curedIncr);
                    printf("累计死亡: %-7d, 较昨日:%-5d\r\n", 
                        dataGlobal->curedCount, dataGlobal->deadIncr);
                }
                
                /* 毫秒级时间戳转字符串 */
                updateTime = (time_t )(cJSON_GetObjectItem(results, "updateTime")->valuedouble/1000);
                updateTime += 8*60*60; /* UTC8校正 */
                time = localtime(&updateTime);
                /* 格式化时间 */
                strftime(dataChina->updateTime, 20, "%m-%d %H:%M", time);
                printf("更新于:%s\r\n", dataChina->updateTime);/* 06-24 11:21 */
            }
        }
    }
	
	/* 释放内存 */
    cJSON_Delete(root);

    printf("*********更新完成*********\r\n");
    return 1;
}

在调用cJSON_Parse()之后,一定要调用cJSON_Delete()释放内存,否则会造成内存泄露。

如果解析失败,可以把启动文件里的堆栈大小设置大一点:

2020-06-27_123459

TFT显示

液晶屏使用的是1.8寸TFT,128*160分辨率,SPI接口,使用的GPIO软件模拟的方式。由于屏幕分辨率比较低,可显示的内容有限,所以只是显示了最基本的几个疫情数据。为了减小程序大小,LCD驱动只实现了基本的画点,画线函数,字符的显示,采用的是部分字符取模,只对程序中用到的汉子和字符进行取模。为了增强可移植性,程序中并没有使用外置SPI Flash存储整个字库,下面是显示效果:

全球疫情数据展示

待优化和调整

目前只使用到了一个API接口,当然,这个平台也提供其他接口可供使用:

  • 最新的疫情新闻

https://lab.isaaclin.cn//nCoV/api/news

  • 最新的各省市疫情数据

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

代码已经开源,地址在文末,欢迎大家参与,丰富这个小项目的功能!

GitHub开源地址:https://github.com/whik/mm32_2019_ncov

如果你手上的硬件和我的一样,只需要修改工程中的USER\config.h文件中的WiFi信息,就可以直接使用了。

%E6%B1%82%E5%85%B3%E6%B3%A8.jpg


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK