|
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_API_Example |
| 3 | +# Coded for Circuit Python 8.2.x |
| 4 | +"""OpenSky-Network.org Single Flight Private 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
|
|
19 | 17 | # All active flights JSON: https://opensky-network.org/api/states/all # PICK ONE! :)
|
20 | 18 | # JSON order: transponder, callsign, country
|
21 | 19 | # ACTIVE transpondes only, for multiple "c822af&icao24=cb3993&icao24=c63923"
|
22 |
| -transponder = "7c6b2d" |
| 20 | +TRANSPONDER = "4b1812" |
23 | 21 |
|
24 |
| -# Initialize WiFi Pool (There can be only 1 pool & top of script) |
25 |
| -pool = socketpool.SocketPool(wifi.radio) |
| 22 | +# Get WiFi details, ensure these are setup in settings.toml |
| 23 | +ssid = os.getenv("CIRCUITPY_WIFI_SSID") |
| 24 | +password = os.getenv("CIRCUITPY_WIFI_PASSWORD") |
| 25 | +osnusername = os.getenv("OSN_USERNAME") # Website Credentials |
| 26 | +osnpassword = os.getenv("OSN_PASSWORD") # Website Credentials |
26 | 27 |
|
27 |
| -# Time between API refreshes |
| 28 | +# API Polling Rate |
28 | 29 | # 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour
|
29 | 30 | # OpenSky-Networks IP bans for too many requests, check rate limit.
|
30 | 31 | # https://openskynetwork.github.io/opensky-api/rest.html#limitations
|
31 |
| -sleep_time = 1800 |
| 32 | +SLEEP_TIME = 1800 |
32 | 33 |
|
33 |
| -# Get WiFi details, ensure these are setup in settings.toml |
34 |
| -ssid = os.getenv("CIRCUITPY_WIFI_SSID") |
35 |
| -password = os.getenv("CIRCUITPY_WIFI_PASSWORD") |
36 |
| -osnu = os.getenv("OSN_Username") |
37 |
| -osnp = os.getenv("OSN_Password") |
38 |
| - |
39 |
| -osn_cred = str(osnu) + ":" + str(osnp) |
40 |
| -bytes_to_encode = b" " + str(osn_cred) + " " |
41 |
| -base64_string = base64.encodebytes(bytes_to_encode) |
42 |
| -base64cred = repr(base64_string)[2:-1] |
43 |
| - |
44 |
| -Debug_Auth = False # STREAMER WARNING this will show your credentials! |
45 |
| -if Debug_Auth: |
46 |
| - osn_cred = str(osnu) + ":" + str(osnp) |
47 |
| - bytes_to_encode = b" " + str(osn_cred) + " " |
48 |
| - print(repr(bytes_to_encode)) |
49 |
| - base64_string = base64.encodebytes(bytes_to_encode) |
50 |
| - print(repr(base64_string)[2:-1]) |
51 |
| - base64cred = repr(base64_string)[2:-1] |
52 |
| - print("Decoded Bytes:", str(base64cred)) |
| 34 | +# Set debug to True for full JSON response. |
| 35 | +# WARNING: makes credentials visible |
| 36 | +DEBUG = False |
| 37 | + |
| 38 | +# Initalize Wifi, Socket Pool, Request Session |
| 39 | +pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio) |
| 40 | +ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio) |
| 41 | +requests = adafruit_requests.Session(pool, ssl_context) |
| 42 | + |
| 43 | +# -- Base64 Conversion -- |
| 44 | +OSN_CREDENTIALS = str(osnusername) + ":" + str(osnpassword) |
| 45 | +# base64 encode and strip appended \n from bytearray |
| 46 | +OSN_CREDENTIALS_B = binascii.b2a_base64(OSN_CREDENTIALS.encode()).strip() |
| 47 | +BASE64_STRING = OSN_CREDENTIALS_B.decode() # bytearray |
| 48 | + |
| 49 | + |
| 50 | +if DEBUG: |
| 51 | + print("Base64 ByteArray: ", BASE64_STRING) |
53 | 52 |
|
54 | 53 | # Requests URL - icao24 is their endpoint required for a transponder
|
55 | 54 | # example https://opensky-network.org/api/states/all?icao24=a808c5
|
56 |
| -# OSN private requires your username:password to be base64 encoded |
57 |
| -osn_header = {"Authorization": "Basic " + str(base64cred)} |
58 |
| -OPENSKY_SOURCE = "https://opensky-network.org/api/states/all?" + "icao24=" + transponder |
| 55 | +# OSN private: requires your website username:password to be base64 encoded |
| 56 | +OPENSKY_HEADER = {"Authorization": "Basic " + BASE64_STRING} |
| 57 | +OPENSKY_SOURCE = "https://opensky-network.org/api/states/all?" + "icao24=" + TRANSPONDER |
59 | 58 |
|
60 | 59 |
|
61 |
| -# Converts seconds to human readable minutes/hours/days |
62 |
| -def time_calc(input_time): # input_time in seconds |
| 60 | +def time_calc(input_time): |
| 61 | + """Converts seconds to minutes/hours/days""" |
63 | 62 | if input_time < 60:
|
64 |
| - sleep_int = input_time |
65 |
| - time_output = f"{sleep_int:.0f} seconds" |
66 |
| - elif 60 <= input_time < 3600: |
67 |
| - sleep_int = input_time / 60 |
68 |
| - time_output = f"{sleep_int:.0f} minutes" |
69 |
| - elif 3600 <= input_time < 86400: |
70 |
| - sleep_int = input_time / 60 / 60 |
71 |
| - time_output = f"{sleep_int:.1f} hours" |
72 |
| - else: |
73 |
| - sleep_int = input_time / 60 / 60 / 24 |
74 |
| - time_output = f"{sleep_int:.1f} days" |
75 |
| - return time_output |
| 63 | + return f"{input_time:.0f} seconds" |
| 64 | + if input_time < 3600: |
| 65 | + return f"{input_time / 60:.0f} minutes" |
| 66 | + if input_time < 86400: |
| 67 | + return f"{input_time / 60 / 60:.0f} hours" |
| 68 | + return f"{input_time / 60 / 60 / 24:.1f} days" |
76 | 69 |
|
77 | 70 |
|
78 | 71 | def _format_datetime(datetime):
|
79 |
| - return "{:02}/{:02}/{} {:02}:{:02}:{:02}".format( |
80 |
| - datetime.tm_mon, |
81 |
| - datetime.tm_mday, |
82 |
| - datetime.tm_year, |
83 |
| - datetime.tm_hour, |
84 |
| - datetime.tm_min, |
85 |
| - datetime.tm_sec, |
| 72 | + return ( |
| 73 | + f"{datetime.tm_mon:02}/" |
| 74 | + + f"{datetime.tm_mday:02}/" |
| 75 | + + f"{datetime.tm_year:02} " |
| 76 | + + f"{datetime.tm_hour:02}:" |
| 77 | + + f"{datetime.tm_min:02}:" |
| 78 | + + f"{datetime.tm_sec:02}" |
86 | 79 | )
|
87 | 80 |
|
88 | 81 |
|
89 |
| -# Connect to Wi-Fi |
90 |
| -print("\n===============================") |
91 |
| -print("Connecting to WiFi...") |
92 |
| -request = adafruit_requests.Session(pool, ssl.create_default_context()) |
93 |
| -while not wifi.radio.ipv4_address: |
| 82 | +while True: |
| 83 | + # Connect to Wi-Fi |
| 84 | + print("\nConnecting to WiFi...") |
| 85 | + while not wifi.radio.ipv4_address: |
| 86 | + try: |
| 87 | + wifi.radio.connect(ssid, password) |
| 88 | + except ConnectionError as e: |
| 89 | + print("❌ Connection Error:", e) |
| 90 | + print("Retrying in 10 seconds") |
| 91 | + print("✅ Wifi!") |
| 92 | + |
94 | 93 | try:
|
95 |
| - wifi.radio.connect(ssid, password) |
96 |
| - except ConnectionError as e: |
97 |
| - print("Connection Error:", e) |
98 |
| - print("Retrying in 10 seconds") |
99 |
| - time.sleep(10) |
100 |
| -print("Connected!\n") |
| 94 | + print(" | Attempting to GET OpenSky-Network Single Private Flight JSON!") |
| 95 | + print(" | Website Credentials Required! Allows more daily calls than Public.") |
| 96 | + try: |
| 97 | + opensky_response = requests.get(url=OPENSKY_SOURCE, headers=OPENSKY_HEADER) |
| 98 | + opensky_json = opensky_response.json() |
| 99 | + except ConnectionError as e: |
| 100 | + print("Connection Error:", e) |
| 101 | + print("Retrying in 10 seconds") |
101 | 102 |
|
102 |
| -while True: |
103 |
| - # STREAMER WARNING this will show your credentials! |
104 |
| - debug_request = False # Set True to see full request |
105 |
| - if debug_request: |
106 |
| - print("Full API HEADER: ", str(osn_header)) |
107 |
| - print("Full API GET URL: ", OPENSKY_SOURCE) |
108 |
| - print("===============================") |
| 103 | + print(" | ✅ OpenSky-Network JSON!") |
109 | 104 |
|
110 |
| - print("\nAttempting to GET OpenSky-Network Data!") |
111 |
| - opensky_response = request.get(url=OPENSKY_SOURCE, headers=osn_header).json() |
| 105 | + if DEBUG: |
| 106 | + print("Full API GET URL: ", OPENSKY_SOURCE) |
| 107 | + print("Full API GET Header: ", OPENSKY_HEADER) |
| 108 | + print(opensky_json) |
112 | 109 |
|
113 |
| - # Print Full JSON to Serial (doesn't show credentials) |
114 |
| - debug_response = False # Set True to see full response |
115 |
| - if debug_response: |
116 |
| - dump_object = json.dumps(opensky_response) |
117 |
| - print("JSON Dump: ", dump_object) |
| 110 | + # ERROR MESSAGE RESPONSES |
| 111 | + if "timestamp" in opensky_json: |
| 112 | + osn_timestamp = opensky_json["timestamp"] |
| 113 | + print(f"❌ Timestamp: {osn_timestamp}") |
118 | 114 |
|
119 |
| - # Key:Value Serial Debug (doesn't show credentials) |
120 |
| - osn_debug_keys = True # Set True to print Serial data |
121 |
| - if osn_debug_keys: |
122 |
| - try: |
123 |
| - osn_flight = opensky_response["time"] |
124 |
| - print("Current Unix Time: ", osn_flight) |
125 |
| - |
126 |
| - current_struct_time = time.localtime(osn_flight) |
127 |
| - current_date = "{}".format(_format_datetime(current_struct_time)) |
128 |
| - print(f"Unix to Readable Time: {current_date}") |
129 |
| - |
130 |
| - # Current flight data for single callsign (right now) |
131 |
| - osn_single_flight_data = opensky_response["states"] |
132 |
| - |
133 |
| - if osn_single_flight_data is not None: |
134 |
| - print("Flight Data: ", osn_single_flight_data) |
135 |
| - transponder = opensky_response["states"][0][0] |
136 |
| - print("Transponder: ", transponder) |
137 |
| - callsign = opensky_response["states"][0][1] |
138 |
| - print("Callsign: ", callsign) |
139 |
| - country = opensky_response["states"][0][2] |
140 |
| - print("Flight Country: ", country) |
| 115 | + if "message" in opensky_json: |
| 116 | + osn_message = opensky_json["message"] |
| 117 | + print(f"❌ Message: {osn_message}") |
| 118 | + |
| 119 | + if "error" in opensky_json: |
| 120 | + osn_error = opensky_json["error"] |
| 121 | + print(f"❌ Error: {osn_error}") |
| 122 | + |
| 123 | + if "path" in opensky_json: |
| 124 | + osn_path = opensky_json["path"] |
| 125 | + print(f"❌ Path: {osn_path}") |
| 126 | + |
| 127 | + if "status" in opensky_json: |
| 128 | + osn_status = opensky_json["status"] |
| 129 | + print(f"❌ Status: {osn_status}") |
| 130 | + |
| 131 | + # Current flight data for single callsign (right now) |
| 132 | + osn_single_flight_data = opensky_json["states"] |
| 133 | + |
| 134 | + if osn_single_flight_data is not None: |
| 135 | + if DEBUG: |
| 136 | + print(f" | | Single Private Flight Data: {osn_single_flight_data}") |
| 137 | + |
| 138 | + last_contact = opensky_json["states"][0][4] |
| 139 | + # print(f" | | Last Contact Unix Time: {last_contact}") |
| 140 | + lc_struct_time = time.localtime(last_contact) |
| 141 | + lc_readable_time = f"{_format_datetime(lc_struct_time)}" |
| 142 | + print(f" | | Last Contact: {lc_readable_time}") |
| 143 | + |
| 144 | + flight_transponder = opensky_json["states"][0][0] |
| 145 | + print(f" | | Transponder: {flight_transponder}") |
| 146 | + |
| 147 | + callsign = opensky_json["states"][0][1] |
| 148 | + print(f" | | Callsign: {callsign}") |
| 149 | + |
| 150 | + squawk = opensky_json["states"][0][14] |
| 151 | + print(f" | | Squawk: {squawk}") |
| 152 | + |
| 153 | + country = opensky_json["states"][0][2] |
| 154 | + print(f" | | Origin: {country}") |
| 155 | + |
| 156 | + longitude = opensky_json["states"][0][5] |
| 157 | + print(f" | | Longitude: {longitude}") |
| 158 | + |
| 159 | + latitude = opensky_json["states"][0][6] |
| 160 | + print(f" | | Latitude: {latitude}") |
| 161 | + |
| 162 | + # Return Air Flight data if not on ground |
| 163 | + on_ground = opensky_json["states"][0][8] |
| 164 | + if on_ground is True: |
| 165 | + print(f" | | On Ground: {on_ground}") |
141 | 166 | else:
|
142 |
| - print("Flight has no active data or you're polling too fast.") |
143 |
| - |
144 |
| - print("\nFinished!") |
145 |
| - print("Board Uptime: ", time_calc(time.monotonic())) |
146 |
| - print("Next Update: ", time_calc(sleep_time)) |
147 |
| - time.sleep(sleep_time) |
148 |
| - print("===============================") |
149 |
| - |
150 |
| - except (ConnectionError, ValueError, NameError) as e: |
151 |
| - print("OSN Connection Error:", e) |
152 |
| - print("Next Retry: ", time_calc(sleep_time)) |
153 |
| - time.sleep(sleep_time) |
| 167 | + altitude = opensky_json["states"][0][7] |
| 168 | + print(f" | | Barometric Altitude: {altitude}") |
| 169 | + |
| 170 | + velocity = opensky_json["states"][0][9] |
| 171 | + if velocity != "null": |
| 172 | + print(f" | | Velocity: {velocity}") |
| 173 | + |
| 174 | + vertical_rate = opensky_json["states"][0][11] |
| 175 | + if vertical_rate != "null": |
| 176 | + print(f" | | Vertical Rate: {vertical_rate}") |
| 177 | + |
| 178 | + else: |
| 179 | + print(" | | ❌ Flight has no active data or you're polling too fast.") |
| 180 | + |
| 181 | + opensky_response.close() |
| 182 | + print("✂️ Disconnected from OpenSky-Network API") |
| 183 | + |
| 184 | + print("\nFinished!") |
| 185 | + print(f"Board Uptime: {time_calc(time.monotonic())}") |
| 186 | + print(f"Next Update: {time_calc(SLEEP_TIME)}") |
| 187 | + print("===============================") |
| 188 | + |
| 189 | + except (ValueError, RuntimeError) as e: |
| 190 | + print(f"Failed to get data, retrying\n {e}") |
| 191 | + time.sleep(60) |
| 192 | + break |
| 193 | + time.sleep(SLEEP_TIME) |
0 commit comments