1
- # SPDX-FileCopyrightText: 2023 DJDevon3
1
+ # SPDX-FileCopyrightText: 2024 DJDevon3
2
2
# 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"""
5
5
6
- import json
6
+ import binascii
7
7
import os
8
- import ssl
9
8
import time
10
9
11
- import circuitpython_base64 as base64
12
- import socketpool
10
+ import adafruit_connection_manager
13
11
import wifi
14
12
15
13
import adafruit_requests
16
14
17
15
# OpenSky-Network.org Website Login required for this API
16
+ # Increased call limit vs Public.
18
17
# REST API: https://openskynetwork.github.io/opensky-api/rest.html
19
-
20
18
# 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
25
23
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
28
29
29
- # Time between API refreshes
30
+ # API Polling Rate
30
31
# 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour
31
32
# OpenSky-Networks IP bans for too many requests, check rate limit.
32
33
# https://openskynetwork.github.io/opensky-api/rest.html#limitations
33
- sleep_time = 1800
34
+ SLEEP_TIME = 1800
34
35
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.
62
60
# https://opensky-network.org/api/states/all?lamin=25.21&lomin=-84.36&lamax=30.0&lomax=-78.40
63
61
OPENSKY_SOURCE = (
64
62
"https://opensky-network.org/api/states/all?"
65
63
+ "lamin="
66
- + latmin
64
+ + LATMIN
67
65
+ "&lomin="
68
- + lonmin
66
+ + LONMIN
69
67
+ "&lamax="
70
- + latmax
68
+ + LATMAX
71
69
+ "&lomax="
72
- + lonmax
70
+ + LONMAX
73
71
)
74
72
75
73
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"""
78
76
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"
91
83
92
84
93
85
def _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} "
101
94
)
102
95
103
96
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
+
109
108
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" )
116
116
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!" )
124
118
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 )
127
122
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 } " )
133
127
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 } " )
140
139
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 } " )
144
143
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 } " )
147
156
148
157
if osn_all_flights is not None :
149
158
# print("Flight Data: ", osn_all_flights)
150
159
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 ]} "
153
162
osn_o = f"Origin:{ flights [2 ]} "
154
163
osn_tm = f"Time:{ flights [3 ]} "
155
164
osn_l = f"Last:{ flights [4 ]} "
@@ -171,16 +180,20 @@ def _format_datetime(datetime):
171
180
string2 = f"{ osn_la } { osn_ba } { osn_g } { osn_v } { osn_h } { osn_vr } "
172
181
string3 = f"{ osn_s } { osn_ga } { osn_sq } { osn_pr } { osn_ps } { osn_ca } "
173
182
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