オープンデータ様様。
tl;dr
- Githubや神奈川県HPでCovid-19関係のデータが公開されている
- Node-REDでそれらをパースする仕組みを作りWebAPI化
- それをESP32から叩いてOLEDに表示
- 陽性者数だけ見てていいのかは分からない
コロナ禍
テレビを付けると、嫌でもコロナの話題が耳に入ってくるんですが、たいてい東京の新規陽性者数の話題ばっかりでちょっとうんざり。自分が住んでる都道府県はどうなのか気になるのですが、どうしてもピーキーなデータが出てる部分ばかり報道されるんですよねぇ。
というわけで今回は、私が住んでいる神奈川県の新規陽性者数のデイリーデータを表示できるガジェットを作ってみました。
データはどこから?
ざっくりググると、以下の2ヶ所から取れそう。
kaz-ogiwara氏がまとめている下記のリポジトリで、国内の詳細なデータが公開されている。
github.com
特に、都道府県別のデータはこちらのCSVファイルで取得可能。検査数と陽性者数を得ることができます。
covid19/prefectures.csv at master · kaz-ogiwara/covid19 · GitHub
神奈川県HP
こちらは神奈川県が独自に公開しているデータ。
www.pref.kanagawa.jp
こちらもCSVデータで取得可能だが、陽性者のひとりひとりの属性(性別や年代)も取得可能。今回は使ってませんが、これはこれで可視化できると楽しそう。
CSVをNode-REDで取得してみる
さて、先程のCSVをNode-REDで取得してみます。
普通にHTTP-GETで文字列を取得して、CSVノードでJavaScriptのObjectデータに変換すれば便利に使えそうです。ただし、神奈川県のデータは日本語が含まれていてShift-JISでエンコードされているため、HTTP-GETでバイナリバッファとして取得し、その後node-red-contrib-iconv
で変換してからCSVノードに渡しています。あとはよしなーにパースするだけ。
とりあえず、神奈川県のデータを使って、WebAPI化してみました。
これをESP32から叩いてみる
このWebAPIを前回触ったOLED付きのESP32から叩いてみます。
relativelayout.hatenablog.com
表示内容はNode-RED側ですべて準備してしまうので、ESP32側はHTTP-GETしてそれをそのまま表示するだけです。
#include <SSD1306.h>
#include <WiFi.h>
#include <HTTPClient.h>
#define SSID "xxxxx"
#define PASSWORD "xxxxx"
#define URL "xxxxx"
#define DELAY 5 * 60 * 1000
SSD1306 display(0x3c, 5, 4);
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println();
connectWiFi();
display.init();
display.setFont(ArialMT_Plain_16);
}
void loop() {
show(get());
delay(DELAY);
}
void connectWiFi() {
WiFi.begin(SSID, PASSWORD);
Serial.print("connecting...");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(100);
}
Serial.print("connected : ");
Serial.println(WiFi.localIP());
}
String get() {
HTTPClient http;
http.begin(URL);
int httpCode = http.GET();
if (httpCode != HTTP_CODE_OK) {
Serial.println("http-get failed.");
return "http-get failed.";
}
String body = http.getString();
Serial.println(body);
return body;
}
void show(String message) {
display.clear();
display.setTextAlignment(TEXT_ALIGN_CENTER);
display.drawString(64, 4, message);
display.display();
}
これを実行すると・・・
おー。表示できました。今日は33人のようですねぇ。
これで陽性者をウォッチできますね!
まだまだ神奈川県の陽性者数は少ないようですが、気を抜けませんねぇ。
あとは、陽性者数だけ見てても良いわけはなくて、陽性率だったり重症患者数、医療体制の逼迫具合なども勘案して総合的に判断していきたいものです。それらもよしなにNode-REDのダッシュボードなどで可視化できるといいですねぇ。
Node-REDのフローはこちら
[
{
"id": "e16d9838.5519a",
"type": "tab",
"label": "Covid-19",
"disabled": false,
"info": ""
},
{
"id": "6092239b.0dbd9c",
"type": "inject",
"z": "e16d9838.5519a",
"name": "Githubから取得",
"topic": "",
"payload": "",
"payloadType": "date",
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"x": 180,
"y": 80,
"wires": [
[
"48ec884a.e861f"
]
]
},
{
"id": "48ec884a.e861f",
"type": "http request",
"z": "e16d9838.5519a",
"name": "",
"method": "GET",
"ret": "txt",
"paytoqs": false,
"url": "https://raw.githubusercontent.com/kaz-ogiwara/covid19/master/data/prefectures.csv",
"tls": "",
"persist": false,
"proxy": "",
"authType": "",
"x": 170,
"y": 120,
"wires": [
[
"82278aeb.234d4"
]
]
},
{
"id": "82278aeb.234d4",
"type": "csv",
"z": "e16d9838.5519a",
"name": "",
"sep": ",",
"hdrin": true,
"hdrout": "",
"multi": "mult",
"ret": "\\n",
"temp": "",
"skip": "0",
"strings": true,
"x": 150,
"y": 160,
"wires": [
[
"3b7e8bd6.48c4a4"
]
]
},
{
"id": "bfd441d3.dbdd18",
"type": "debug",
"z": "e16d9838.5519a",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"x": 170,
"y": 280,
"wires": []
},
{
"id": "3b7e8bd6.48c4a4",
"type": "function",
"z": "e16d9838.5519a",
"name": "神奈川だけフィルタ",
"func": "var kanagawaList = [];\n\nmsg.payload.forEach(element => {\n if(element.prefectureNameE === \"Kanagawa\"){\n kanagawaList.push(element);\n } \n});\n\nmsg.payload = kanagawaList;\nreturn msg;",
"outputs": 1,
"noerr": 0,
"x": 200,
"y": 200,
"wires": [
[
"cbcbd716.db236"
]
]
},
{
"id": "cbcbd716.db236",
"type": "function",
"z": "e16d9838.5519a",
"name": "最新のデータだけ抽出",
"func": "var data = msg.payload.slice();\nvar todayData = data[data.length - 1];\nvar yesterdayData = data[data.length - 2];\n\nvar testedPositive = todayData.testedPositive - yesterdayData.testedPositive;\n\nmsg.payload = {};\nmsg.payload.date = todayData.year + \"/\" + todayData.month + \"/\" + todayData.date;\nmsg.payload.testedPositive = testedPositive;\n\nreturn msg;",
"outputs": 1,
"noerr": 0,
"x": 200,
"y": 240,
"wires": [
[
"bfd441d3.dbdd18"
]
]
},
{
"id": "e6bec6cf.0856a",
"type": "http request",
"z": "e16d9838.5519a",
"name": "",
"method": "GET",
"ret": "bin",
"paytoqs": false,
"url": "https://www.pref.kanagawa.jp/osirase/1369/data/csv/patient.csv",
"tls": "",
"persist": false,
"proxy": "",
"authType": "",
"x": 490,
"y": 120,
"wires": [
[
"be7264f5.2fbbc"
]
]
},
{
"id": "1c08950f.ec2513",
"type": "csv",
"z": "e16d9838.5519a",
"name": "",
"sep": ",",
"hdrin": true,
"hdrout": "",
"multi": "mult",
"ret": "\\n",
"temp": "",
"skip": "0",
"strings": true,
"x": 470,
"y": 200,
"wires": [
[
"c3f97218.b9c768"
]
]
},
{
"id": "a11490b1.e1df38",
"type": "inject",
"z": "e16d9838.5519a",
"name": "神奈川県HPから取得",
"topic": "",
"payload": "",
"payloadType": "date",
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"x": 520,
"y": 80,
"wires": [
[
"e6bec6cf.0856a"
]
]
},
{
"id": "2cdc0310.c76f24",
"type": "debug",
"z": "e16d9838.5519a",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"x": 490,
"y": 280,
"wires": []
},
{
"id": "be7264f5.2fbbc",
"type": "converter",
"z": "e16d9838.5519a",
"name": "Shift_JIS変換",
"from": "Shift_JIS",
"x": 500,
"y": 160,
"wires": [
[
"1c08950f.ec2513"
]
]
},
{
"id": "c3f97218.b9c768",
"type": "function",
"z": "e16d9838.5519a",
"name": "最新のデータだけ抽出",
"func": "var data = msg.payload.slice();\nvar lastData = data[data.length - 1];\nvar lastDate = lastData[\"発表日\"];\n\nvar testedPositive = 0;\ndata.forEach(element => {\n if(element[\"発表日\"] === lastDate){\n testedPositive++;\n }\n})\n\nmsg.payload = {};\nmsg.payload.date = lastDate.split('-').join('/').split('/0').join('/');\nmsg.payload.testedPositive = testedPositive;\n\nreturn msg;",
"outputs": 1,
"noerr": 0,
"x": 520,
"y": 240,
"wires": [
[
"2cdc0310.c76f24",
"f5d3f833.c57c78"
]
]
},
{
"id": "6ee2fb46.fcd2ec",
"type": "http in",
"z": "e16d9838.5519a",
"name": "",
"url": "/covid",
"method": "get",
"upload": false,
"swaggerDoc": "",
"x": 160,
"y": 440,
"wires": [
[
"e6bec6cf.0856a"
]
]
},
{
"id": "eccb91a6.147fb8",
"type": "http response",
"z": "e16d9838.5519a",
"name": "",
"statusCode": "",
"headers": {},
"x": 590,
"y": 440,
"wires": []
},
{
"id": "f5d3f833.c57c78",
"type": "function",
"z": "e16d9838.5519a",
"name": "レスポンス作成",
"func": "var message = '';\n\nmessage += 'Positive Tests\\nReported : ';\nmessage += msg.payload.testedPositive + '\\n';\nmessage += '( ' + msg.payload.date + ' )'\n\n\nmsg.payload = message;\nreturn msg;",
"outputs": 1,
"noerr": 0,
"x": 380,
"y": 440,
"wires": [
[
"eccb91a6.147fb8"
]
]
}
]