Coronameter: Difference between revisions

From Alnwlsn - Projects Repository
Jump to navigation Jump to search
(Created page with "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 d...")
 
No edit summary
Line 93: Line 93:
        
        
}</pre>
}</pre>
==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.
<pre>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(.*?)&nbsp; (.*?)<", 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)
</pre>

Revision as of 21:05, 14 March 2020

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)