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 API Example"""
5+ # pylint: disable=import-error
56
6- import json
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
14+ from adafruit_binascii import b2a_base64
1615
1716# OpenSky-Network.org Website Login required for this API
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 )
28-
29- # Time between API refreshes
30- # 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour
31- # OpenSky-Networks IP bans for too many requests, check rate limit.
32- # https://openskynetwork.github.io/opensky-api/rest.html#limitations
33- sleep_time = 1800
24+ # Github developer token required.
25+ username = os .getenv ("GITHUB_USERNAME" )
26+ token = os .getenv ("GITHUB_TOKEN" )
3427
3528# Get WiFi details, ensure these are setup in settings.toml
3629ssid = os .getenv ("CIRCUITPY_WIFI_SSID" )
3730password = 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.
31+ osnusername = os .getenv ("OSN_USERNAME" ) # Website Credentials
32+ osnpassword = os .getenv ("OSN_PASSWORD" ) # Website Credentials
33+
34+ # API Polling Rate
35+ # 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour
36+ # OpenSky-Networks IP bans for too many requests, check rate limit.
37+ # https://openskynetwork.github.io/opensky-api/rest.html#limitations
38+ SLEEP_TIME = 1800
39+
40+ # Set debug to True for full JSON response.
41+ # WARNING: makes credentials visible
42+ DEBUG = False
43+
44+ # Initalize Wifi, Socket Pool, Request Session
45+ pool = adafruit_connection_manager .get_radio_socketpool (wifi .radio )
46+ ssl_context = adafruit_connection_manager .get_radio_ssl_context (wifi .radio )
47+ requests = adafruit_requests .Session (pool , ssl_context )
48+
49+ # -- Base64 Conversion --
50+ OSN_CREDENTIALS = str (osnusername ) + ":" + str (osnpassword )
51+ OSN_CREDENTIALS_B = b"" + str (OSN_CREDENTIALS ) + ""
52+ BASE64_ASCII = b2a_base64 (OSN_CREDENTIALS_B )
53+ BASE64_STRING = str (BASE64_ASCII ) # bytearray
54+ TRUNCATED_BASE64_STRING = BASE64_STRING [2 :- 1 ] # truncate bytearray head/tail
55+
56+ if DEBUG :
57+ print ("Original Binary Data: " , OSN_CREDENTIALS_B )
58+ print ("Base64 ByteArray: " , BASE64_ASCII )
59+ print (f"Base64 String: { TRUNCATED_BASE64_STRING } " )
60+
61+ # Area requires OpenSky-Network.org username:password to be base64 encoded
62+ OSN_HEADER = {"Authorization" : "Basic " + str (TRUNCATED_BASE64_STRING )}
63+
64+ # Example request of all traffic over Florida.
65+ # Geographic areas calls cost less against the limit.
6266# https://opensky-network.org/api/states/all?lamin=25.21&lomin=-84.36&lamax=30.0&lomax=-78.40
6367OPENSKY_SOURCE = (
6468 "https://opensky-network.org/api/states/all?"
6569 + "lamin="
66- + latmin
70+ + LATMIN
6771 + "&lomin="
68- + lonmin
72+ + LONMIN
6973 + "&lamax="
70- + latmax
74+ + LATMAX
7175 + "&lomax="
72- + lonmax
76+ + LONMAX
7377)
7478
7579
76- # Converts seconds to human readable minutes/hours/days
77- def time_calc ( input_time ): # input_time in seconds
80+ def time_calc ( input_time ):
81+ """Converts seconds to minutes/hours/days"""
7882 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
83+ return f"{ input_time :.0f} seconds"
84+ if input_time < 3600 :
85+ return f"{ input_time / 60 :.0f} minutes"
86+ if input_time < 86400 :
87+ return f"{ input_time / 60 / 60 :.0f} hours"
88+ return f"{ input_time / 60 / 60 / 24 :.1f} days"
9189
9290
9391def _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 ,
92+ """F-String formatted struct time conversion"""
93+ return (
94+ f"{ datetime .tm_mon :02} /"
95+ + f"{ datetime .tm_mday :02} /"
96+ + f"{ datetime .tm_year :02} "
97+ + f"{ datetime .tm_hour :02} :"
98+ + f"{ datetime .tm_min :02} :"
99+ + f"{ datetime .tm_sec :02} "
101100 )
102101
103102
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 :
103+ while True :
104+ # Connect to Wi-Fi
105+ print ("\n Connecting to WiFi..." )
106+ while not wifi .radio .ipv4_address :
107+ try :
108+ wifi .radio .connect (ssid , password )
109+ except ConnectionError as e :
110+ print ("❌ Connection Error:" , e )
111+ print ("Retrying in 10 seconds" )
112+ print ("✅ Wifi!" )
113+
109114 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 " )
115+ print (" | Attempting to GET OpenSky-Network Area Flights JSON!" )
116+ try :
117+ opensky_response = requests .get (url = OPENSKY_SOURCE , headers = OSN_HEADER )
118+ opensky_json = opensky_response .json ()
119+ except ConnectionError as e :
120+ print ("Connection Error:" , e )
121+ print ("Retrying in 10 seconds" )
116122
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 ("===============================" )
123+ print (" | ✅ OpenSky-Network JSON!" )
124124
125- print ("\n Attempting to GET OpenSky-Network Data!" )
126- opensky_response = request .get (url = OPENSKY_SOURCE , headers = osn_header ).json ()
125+ if DEBUG :
126+ print ("Full API GET URL: " , OPENSKY_SOURCE )
127+ print (opensky_json )
127128
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 )
129+ # ERROR MESSAGE RESPONSES
130+ if "timestamp" in opensky_json :
131+ osn_timestamp = opensky_json ["timestamp" ]
132+ print (f"❌ Timestamp: { osn_timestamp } " )
133133
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 )
134+ if "message" in opensky_json :
135+ osn_message = opensky_json ["message" ]
136+ print (f"❌ Message: { osn_message } " )
137+
138+ if "error" in opensky_json :
139+ osn_error = opensky_json ["error" ]
140+ print (f"❌ Error: { osn_error } " )
141+
142+ if "path" in opensky_json :
143+ osn_path = opensky_json ["path" ]
144+ print (f"❌ Path: { osn_path } " )
145+
146+ if "status" in opensky_json :
147+ osn_status = opensky_json ["status" ]
148+ print (f"❌ Status: { osn_status } " )
140149
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 } " )
150+ # Current flight data for single callsign (right now)
151+ osn_all_flights = opensky_json ["states" ]
144152
145- # Current flight data for single callsign (right now)
146- osn_all_flights = opensky_response ["states" ]
153+ if osn_all_flights is not None :
154+ if DEBUG :
155+ print (f" | | Area Flights Full Response: { osn_all_flights } " )
156+
157+ osn_time = opensky_json ["time" ]
158+ # print(f" | | Last Contact Unix Time: {osn_time}")
159+ osn_struct_time = time .localtime (osn_time )
160+ osn_readable_time = f"{ _format_datetime (osn_struct_time )} "
161+ print (f" | | Last Contact: { osn_readable_time } " )
147162
148163 if osn_all_flights is not None :
149164 # print("Flight Data: ", osn_all_flights)
150165 for flights in osn_all_flights :
151- osn_t = f"Trans:{ flights [0 ]} "
152- osn_c = f"Sign:{ flights [1 ]} "
166+ osn_t = f" | | Trans:{ flights [0 ]} "
167+ osn_c = f"Sign:{ flights [1 ]} "
153168 osn_o = f"Origin:{ flights [2 ]} "
154169 osn_tm = f"Time:{ flights [3 ]} "
155170 osn_l = f"Last:{ flights [4 ]} "
@@ -171,16 +186,20 @@ def _format_datetime(datetime):
171186 string2 = f"{ osn_la } { osn_ba } { osn_g } { osn_v } { osn_h } { osn_vr } "
172187 string3 = f"{ osn_s } { osn_ga } { osn_sq } { osn_pr } { osn_ps } { osn_ca } "
173188 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 )
189+
190+ else :
191+ print (" | | ❌ Area has no active data or you're polling too fast." )
192+
193+ opensky_response .close ()
194+ print ("✂️ Disconnected from OpenSky-Network API" )
195+
196+ print ("\n Finished!" )
197+ print (f"Board Uptime: { time_calc (time .monotonic ())} " )
198+ print (f"Next Update: { time_calc (SLEEP_TIME )} " )
199+ print ("===============================" )
200+
201+ except (ValueError , RuntimeError ) as e :
202+ print (f"Failed to get data, retrying\n { e } " )
203+ time .sleep (60 )
204+ break
205+ time .sleep (SLEEP_TIME )
0 commit comments