1- # SPDX-FileCopyrightText: 2023 DJDevon3
1+ # SPDX-FileCopyrightText: 2024 DJDevon3
22# SPDX-License-Identifier: MIT
3- # Coded for Circuit Python 8.1
4- # DJDevon3 ESP32-S3 OpenSkyNetwork_Private_Area_API_Example
3+ # Coded for Circuit Python 8.2.x
4+ """OpenSky-Network.org Private Area API Example"""
55
6- import json
6+ import binascii
77import os
8- import ssl
98import time
109
11- import circuitpython_base64 as base64
12- import socketpool
10+ import adafruit_connection_manager
1311import wifi
1412
1513import adafruit_requests
1614
1715# OpenSky-Network.org Website Login required for this API
16+ # Increased call limit vs Public.
1817# REST API: https://openskynetwork.github.io/opensky-api/rest.html
19-
2018# Retrieves all traffic within a geographic area (Orlando example)
21- latmin = "27.22" # east bounding box
22- latmax = "28.8" # west bounding box
23- lonmin = "-81.46" # north bounding box
24- lonmax = "-80.40" # south bounding box
19+ LATMIN = "27.22" # east bounding box
20+ LATMAX = "28.8" # west bounding box
21+ LONMIN = "-81.46" # north bounding box
22+ LONMAX = "-80.40" # south bounding box
2523
26- # Initialize WiFi Pool (There can be only 1 pool & top of script)
27- pool = socketpool .SocketPool (wifi .radio )
24+ # Get WiFi details, ensure these are setup in settings.toml
25+ ssid = os .getenv ("CIRCUITPY_WIFI_SSID" )
26+ password = os .getenv ("CIRCUITPY_WIFI_PASSWORD" )
27+ osnusername = os .getenv ("OSN_USERNAME" ) # Website Credentials
28+ osnpassword = os .getenv ("OSN_PASSWORD" ) # Website Credentials
2829
29- # Time between API refreshes
30+ # API Polling Rate
3031# 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour
3132# OpenSky-Networks IP bans for too many requests, check rate limit.
3233# https://openskynetwork.github.io/opensky-api/rest.html#limitations
33- sleep_time = 1800
34+ SLEEP_TIME = 1800
3435
35- # Get WiFi details, ensure these are setup in settings.toml
36- ssid = os .getenv ("CIRCUITPY_WIFI_SSID" )
37- password = os .getenv ("CIRCUITPY_WIFI_PASSWORD" )
38- # No token required, only website login
39- osnu = os .getenv ("OSN_Username" )
40- osnp = os .getenv ("OSN_Password" )
41-
42- osn_cred = str (osnu ) + ":" + str (osnp )
43- bytes_to_encode = b" " + str (osn_cred ) + " "
44- base64_string = base64 .encodebytes (bytes_to_encode )
45- base64cred = repr (base64_string )[2 :- 1 ]
46-
47- Debug_Auth = False # STREAMER WARNING this will show your credentials!
48- if Debug_Auth :
49- osn_cred = str (osnu ) + ":" + str (osnp )
50- bytes_to_encode = b" " + str (osn_cred ) + " "
51- print (repr (bytes_to_encode ))
52- base64_string = base64 .encodebytes (bytes_to_encode )
53- print (repr (base64_string )[2 :- 1 ])
54- base64cred = repr (base64_string )[2 :- 1 ]
55- print ("Decoded Bytes:" , str (base64cred ))
56-
57- # OSN requires your username:password to be base64 encoded
58- # so technically it's not transmitted in the clear but w/e
59- osn_header = {"Authorization" : "Basic " + str (base64cred )}
60-
61- # Example request of all traffic over Florida, geographic areas cost less per call.
36+ # Set debug to True for full JSON response.
37+ # WARNING: makes credentials visible. based on how many flights
38+ # in your area, full response could crash microcontroller
39+ DEBUG = False
40+
41+ # Initalize Wifi, Socket Pool, Request Session
42+ pool = adafruit_connection_manager .get_radio_socketpool (wifi .radio )
43+ ssl_context = adafruit_connection_manager .get_radio_ssl_context (wifi .radio )
44+ requests = adafruit_requests .Session (pool , ssl_context )
45+
46+ # -- Base64 Conversion --
47+ OSN_CREDENTIALS = str (osnusername ) + ":" + str (osnpassword )
48+ # base64 encode and strip appended \n from bytearray
49+ OSN_CREDENTIALS_B = binascii .b2a_base64 (OSN_CREDENTIALS .encode ()).strip ()
50+ BASE64_STRING = OSN_CREDENTIALS_B .decode () # bytearray
51+
52+ if DEBUG :
53+ print ("Base64 ByteArray: " , BASE64_STRING )
54+
55+ # Area requires OpenSky-Network.org username:password to be base64 encoded
56+ OSN_HEADER = {"Authorization" : "Basic " + BASE64_STRING }
57+
58+ # Example request of all traffic over Florida.
59+ # Geographic areas calls cost less against the limit.
6260# https://opensky-network.org/api/states/all?lamin=25.21&lomin=-84.36&lamax=30.0&lomax=-78.40
6361OPENSKY_SOURCE = (
6462 "https://opensky-network.org/api/states/all?"
6563 + "lamin="
66- + latmin
64+ + LATMIN
6765 + "&lomin="
68- + lonmin
66+ + LONMIN
6967 + "&lamax="
70- + latmax
68+ + LATMAX
7169 + "&lomax="
72- + lonmax
70+ + LONMAX
7371)
7472
7573
76- # Converts seconds to human readable minutes/hours/days
77- def time_calc ( input_time ): # input_time in seconds
74+ def time_calc ( input_time ):
75+ """Converts seconds to minutes/hours/days"""
7876 if input_time < 60 :
79- sleep_int = input_time
80- time_output = f"{ sleep_int :.0f} seconds"
81- elif 60 <= input_time < 3600 :
82- sleep_int = input_time / 60
83- time_output = f"{ sleep_int :.0f} minutes"
84- elif 3600 <= input_time < 86400 :
85- sleep_int = input_time / 60 / 60
86- time_output = f"{ sleep_int :.1f} hours"
87- else :
88- sleep_int = input_time / 60 / 60 / 24
89- time_output = f"{ sleep_int :.1f} days"
90- return time_output
77+ return f"{ input_time :.0f} seconds"
78+ if input_time < 3600 :
79+ return f"{ input_time / 60 :.0f} minutes"
80+ if input_time < 86400 :
81+ return f"{ input_time / 60 / 60 :.0f} hours"
82+ return f"{ input_time / 60 / 60 / 24 :.1f} days"
9183
9284
9385def _format_datetime (datetime ):
94- return "{:02}/{:02}/{} {:02}:{:02}:{:02}" .format (
95- datetime .tm_mon ,
96- datetime .tm_mday ,
97- datetime .tm_year ,
98- datetime .tm_hour ,
99- datetime .tm_min ,
100- datetime .tm_sec ,
86+ """F-String formatted struct time conversion"""
87+ return (
88+ f"{ datetime .tm_mon :02} /"
89+ + f"{ datetime .tm_mday :02} /"
90+ + f"{ datetime .tm_year :02} "
91+ + f"{ datetime .tm_hour :02} :"
92+ + f"{ datetime .tm_min :02} :"
93+ + f"{ datetime .tm_sec :02} "
10194 )
10295
10396
104- # Connect to Wi-Fi
105- print ("\n ===============================" )
106- print ("Connecting to WiFi..." )
107- request = adafruit_requests .Session (pool , ssl .create_default_context ())
108- while not wifi .radio .ipv4_address :
97+ while True :
98+ # Connect to Wi-Fi
99+ print ("\n Connecting to WiFi..." )
100+ while not wifi .radio .ipv4_address :
101+ try :
102+ wifi .radio .connect (ssid , password )
103+ except ConnectionError as e :
104+ print ("❌ Connection Error:" , e )
105+ print ("Retrying in 10 seconds" )
106+ print ("✅ Wifi!" )
107+
109108 try :
110- wifi .radio .connect (ssid , password )
111- except ConnectionError as e :
112- print ("Connection Error:" , e )
113- print ("Retrying in 10 seconds" )
114- time .sleep (10 )
115- print ("Connected!\n " )
109+ print (" | Attempting to GET OpenSky-Network Area Flights JSON!" )
110+ try :
111+ opensky_response = requests .get (url = OPENSKY_SOURCE , headers = OSN_HEADER )
112+ opensky_json = opensky_response .json ()
113+ except ConnectionError as e :
114+ print ("Connection Error:" , e )
115+ print ("Retrying in 10 seconds" )
116116
117- while True :
118- # STREAMER WARNING this will show your credentials!
119- debug_request = False # Set True to see full request
120- if debug_request :
121- print ("Full API HEADER: " , str (osn_header ))
122- print ("Full API GET URL: " , OPENSKY_SOURCE )
123- print ("===============================" )
117+ print (" | ✅ OpenSky-Network JSON!" )
124118
125- print ("\n Attempting to GET OpenSky-Network Data!" )
126- opensky_response = request .get (url = OPENSKY_SOURCE , headers = osn_header ).json ()
119+ if DEBUG :
120+ print ("Full API GET URL: " , OPENSKY_SOURCE )
121+ print (opensky_json )
127122
128- # Print Full JSON to Serial (doesn't show credentials)
129- debug_response = False # Set True to see full response
130- if debug_response :
131- dump_object = json .dumps (opensky_response )
132- print ("JSON Dump: " , dump_object )
123+ # ERROR MESSAGE RESPONSES
124+ if "timestamp" in opensky_json :
125+ osn_timestamp = opensky_json ["timestamp" ]
126+ print (f"❌ Timestamp: { osn_timestamp } " )
133127
134- # Key:Value Serial Debug (doesn't show credentials)
135- osn_debug_keys = True # Set True to print Serial data
136- if osn_debug_keys :
137- try :
138- osn_flight = opensky_response ["time" ]
139- print ("Current Unix Time: " , osn_flight )
128+ if "message" in opensky_json :
129+ osn_message = opensky_json ["message" ]
130+ print (f"❌ Message: { osn_message } " )
131+
132+ if "error" in opensky_json :
133+ osn_error = opensky_json ["error" ]
134+ print (f"❌ Error: { osn_error } " )
135+
136+ if "path" in opensky_json :
137+ osn_path = opensky_json ["path" ]
138+ print (f"❌ Path: { osn_path } " )
140139
141- current_struct_time = time . localtime ( osn_flight )
142- current_date = "{}" . format ( _format_datetime ( current_struct_time ))
143- print (f"Unix to Readable Time : { current_date } " )
140+ if "status" in opensky_json :
141+ osn_status = opensky_json [ "status" ]
142+ print (f"❌ Status : { osn_status } " )
144143
145- # Current flight data for single callsign (right now)
146- osn_all_flights = opensky_response ["states" ]
144+ # Current flight data for single callsign (right now)
145+ osn_all_flights = opensky_json ["states" ]
146+
147+ if osn_all_flights is not None :
148+ if DEBUG :
149+ print (f" | | Area Flights Full Response: { osn_all_flights } " )
150+
151+ osn_time = opensky_json ["time" ]
152+ # print(f" | | Last Contact Unix Time: {osn_time}")
153+ osn_struct_time = time .localtime (osn_time )
154+ osn_readable_time = f"{ _format_datetime (osn_struct_time )} "
155+ print (f" | | Timestamp: { osn_readable_time } " )
147156
148157 if osn_all_flights is not None :
149158 # print("Flight Data: ", osn_all_flights)
150159 for flights in osn_all_flights :
151- osn_t = f"Trans:{ flights [0 ]} "
152- osn_c = f"Sign:{ flights [1 ]} "
160+ osn_t = f" | | Trans:{ flights [0 ]} "
161+ osn_c = f"Sign:{ flights [1 ]} "
153162 osn_o = f"Origin:{ flights [2 ]} "
154163 osn_tm = f"Time:{ flights [3 ]} "
155164 osn_l = f"Last:{ flights [4 ]} "
@@ -171,16 +180,20 @@ def _format_datetime(datetime):
171180 string2 = f"{ osn_la } { osn_ba } { osn_g } { osn_v } { osn_h } { osn_vr } "
172181 string3 = f"{ osn_s } { osn_ga } { osn_sq } { osn_pr } { osn_ps } { osn_ca } "
173182 print (f"{ string1 } { string2 } { string3 } " )
174- else :
175- print ("Flight has no active data or you're polling too fast." )
176-
177- print ("\n Finished!" )
178- print ("Board Uptime: " , time_calc (time .monotonic ()))
179- print ("Next Update: " , time_calc (sleep_time ))
180- time .sleep (sleep_time )
181- print ("===============================" )
182-
183- except (ConnectionError , ValueError , NameError ) as e :
184- print ("OSN Connection Error:" , e )
185- print ("Next Retry: " , time_calc (sleep_time ))
186- time .sleep (sleep_time )
183+
184+ else :
185+ print (" | | ❌ Area has no active data or you're polling too fast." )
186+
187+ opensky_response .close ()
188+ print ("✂️ Disconnected from OpenSky-Network API" )
189+
190+ print ("\n Finished!" )
191+ print (f"Board Uptime: { time_calc (time .monotonic ())} " )
192+ print (f"Next Update: { time_calc (SLEEP_TIME )} " )
193+ print ("===============================" )
194+
195+ except (ValueError , RuntimeError ) as e :
196+ print (f"Failed to get data, retrying\n { e } " )
197+ time .sleep (60 )
198+ break
199+ time .sleep (SLEEP_TIME )
0 commit comments