enebular-agentを使って情報を表示するための端末を作ってみたいと思って、ちょっと試行錯誤しながら作ってみました。
enebular-agentはRaspberry Piに対応しているので、Raspberry PiとRaspberry Piの上に搭載できるHDMI入力の液晶モジュールを使って、表示端末を作ってみたいと思います。
今回、表示する内容は現在の天気としました。
準備するもの
- Raspberry Pi 3B+
- microSDカード(Raspbian Buster base + enebular-agent v2.15.2 )
- enebular-agentのインストール方法はenebular-agentのドキュメントを参照ください
- 電源
- microSDカード(Raspbian Buster base + enebular-agent v2.15.2 )
- 3.5インチ モバイルモニター Raspberry Pi用 ※他のディスプレイでも可
表示端末としての設定
enebular-agentは、バックグランドとして動作するアプリケーションのため、ウィンドウを持ちません。そのため、情報を表示するためにdashboardノードを使用します。
dashboardノードの出力はブラウザで表示できるので、フローの起動時にブラウザをフルスクリーン表示するようにすればディスプレイでフローで作った情報を確認できます。
また、Raspberry Piの初期設定状態だとしばらくすると画面がスリープしてしまうので、スリープしないようにする設定もフローから行います。
dashboardノード
dashboardノードはフローに配置するとブラウザ上にUIを作れるノードです。例えば以下のようになります。
enebularではdashboardノードが標準ノードとして組み込まれているのでノードを追加する必要はありません。
ブラウザをフルスクリーンで起動
ブラウザはRaspberry Pi標準のChromiumをコマンドで起動します。コマンドの実行にはexecノードを使用します。
フロー上では、ブラウザを表示するディスプレイを指定する必要があるので、その指定とフルスクリーン表示するためのオプション、URL(dashboardノード出力先のURL)を引数とし、以下のコマンドになります。
DISPLAY=:0 chromium-browser --noerrdialogs --kiosk --incognito http://localhost:1880/ui
これをexecノードのコマンド欄に設定します。
スリープしないようにする
ディスプレイをスリープしないようにするには以下のコマンドを実行します。
xset -dpms
xset s off
xset s noblank
これもexecノードのコマンド欄にディスプレイを指定して、連続で実行されるように設定します。
DISPLAY=:0 xset -dpms;DISPLAY=:0 xset s off;DISPLAY=:0 xset s noblank
天気予報取得
表示する天気予報はOpenWeatherMapというおそらく一番有名な天気情報を取得するAPIを使って取得します。
アカウント登録(無料)を行ってAPIキーを取得すれば、指定した緯度経度の天気情報が取得できます。
東京の天気情報を取得するクエリの例です。
https://api.openweathermap.org/data/2.5/onecall?lat=35.681236&lon=139.767125&units=metric&lang=ja&appid={APIキー}
以下の様にJSON型でレスポンスが返ってきます。
{
"lat": 35.68,
"lon": 139.77,
"timezone": "Asia/Tokyo",
"timezone_offset": 32400,
"current": {
"dt": 1607188618,
"sunrise": 1607204202,
"sunset": 1607239652,
"temp": 8.56,
"feels_like": 4.13,
"pressure": 1024,
"humidity": 76,
"dew_point": 4.58,
"uvi": 0,
"clouds": 75,
"visibility": 10000,
"wind_speed": 4.6,
"wind_deg": 360,
"weather": [
{
"id": 803,
"main": "Clouds",
"description": "曇りがち",
"icon": "04n"
}
]
},
"minutely": [
・・・省略・・・
],
"hourly": [
・・・省略・・・
],
"daily": [
・・・省略・・・
]
}
フロー
これまでの内容を組み合わせたフローが以下となります。
[{"id":"e98aabc1.0347c8","type":"tab","label":"フロー 1","disabled":false,"info":""},{"id":"85407b90.7d4138","type":"inject","z":"e98aabc1.0347c8","name":"フロー起動時に1回のみ","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":true,"onceDelay":"2","x":190,"y":200,"wires":[["53632fbe.f963a","cc26bd5c.b8bca","f52f3df2.aee3a"]]},{"id":"53632fbe.f963a","type":"exec","z":"e98aabc1.0347c8","command":"DISPLAY=:0 chromium-browser ","addpay":false,"append":"--noerrdialogs --kiosk --incognito http://localhost:1880/ui","useSpawn":"false","timer":"","oldrc":false,"name":"","x":490,"y":220,"wires":[[],[],[]]},{"id":"b3703400.f25008","type":"ui_text","z":"e98aabc1.0347c8","group":"65b0e5ae.ce33dc","order":2,"width":0,"height":0,"name":"","label":"時刻","format":"{{msg.payload.time}}","layout":"row-left","x":830,"y":340,"wires":[]},{"id":"f52f3df2.aee3a","type":"http request","z":"e98aabc1.0347c8","name":"","method":"GET","ret":"obj","paytoqs":false,"url":"https://api.openweathermap.org/data/2.5/onecall?lat=35.681236&lon=139.767125&units=metric&lang=ja&appid={API Key}","tls":"","persist":false,"proxy":"","authType":"","x":350,"y":420,"wires":[["6753aba1.420674","e1ab93f4.ba407","3d3e17fe.542668"]]},{"id":"d12b5235.ecffb","type":"inject","z":"e98aabc1.0347c8","name":"5分周期","topic":"","payload":"","payloadType":"date","repeat":"300","crontab":"","once":false,"onceDelay":0.1,"x":140,"y":420,"wires":[["f52f3df2.aee3a"]]},{"id":"6753aba1.420674","type":"function","z":"e98aabc1.0347c8","name":"現在の天気情報を分解","func":"var json = msg.payload;\nconst dt = new Date(json.current.dt * 1000 + ((new Date().getTimezoneOffset() + (9 * 60)) * 60 * 1000));\nvar text;\n\nmsg.payload.date = dt.getFullYear() + '年'\n + (dt.getMonth() + 1) + '月'\n + dt.getDate() + '日 ';\n\nmsg.payload.time = dt.getHours() + '時'\n + dt.getMinutes() + '分'\n + dt.getSeconds() + '秒\\n';\n\n\nmsg.payload.temp = json.current.temp + '℃';\nmsg.payload.humidity = json.current.humidity + '%';\nmsg.payload.pressure = json.current.pressure + 'hPa';\nmsg.payload.weather = json.current.weather[0].description;\nmsg.payload.rain = 0;\nif('rain' in json.current) {\n if('1h' in json.current.rain){\n msg.payload.rain = json.current.rain[\"1h\"];\n }\n}\n\nreturn msg;\n","outputs":1,"noerr":0,"x":580,"y":420,"wires":[["b3703400.f25008","a843f73a.c73d88","79b30ea7.fdb4","e0459366.3c27f","8ba22476.d72fc8","5458a957.1e2b08","ae888926.2b90e8"]]},{"id":"a843f73a.c73d88","type":"ui_text","z":"e98aabc1.0347c8","group":"65b0e5ae.ce33dc","order":3,"width":0,"height":0,"name":"","label":"気温","format":"{{msg.payload.temp}}","layout":"row-left","x":830,"y":380,"wires":[]},{"id":"79b30ea7.fdb4","type":"ui_text","z":"e98aabc1.0347c8","group":"65b0e5ae.ce33dc","order":4,"width":0,"height":0,"name":"","label":"湿度","format":"{{msg.payload.humidity}}","layout":"row-left","x":830,"y":420,"wires":[]},{"id":"e0459366.3c27f","type":"ui_text","z":"e98aabc1.0347c8","group":"65b0e5ae.ce33dc","order":5,"width":0,"height":0,"name":"","label":"気圧","format":"{{msg.payload.pressure}}","layout":"row-left","x":830,"y":460,"wires":[]},{"id":"8ba22476.d72fc8","type":"ui_text","z":"e98aabc1.0347c8","group":"65b0e5ae.ce33dc","order":6,"width":0,"height":0,"name":"","label":"天気","format":"{{msg.payload.weather}}","layout":"row-left","x":830,"y":500,"wires":[]},{"id":"5458a957.1e2b08","type":"ui_text","z":"e98aabc1.0347c8","group":"65b0e5ae.ce33dc","order":7,"width":0,"height":0,"name":"","label":"降水量(1時間当たり)","format":"{{msg.payload.rain}}","layout":"row-left","x":880,"y":540,"wires":[]},{"id":"8923cbfd.6484a8","type":"ui_chart","z":"e98aabc1.0347c8","name":"","group":"95aa5e6a.2039a","order":6,"width":"5","height":"5","label":"気温","chartType":"line","legend":"false","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","dot":false,"ymin":"","ymax":"","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"3600","cutout":0,"useOneColor":false,"useUTC":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"useOldStyle":false,"outputs":1,"x":830,"y":580,"wires":[[]]},{"id":"e1ab93f4.ba407","type":"function","z":"e98aabc1.0347c8","name":"現在気温を抽出","func":"var json = msg.payload;\nconst dt = new Date(json.current.dt * 1000 + ((new Date().getTimezoneOffset() + (9 * 60)) * 60 * 1000));\n\nmsg.payload = json.current.temp;\n\nreturn msg;\n","outputs":1,"noerr":0,"x":580,"y":580,"wires":[["8923cbfd.6484a8"]]},{"id":"3d3e17fe.542668","type":"function","z":"e98aabc1.0347c8","name":"現在気圧を抽出","func":"var json = msg.payload;\nconst dt = new Date(json.current.dt * 1000 + ((new Date().getTimezoneOffset() + (9 * 60)) * 60 * 1000));\n\nmsg.payload = json.current.pressure;\n\nreturn msg;\n","outputs":1,"noerr":0,"x":580,"y":640,"wires":[["c1abef76.8fe13"]]},{"id":"c1abef76.8fe13","type":"ui_chart","z":"e98aabc1.0347c8","name":"","group":"95aa5e6a.2039a","order":6,"width":"5","height":"5","label":"気圧","chartType":"line","legend":"false","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","dot":false,"ymin":"","ymax":"","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"3600","cutout":0,"useOneColor":false,"useUTC":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"useOldStyle":false,"outputs":1,"x":830,"y":640,"wires":[[]]},{"id":"ae888926.2b90e8","type":"ui_text","z":"e98aabc1.0347c8","group":"65b0e5ae.ce33dc","order":1,"width":0,"height":0,"name":"","label":"日付","format":"{{msg.payload.date}}","layout":"row-left","x":830,"y":300,"wires":[]},{"id":"cc26bd5c.b8bca","type":"exec","z":"e98aabc1.0347c8","command":"DISPLAY=:0 xset -dpms;DISPLAY=:0 xset s off;DISPLAY=:0 xset s noblank","addpay":false,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"","x":630,"y":160,"wires":[[],[],[]]},{"id":"65b0e5ae.ce33dc","type":"ui_group","z":"","name":"現在の天気予報","tab":"d6259073.9e544","order":1,"disp":true,"width":"5","collapse":false},{"id":"95aa5e6a.2039a","type":"ui_group","z":"","name":"グラフ","tab":"d6259073.9e544","order":2,"disp":false,"width":"5","collapse":false},{"id":"d6259073.9e544","type":"ui_tab","z":"","name":"ホーム","icon":"dashboard","disabled":false,"hidden":false}]
※使用する場合はhttp requestノードのURLで{API key}の部分を書き換えてください。
このフローをenebular-agentにデプロイすれば現在の天気情報+気温と気圧の変化グラフを表示するための端末ができあがります。
さらに
enebular-agentは普通にインストールするとRaspberry Piが起動するだけで、裏で起動し、フローでブラウザが起動するので、電源を入れるだけで表示端末として機能するのですが、表示端末として使用する場合はマウスカーソルが邪魔になります。
マウスカーソルを非表示にするためには以下のコマンドでunclutter
をインストールし、再起動すれば非表示になります。
> sudo apt-get install unclutter
ただ、今回はこのインストールをenebularのファイルデプロイ機能を使ってやってみました。
ファイルデプロイ機能はファイルをenebular-agentにデプロイできる機能です。デプロイする際にファイルを実行できるので、シェルスクリプトのファイルを用意して、インストールするコマンドを実行するようにします。シェルスクリプトのファイルの内容を以下のようにしました。
#!/bin/bash
sudo apt-get install unclutter
sudo reboot
このファイルをファイルアセットとしてenebularに登録します。(Excute On Deploy
をONするとデプロイ時に実行されます。)
登録が完了したらenebular-agentにデプロイするとインストール後、自動的に再起動してマウスカーソルが表示となりました。
端末を複数用意する場合など、1台1台インストールするのが面倒な場合はスクリプトを作って、enebularからファイルデプロイすると楽です。