非力な初代Raspberry Piに鞭打って運用している、ADS-BレシーバPiAwareの稼働状態をREST APIを通じ、Home Assistantから監視できるようにします。
PiAwareが発信する稼働情報
Raspberry Piで組まれたFlightAwareデータフィーダー、PiAwareの死活監視はブラウザでPiAwareの 80/tcp ポートを開くこのページで行えます。
このページ自体は、ステータス情報が刻まれたJSONファイル status.json を再帰的に呼び出して更新する仕組み。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
{ "modes_enabled" : true, "dump978_version" : "dump978-fa 8.2", "interval" : 5000, "cpu_load_percent" : 50, "time" : 1724290307500, "site_url" : "https://flightaware.com/adsb/stats/user/USERNAME#stats-NNNNNN", "system_uptime" : 257542, "expiry" : 1724290318500, "piaware" : { "status" : "green", "message" : "PiAware 8.2 is running" }, "cpu_temp_celcius" : 62.142, "uat_enabled" : false, "dump1090_version" : "dump1090-fa 8.2", "adept" : { "status" : "green", "message" : "Connected to FlightAware and logged in" }, "mlat" : { "status" : "amber", "message" : "Local clock source is unstable" }, "piaware_version" : "8.2", "radio" : { "status" : "green", "message" : "Received Mode S data recently" } } |
今回は、このJSONファイルをHome AssistantからRESTful APIを通じて取得、Template機能で必要な情報を抜き出して、エンティティとして使えるようにします。
SkyAwareが8080/tcpで発信する受信データ
一方、 8080/tcp ポートで提供されるSkyAwareも、普段見るマップページとは別にその情報をJSONファイルで取得することが可能です。

図2.SkyAwareマップページ
提供されるJSONファイルは以下の3つ。
aircraft.json
受信中の航空機データが収められています。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
{ "now" : 1724290431.1, "messages" : 5795182, "aircraft" : [ {"hex":"781b48","alt_baro":9100,"alt_geom":9600,"gs":304.6,"tas":292,"track":78.1,"track_rate":0.03,"roll":0.4,"baro_rate":1728,"squawk":"3507","emergency":"none","nav_qnh":1011.2,"nav_altitude_mcp":12000,"lat":22.365509,"lon":113.813865,"nic":8,"rc":186,"seen_pos":0.5,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":1,"sda":3,"mlat":[],"tisb":[],"messages":231,"seen":0.4,"rssi":-15.4}, {"hex":"781385","alt_baro":31100,"gs":466.0,"tas":462,"track":245.7,"track_rate":-0.75,"roll":-17.4,"mlat":[],"tisb":[],"messages":9,"seen":0.5,"rssi":-21.5}, {"hex":"7805f8","flight":"CSN8313 ","alt_baro":22450,"alt_geom":23000,"gs":384.6,"ias":274,"tas":390,"mach":0.612,"track":162.1,"track_rate":-0.06,"roll":0.2,"mag_heading":165.6,"baro_rate":1472,"geom_rate":1568,"squawk":"3101","emergency":"none","category":"A3","nav_qnh":1013.6,"nav_altitude_mcp":24992,"nav_heading":0.0,"lat":22.339302,"lon":113.426259,"nic":8,"rc":186,"seen_pos":5.9,"version":2,"nic_baro":1,"nac_p":9,"nac_v":1,"sil":3,"sil_type":"perhour","gva":2,"sda":2,"mlat":[],"tisb":[],"messages":398,"seen":1.0,"rssi":-17.5}, {"hex":"448465","category":"A5","version":2,"sil_type":"perhour","mlat":[],"tisb":[],"messages":532,"seen":119.8,"rssi":-22.2}, {"hex":"780839","category":"A3","version":2,"sil_type":"perhour","mlat":[],"tisb":[],"messages":142,"seen":111.4,"rssi":-21.6}, {"hex":"424799","alt_baro":4275,"alt_geom":4475,"gs":180.9,"track":251.0,"baro_rate":-832,"squawk":"0466","category":"A3","nav_qnh":1011.2,"nav_altitude_mcp":1824,"nav_altitude_fms":1808,"nav_modes":["autopilot","vnav","tcas"],"version":2,"nic_baro":1,"nac_p":9,"nac_v":2,"sil":3,"sil_type":"perhour","mlat":[],"tisb":[],"messages":1094,"seen":31.2,"rssi":-17.0}, {"hex":"780519","category":"A3","version":2,"sil_type":"perhour","mlat":[],"tisb":[],"messages":1172,"seen":205.2,"rssi":-19.1}, {"hex":"899023","category":"A0","version":0,"sil_type":"unknown","mlat":[],"tisb":[],"messages":1150,"seen":187.2,"rssi":-19.9}, {"hex":"781958","category":"A2","version":2,"sil_type":"perhour","mlat":[],"tisb":[],"messages":5684,"seen":193.1,"rssi":-19.5} ] } |
このデータを元に、受信中の航空機数も取得してみたいので、こちらのフォーラムスレッドを参考にシェルスクリプトを組んでみました。
|
1 2 |
$ curl -s http://#IP_ADDRESS#:8080/data/aircraft.json | jq '.aircraft | length' 23 |
今回は参照しませんが、他に以下の2つの情報がJSON形式で提供されています。
receiver.json
PiAwareのバージョン情報や、設置場所緯度経度情報などが記されています。
|
1 2 3 4 5 6 7 |
{ "version": "8.2", "refresh": 1000, "history": 120, "lat": 22.29082, "lon": 114.20074 } |
stats.json
これまで受信したデータの統計情報が、記されています。
|
1 2 3 4 5 6 7 |
{ "latest":{"start":1724290547.8,"end":1724290547.8,"local":{"samples_processed":0,"samples_dropped":0,"modeac":0,"modes":0,"bad":0,"unknown_icao":0,"accepted":[0,0],"strong_signals":0,"gain_db":58.6},"remote":{"modeac":0,"modes":0,"bad":0,"unknown_icao":0,"accepted":[0,0]},"cpr":{"surface":0,"airborne":0,"global_ok":0,"global_bad":0,"global_range":0,"global_speed":0,"global_skipped":0,"local_ok":0,"local_aircraft_relative":0,"local_receiver_relative":0,"local_skipped":0,"local_range":0,"local_speed":0,"filtered":0},"altitude_suppressed":0,"cpu":{"demod":0,"reader":0,"background":0},"tracks":{"all":0,"single_message":0,"unreliable":0},"messages":0,"messages_by_df":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}, "last1min":{"start":1724290487.7,"end":1724290547.8,"local":{"samples_processed":112197632,"samples_dropped":31850496,"modeac":0,"modes":1211224,"bad":4964447,"unknown_icao":495253,"accepted":[712,67],"signal":-13.1,"noise":-24.7,"peak_signal":-5.4,"strong_signals":0,"gain_db":58.6},"remote":{"modeac":0,"modes":0,"bad":0,"unknown_icao":0,"accepted":[0,0]},"cpr":{"surface":0,"airborne":48,"global_ok":38,"global_bad":0,"global_range":0,"global_speed":0,"global_skipped":0,"local_ok":6,"local_aircraft_relative":0,"local_receiver_relative":0,"local_skipped":4,"local_range":0,"local_speed":0,"filtered":0},"altitude_suppressed":0,"cpu":{"demod":12407,"reader":7602,"background":1185},"tracks":{"all":6,"single_message":4,"unreliable":4},"messages":779,"messages_by_df":[158,0,0,0,123,24,0,0,0,0,0,192,0,0,0,0,20,167,1,0,68,26,0,0,0,0,0,0,0,0,0,0]}, "last5min":{"start":1724290247.8,"end":1724290547.8,"local":{"samples_processed":671350784,"samples_dropped":48627712,"modeac":0,"modes":7296009,"bad":29929594,"unknown_icao":2976256,"accepted":[2772,282],"signal":-14.2,"noise":-24.7,"peak_signal":-5.4,"strong_signals":0,"gain_db":58.6},"remote":{"modeac":0,"modes":0,"bad":0,"unknown_icao":0,"accepted":[0,0]},"cpr":{"surface":0,"airborne":217,"global_ok":171,"global_bad":0,"global_range":0,"global_speed":0,"global_skipped":0,"local_ok":22,"local_aircraft_relative":0,"local_receiver_relative":0,"local_skipped":24,"local_range":0,"local_speed":0,"filtered":0},"altitude_suppressed":0,"cpu":{"demod":73742,"reader":46811,"background":6806},"tracks":{"all":24,"single_message":18,"unreliable":18},"messages":3054,"messages_by_df":[709,0,0,0,353,59,0,0,0,0,0,792,0,0,0,0,109,648,6,0,267,111,0,0,0,0,0,0,0,0,0,0]}, "last15min":{"start":1724289647.8,"end":1724290547.8,"local":{"samples_processed":2082603008,"samples_dropped":78118912,"modeac":0,"modes":22660929,"bad":92902488,"unknown_icao":9246511,"accepted":[14043,1108],"signal":-10.7,"noise":-24.1,"peak_signal":-1.0,"strong_signals":305,"gain_db":58.6},"remote":{"modeac":0,"modes":0,"bad":0,"unknown_icao":0,"accepted":[0,0]},"cpr":{"surface":0,"airborne":1218,"global_ok":1077,"global_bad":0,"global_range":0,"global_speed":0,"global_skipped":0,"local_ok":76,"local_aircraft_relative":0,"local_receiver_relative":0,"local_skipped":65,"local_range":0,"local_speed":0,"filtered":0},"altitude_suppressed":0,"cpu":{"demod":228563,"reader":147822,"background":21658},"tracks":{"all":76,"single_message":58,"unreliable":58},"messages":15151,"messages_by_df":[3560,0,0,0,1514,228,0,0,0,0,0,3703,0,0,0,0,626,3323,28,0,1597,572,0,0,0,0,0,0,0,0,0,0]}, "total":{"start":1724030267.6,"end":1724290547.8,"local":{"samples_processed":591214018560,"samples_dropped":27343716352,"modeac":0,"modes":2096542701,"bad":419295184,"unknown_icao":2610995552,"accepted":[5356435,440571],"signal":-10.5,"noise":-24.9,"peak_signal":-1.0,"strong_signals":86823,"gain_db":58.6},"remote":{"modeac":0,"modes":0,"bad":0,"unknown_icao":0,"accepted":[0,0]},"cpr":{"surface":87,"airborne":500263,"global_ok":443662,"global_bad":8,"global_range":6,"global_speed":0,"global_skipped":0,"local_ok":35097,"local_aircraft_relative":0,"local_receiver_relative":0,"local_skipped":21583,"local_range":1,"local_speed":1,"filtered":0},"altitude_suppressed":0,"cpu":{"demod":64847804,"reader":41382421,"background":6218710},"tracks":{"all":21960,"single_message":14912,"unreliable":15537},"messages":5797006,"messages_by_df":[1129168,0,0,0,531735,90495,0,0,0,0,0,1524712,0,0,0,0,195516,1350292,7655,0,729195,238238,0,0,0,0,0,0,0,0,0,0]} } |
Home Assistant: configuration.yamlの準備
今回はインテグレーションやアドオンのインストールは不要、Home Assistantの基本機能で実現可能ですが、設定ファイルをひたすら手打ちで記述することになります。
まず、 configuration.yaml に次の3つの項目を新設します。
- rest:
- template:
- command_line:
新設した項目の下にずらずらと記述せず、それぞれのインクルードフォルダを設定して、その中にインクルードファイルの形で記述する構造を採ります(以前にmqtt項をインクルードした例はこちら)。
ファイル構造は以下の通り。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
homeassistant ├── command_line_sensors │ └── piawares.yaml │ ├── configuration.yaml │ ├── mqtt_sensors │ ├── mqtt_sensors_esp_home.yaml │ ├── mqtt_sensors_esp_ysuka.yaml │ ├── mqtt_sensors_homepi2.yaml │ ├── mqtt_sensors_homepi3.yaml │ ├── mqtt_sensors_piaware1.yaml │ └── mqtt_sensors_piaware2.yaml │ ├── rest_sensors │ └── piawares.yaml │ └── template_sensors └── piawares.yaml |
configuration.yaml には次の設定を追記しました。
|
1 2 3 4 5 6 |
rest: !include_dir_merge_list rest_sensors/ template: sensor: !include_dir_merge_list template_sensors/ command_line: !include_dir_merge_list command_line_sensors/ |
以上を保存したら設定ファイルの再読込ではなく、一度Home Assistantを再起動して設定を反映させる方が確実です。
以降、インクルードファイルの追加や変更は、上図の各項目を再読込みさせるだけで反映されるので、その都度Home Assistantを再起動する必要はありません。
Home Assistant: 各インクルードファイルの記述
実際にどのように記述すれば良いのか、こちらのフォーラムスレッドが参考になりました。
それではまず、 rest: 項のインクルードファイルから。2機保有しているPiAwareそれぞれについて、 status.json の取得要領を記述しています(このとき、 name: は一意であることが必須なので注意)。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# PIAWARE2 RTL-SDR STATUS - resource: http://#IP_ADDRESS_2#/status.json scan_interval: 60 sensor: - name: "Piaware2_Status" value_template: "OK" json_attributes: - "modes_enabled" - "dump978_version" - "cpu_load_percent" - "site_url" - "time" - "uat_radio" - "piaware" - "cpu_temp_celcius" - "uat_enabled" - "dump1090_version" - "adept" - "mlat" - "piaware_version" - "radio" # PIAWARE1 RTL-SDR STATUS - resource: http://#IP_ADDRESS_1#/status.json scan_interval: 60 sensor: - name: "Piaware1_Status" value_template: "OK" json_attributes: - "modes_enabled" - "dump978_version" - "cpu_load_percent" - "site_url" - "time" - "uat_radio" - "piaware" - "cpu_temp_celcius" - "uat_enabled" - "dump1090_version" - "adept" - "mlat" - "piaware_version" - "radio" |
続いて、 rest: 項で取得したJSONオブジェクトから必要なデータを切り抜き、エンティティにする template: 項。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# PIAWARE2 RTL-SDR STATUS - name: "Piaware2_Status_CPU_Load" unit_of_measurement: "%" state: "{{ state_attr('sensor.piaware2_status', 'cpu_load_percent')|float(default=0) }}" state_class: 'measurement' - name: "Piaware2_Status_CPU_Temperature" unit_of_measurement: "°C" state: "{{ state_attr('sensor.piaware2_status', 'cpu_temp_celcius')|float(default=1) }}" device_class: 'temperature' state_class: 'measurement' - name: "Piaware2_Status_978_Radio" state: "{{ state_attr('sensor.piaware2_status', 'uat_radio')['status'] }}" attributes: message: "{{ state_attr('sensor.piaware2_status', 'uat_radio')['message'] }}" - name: "Piaware2_Status_1090_Radio" state: "{{ state_attr('sensor.piaware2_status', 'radio')['status'] }}" attributes: message: "{{ state_attr('sensor.piaware2_status', 'radio')['message'] }}" - name: "Piaware2_Status_Daemon" state: "{{ state_attr('sensor.piaware2_status', 'piaware')['status'] }}" attributes: message: "{{ state_attr('sensor.piaware2_status', 'piaware')['message'] }}" - name: "Piaware2_Status_Connection" state: "{{ state_attr('sensor.piaware2_status', 'adept')['status'] }}" attributes: message: "{{ state_attr('sensor.piaware2_status', 'adept')['message'] }}" - name: "Piaware2_Status_MLAT" state: "{{ state_attr('sensor.piaware2_status', 'mlat')['status'] }}" attributes: message: "{{ state_attr('sensor.piaware2_status', 'mlat')['message'] }}" # PIAWARE1 RTL-SDR STATUS - name: "Piaware1_Status_CPU_Load" unit_of_measurement: "%" state: "{{ state_attr('sensor.piaware1_status', 'cpu_load_percent')|float(default=0) }}" state_class: 'measurement' - name: "Piaware1_Status_CPU_Temperature" unit_of_measurement: "°C" state: "{{ state_attr('sensor.piaware1_status', 'cpu_temp_celcius')|float(default=1) }}" device_class: 'temperature' state_class: 'measurement' - name: "Piaware1_Status_978_Radio" state: "{{ state_attr('sensor.piaware1_status', 'uat_radio')['status'] }}" attributes: message: "{{ state_attr('sensor.piaware1_status', 'uat_radio')['message'] }}" - name: "Piaware1_Status_1090_Radio" state: "{{ state_attr('sensor.piaware1_status', 'radio')['status'] }}" attributes: message: "{{ state_attr('sensor.piaware1_status', 'radio')['message'] }}" - name: "Piaware1_Status_Daemon" state: "{{ state_attr('sensor.piaware1_status', 'piaware')['status'] }}" attributes: message: "{{ state_attr('sensor.piaware1_status', 'piaware')['message'] }}" - name: "Piaware1_Status_Connection" state: "{{ state_attr('sensor.piaware1_status', 'adept')['status'] }}" attributes: message: "{{ state_attr('sensor.piaware1_status', 'adept')['message'] }}" - name: "Piaware1_Status_MLAT" state: "{{ state_attr('sensor.piaware1_status', 'mlat')['status'] }}" attributes: message: "{{ state_attr('sensor.piaware1_status', 'mlat')['message'] }}" |
そして、 command_line: 項のインクルードファイルには、 aircraft.json から航空機数を算出し、エンティティとする定義を収めました。
|
1 2 3 4 5 6 7 8 9 10 11 |
- sensor: name: "Piaware2_Status_Aircraft_Nearby" command: "curl -s http://#IP_ADDRESS_2#:8080/data/aircraft.json | jq '.aircraft | length'" scan_interval: 60 state_class: 'measurement' - sensor: name: "Piaware1_Status_Aircraft_Nearby" command: "curl -s http://#IP_ADDRESS_1#:8080/data/aircraft.json | jq '.aircraft | length'" scan_interval: 60 state_class: 'measurement' |
エンティティ確認とカード作成例
以上のインクルードファイルの内容を反映させた後、追加したエンティティを確認してみます。
これらのエンティティをダッシュボードに表示させる、シンプルなエンティティカードを作成しました。
ダッシュボード上に表示させるとこんな感じに。かなり素っ気ないので、せめて状態を色表示できるようにするつもり。
ちなみにカード上のエンティティをクリックすると、履歴をグラフで確認できます。
せっかく、PiAwareからデータを取得する要領を会得できたので、次回はaircraft.jsonから受信中の航空機一覧をテーブル形式で表示するようなカードを作ってみたいと思います。









