|
| 1 | +# SPDX-FileCopyrightText: 2022 Tim C, written for Adafruit Industries |
| 2 | +# |
| 3 | +# SPDX-License-Identifier: Unlicense |
| 4 | +""" |
| 5 | +MagTag status display for James Webb Telescope |
| 6 | +""" |
| 7 | +import time |
| 8 | +import json |
| 9 | +import ssl |
| 10 | +import board |
| 11 | +import displayio |
| 12 | +import terminalio |
| 13 | +import supervisor |
| 14 | +from adafruit_bitmap_font import bitmap_font |
| 15 | +from adafruit_display_text import bitmap_label |
| 16 | +import wifi |
| 17 | +import socketpool |
| 18 | +import alarm |
| 19 | +import adafruit_requests as requests |
| 20 | + |
| 21 | +try: |
| 22 | + from secrets import secrets |
| 23 | +except ImportError: |
| 24 | + print("WiFi secrets are kept in secrets.py, please add them there!") |
| 25 | + raise |
| 26 | + |
| 27 | +# Update once per hour |
| 28 | +SLEEP_TIME = 60 * 60 # seconds |
| 29 | + |
| 30 | +# Update once per day |
| 31 | +# SLEEP_TIME = 60 * 60 * 24 # seconds |
| 32 | + |
| 33 | +# URL to fetch the data from |
| 34 | +JSON_GET_URL = "https://api.jwst-hub.com/track" |
| 35 | + |
| 36 | +# Whether to fetch live data or use cached |
| 37 | +TEST_RUN = False |
| 38 | +# Cached data, helpful when developing interface |
| 39 | +# pylint: disable=line-too-long |
| 40 | +FAKE_DATA = '{"distanceEarthKm":"1103975.4","launchElapsedTime":"15:03:47:44","distanceL2Km":342356.2,"percentageCompleted":76.3291,"speedKmS":0.3778,"deploymentImgURL":"https://webb.nasa.gov/content/webbLaunch/assets/images/deployment/1000pxWide/123Crop.png","currentDeploymentStep":"WEBB IS FULLY DEPLOYED! - The largest, most complex telescope ever launched into space is fully deployed.","tempC":{"tempWarmSide1C":55,"tempWarmSide2C":11,"tempCoolSide1C":-178,"tempCoolSide2C":-200},"timestamp":"2022-01-09T16:07:44.147Z"}' |
| 41 | + |
| 42 | +# Background Color for the label texts |
| 43 | +LBL_BACKGROUND = 0x444444 |
| 44 | + |
| 45 | + |
| 46 | +def try_refresh(): |
| 47 | + """Attempt to refresh the display. Catch 'refresh too soon' error |
| 48 | + and retry after waiting 10 seconds. |
| 49 | + """ |
| 50 | + try: |
| 51 | + board.DISPLAY.refresh() |
| 52 | + except RuntimeError as too_soon_error: |
| 53 | + # catch refresh too soon |
| 54 | + print(too_soon_error) |
| 55 | + print("waiting before retry refresh()") |
| 56 | + time.sleep(10) |
| 57 | + board.DISPLAY.refresh() |
| 58 | + |
| 59 | + |
| 60 | +# Get the display object |
| 61 | +display = board.DISPLAY |
| 62 | + |
| 63 | +if not TEST_RUN: |
| 64 | + print("Connecting to AP...") |
| 65 | + try: |
| 66 | + # wifi connect |
| 67 | + wifi.radio.connect(secrets["ssid"], secrets["password"]) |
| 68 | + |
| 69 | + # Create Socket, initialize requests |
| 70 | + socket = socketpool.SocketPool(wifi.radio) |
| 71 | + requests = requests.Session(socket, ssl.create_default_context()) |
| 72 | + except OSError: |
| 73 | + print("Failed to connect to AP. Rebooting in 3 seconds...") |
| 74 | + time.sleep(3) |
| 75 | + supervisor.reload() |
| 76 | + |
| 77 | + |
| 78 | +def make_label_text(text, anchor_point, anchored_position): |
| 79 | + """ |
| 80 | + Create label object for labeling data values. |
| 81 | + It will get a background color box and appropriate padding. |
| 82 | +
|
| 83 | + :param text: Text to show |
| 84 | + :param anchor_point: location anchor_point |
| 85 | + :param anchored_position: location anchored_position |
| 86 | + :return bitmap_label.Label: the Label object |
| 87 | + """ |
| 88 | + return bitmap_label.Label( |
| 89 | + font, |
| 90 | + text=text, |
| 91 | + anchor_point=anchor_point, |
| 92 | + anchored_position=anchored_position, |
| 93 | + background_color=LBL_BACKGROUND, |
| 94 | + padding_left=4, |
| 95 | + padding_right=4, |
| 96 | + padding_bottom=3, |
| 97 | + padding_top=3, |
| 98 | + ) |
| 99 | + |
| 100 | + |
| 101 | +def make_value_text(anchor_point, anchored_position, custom_font=True): |
| 102 | + """ |
| 103 | + Create label object for showing data values. |
| 104 | +
|
| 105 | + :param anchor_point: location anchor_point |
| 106 | + :param anchored_position: location anchored_position |
| 107 | + :param bool custom_font: weather to use the custom font or system font |
| 108 | + :return bitmap_label.Label: the Label object |
| 109 | + """ |
| 110 | + if custom_font: |
| 111 | + _font = font |
| 112 | + else: |
| 113 | + _font = terminalio.FONT |
| 114 | + return bitmap_label.Label( |
| 115 | + _font, text="", anchor_point=anchor_point, anchored_position=anchored_position |
| 116 | + ) |
| 117 | + |
| 118 | + |
| 119 | +# main_group to show things |
| 120 | +main_group = displayio.Group() |
| 121 | + |
| 122 | +# initialize custom font |
| 123 | +font = bitmap_font.load_font("fonts/LeagueSpartan-Light.bdf") |
| 124 | + |
| 125 | +# value text initialization |
| 126 | + |
| 127 | +# top left |
| 128 | +elapsed_time_val = make_value_text(anchor_point=(0, 0), anchored_position=(6, 6)) |
| 129 | + |
| 130 | +# top right |
| 131 | +distance_from_earth_val = make_value_text( |
| 132 | + anchor_point=(1.0, 0), anchored_position=(display.width - 6, 6) |
| 133 | +) |
| 134 | + |
| 135 | +# middle right |
| 136 | +distance_to_l2_val = make_value_text( |
| 137 | + anchor_point=(1.0, 0), anchored_position=(display.width - 6, 56) |
| 138 | +) |
| 139 | + |
| 140 | +# bottom right |
| 141 | +percent_complete_val = make_value_text( |
| 142 | + anchor_point=(1.0, 1.0), anchored_position=(display.width - 6, display.height - 6) |
| 143 | +) |
| 144 | + |
| 145 | +# middle left |
| 146 | +speed_val = make_value_text(anchor_point=(0, 0), anchored_position=(6, 56)) |
| 147 | + |
| 148 | +# bottom left |
| 149 | +timestamp_val = make_value_text( |
| 150 | + anchor_point=(0, 1.0), anchored_position=(6, display.height - 6), custom_font=False |
| 151 | +) |
| 152 | + |
| 153 | +# center |
| 154 | +temperature_val = make_value_text( |
| 155 | + anchor_point=(0.5, 0.0), anchored_position=(display.width // 2, 6) |
| 156 | +) |
| 157 | + |
| 158 | +main_group.append(elapsed_time_val) |
| 159 | +main_group.append(distance_from_earth_val) |
| 160 | +main_group.append(distance_to_l2_val) |
| 161 | +main_group.append(percent_complete_val) |
| 162 | +main_group.append(speed_val) |
| 163 | +main_group.append(timestamp_val) |
| 164 | + |
| 165 | +# label text initialization |
| 166 | + |
| 167 | +# top left |
| 168 | +elapsed_time_lbl = make_label_text( |
| 169 | + text="Elapsed", anchor_point=(0.0, 0), anchored_position=(6, 26) |
| 170 | +) |
| 171 | + |
| 172 | +# top right |
| 173 | +distance_from_earth_lbl = make_label_text( |
| 174 | + text="From Earth", anchor_point=(1.0, 0), anchored_position=(display.width - 6, 26) |
| 175 | +) |
| 176 | + |
| 177 | +# middle right |
| 178 | +distance_to_l2_lbl = make_label_text( |
| 179 | + text="To L2 Orbit", anchor_point=(1.0, 0), anchored_position=(display.width - 6, 76) |
| 180 | +) |
| 181 | + |
| 182 | +# center |
| 183 | +temperature_lbl = make_label_text( |
| 184 | + text="Temp", anchor_point=(0.5, 0.0), anchored_position=(display.width // 2, 54) |
| 185 | +) |
| 186 | + |
| 187 | +# middle left |
| 188 | +speed_lbl = make_label_text( |
| 189 | + text="Speed", anchor_point=(0, 0), anchored_position=(6, 76) |
| 190 | +) |
| 191 | + |
| 192 | +main_group.append(elapsed_time_lbl) |
| 193 | +main_group.append(distance_from_earth_lbl) |
| 194 | +main_group.append(distance_to_l2_lbl) |
| 195 | +main_group.append(temperature_lbl) |
| 196 | +main_group.append(speed_lbl) |
| 197 | +main_group.append(temperature_val) |
| 198 | + |
| 199 | +if not TEST_RUN: |
| 200 | + try: |
| 201 | + print("Fetching JSON data from %s" % JSON_GET_URL) |
| 202 | + response = requests.get(JSON_GET_URL, timeout=30) |
| 203 | + except (RuntimeError, OSError) as e: |
| 204 | + print(e) |
| 205 | + print("Failed GET request. Rebooting in 3 seconds...") |
| 206 | + time.sleep(3) |
| 207 | + supervisor.reload() |
| 208 | + |
| 209 | + print("-" * 40) |
| 210 | + text_data = response.text |
| 211 | + text_data = text_data.replace('"distanceEarthKm":', '"distanceEarthKm":"').replace( |
| 212 | + ',"launchElapsedTime"', '","launchElapsedTime"' |
| 213 | + ) |
| 214 | + |
| 215 | + json_data = json.loads(text_data) |
| 216 | + |
| 217 | + print("JSON Response: ", json_data) |
| 218 | + print("-" * 40) |
| 219 | + response.close() |
| 220 | +else: |
| 221 | + json_data = json.loads(FAKE_DATA) |
| 222 | + |
| 223 | +# update the labels to display values |
| 224 | +elapsed_time_val.text = json_data["launchElapsedTime"] |
| 225 | +distance_from_earth_val.text = "{}km".format(json_data["distanceEarthKm"]) |
| 226 | +distance_to_l2_val.text = "{}km".format(str(json_data["distanceL2Km"])) |
| 227 | +percent_complete_val.text = "{}%".format(str(json_data["percentageCompleted"])) |
| 228 | +speed_val.text = "{}km/s".format(str(json_data["speedKmS"])) |
| 229 | +timestamp_val.text = str(json_data["timestamp"]) |
| 230 | +temperature_val.text = "{}c | {}c\n{}c | {}c".format( |
| 231 | + json_data["tempC"]["tempWarmSide1C"], |
| 232 | + json_data["tempC"]["tempCoolSide1C"], |
| 233 | + json_data["tempC"]["tempWarmSide2C"], |
| 234 | + json_data["tempC"]["tempCoolSide2C"], |
| 235 | +) |
| 236 | + |
| 237 | +# show the group |
| 238 | +display.show(main_group) |
| 239 | + |
| 240 | +# refresh display |
| 241 | +try_refresh() |
| 242 | + |
| 243 | +# Create a an alarm that will trigger to wake us up |
| 244 | +time_alarm = alarm.time.TimeAlarm(monotonic_time=time.monotonic() + SLEEP_TIME) |
| 245 | + |
| 246 | +# Exit the program, and then deep sleep until the alarm wakes us. |
| 247 | +alarm.exit_and_deep_sleep_until_alarms(time_alarm) |
| 248 | +# Does not return, so we never get here. |
0 commit comments