|
1 |
| -# SPDX-FileCopyrightText: 2023 DJDevon3 |
| 1 | +# SPDX-FileCopyrightText: 2024 DJDevon3 |
2 | 2 | # SPDX-License-Identifier: MIT
|
3 |
| -# Coded for Circuit Python 8.1 |
4 |
| -# Adafruit Feather ESP32-S3 OpenSkyNetwork_Public_API_Example |
5 |
| -import json |
| 3 | +# Coded for Circuit Python 8.2.x |
| 4 | +"""OpenSky-Network.org Public API Example""" |
| 5 | +# pylint: disable=import-error |
| 6 | + |
6 | 7 | import os
|
7 |
| -import ssl |
8 | 8 | import time
|
9 | 9 |
|
10 |
| -import socketpool |
| 10 | +import adafruit_connection_manager |
11 | 11 | import wifi
|
12 | 12 |
|
13 | 13 | import adafruit_requests
|
|
17 | 17 | # All active flights JSON: https://opensky-network.org/api/states/all PICK ONE!
|
18 | 18 | # JSON order: transponder, callsign, country
|
19 | 19 | # ACTIVE transpondes only, for multiple "c822af&icao24=cb3993&icao24=c63923"
|
20 |
| -transponder = "ab1644" |
| 20 | +TRANSPONDER = "3c5ef8" |
21 | 21 |
|
22 |
| -# Initialize WiFi Pool (There can be only 1 pool & top of script) |
23 |
| -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 |
24 | 27 |
|
25 |
| -# Time between API refreshes |
| 28 | +# API Polling Rate |
26 | 29 | # 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour
|
27 |
| -# OpenSky-Networks will temp ban your IP for too many daily requests. |
| 30 | +# OpenSky-Networks IP bans for too many requests, check rate limit. |
28 | 31 | # https://openskynetwork.github.io/opensky-api/rest.html#limitations
|
29 |
| -sleep_time = 1800 |
| 32 | +SLEEP_TIME = 1800 |
30 | 33 |
|
31 |
| -# Get WiFi details, ensure these are setup in settings.toml |
32 |
| -ssid = os.getenv("CIRCUITPY_WIFI_SSID") |
33 |
| -password = os.getenv("CIRCUITPY_WIFI_PASSWORD") |
| 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) |
34 | 42 |
|
35 | 43 | # Requests URL - icao24 is their endpoint required for a transponder
|
36 | 44 | # example https://opensky-network.org/api/states/all?icao24=a808c5
|
37 |
| -OPENSKY_SOURCE = "https://opensky-network.org/api/states/all?" + "icao24=" + transponder |
| 45 | +OPENSKY_SOURCE = "https://opensky-network.org/api/states/all?" + "icao24=" + TRANSPONDER |
38 | 46 |
|
39 | 47 |
|
40 |
| -# Converts seconds to human readable minutes/hours/days |
41 |
| -def time_calc(input_time): # input_time in seconds |
| 48 | +def time_calc(input_time): |
| 49 | + """Converts seconds to minutes/hours/days""" |
42 | 50 | if input_time < 60:
|
43 |
| - sleep_int = input_time |
44 |
| - time_output = f"{sleep_int:.0f} seconds" |
45 |
| - elif 60 <= input_time < 3600: |
46 |
| - sleep_int = input_time / 60 |
47 |
| - time_output = f"{sleep_int:.0f} minutes" |
48 |
| - elif 3600 <= input_time < 86400: |
49 |
| - sleep_int = input_time / 60 / 60 |
50 |
| - time_output = f"{sleep_int:.1f} hours" |
51 |
| - else: |
52 |
| - sleep_int = input_time / 60 / 60 / 24 |
53 |
| - time_output = f"{sleep_int:.1f} days" |
54 |
| - return time_output |
| 51 | + return f"{input_time:.0f} seconds" |
| 52 | + if input_time < 3600: |
| 53 | + return f"{input_time / 60:.0f} minutes" |
| 54 | + if input_time < 86400: |
| 55 | + return f"{input_time / 60 / 60:.0f} hours" |
| 56 | + return f"{input_time / 60 / 60 / 24:.1f} days" |
55 | 57 |
|
56 | 58 |
|
57 | 59 | def _format_datetime(datetime):
|
58 |
| - return "{:02}/{:02}/{} {:02}:{:02}:{:02}".format( |
59 |
| - datetime.tm_mon, |
60 |
| - datetime.tm_mday, |
61 |
| - datetime.tm_year, |
62 |
| - datetime.tm_hour, |
63 |
| - datetime.tm_min, |
64 |
| - datetime.tm_sec, |
| 60 | + """F-String formatted struct time conversion""" |
| 61 | + return ( |
| 62 | + f"{datetime.tm_mon:02}/" |
| 63 | + + f"{datetime.tm_mday:02}/" |
| 64 | + + f"{datetime.tm_year:02} " |
| 65 | + + f"{datetime.tm_hour:02}:" |
| 66 | + + f"{datetime.tm_min:02}:" |
| 67 | + + f"{datetime.tm_sec:02}" |
65 | 68 | )
|
66 | 69 |
|
67 | 70 |
|
68 |
| -# Connect to Wi-Fi |
69 |
| -print("\n===============================") |
70 |
| -print("Connecting to WiFi...") |
71 |
| -requests = adafruit_requests.Session(pool, ssl.create_default_context()) |
72 |
| -while not wifi.radio.ipv4_address: |
73 |
| - try: |
74 |
| - wifi.radio.connect(ssid, password) |
75 |
| - except ConnectionError as e: |
76 |
| - print("Connection Error:", e) |
77 |
| - print("Retrying in 10 seconds") |
78 |
| - time.sleep(10) |
79 |
| -print("Connected!\n") |
80 |
| - |
81 | 71 | while True:
|
82 |
| - debug_request = True # Set true to see full request |
83 |
| - if debug_request: |
84 |
| - print("Full API GET URL: ", OPENSKY_SOURCE) |
85 |
| - print("===============================") |
| 72 | + # Connect to Wi-Fi |
| 73 | + print("\nConnecting to WiFi...") |
| 74 | + while not wifi.radio.ipv4_address: |
| 75 | + try: |
| 76 | + wifi.radio.connect(ssid, password) |
| 77 | + except ConnectionError as e: |
| 78 | + print("❌ Connection Error:", e) |
| 79 | + print("Retrying in 10 seconds") |
| 80 | + print("✅ Wifi!") |
| 81 | + |
86 | 82 | try:
|
87 |
| - print("\nAttempting to GET OpenSky-Network Stats!") |
88 |
| - opensky_response = requests.get(url=OPENSKY_SOURCE) |
89 |
| - osn_json = opensky_response.json() |
90 |
| - except (ConnectionError, ValueError, NameError) as e: |
91 |
| - print("Host No Response Error:", e) |
92 |
| - |
93 |
| - # Print Full JSON to Serial |
94 |
| - debug_response = False # Set true to see full response |
95 |
| - if debug_response: |
96 |
| - dump_object = json.dumps(osn_json) |
97 |
| - print("JSON Dump: ", dump_object) |
98 |
| - |
99 |
| - # Print to Serial |
100 |
| - osn_debug_keys = True # Set true to print Serial data |
101 |
| - if osn_debug_keys: |
| 83 | + print(" | Attempting to GET OpenSky-Network Single Flight JSON!") |
102 | 84 | try:
|
103 |
| - osn_flight = osn_json["time"] |
104 |
| - print("Current Unix Time: ", osn_flight) |
105 |
| - |
106 |
| - current_struct_time = time.localtime(osn_flight) |
107 |
| - current_date = "{}".format(_format_datetime(current_struct_time)) |
108 |
| - print(f"Unix to Readable Time: {current_date}") |
109 |
| - |
110 |
| - osn_single_flight_data = osn_json["states"] |
111 |
| - if osn_single_flight_data is not None: |
112 |
| - print("Flight Data: ", osn_single_flight_data) |
113 |
| - transponder = osn_json["states"][0][0] |
114 |
| - print("Transponder: ", transponder) |
115 |
| - callsign = osn_json["states"][0][1] |
116 |
| - print("Callsign: ", callsign) |
117 |
| - country = osn_json["states"][0][2] |
118 |
| - print("Flight Country: ", country) |
| 85 | + opensky_response = requests.get(url=OPENSKY_SOURCE) |
| 86 | + opensky_json = opensky_response.json() |
| 87 | + except ConnectionError as e: |
| 88 | + print("Connection Error:", e) |
| 89 | + print("Retrying in 10 seconds") |
| 90 | + |
| 91 | + print(" | ✅ OpenSky-Network JSON!") |
| 92 | + |
| 93 | + if DEBUG: |
| 94 | + print("Full API GET URL: ", OPENSKY_SOURCE) |
| 95 | + print(opensky_json) |
| 96 | + |
| 97 | + # ERROR MESSAGE RESPONSES |
| 98 | + if "timestamp" in opensky_json: |
| 99 | + osn_timestamp = opensky_json["timestamp"] |
| 100 | + print(f"❌ Timestamp: {osn_timestamp}") |
| 101 | + |
| 102 | + if "message" in opensky_json: |
| 103 | + osn_message = opensky_json["message"] |
| 104 | + print(f"❌ Message: {osn_message}") |
| 105 | + |
| 106 | + if "error" in opensky_json: |
| 107 | + osn_error = opensky_json["error"] |
| 108 | + print(f"❌ Error: {osn_error}") |
| 109 | + |
| 110 | + if "path" in opensky_json: |
| 111 | + osn_path = opensky_json["path"] |
| 112 | + print(f"❌ Path: {osn_path}") |
| 113 | + |
| 114 | + if "status" in opensky_json: |
| 115 | + osn_status = opensky_json["status"] |
| 116 | + print(f"❌ Status: {osn_status}") |
| 117 | + |
| 118 | + # Current flight data for single callsign (right now) |
| 119 | + osn_single_flight_data = opensky_json["states"] |
| 120 | + |
| 121 | + if osn_single_flight_data is not None: |
| 122 | + if DEBUG: |
| 123 | + print(f" | | Single Flight Public Data: {osn_single_flight_data}") |
| 124 | + |
| 125 | + last_contact = opensky_json["states"][0][4] |
| 126 | + # print(f" | | Last Contact Unix Time: {last_contact}") |
| 127 | + lc_struct_time = time.localtime(last_contact) |
| 128 | + lc_readable_time = f"{_format_datetime(lc_struct_time)}" |
| 129 | + print(f" | | Last Contact: {lc_readable_time}") |
| 130 | + |
| 131 | + flight_transponder = opensky_json["states"][0][0] |
| 132 | + print(f" | | Transponder: {flight_transponder}") |
| 133 | + |
| 134 | + callsign = opensky_json["states"][0][1] |
| 135 | + print(f" | | Callsign: {callsign}") |
| 136 | + |
| 137 | + squawk = opensky_json["states"][0][14] |
| 138 | + print(f" | | Squawk: {squawk}") |
| 139 | + |
| 140 | + country = opensky_json["states"][0][2] |
| 141 | + print(f" | | Origin: {country}") |
| 142 | + |
| 143 | + longitude = opensky_json["states"][0][5] |
| 144 | + print(f" | | Longitude: {longitude}") |
| 145 | + |
| 146 | + latitude = opensky_json["states"][0][6] |
| 147 | + print(f" | | Latitude: {latitude}") |
| 148 | + |
| 149 | + # Return Air Flight data if not on ground |
| 150 | + on_ground = opensky_json["states"][0][8] |
| 151 | + if on_ground is True: |
| 152 | + print(f" | | On Ground: {on_ground}") |
119 | 153 | else:
|
120 |
| - print("This flight has no active data or you're polling too fast.") |
121 |
| - print( |
122 |
| - "Read: https://openskynetwork.github.io/opensky-api/rest.html#limitations" |
123 |
| - ) |
124 |
| - print( |
125 |
| - "Public Limits: 10 second max poll rate & 400 weighted calls daily" |
126 |
| - ) |
127 |
| - |
128 |
| - print("\nFinished!") |
129 |
| - print("Board Uptime: ", time_calc(time.monotonic())) |
130 |
| - print("Next Update: ", time_calc(sleep_time)) |
131 |
| - time.sleep(sleep_time) |
132 |
| - print("===============================") |
133 |
| - |
134 |
| - except (ConnectionError, ValueError, NameError) as e: |
135 |
| - print("OSN Connection Error:", e) |
136 |
| - print("Next Retry: ", time_calc(sleep_time)) |
137 |
| - time.sleep(sleep_time) |
| 154 | + altitude = opensky_json["states"][0][7] |
| 155 | + print(f" | | Barometric Altitude: {altitude}") |
| 156 | + |
| 157 | + velocity = opensky_json["states"][0][9] |
| 158 | + if velocity != "null": |
| 159 | + print(f" | | Velocity: {velocity}") |
| 160 | + |
| 161 | + vertical_rate = opensky_json["states"][0][11] |
| 162 | + if vertical_rate != "null": |
| 163 | + print(f" | | Vertical Rate: {vertical_rate}") |
| 164 | + else: |
| 165 | + print("This flight has no active data or you're polling too fast.") |
| 166 | + print("Public Limits: 10 second max poll & 400 weighted calls daily") |
| 167 | + |
| 168 | + opensky_response.close() |
| 169 | + print("✂️ Disconnected from OpenSky-Network API") |
| 170 | + |
| 171 | + print("\nFinished!") |
| 172 | + print(f"Board Uptime: {time_calc(time.monotonic())}") |
| 173 | + print(f"Next Update: {time_calc(SLEEP_TIME)}") |
| 174 | + print("===============================") |
| 175 | + |
| 176 | + except (ValueError, RuntimeError) as e: |
| 177 | + print(f"Failed to get data, retrying\n {e}") |
| 178 | + time.sleep(60) |
| 179 | + break |
| 180 | + time.sleep(SLEEP_TIME) |
0 commit comments