目的
羽田新ルートをmisskey上で投稿にて見ることができるようにした。マイコンボードを用いて常時運用としたかった。
必要構成
- ESP32(ESP32-DevKitC-VE 秋月購入)
- circuitpython 8.2.3
- smtp_circuitpython.py
- adafruit_ntp.mpy
- adafruit_requests.mpy
- Misskeyアカウトとアクセストークン
- メールアドレスアカウント
- linux mint 22
- python
- wifi環境 2.4GHz
内容
flightrader24のAPIによるJson情報を使ってMisskey上でノートするというものである。
監視する空域は経緯緯度で指定しないと全情報を取得してしまうので注意が必要である。今回は羽田新ルートを対象にした。
導入
ESP32の開発環境の準備、misskeyの準備、あればメールアカウントの準備を順にしておく。
ESP32の準備
ESP32のベースとして秋月でDevKitCを購入した。microUSBケーブルと適当なUSBACアダプタをつなげる。
開発環境としてcircuitpythonを採用した。
ESP32のファームとしてadafruit-circuitpython-doit_esp32_devkit_v1-ja-8.2.3.binを公式よりダウンロードして、esp32に書き込む。
書き込みはブラウザ上からできるためchrom系列で書き込んだ。ESP Web Flasherを使用して書き込みが参考になる。ただし、circuitpyとしてドライフが表示されない。esp32のベースとして表示されない場合もある。
circuitpythonはwebブラウザだけでファイルのアップロードや編集が行える。
WIFI環境は個人によって異なるため 2.4G帯を利用できるようにしておく。
MisskeyのAPI利用の準備
Misskeyのアカウトとアクセストークンは公式にしたがって取得しておく。
Misskeyのモジュールをインストールしておく。
メールアカウントの準備
あればでいいが、メールアカウントがあると飛行情報の確保とESP32の情報が取得できる。今回は自分はメールアカウントを持っており、取得データをメールに飛ばすようにした。
- エラーログはesp32_logのメールアドレス
- 取得データはdataのメールアドレス
とした。
プログラム本体
-
secrets.py
wifiアクセスにおいて自身で管理しているSSID,パスワードを保管しておく。 -
code.py
ブート時に自動的に読みこれまれる
import airplane
- airplane.py
#
#羽田新ルートの侵入を検知
#ESP32にて判定
#緯度(北35.71878,南35.61535)経度(西139.64919,東139.74154)
import gc
import board
import digitalio
import time
import json
import ssl
import wifi
import socketpool
import smtp_circuitpython
import adafruit_requests
import rtc
import adafruit_ntp
import supervisor
SMTP_SERVER = "契約しているSMTPサーバー"
SMTP_PORT = 465
TZ_OFFSET = 9 #日本のUTCから時差
# Add a secrets.py to your filesystem that has a dictionary called secrets with "ssid" and
# "password" keys with your WiFi credentials. DO NOT share that file or commit it into Git or other
# source control.
# pylint: disable=no-name-in-module,wrong-import-order
try:
from secrets import secrets
except ImportError:
print("WiFi secrets are kept in secrets.py, please add them there!")
raise
print("\n===============================")
print("Connecting to WiFi...")
while not wifi.radio.ipv4_address:
try:
wifi.radio.connect(secrets["ssid"], secrets["password"])
except ConnectionError as e:
print("Connection Error:", e)
print("Retrying in 10 seconds")
time.sleep(10)
gc.collect()
print("Connected!\n")
FR_URL= "https://data.flightradar24.com/zones/fcgi/feed.js?bounds=35.71878,35.61535,139.64919,139.74154&adsb=1&mlat=1&faa=1&flarm=1&estimated=1&air=1&gnd=1&vehicles=1&gliders=1&array=1"
misskey_api_url = "https://misskey.io/api/notes/create"
misskey_token = "misskeyで取得した投稿用のトークン"
header = {
'Content-Type' : 'application/json',"User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:47.0) Gecko/20100101 Firefox/47.0",
}
check_airplanename_before = [] #前回の機体名
no_airplane_countdown_default = 36 #3分飛行情報をカウント出来ない場合
no_airplane_countdown = no_airplane_countdown_default
interval_time = 10 #監視間隔時間sec
pool = socketpool.SocketPool(wifi.radio)
context = ssl.create_default_context()
def send_mail(mail_to, mail_subject, mail_body):
try:
smtp = smtp_circuitpython.SMTP(host=SMTP_SERVER, port=SMTP_PORT,
pool=pool, ssl_context=context, use_ssl = True,
username=secrets['mail_user'],password=secrets['mail_password'],
debug = True)
mail_to += "メールアドレス"
smtp.to(mail_to)
smtp.body("Subject: "+ mail_subject +"\r\n\r\n"+ mail_body)
smtp.quit()
del smtp
gc.collect
except Exception as e:
gc.collect
print("SMTP Error:"+ str(e))
#supervisor.reload()
def set_now_time():
"""NTPサーバーから現時刻を設定する"""
try:
ntp = adafruit_ntp.NTP(pool, tz_offset=TZ_OFFSET)
rtc.RTC().datetime = ntp.datetime
del ntp
gc.collect
except Exception as e:
del ntp
gc.collect()
print("NTPサーバーエラー:" + str(e))
send_mail("esp32_log","", "NTPサーバーエラー:"+ str(e))
def response(method, url, data):
"""httpのリクエスト"""
requests = adafruit_requests.Session(pool, context)
if(method == "get"):
try:
response = requests.get(url=url,headers=header)
body = response.json()
response.close()
del response,requests
gc.collect()
print(body)
return body
except Exception as e:
print("APIエラー:"+ str(e))
del requests
gc.collect()
send_mail("esp32_log","","getエラー:" + str(e))
no_airplane_countdown = no_airplane_countdown_default
if(method == "post"):
try:
res = requests.post(url = url, data = json.dumps(data).encode("utf-8"), headers= header)
body = res.json()
if(body['error'] != []):
res.close()
del res
time.sleep(60)
raise("サーバー制限")
res.close()
del res,requests
gc.collect
print(body)
return body
except Exception as e:
print("APIエラー:"+ str(e))
time.sleep(60)
del requests
gc.collect()
send_mail("esp32_log","","postエラー:" + str(e))
set_now_time()
gc.collect()
while True:
try:
body = response("get", FR_URL, "")
check_airplanename = []
infos = []
for info in body['aircraft']:
infos.append(info)
check_airplanename.append(info[-2]) #機体名取得
approach_airport = info[-6] #着陸空港
print(check_airplanename)
del body
gc.collect
if ( (check_airplanename != []) and (set(check_airplanename) ^ set(check_airplanename_before)) and (approach_airport == "HND") ):
if( (check_airplanename_before == []) or (check_airplanename_before[-1] != infos[-1][-2])): #note_before != ("羽田新ルート情報実験中:"+ (','.join(map(str,body['aircraft'][-1]))) + "[FlightRadar24](https://www.flightradar24.com/airport/hnd)")
data = {"i": misskey_token,"text":"羽田新ルート情報実験中:"+ " 出発:"+ str(infos[-1][-7]) + " #" + str(infos[-1][-10]) + " " + str(infos[-1][-2]) + " " + "[FlightRadar24](https://www.flightradar24.com/"+str(infos[-1][-2])+"/"+str(infos[-1][0])+")"}
try:
interval_time = 10
res = response("post", misskey_api_url, data)
del res,data
gc.collect()
raw_airplane_info = ','.join(map(str,infos[-1]))
send_mail("data","", raw_airplane_info)
del raw_airplane_info
check_airplanename_before.clear()
gc.collect()
for tmp_info in check_airplanename:
check_airplanename_before.append(tmp_info)
except Exception as e:
print(str(e))
check_airplanename_before.clear()
del data
gc.collect()
end_mail("esp32_log","","投稿エラー:"+ str(e))
time.sleep(interval_time)
del infos
gc.collect()
now = time.localtime()
if((check_airplanename == [])):
no_airplane_countdown = no_airplane_countdown - 1
print("飛行機情報なし:"+str(no_airplane_countdown)+"残り確認回数")
if( no_airplane_countdown == 0):
if( interval_time == 10):
send_mail("esp32_log","","飛行機情報なしのため2分間隔に変更")
interval_time = 120
if( now.tm_hour == 0):
send_mail("esp32_log","","0時のため6時間投稿中止")
time.sleep(21600)
set_now_time()
gc.collect
no_airplane_countdown = no_airplane_countdown_default
else: #ゴーアラの場合はカウントダウンをリセット
no_airplane_countdown = no_airplane_countdown_default
if(now.tm_hour == 14):
interval_time = 10
print("間隔を10秒にしました")
del now
except Exception as e:
print("異常終了:" + str(e))
gc.collect
send_mail("esp32_log","","異常終了:" + str(e))
no_airplane_countdown = no_airplane_countdown_default
time.sleep(10)
supervisor.reload()
finally:
gc.collect()
まとめ
空域に侵入を確認するとmisskeyにノートをしてくれることを確認できた。
エラー処理と動作確認としてesp32_logのメールアドレスに原因を送信された。
問題点
- 空域に複数機体が侵入した場合jsonに依存する
- 更新頻度の問題
課題
- 情報精度の向上
- さらなる低消費電力の追求
感想
- esp32は常時サーバーとしては低消費電力で昨今のように電力代高騰の折にはうってつけである
- 小型なのも利便性が高い
- 運用に際して一月ほど連続稼働を目指したため稼働実験に3ヶ月くらい試行錯誤していた