Coronameter
As Michigan gets it's first cases of COVID-19 and events, schools, and businesses close up, this global pandemic starts to feel a lot more real. To stay properly panicked, I decided to re-purpose my old temperature display from college as a dedicated screen showing what the score is.
Screen
Hardware
The hardware is nothing more than a ST7920 LCD wired to an ESP8266. The screen runs at 5V, so there is a logic level converter thrown in, too. This screen used to be a display for my indoor outdoor temperature thingy, is crudely soldered, and had no case. First order of business was to design and 3D print a small plastic enclosure for it. After wiring up a 5V wall adapter, the hardware is done.
Software
When it was a temperature display, all the fetching of data was done on the ESP8266 itself. For the new system, I decided to just make the display dump the contents of raw UDP packets. That way, I can do the hard work in an external program (like Python) and send it over to the screen when done. I did the same thing with the Winter Camp Mission timer setup with the Nixie Clocks, and a character VFD hooked to an 8266, and it worked very well.
The screen is driven using u8g2lib, and since the ST7920 doesn't have a built in text mode with this library, it was up to me to pick out a font and position the characters on the screen. U8g2lib will draw the characters graphically. I wound up with 21 columns and 7 rows, 147 characters total. On a screen this size, this makes for large and clearly readable text, even at a distance.
#include <Arduino.h>
#include <U8g2lib.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <ESP8266mDNS.h>
#include <ArduinoOTA.h>
const char* ssid = "ssidname"; // your network SSID (name)
const char* pass = "password"; // your network password
unsigned int localPort = 4212;
WiFiUDP Udp;
U8G2_ST7920_128X64_F_SW_SPI u8g2(U8G2_R0, /* clock=*/ 12, /* data=*/ 13, /* CS=*/ 4, /* reset=*/ 14);
char zbuffer[32];
char sbuf[148];
unsigned int millistimer=0;
void setup(void) {
u8g2.begin();
Serial.begin(74880);
u8g2.setFont(u8g2_font_profont11_tr);
u8g2.setFontRefHeightExtendedText();
u8g2.setDrawColor(1);
u8g2.setFontPosTop();
u8g2.setFontDirection(0);
sprintf(zbuffer, "Starting");
u8g2.drawStr(0, 0, zbuffer);
u8g2.sendBuffer();
WiFi.mode(WIFI_STA);
WiFi.softAPdisconnect (true);
WiFi.begin(ssid, pass);
while(WiFi.status() != WL_CONNECTED){
delay(1000);
}
Serial.println(WiFi.localIP());
u8g2.clearBuffer();
sprintf(zbuffer, "OK: %d.%d.%d.%d",WiFi.localIP()[0],WiFi.localIP()[1],WiFi.localIP()[2],WiFi.localIP()[3]);
u8g2.drawStr(0, 0, zbuffer);
u8g2.sendBuffer();
Udp.begin(localPort);
ArduinoOTA.setHostname("graphomatic-udp");
ArduinoOTA.begin();
millistimer=millis();
}
void loop(void) {
ArduinoOTA.handle();
int packetSize = Udp.parsePacket();
if(packetSize){
Udp.read(sbuf, sizeof(sbuf));
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_profont11_tr);
u8g2.setFontRefHeightExtendedText();
u8g2.setDrawColor(1);
u8g2.setFontPosTop();
u8g2.setFontDirection(0);
byte x=0;
byte y=0;
byte c=0;
while(sbuf[c]!=0&&c!=255){
if(sbuf[c]>31&&sbuf[c]<127){
u8g2.drawGlyph(x*6+1,y*9-1,sbuf[c]);
}
if(sbuf[c]==10){y++; x=255;}
x++;
if(x==21){x=0; y++;}
c++;
}
u8g2.sendBuffer();
}
}
Getting the data
Next, I need to get the stats, format it for the screen, and send it over. Python makes this easy to do, even though I hadn't done this before.
Some of the numbers I get using a regex on the raw html I get when doing a request to a webpage. Another site I pick from that shows the COVID-19 world and country stats gives a JSON response, which Python also has good tools to work with.
import requests
import json
import re
import socket
import datetime
import time
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while 1:
try:
rW = requests.get('https://covid19.mathdro.id/api')
wc = rW.json().get('confirmed').get('value')
wd = rW.json().get('deaths').get('value')
wr = rW.json().get('recovered').get('value')
except:
wc=0
wd=0
wr=0
try:
rA = requests.get('https://covid19.mathdro.id/api/countries/USA')
ac = rA.json().get('confirmed').get('value')
ad = rA.json().get('deaths').get('value')
ar = rA.json().get('recovered').get('value')
except:
ac=0
ad=0
ar=0
try:
rM = requests.get('https://covid19.mathdro.id/api/countries/USA/confirmed')
rMj = [x for x in rM.json() if x.get("provinceState") == "Michigan"][0]
#mc = rMj.get('confirmed')
md = rMj.get('deaths')
mr = rMj.get('recovered')
except:
md=0
mr=0
try:
rD = requests.get('https://www.michigan.gov/Coronavirus')
mc = int(re.search("Positive for 2019-nCoV(.*?) (.*?)<", rD.text, re.DOTALL).group(2))
except:
mc=0
try:
rD = requests.get('https://finance.yahoo.com/quote/%5EDJI?p=%5EDJI')
dow = float(re.search("D\(b\)\" data-reactid=\"14\">(.*?)<", rD.text).group(1).replace(",",""))
except:
dow=0
try:
rD = requests.get('https://finance.yahoo.com/quote/%5EGSPC?p=%5EGSPC')
sp = float(re.search("D\(b\)\" data-reactid=\"14\">(.*?)<", rD.text).group(1).replace(",",""))
except:
sp = 0
try:
rD = requests.get('https://finance.yahoo.com/quote/CLX?p=CLX')
clx = float(re.search("D\(b\)\" data-reactid=\"14\">(.*?)<", rD.text).group(1).replace(",",""))
except:
clx=0
buf = "Wilson's Coronameter "
buf += ("W %d|%d|%d" % (wc, wd, wr)).ljust(21)
buf += ("USA %d|%d|%d" % (ac, ad, ar)).ljust(21)
buf += ("MI %d|%d|%d" % (mc, md, mr)).ljust(21)
buf += ("DJ %.1f S&P %.1f" % (dow, sp)).ljust(21)
buf += ("CLX %.1f" % (clx)).ljust(21)
buf += (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")).ljust(21)
sock.sendto(buf.encode(), ('192.168.1.44',4212))
print(buf)
j = datetime.datetime.now().strftime("%M")
while j == datetime.datetime.now().strftime("%M"):
time.sleep(1)