【2020】新冠疫情数据实时监控

2020-02-042.3万
精华项目
AI 快速预览详细 收起
本文介绍了如何使用树莓派和Arduino构建一个疫情数据实时监控系统。首先,通过Linux命令(如wget、sed)从网页中提取所需数据,然后通过串口通讯将数据发送到Arduino,并在LCD显示屏上显示。项目实现了每秒更新一次数据的功能,解决了手动查询数据不便的问题。此外,还探讨了数据存储和应用的可能性,包括使用gnuplot进行简单绘图。
疫情数据实时监控

关键词:树莓派、Arduino、串口通讯、Linux命令


        Hi!我是Shuuei,很高兴能参加这次“春节宅家一起造”活动。
        这个年确实太不寻常!每天早上醒来第一件事是打开知乎热榜看看最新的确诊病例数,这几天疑似病例已经破万了...不知道接下来的高峰期会怎么样(因为大致功能实现的时候是1月底... 2月4日提交的时候确诊都已经破两万了...),希望都大家能顺利。武汉加油!中国加油!

        不过这也算是我想法的由来,因为每次都要打开手机不方便,所以懒病的我琢磨着能不能直接自动告诉我现在疫情的最新数据。早上起来查完疫情数据,放下手机叹了口气挠了挠头,想着国难当头而自己却无能为力。发呆看着桌子上连在Arduino上的显示屏------上面有温湿度、时间,以及在一旁的的树莓派2B------瞪着独眼(摄像头)瞅着我...  
              
        突然想起以前学c语言时有一章讲的是“利用程序读取网上你想要的新闻”,感觉不错。于是思路为:

网络->树莓派->Arduino->lcd显示屏

        想了解的数据:确诊病例、重症病例、死亡人数、痊愈人数、疑似病例

        基本的想法成型了,那么接下来是实践部分。

        先上几张成果图:


【图片描述0-0:lcd显示之前1月底的确诊数据】\n



【图片描述0-1:lcd显示之前2月初的确诊数据

\n


图片描述0-2: 2月7日家里没有余粮了搬到乡下后的实物图】
\n


【图片描述0-3:简单的绘图 于2月13日更新,确诊人数激增、疑似持续走低。希望这是一个拐点或者极值点,武汉加油!中国加油!】

【补充:于2月22日更新 确诊人数增长放缓;疑似病例数持续走低(估计是随着医疗力量的抵达,疑似出现的速度跟不上化验的速度);治愈人数破2万,是死亡人数的10倍。】


ok, 从头开始。

I. 从网络到树莓派 (Linux命令: wget, head, cut,cat,  grep,  sed,  tr ,  | ,  > , >>)
        a. 下载网页
        最初翻书找到了那“利用程序读取网上你想要的新闻”的章节,发现那是利用一个python程序读取筛选RSS源推送的新闻中你想要的部分,也尝试过几次,但好像RSS源好多都挂了... 总之没有成功,对python也不太熟,便放弃了。

        后面看了看知乎疫情新闻网页的html文件,ctrl+F查找后发现自己筛选出那几个数据出来也不会太难。便用linux命令「wget」了其html页面,用以接下来的操作。




【图片描述1-a-0.:找到数据来源的网址,查找想要的字段数据】



【图片描述1-a-1:使用「wget」命令下载html源文件 -O 参数为重命名同时可指定保存位置】


【图片描述1-a-2:未经处理的html源文件(后面还有,太长了就不显示了)
文件名:103691078,「cat」命令显示文档内容】

       b. 数据筛选
        下载下来的html文件中,需要的只是那几个数据,利用一些Linux命令可以方便地分割出那些数据。不过得自己先查看html文件后再制定筛取计划,但只要制定一次就行,以后源网站更新了这边就自动处理了,这种实时更新的版面变动一般不会太大。以下为步骤:

        1. 观察一下后发现我们只需要前两行就行,想着首先肯定是先把前两行单独提出来,然后是从第二行数据中先剪下一个大段,两端留好空间以防稍许变动;



【图片描述1-b-0:先拎出前两行,再剪下一段,两端留好适当空余。
  | 为导管,上一个命令的输出接至下一个命令的输入;
「head」命令 -n 参数 数值2 取前两行
「cut」命令 -c 参数 数值 1200-1600 剪下第1200个字符至第1600个字符
> 重定向至extract.txt文档便于观察以及下次操作】


       2. 发现数字的写法不利于后续使用,想着将单字节逗号“,”删除以及将数字两边的空格删除(双字节数字变为单字节数字?);



【图片描述1-b-1:对数字前后字符进行操作便于后续操作
| 为导管,上一个命令的输出接至下一个命令的输入;
「sed」命令 ‘s/://g’参数 可将冒号替换为null即删除冒号,关注点为三个斜线所围成的两个可填入字段,左边的为要被替换的内容,右边的为替换的内容。此处被替换内容为空,即删除冒号。同理‘s/,//g’,‘s/ //g’分别为删除逗号和空格。
> 输出重定向至pretreat.txt文档便于观察以及下次操作】


      3. 接下来便是将先前预留的空余删除,分割出纯粹的所需内容;



【图片描述1-b-3:分割出纯粹所需内容
| 为导管,上一个命令的输出接至下一个命令的输入;
「sed」命令 ‘s/^.*截止//g’参数 可将“截至”及其以前的字符全部删除,
                         ‘s/据不完全.*$//g’参数 可将“据不完全”及其之后的字符全部删除,
                          ‘s/\n//g’参数 可将换行符删除;
【2月3日补充: 此处‘s/\n//g’参数无法删除,后用tr命令删除换行符,小结处有解释】
> 输出重定向至reading.txt文档便于观察以及下次操作】


       4. 分离出数据



【图片描述1-b-4:分离数据
| 为导管,上一个命令的输出接至下一个命令的输入;
「sed」命令 s/\n/,/g’参数 可将换行符替换为逗号以便字段分离;
【2月3日补充,后面换成tr命令得到解决,小结处有解释】
> 输出重定向至data.txt文档便于观察以及下次操作;
>> 为在重定向文档的末尾添加 而 > 为直接覆盖,使用时需注意】


此处问题:按理说换行符应该都被替换为逗号了,可不知为何还是有换行;而且奇怪的是在windows里用txt记事本打开data.txt没有换行,这俩操作系统是对换行符解释上是不是有点区别…,但notepad++显示了换行… 是txt记事本直接忽视了换行吗?求教!】

[图片描述:txt read in win]

[图片描述:notpad++ read in win]

【解决:后面一边写一边查了,想起来了:
是\r, \n, \r\n的区别 回车、换行、回车换行
Windows: \r\n,Linux: \n, Mac: \r】


        5. C语言整合一下数据,添加首末标志,并定义一个结构以便未来拓展。






【图片描述1-b-5:C语言程序读取data.txt,添加冒号分号作为首末标志,输出到sending.txt文件中】


【注:为了前后一致性还是保留了之前的截图,以下为加入时间后的sending.txt】



【图片描述1-b-6:最后加入了unix时间,便于后续直接调用time库计算
        unix时间:从协调世界时1970年1月1日0时0分0秒起至现在的总秒数,不考虑闰秒。 在多数Unix系统上Unix时间可以透过date +%s指令来检查。
        p.s. 和上一次截图相比...这两天爆发的有点多...武汉加油!中国加油!】



小结I:

至此完成了串口数据发送的数据准备工作,以下为一些补充说明与后续改进。
1. 因为是第一次用命令行进行数据处理,所以分了好几次来观察。其效率不高有重复,但便于理解,也就没有再进一步简化整合。【2月3日补充,该小结最后有改进】

2. C程序按理说不需要,但是想着便于集中数据便于今后的拓展使用,还是添上了。

3. 至于如何自动获取并生成最新数据,即“自动化”上述过程:        
        之前整体考虑的是时间到了,直接是从头到尾运行一次发送一次,不用一直运行串口发送程序,故原打算用crontab来实现定时执行,但crontab没有实现 /惨。而且发现只发送一次就关闭串口的话arduino不一定收的到,还得接收回传数据…
        于是目前偷懒用while-do-done + sleep 1s的 bash脚本程序来每隔1秒一直实现html下载,数据处理,C语言汇总并添加时间的数据准备工作。而另一个python程序则负责一直与arduino建立串口通信的工作。        



【图片描述I-0:实现数据准备工作,详见注释以及之前描述】


【2月3号补充:之前写的脚本有点糟糕,下面为改进并整合后的脚本与输出】


【图片描述I-1:数据准备工作改为完全由shell脚本执行,c语言用于拓展用途,详见注释 】



【图片描述I-2:上述脚本输出数据】

ok,到此完成了数据准备工作,数据就让树莓派每秒「wget」一个html文件到本地,「sed」等命令配合生成发送数据。虽然源网页是半小时更新,听着真的蛮辛苦2B了(树莓派型号2B+ 尼尔机械纪元...)
【P.S. 其实2B是我听那个树莓派讲座的时候 two B, two B, ...突然想起来的。 所以之前我树莓派都是取名为ikaros...  以后要更名为2B了!/  呲牙】

好了,接下来就是发送到arduino.

II.  从树莓派到Arduino到lcd显示屏
        串口这儿折腾了好久,因为一开始没有意识到Arduino的Serial.read()是1byte即八位八位地读取数据,返回值是ASCII码,需要转换成数字并进行累计计算。以前是用c语言套接字进行数据传输,那时好像只要定义一个数组缓冲区长度后,剩下就是发送和接收了,什么结尾开始都有TCP/IP协议帮你搞定,现在这串口通讯还得自己设计开头结尾...不过还行,还好没有中途放弃使用串口转去用蓝牙… / 笑
        不过以后可以试试,毕竟都是要无线的。

        c. 树莓派侧程序        (上接I. 从网络到树莓派 b. 数据筛选脚本)
        树莓派这边的python确实 “人生苦短我用python”。本来想用c语言发送数据的,但那一长串的串口操作流程让我止步。反观在python中就几句完事...让我这个没怎么碰过python且有点点不适应的人长吁不已。但因为只是网上查了一些语法就写上了,虽然大体功能可以实现,但总有那么点搞不太明白。

        现在的python串口通信流程大致是,while一直查看串口接收数据,if else判断来决定发送下一次发送:若无接收数据则发送一次数据,若接收接收到数据则显示接收到的数据和上一次发送的数据。但 arduino上lcd显示屏数据更新间隔10s,感觉好慢!

        尝试过几种python程序发送方案, 几个方案改动的基本是若无接收数据的话隔多长时间重新发送,调整过三种方案:
        1.若串口无数据仅仅发送一次数据,直到接收串口数据后再重新发送
        2.若串口无数据则发送一次数据,若数秒后无接收则重新发送一次新的数据
        3若串口无数据则一直发送新的数据



【图片描述2-c-0:为第二种方案,隔一段时间尝试重新发送        
        结果好像都没啥用,都差不多更新时间为10s:不管是python从发送到接收回传时间隔;lcd显示数据更新的间隔,都是10s左右。想着是不是基本原理没搞清楚,请指教!
        又打开了一个写文件是后续想把arduino回传的数据用c语言来读取并操作,虽然目前没有新增类似传感器数据之类的内容】


       d. Arduino侧程序
        arduino程序的主要内容为以下几个部分:
        loop();串口中断;计算以逗号分隔的每个字段的值;数据发送;lcd显示;


【图片描述2-d-0:loop(),RecvComplete为串口是否接收完毕判断符】



【图片描述2-d-1:串口中断程序,读取字节,分号开始 冒号结束,详见注释】



【图片描述2-d-2:计算每个逗号之间的字段的值,tmp数组存储的是个位、十位、百位等数位上的值】



【图片描述2-d-3:函数display()中,数组最后一个值dep[LEN-1]存储的是Unix时间,取模后用于指示数据更新
函数response()为回传数据给树莓派】




【图片描述2-d-4:定义用于lcd显示的字符数组,以便未来拓展与通用性】


小结II
到此为止基本完成了之前设想的功能。以下为一些问题和思路的记录。
1. 发现按以前的思路…: loop里面if(serial.available) 接收数据,else回传数据,lcd显示仍用delay(),结果和用串口中断和millis()延时,更新的速度都一样… 10s左右。本以为用串口中断会更加及时不会被delay延时所影响,但实际结果不像自己想象的那样。

2. 感觉好慢。我是2s有新数据,如果按照我现在的程序的话,只有五分之一的数据实时显示了。我以为是arduino侧读取计算字节每个字段太慢了,但计算后发现1ms都不到。后面才明白过来,10s,6个字段,5次lcd.clear(),每次延时2s,可不就10s吗?!!!果然,去掉lcd显示,真的就能近似实时接收并回传数据,但timeout得设置,且不能为0,要不然arduino会来不及接收。

3. python的串口通信和最初相比自认为有了一定的改进,加入了树莓派端读入串口缓冲区字节数来判断是否需要再次发送,虽然对arduino接收的总字符数有影响,但是对实际使用到的无影响,多余的字段会被舍弃,只会盯住开头“:”和结尾“;”。但...好像对显示数据更新的实时性没有多大帮助,都是一样的10s间隔。看来得改进一下之前所说的lcd刷新用中断,不用millis()或者delay()。


III. 杂谈:改进与拓展
       a. Arduino方面: 其他传感器数据的加入
        大体功能完成后,想着字符串数组能不能存到flash里面,用来节省动态内存。但看了看好像动态内存用了百分之二十不到,还行,以后拓展功能了再说吧。PROGMEM

        至于拓展功能,可以有很多吧,因为之前搭建的时候就考虑到以后的扩展,以后加入一些其他的数据:比如温湿度、光强、经纬度、噪音、pm2.5、PIR等传感器数据的时候只需要修改一下数组的大小就行了,其他的还可以rtc时钟、喇叭、led灯、按钮等外设,那时候就要充分利用仅仅只有2K的RAM内存了。

        还有就是lcd显示时用定时中断,琢磨一下多线程。

        b. 树莓派方面:  数据的本地存储以及数据的利用(目前只实现简单画图)
        首先数据的来源的话,可以不止一个网站,毕竟知乎好像是半个小时更新一次,0点到6点不更新。今天(2月1号)昨天在查树莓派有关问题时,偶尔发现也有个同学和我的想法一样,实时了解疫情动态(https://talk.quwj.com/topic/644),他的数据来源是丁香园,用一个python就能搞定,(爬虫程序?)。还有一些群友也是实时显示疫情信息,数据来源好像有api套接字直接返回,不需要再像我一样分割出来。

        其次是数据的处理,用命令行确实可以,但好像效率和C比起来有点不高,但目前来说我这小玩意好像不用考虑效率。。。但以前导师教过:

        要时刻考虑你有1000个10000个甚至更多的相同功能的小玩意同时运行!你需要时时刻刻的数据,按1s的间隔来更新!这么多数据要被一个服务器同时接收!处理!你该怎么做?
        好吧,我得考虑考虑…

        c. 考虑考虑
        再者是既然得到了数据,又会自动运行,为什么不把它们存储起来用于数据分析呢?如果本地能进行一部分的处理筛选再发送至上一级服务器(比如云端)的话,那不是可以大大减少了主服务器(主节点)的工作量吗?(雾计算)
基于这样的想法有以下两点需要考虑:
1.  本地存储
        目前是简单地用c语言把一条新的数据添到一个txt文件中了,数据更新时间为1s,目前加入Unix时间后一条数据的大小大概40B左右



【图片描述3-c-0:2月2号一天的大小变化】

        一天就将近1MB,这才只是一个节点(arduino)的数据,树莓派还可以连接多个arduino(节点),或者类似的嵌入式设备:esp32等单片机,存储所有节点发送的数据(当然本次我做的项目性质不一样,数据是从树莓派发送给arduino,arduino再回传数据,目前没有添加传感器数据,不过概念是一样的)。

        就实现功能来说,每秒wget一次html页面有点蠢可能,因为知乎上大约半小时更新一次。也有考虑过是否过于频繁,但一想对以后的传感器系统是由帮助的,于是还是按照1s一次的频率来更新源数据。数据获取的频率可以高,但真正有效的需要的数据由树莓派来判断该不该保存

2. 数据的应用
        目前就只是最简单地利用每秒得到的数据在树莓派上用gnuplot画了一个很丑的图





【图片描述3-c-1:大致就是1月底2月初的数据,有点难看。
首先是按每秒存储数据的后果在画图的时候就显现出来了,太多的重复数据了!一道长长的水平线是有够难看的;
其次便是横坐标没有显示,原因是unix时间目前没有转换成对应日期,干脆就「unset xtics」取消显示横坐标了
虽然有够粗糙的,不过大致还是可以看出,最近(2月初)黄线的治愈人数反超了红线死亡人数。武汉FIGHT!中国FIGHT!
需要以后再进一步改进,有数据可视化这方面经验的同学麻烦给个建议!谢谢!】




【图片描述3-c-2:以上为gnuplot的配置文件,然后只要cat gnuplot.conf | gnuplot 后就可以得到png图像了
详细的参数解释我就懒得从网上复制粘贴了,大概:
unset xtics 为取消横轴显示 set grid 为显示网格 其他plot前面的都能看懂
plot后面的参数 重要的是 u=using, 利用哪两列的数据,冒号前为横轴,冒号后为纵轴
w=with, with后可以好多好多参数,什么lc=line color, pt 大小粗细之类的
还有好多可以设置让图像变美观的参数和设置,但这就和html/css一样样式太多了, 我又比较懒,就... 大家自行百度吧
但能看到自己处理生成的数据画出来,总会有些激动的感觉~不妨试试】




【图片描述3-c-3:这个为gnuplot读取的数据文件,空格分隔,可以通过c语言或者之前的linux命令来获得】


//之前说的:数据获取的频率可以高,但真正有效的需要的数据由树莓派来判断该不该保存。
【2月3号补充:更新了C程序,数据获得虽然还是sleep 1s, 串口发送仍然是实时发送获得到的每秒更新的数据
  但是树莓派可以初步判断哪些数据需要存储--存储间隔15分钟以上的数据】、




【图片描述3-c-4:如果上一次保存的数据和现在获得数据间隔超过一定时间,才进行存储】

【2月3号补充:随之也更新了脚本程序,因为还得创建一个文件流
目的是获得存储的数据文件中的最后一行,用于C程序读入判断上一次存储的时间】


【图片描述3-c-5:主要完成文件流创建和时间间隔参数的传递
执行shell脚本时附带的参数可以传给c程序来定义间隔时间,无参数则传递默认参数】



【图片描述3-c-6:此时无参数默认间隔为10s (后默认值改成900s)】


【图片描述3-c-7:此为现默认900s间隔存储的脚本运行结果
可以看到中间部分间隔900s才存入数据】


【图片描述3-c-8:此为现默认900s间隔下的存储文件,tail -n 30取最后三十行,| 为导管(pipe)
高亮上方为先前每条数据均保存,高亮下方为15分钟保存一次数据】



【图片描述3-c-9:更新绘图结果(2月4日),还是不太好看,因为那一段长长的斜线。估摸着应该等以后数据量多了就不会显得那么难看了】

V. 备忘

  嗯,今后还是得考虑下,把之前做的gravity项目给带一下:esp32、阿里云什么的。

  算是2020年的“研究计划”。

  以后也要考虑未来加入其他模块的便捷性、通用性,要考虑效率,速度,数据量的压缩(位操作)

   

【参考资料】
《Linux入门很简单》 刘金鹏 著
《鸟哥的Linux私房菜》 鸟哥 著
《Linux命令行与shell脚本编程大全》 门佳、武海峰 译
《Arduino权威指南》 杨昆云 译
《21天学通C语言》 第七版 姜佑 译
《嗨翻C语言》 程亦超 译
“度娘”

#-------------------------------------
#以上,希望和大家多多交流学习有关嵌入式设备、物联网IoT、Linux、数据处理与可视化等方面的知识!
#20200204 立春快乐!
#Jerry Shuuei
#罗马烟花筒

附】流程图、视频链接、程序源码: https://make.quwj.com/project/199/ (Request For Comment!!!)








VI. 更新
20200209
这几天发现数据出了问题,无法画出正确的图,一直在调试。一开始以为是数据保存端的问题,后面发现是数据生成端的问题,尝试了很多方但一直没有解决。详见日志 https://make.quwj.com/project/199/ 今天早上醒来,想着如果生成成错误无法避免,不妨加入进一步判断使得错误的数据不要被保存。像传感器之类的数据也会有噪音,而由于各种原因由程序产生的数据也会有“噪音”。于是目前为了走向课题研究的下一阶段,小结了错误原因并记录,待以后详细了解了命令背后的工作原理后再处理数据生成问题。就像软件的瓶颈是硬件的话,类比到目前的状况便是:数据保存正确性的瓶颈便是数据生成的可靠性。


记录:

问题描述:数据生成端出了问题导致无法利用保存的数据画图

产生原因:

一是无线网络问题,本来在二楼信号就比较弱,wget长时间未接收到数据包便自动退出,但因为-O参数的原因导致最初的文件已被覆盖,在未下载完成的情况下传递给后面sed、grep命令的文件实为空文件,加之之前偷懒用于保存数据的c程序中数据结构没有初始化,故fscanf格式化输入字段后得到之前的错误数据--比较像系统打印未赋值变量的值(不知道是否可以在最初端加入判断,wget、sed等的返回值?);

二是有位前辈指出的grep的“标准输入匹配到二进制”提示、sed对含有中文输入的文件会出现卡死等情况导致。


解决方法(目前):因错误产生数据均有规律可循,现已尝试加入判断:即若显然是错误数据,则丢弃不保存,并修改python串口发送程序的文件。



【图片描述:前几个字段未生成,c程序读取的数据文件只有一个用date命令和产生的unix时间】


【图片描述:卡死,推测为sed处理含有中文字符时卡死,编码环境locale问题 LC_ALL】


【图片描述:grep匹配到二进制文件报的提示,修改为grep -a】



【图片描述:错误的数据gnuplot将绘出错误的图片】



20200206
修改了一下筛选脚本,可惜了那一段增长地很明显的数据。知乎好像每分钟更新了。。。



【图片描述:根据源网页制定筛取计划】


【图片描述:筛取结果】



【图片描述:加入unix时间】



【图片描述:2月6号晚数据,可以看出随着医疗力量的到达,越来越多的疑似病例被确诊,但好消息是治愈人数也在不断攀升!FIGHT!
钟南山老爷子说预计周六到达顶峰,ok,期待热乎乎的数据


20200205
知乎源html数据变动了,得重新修改脚本。
p.s.知乎加入了数据可视化功能 人画得可真好!  我得继续跟进




















创作许可协议

本项目采用 None(不开放任何权利,保留所有权利) 进行许可。

评论(11)
rzegkly
rzegkly
分享的很好,学习
rzegkly
rzegkly回复Shuuei
相互学习
Shuuei
Shuuei
作者
回复rzegkly
评论内容
20060606
20060606
Arduino可以去掉,直接把1602接树莓派上
Shuuei
Shuuei
作者
回复20060606
噢噢 想学习从树莓派到arduino的数据传输来着
王卓荦
王卓荦
好东西肯定是要分享的呀!
Shuuei
Shuuei
作者
回复王卓荦
是啊 lets dance~
gada888
gada888
有爱的作品
Shuuei
Shuuei
作者
回复gada888
我让我家2B小姐姐给gada比心~
kylinpoet
kylinpoet
楼主强大,多谢分享。
Shuuei
Shuuei
作者
回复kylinpoet
感谢支持!仍需努力!
红通通
红通通
太棒了
Shuuei
Shuuei
作者
回复红通通
蟹蟹 进一步学习ing
且歌且行
且歌且行
大佬~
gray6666
gray6666
技术硬禾贴
Shuuei
Shuuei
作者
回复gray6666
啊 缺陷好多 还得改进
dbc0301
dbc0301
我觉得Arduino可以去掉,然后直接把LCD屏接树莓派上,这样看起来更简洁一些。
Shuuei
Shuuei
作者
回复dbc0301
收到!多谢提建议!
但是我想应用课题研究的物联网IoT分层模型,arduino是端节点,树莓派是中间节点。树莓派收集像arduino、esp32类似的端节点的数据,进行数据筛选后发送云服务器上进行进一步处理。
就实现疫情展示的功能而言,确实直接连到树莓派上就行了。但感觉树莓派gpio口连lcd显示屏有点浪费。。以后想用gpio进行语音识别和语音播报之类的功能。
不过我还没有试过在树莓派上直接连lcd,可能实时性会好很多。回头试试!多谢建议!
dbc0301
dbc0301
厉害了大佬!
希望教程能出得更详细一点。
红通通
红通通回复Shuuei
太谦虚了
Shuuei
Shuuei
作者
回复dbc0301
额... 我尽力... 离大佬还差的远 / 捂脸
DFrJ5KYVQaH
DFrJ5KYVQaH
在家防控疫情
Shuuei
Shuuei
作者
回复DFrJ5KYVQaH
是啊 国难当头 我辈定当宅家不出门
- 没有更多了 -