0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

気象庁サイトの注警報情報を取得し、 Ulanzi TC001 で表示させる

Posted at

気象庁サイトの注警報 JSON ファイル

これまで、 気象庁予報の JSON ファイルを Node-Red で Awtrix 用に変換してみる気象庁のアメダス情報を取得し、 PHP で1地点だけ情報を抽出する の記事で、気象庁サイトの情報を TC001 に表示させる方法を紹介してきた。
今回は第3弾。気象庁サイトの注警報ページの情報取得を試みる。

気象庁サイトの注警報ページでも何種類かの JSON ファイルが読み込まれる。

そのうち、 https://www.jma.go.jp/bosai/warning/data/warning/地域コード.json が注警報情報の入った JSON になる。
およその構造は以下の通り。
jma_warn1.png

["areaTypes"]["0"] 以下には一次細分区域ごとの注警報情報が、 ["areaTypes"]["1"] 以下には市区町村単位(一部はさらに分割)の注警報情報が入っている。
["areas"] [連番] とたどると、 ["code"] (市区町村コード) と ["warnings"] に分かれており、 ["warnings"] の下は発表中の注警報の数の分だけ [連番] で注警報コードと状態が収められている。

そしてやっかいなことに、注警報コードを取り出すだけではだめで、状態が 解除 の時はその注警報コードを無視しないといけない。

JavaScript で注警報情報を取得する

JSON上最初に出てくる市区町村(長野県の場合は長野市)の注警報を取得するスクリプトとして、このようなものを作成した。

parser A
var warn_array = msg.payload;
var area_num = 0;
var warn_code = warn_array.areaTypes[1].areas[area_num].warnings;
var code = [];
var status = [];
var warn = [];

function warn_exist() {
    for (var i = 0; i < warn_code.length; i++) {
        if (warn_code[i].status !== "解除") {
            code.push(warn_code[i].code);
        }

    }

    if (code.find(value => value.match(/^3/))) {
        warn = "1";
        return;
    } else if (code.find(value => value.match(/^0/))) {
        warn = "2";
        return;
    } else if (code.find(value => value.match(/^1/))) {
        warn = "3";
        return;
    } else if (code.find(value => value.match(/^2/))) {
        warn = "3";
        return;
    } else {
        warn = "0";
        return;
    }
}

var key_num = Object.keys(warn_code[0]).length;
if (key_num > 1) {
    warn_exist();
} else {
    warn = "0";
}

msg.payload = warn;
return msg;

流れとしては

  • 最初の連想配列を読み取り、 key が1つ(code がなく status のみ)なら無条件で注警報無し
  • 連想配列を順に読み取り、 status が 解除 のもの以外を code に追加

そして code に入っている注警報コードに従って判定していく。
注警報コードは以下のページの 15 報種別コード に書かれている通り。

  • 先頭が3:特別警報
  • 先頭が0:警報
  • 先頭が1か2:注意報
  • いずれも無い:注警報無し

と上から順に判定し(抽出後に配列内の要素数を count で数え、0より大きい=要素ありと判定)、1:特別警報、2:警報、3:注意報、0:注警報無し を返すようにした。
注警報の種類を細かく取得することも可能だが、煩雑になるため上位種別のみの表示とする。

Node-RED でアプリとして TC001 に送信する

種別コードに変換できれば、あとは種別に合わせた JSON を生成できれば mqtt out で TC001 に情報を送ることができる。
jma_warn4.png
jma_warn5.png
custom 配下の MQTT トピック指定はアプリ方式で、自動めくりの1ページ分となる。
注警報発表中はページが作られ、注警報が解除された時点で空メッセージをトピックに送るとアプリごと削除され表示されなくなる。

前回の注警報発表情報を保存しておき、相違があったら notify で表示

続いて、注警報発表・解除時に notify 機能を使って通知する方法を考える。
方略としては前回の注警報状況をファイルに保存しておき、現在の注警報を取得後に比較、違いがあればメッセージを送り出すという流れになる。
が、複数のデータを Node-RED のフローで入力する方法がわからずにいた。

解決の鍵はコンテキストにあった。

この公式ドキュメントを参考に、 change フローを用いていく。
注警報状況の保存は change で flow.warn_code ← msg.payload

呼び出しは逆に change で msg.old_data ← flow.warn_code
ここで、別途 Web から取得した msg.payload の情報をこのノードに流しても、呼び出した変数情報は保存される。

この2つを使って function ノードで処理をさせ、両者が異なる場合のみメッセージを作成、 mqtt out ノードで awtirx/notify へメッセージを送信すればよい(空のメッセージの場合は何も起きない)。

ノード図はこのようになる。
jma_warn6.png

メッセージの色・文字列はアプリと同様だが、

  • "blinkText": 1000 で1秒点滅(繰り返し)
  • 特別警報・警報時は "hold":true として、本体ボタンが押されない限り表示し続ける設定
  • 特別警報・警報時は "sound":"siren" も追加、 MELODIES フォルダにある siren.txt の rtttl 音を流す(Rtttl Buzzer - ESPHome にある siren の rtttl を利用した)
  • 注意報と解除時は hold しないかわりに30秒表示

とした。

parser 2
var new_code = msg.payload;
var old_code = msg.old_code;

if (new_code != old_code) {
    if (new_code == 1) {
        msg.payload = {"text": "SP WARN", "background": "#ff00ff", "blinkText": 1000, "sound": "siren", "hold": true};
    } else if (new_code == 2) {
        msg.payload = {"text": "WARNING", "background": "#ff0000", "blinkText": 1000, "sound": "siren", "hold": true};
    } else if (new_code == 3) {
        msg.payload = {"text": "Advisory", "color": "#ffff00", "blinkText": 1000, "duration": 30};
    } else {
        msg.payload = {"text": "No Warn", "blinkText": 1000, "duration": 30};
    }
} else {
    msg.payload = "";
}

return msg;

警報はこのようになる
jma_warn7.gif

なお、 flow.warn_code で保管した注警報情報だが、マニュアルにある通り、デフォルトではメモリのみに保存され、再起動時に値が失われる。
setting.js の contextStorage プロパティ のコメントを5行分外すことで、値は30秒ごとにファイルに保存され、再起動しても flow.warn_code の値が保持される。変更が無ければ notify にメッセージは送られない。

かくして、気象会社・報道機関にありがちなパトライトのような機能を TC001MeePet を使って実現できた。
とはいえ気象庁の Web から情報を引っぱってきているので遅延は発生する。業務用として使用することは勧めない。

Node-RED フロー

最後に、一連のフローを書き出した JSON ファイルを添付する。

jma_warn_flows.json
jma_warn_flows.json
[
    {
        "id": "bac9b1afa5d97b72",
        "type": "group",
        "z": "20b92c4c3ed7c791",
        "style": {
            "stroke": "#999999",
            "stroke-opacity": "1",
            "fill": "none",
            "fill-opacity": "1",
            "label": true,
            "label-position": "nw",
            "color": "#a4a4a4"
        },
        "nodes": [
            "812e1827a3f14517",
            "f276606cfc676492",
            "bfad2ad9c132e2ce",
            "314bebe1cddc315a",
            "921a539198376b4a",
            "aef2171526ef1284",
            "c901c948e9b86da8",
            "9bf90dcb571c154d",
            "004a63261651dac3",
            "290d17d75ec59ed9",
            "166f088815f70fe0"
        ],
        "x": 54,
        "y": 2539,
        "w": 1112,
        "h": 242
    },
    {
        "id": "812e1827a3f14517",
        "type": "comment",
        "z": "20b92c4c3ed7c791",
        "g": "bac9b1afa5d97b72",
        "name": "注警報",
        "info": "http request\n200000.json ファイル名を地域コードにする\n\nparser A\narea_num JSONにおける市区町村の順番(市区町村コードではない)",
        "x": 210,
        "y": 2580,
        "wires": []
    },
    {
        "id": "f276606cfc676492",
        "type": "inject",
        "z": "20b92c4c3ed7c791",
        "g": "bac9b1afa5d97b72",
        "name": "",
        "props": [],
        "repeat": "60",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "x": 150,
        "y": 2660,
        "wires": [
            [
                "bfad2ad9c132e2ce"
            ]
        ]
    },
    {
        "id": "bfad2ad9c132e2ce",
        "type": "http request",
        "z": "20b92c4c3ed7c791",
        "g": "bac9b1afa5d97b72",
        "name": "",
        "method": "GET",
        "ret": "obj",
        "paytoqs": "ignore",
        "url": "https://www.jma.go.jp/bosai/warning/data/warning/200000.json",
        "tls": "",
        "persist": false,
        "proxy": "",
        "insecureHTTPParser": false,
        "authType": "",
        "senderr": false,
        "headers": [],
        "x": 330,
        "y": 2660,
        "wires": [
            [
                "166f088815f70fe0"
            ]
        ]
    },
    {
        "id": "314bebe1cddc315a",
        "type": "function",
        "z": "20b92c4c3ed7c791",
        "g": "bac9b1afa5d97b72",
        "name": "parser 1",
        "func": "var warn_code = msg.payload;\nif (warn_code == \"1\") {\n    var text = '{\"text\": \"SP WARN\", \"background\": \"#ff00ff\"}';\n} else if (warn_code == \"2\") {\n    var text = '{\"text\": \"WARNING\", \"background\": \"#ff0000\"}';\n} else if (warn_code == \"3\") {\n    var text = '{\"text\": \"Advisory\", \"color\": \"#ffff00\"}';\n} else {\n    var text = \"\";\n}\n\nmsg.payload = text;\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 680,
        "y": 2660,
        "wires": [
            [
                "921a539198376b4a"
            ]
        ]
    },
    {
        "id": "921a539198376b4a",
        "type": "mqtt out",
        "z": "20b92c4c3ed7c791",
        "g": "bac9b1afa5d97b72",
        "name": "",
        "topic": "awtrix/custom/warning",
        "qos": "",
        "retain": "",
        "respTopic": "",
        "contentType": "",
        "userProps": "",
        "correl": "",
        "expiry": "",
        "broker": "346df2a95aac5785",
        "x": 870,
        "y": 2660,
        "wires": []
    },
    {
        "id": "aef2171526ef1284",
        "type": "delay",
        "z": "20b92c4c3ed7c791",
        "g": "bac9b1afa5d97b72",
        "name": "",
        "pauseType": "delay",
        "timeout": "5",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 680,
        "y": 2580,
        "wires": [
            [
                "c901c948e9b86da8"
            ]
        ]
    },
    {
        "id": "c901c948e9b86da8",
        "type": "change",
        "z": "20b92c4c3ed7c791",
        "g": "bac9b1afa5d97b72",
        "name": "warn_code",
        "rules": [
            {
                "t": "set",
                "p": "warn_code",
                "pt": "flow",
                "to": "payload",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 850,
        "y": 2580,
        "wires": [
            []
        ]
    },
    {
        "id": "9bf90dcb571c154d",
        "type": "change",
        "z": "20b92c4c3ed7c791",
        "g": "bac9b1afa5d97b72",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "old_code",
                "pt": "msg",
                "to": "warn_code",
                "tot": "flow"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 710,
        "y": 2740,
        "wires": [
            [
                "004a63261651dac3"
            ]
        ]
    },
    {
        "id": "004a63261651dac3",
        "type": "function",
        "z": "20b92c4c3ed7c791",
        "g": "bac9b1afa5d97b72",
        "name": "parser 2",
        "func": "var new_code = msg.payload;\nvar old_code = msg.old_code;\n\nif (new_code != old_code) {\n    if (new_code == 1) {\n        msg.payload = {\"text\": \"SP WARN\", \"background\": \"#ff00ff\", \"blinkText\": 1000, \"sound\": \"siren\", \"hold\": true};\n    } else if (new_code == 2) {\n        msg.payload = {\"text\": \"WARNING\", \"background\": \"#ff0000\", \"blinkText\": 1000, \"sound\": \"siren\", \"hold\": true};\n    } else if (new_code == 3) {\n        msg.payload = {\"text\": \"Advisory\", \"color\": \"#ffff00\", \"blinkText\": 1000, \"duration\": 30};\n    } else {\n        msg.payload = {\"text\": \"No Warn\", \"blinkText\": 1000, \"duration\": 30};\n    }\n} else {\n    msg.payload = \"\";\n}\n\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 900,
        "y": 2740,
        "wires": [
            [
                "290d17d75ec59ed9"
            ]
        ]
    },
    {
        "id": "290d17d75ec59ed9",
        "type": "mqtt out",
        "z": "20b92c4c3ed7c791",
        "g": "bac9b1afa5d97b72",
        "name": "",
        "topic": "awtrix/notify",
        "qos": "",
        "retain": "",
        "respTopic": "",
        "contentType": "",
        "userProps": "",
        "correl": "",
        "expiry": "",
        "broker": "346df2a95aac5785",
        "x": 1070,
        "y": 2740,
        "wires": []
    },
    {
        "id": "166f088815f70fe0",
        "type": "function",
        "z": "20b92c4c3ed7c791",
        "g": "bac9b1afa5d97b72",
        "name": "parser A",
        "func": "var warn_array = msg.payload;\nvar area_num = 0;\nvar warn_code = warn_array.areaTypes[1].areas[area_num].warnings;\nvar code = [];\nvar status = [];\nvar warn = [];\n\nfunction warn_exist() {\n    for (var i = 0; i < warn_code.length; i++) {\n        if (warn_code[i].status !== \"解除\") {\n            code.push(warn_code[i].code);\n        }\n    }\n\n    if (code.find(value => value.match(/^3/))) {\n        warn = \"1\";\n        return;\n    } else if (code.find(value => value.match(/^0/))) {\n        warn = \"2\";\n        return;\n    } else if (code.find(value => value.match(/^1/))) {\n        warn = \"3\";\n        return;\n    } else if (code.find(value => value.match(/^2/))) {\n        warn = \"3\";\n        return;\n    } else {\n        warn = \"0\";\n        return;\n    }\n}\n\nvar key_num = Object.keys(warn_code[0]).length;\nif (key_num > 1) {\n    warn_exist();\n} else {\n    warn = \"0\";\n}\n\nmsg.payload = warn;\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 500,
        "y": 2660,
        "wires": [
            [
                "314bebe1cddc315a",
                "aef2171526ef1284",
                "9bf90dcb571c154d"
            ]
        ]
    },
    {
        "id": "346df2a95aac5785",
        "type": "mqtt-broker",
        "name": "",
        "broker": "localhost",
        "port": "1883",
        "clientid": "",
        "autoConnect": true,
        "usetls": false,
        "protocolVersion": "4",
        "keepalive": "60",
        "cleansession": true,
        "autoUnsubscribe": true,
        "birthTopic": "",
        "birthQos": "0",
        "birthPayload": "",
        "birthMsg": {},
        "closeTopic": "",
        "closeQos": "0",
        "closePayload": "",
        "closeMsg": {},
        "willTopic": "",
        "willQos": "0",
        "willPayload": "",
        "willMsg": {},
        "userProps": "",
        "sessionExpiry": ""
    }
]
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?