Skip to content

Commit 3eb25f1

Browse files
authored
Add files via upload
1 parent 80fb2bc commit 3eb25f1

File tree

2 files changed

+191
-0
lines changed

2 files changed

+191
-0
lines changed
76.1 KB
Binary file not shown.
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
# SPDX-FileCopyrightText: 2019 Limor Fried for Adafruit Industries
2+
# SPDX-FileCopyrightText: 2026 Anne Barela for Adafruit Industries
3+
#
4+
# SPDX-License-Identifier: MIT
5+
"""
6+
This example will access the Space Devs astronaut API, display the number of
7+
humans currently in space and their names and agency on a PyPortal screen.
8+
"""
9+
import time
10+
import board
11+
import gc
12+
import json
13+
import supervisor
14+
from adafruit_pyportal import PyPortal
15+
from adafruit_bitmap_font import bitmap_font
16+
from adafruit_display_text.label import Label
17+
from adafruit_portalbase.network import HttpError
18+
19+
# Set up where we'll be fetching data from
20+
DATA_SOURCE = (
21+
"https://ll.thespacedevs.com/2.2.0/astronaut/"
22+
"?mode=list&in_space=true&limit=16&type=human"
23+
)
24+
25+
# Determine the current working directory
26+
cwd = ("/"+__file__).rsplit('/', 1)[0]
27+
28+
# Initialize the pyportal object - no json_path since we parse manually
29+
pyportal = PyPortal(url=DATA_SOURCE,
30+
status_neopixel=board.NEOPIXEL,
31+
default_bg=cwd+"/astronauts_background.bmp")
32+
gc.collect()
33+
34+
# Font for names and count label
35+
names_font = bitmap_font.load_font(cwd+"/fonts/Helvetica-Bold-10.bdf")
36+
names_font.load_glyphs(
37+
b'abcdefghjiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789- ()'
38+
)
39+
40+
# Display positions and colors
41+
names_position = (6, 140)
42+
names_color = 0xFF00FF
43+
count_color = 0xFFFFFF
44+
count_position = (180, 108)
45+
46+
# Layout constants
47+
screen_width = 320
48+
col_gap = 6
49+
char_width = 7 # slightly wider estimate for Helvetica Bold proportional font
50+
font_height = 12
51+
max_rows = 8
52+
53+
# Agency name to abbreviation lookup
54+
AGENCY_ABBREV = {
55+
"National Aeronautics and Space Administration": "USA",
56+
"Russian Federal Space Agency (ROSCOSMOS)": "RUS",
57+
"European Space Agency": "ESA",
58+
"China National Space Administration": "CHN",
59+
"Japan Aerospace Exploration Agency": "JAX",
60+
"Canadian Space Agency": "CSA",
61+
"SpaceX": "SpX",
62+
"Boeing": "Boe",
63+
}
64+
65+
gc.collect()
66+
67+
def make_count_label(count):
68+
lbl = Label(names_font, text="{} People in Space".format(count))
69+
lbl.color = count_color
70+
lbl.x = count_position[0]
71+
lbl.y = count_position[1]
72+
return lbl
73+
74+
def fetch_astronauts():
75+
"""Fetch and parse astronaut data, filtering out non-humans."""
76+
gc.collect()
77+
raw = pyportal.fetch()
78+
if isinstance(raw, str):
79+
data = json.loads(raw)
80+
else:
81+
data = raw
82+
astronauts = [
83+
a for a in data["results"]
84+
if a.get("type", {}).get("name") != "Non-Human"
85+
]
86+
count = len(astronauts)
87+
del data
88+
del raw
89+
gc.collect()
90+
return count, astronauts
91+
92+
def build_name_list(astronauts):
93+
"""Build display strings from astronaut records."""
94+
result = []
95+
for a in astronauts:
96+
agency_full = a.get("agency", "")
97+
abbrev = AGENCY_ABBREV.get(agency_full, agency_full[:4])
98+
result.append("%s (%s)" % (a["name"], abbrev))
99+
return result
100+
101+
def calculate_columns(names):
102+
"""Calculate column widths and positions based on name lengths.
103+
Col1 names are truncated to fit; col2 start is fixed from col1 width.
104+
"""
105+
col1_x = names_position[0]
106+
col2_names = names[max_rows:]
107+
108+
col2_max_px = (
109+
max(len(n) * char_width for n in col2_names) if col2_names else 0
110+
)
111+
112+
total_available = screen_width - col1_x - col_gap
113+
114+
if col2_names:
115+
# Allocate col2 what it needs, give the rest to col1
116+
col2_width = min(col2_max_px, total_available // 2)
117+
col2_width = max(col2_width, 0)
118+
col1_width = total_available - col2_width - col_gap
119+
col2_x = col1_x + col1_width + col_gap
120+
else:
121+
col1_width = total_available
122+
col2_x = screen_width # unused
123+
124+
# col1 max_chars is the hard truncation limit
125+
max_chars_col1 = col1_width // char_width
126+
max_chars_col2 = col2_width // char_width if col2_names else 0
127+
128+
return col2_x, max_chars_col1, max_chars_col2, bool(col2_names)
129+
130+
def display_astronauts(count, names):
131+
"""Create and append all labels, return list for later cleanup."""
132+
labels = []
133+
134+
# Count label
135+
lbl = make_count_label(count)
136+
pyportal.root_group.append(lbl)
137+
labels.append(lbl)
138+
139+
col2_x, max_chars_col1, max_chars_col2, has_col2 = (
140+
calculate_columns(names)
141+
)
142+
y_start = names_position[1]
143+
144+
for i, name in enumerate(names):
145+
in_col2 = i >= max_rows
146+
max_chars = max_chars_col2 if in_col2 else max_chars_col1
147+
if len(name) > max_chars:
148+
name = name[:max_chars - 1] + "~"
149+
lbl = Label(names_font, text=name)
150+
lbl.color = names_color
151+
lbl.x = col2_x if in_col2 else names_position[0]
152+
lbl.y = (
153+
y_start + (i - max_rows) * font_height
154+
if in_col2
155+
else y_start + i * font_height
156+
)
157+
pyportal.root_group.append(lbl)
158+
labels.append(lbl)
159+
160+
return labels
161+
162+
def clear_labels(labels):
163+
"""Remove all labels from the display group."""
164+
for lbl in labels:
165+
pyportal.root_group.pop()
166+
167+
168+
# Main loop
169+
while True:
170+
try:
171+
count, astronauts = fetch_astronauts()
172+
names = build_name_list(astronauts)
173+
del astronauts
174+
gc.collect()
175+
print("People in space:", count)
176+
for n in names:
177+
print(" ", n)
178+
except HttpError as e:
179+
print("Rate limited, backing off! -", e)
180+
time.sleep(300) # wait 5 minutes before retrying
181+
supervisor.reload()
182+
except (RuntimeError, KeyError, ValueError) as e:
183+
print("Fetch error, retrying! -", e)
184+
time.sleep(30)
185+
supervisor.reload()
186+
187+
labels = display_astronauts(count, names)
188+
time.sleep(3600) # display for 1 hour - data changes infrequently
189+
clear_labels(labels)
190+
gc.collect()
191+
supervisor.reload()

0 commit comments

Comments
 (0)