Androidのメモとか

ポキオの日記です。今日も遅延してない。

Node-REDとESP32で神奈川県内のCovid-19の新規陽性者数を表示してみる

オープンデータ様様。

ポキオ Covid-19 Node-RED ESP32

tl;dr

  • Githubや神奈川県HPでCovid-19関係のデータが公開されている
  • Node-REDでそれらをパースする仕組みを作りWebAPI化
  • それをESP32から叩いてOLEDに表示
  • 陽性者数だけ見てていいのかは分からない

コロナ禍

テレビを付けると、嫌でもコロナの話題が耳に入ってくるんですが、たいてい東京の新規陽性者数の話題ばっかりでちょっとうんざり。自分が住んでる都道府県はどうなのか気になるのですが、どうしてもピーキーなデータが出てる部分ばかり報道されるんですよねぇ。

というわけで今回は、私が住んでいる神奈川県の新規陽性者数のデイリーデータを表示できるガジェットを作ってみました。

データはどこから?

ざっくりググると、以下の2ヶ所から取れそう。

Github

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で取得してみます。

ポキオ Covid-19 Node-RED ESP32

普通にHTTP-GETで文字列を取得して、CSVノードでJavaScriptのObjectデータに変換すれば便利に使えそうです。ただし、神奈川県のデータは日本語が含まれていてShift-JISでエンコードされているため、HTTP-GETでバイナリバッファとして取得し、その後node-red-contrib-iconvで変換してからCSVノードに渡しています。あとはよしなーにパースするだけ。

ポキオ Covid-19 Node-RED ESP32

とりあえず、神奈川県のデータを使って、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();
}

これを実行すると・・・

ポキオ Covid-19 Node-RED ESP32

おー。表示できました。今日は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"
            ]
        ]
    }
]
「Androidのメモとか」は、Amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、Amazonアソシエイト・プログラムの参加者です。

このブログは個人的なメモ書きであったり、考えを書く場所であります。執筆者の所属する団体や企業のコメントや意向とは無関係であります。また、このブログは必ずしも正しいことが書かれているとは限らず、誤字脱字や意図せず誤った情報を載せる場合がありえます。それが原因で読者が不利益を被ったとしても、執筆者はいかなる責任も負いません。ありがとうございます。