Adafruit_Learning_System_Gu.../CLUE/Clue_Shot_Timer/code.py
Anne Barela dc43693f17 Copy CLUE projects to new CLUE subdirectory
To "clean up" the Learn System repo, we need to move groups of guides into subdirectories. This PR duplicates the CLUE guides into the CLUE subdirectory so guides may be changed prior to deleting redundant project repos to make more space.
2025-02-24 11:21:29 -06:00

556 lines
17 KiB
Python

# SPDX-FileCopyrightText: 2023 Eva Herrada for Adafruit Industries
#
# SPDX-License-Identifier: MIT
import array
import random
import time
import gc
import simpleio
import audiobusio
import displayio
from adafruit_display_text import label
from adafruit_bitmap_font import bitmap_font
from adafruit_display_shapes.rect import Rect
import board
import digitalio
settings = {}
with open("/settings.txt", "r") as F:
for line in F:
k, v = line.replace("\n", "").split(",")
print(k, v)
settings[k] = v
MODE = settings["mode"]
if settings["delay"] == "RNDM":
DELAY_TIME = settings["delay"]
else:
DELAY_TIME = int(settings["delay"])
PB = float(settings["pb"])
PAR = float(settings["par"])
SENSITIVITY = int(settings["sensitivity"])
gc.enable()
DEAD_TIME_DELAY = 0.11
arial18 = bitmap_font.load_font("/fonts/Arial-18.pcf")
arialb24 = bitmap_font.load_font("/fonts/Arial-Bold-24.pcf")
lato74 = bitmap_font.load_font("/fonts/Lato-Regular-74.pcf")
display = board.DISPLAY
display.auto_refresh = False
group = displayio.Group()
button_a = digitalio.DigitalInOut(board.BUTTON_A)
button_a.switch_to_input(pull=digitalio.Pull.UP)
button_b = digitalio.DigitalInOut(board.BUTTON_B)
button_b.switch_to_input(pull=digitalio.Pull.UP)
main_time = label.Label(
font=lato74,
anchored_position=(120, 40),
text="00.00",
color=0xFFFFFF,
anchor_point=(0.5, 0.5),
)
group.append(main_time)
shot_num = label.Label(
font=arialb24,
anchored_position=(120, 80),
text="#0 SPL 00.00",
anchor_point=(0.5, 0),
)
group.append(shot_num)
first = label.Label(
font=arialb24,
anchored_position=(120, 120),
text="1st: 00.00",
anchor_point=(0.5, 0),
)
group.append(first)
delay = label.Label(
font=arialb24,
anchored_position=(120, 160),
text=f"Delay: {DELAY_TIME}",
anchor_point=(0.5, 0),
)
group.append(delay)
sens = label.Label(font=arialb24, x=15, y=220, text=f"{SENSITIVITY}")
group.append(sens)
if MODE == "PB":
mode_text = f"{MODE} {PB:05.2f}"
elif MODE == "Par":
mode_text = f"{MODE} {PAR:05.2f}"
else:
mode_text = MODE
mode_label = label.Label(
font=arialb24, anchored_position=(225, 210), text=mode_text, anchor_point=(1, 0)
)
group.append(mode_label)
def normalized_rms(values):
"""Gets the normalized RMS of the mic samples"""
minbuf = int(sum(values) / len(values))
samples_sum = sum(float(sample - minbuf) * (sample - minbuf) for sample in values)
return (samples_sum / len(values)) ** 0.5
def picker(current):
"""Displays screen allowing user to set a time"""
pick[0].text, pick[1].text, pick[2].text, pick[3].text = list(
current.replace(".", "")
)
display.root_group = pick
time.sleep(0.2)
index = 0
while True:
if not button_b.value:
pick[index].background_color = None
pick[index].color = 0xFFFFFF
index += 1
if index == 4:
index = 0
pick[index].background_color = 0xFFFFFF
pick[index].color = 0x000000
started = time.monotonic()
while not button_b.value:
if time.monotonic() - started > 1:
pick[0].color = 0x000000
pick[0].background_color = 0xFFFFFF
pick[1].color = 0xFFFFFF
pick[1].background_color = None
pick[2].color = 0xFFFFFF
pick[2].background_color = None
pick[3].color = 0xFFFFFF
pick[3].background_color = None
return float(
f"{pick[0].text}{pick[1].text}.{pick[2].text}{pick[3].text}"
)
if not button_a.value:
pick[index].text = str(int(pick[index].text) + 1)[-1]
started = time.monotonic()
while not button_a.value:
if time.monotonic() - started > 1:
return current
display.refresh()
display.root_group = displayio.CIRCUITPYTHON_TERMINAL
def rect_maker(shots):
rects = displayio.Group()
for i in range(len(shots)):
if i == 10:
break
rectangle = Rect(x=0, y=24 + (i * 24), width=240, height=1, fill=0xFFFFFF)
rects.append(rectangle)
return rects
def shot_label_maker(grp):
txt = ""
for i, j in enumerate(shot_list):
if i == 0:
split = j
else:
split = j - shot_list[i - 1]
txt = txt + f"{i+1:02}\t{j:05.2f}\t{split:05.2f}\n"
grp.append(
label.Label(
font=arial18,
anchored_position=(120, 3),
text=txt[:-1],
color=0xFFFFFF,
line_spacing=0.82,
anchor_point=(0.5, 0),
)
)
gc.collect()
return grp
def show_shot_list(shots, disp):
done = False
shot_group = rect_maker(shots)
shot_group = shot_label_maker(shot_group)
disp.root_group = shot_group
tracker = 10
while True:
if not button_b.value:
started = time.monotonic()
while not button_b.value:
if time.monotonic() - started > 1:
done = True
break
if tracker < len(shots) and not done:
shot_group[10].y -= 24
tracker += 1
if not button_a.value:
started = time.monotonic()
while not button_a.value:
if time.monotonic() - started > 1:
done = True
break
if tracker > 10 and not done:
shot_group[10].y += 24
tracker -= 1
if done:
break
disp.refresh()
shot_group = None
gc.collect()
def menu_mode(
mode, delay_time, sensitivity_, pb, par, length_, submenus_
): # pylint: disable=too-many-branches,too-many-statements
selected = int(menu[0].y / 40) + 1
display.root_group = menu
display.refresh()
page_ = menu
while not button_a.value:
pass
done = False
while not done:
if not button_a.value and selected < length_:
started = time.monotonic()
while not button_a.value:
if time.monotonic() - started > 1:
if page_ == menu:
display.root_group = group
display.refresh()
done = True
else:
page_ = menu
selected = int(page_[0].y / 40) + 1
length_ = len(page_) - 1
display.root_group = page_
submenus_ = main_menu_opts
display.refresh()
break
else:
if not done:
rgb = page_[selected].color
color = (
((255 - ((rgb >> 16) & 0xFF)) << 16)
+ ((255 - ((rgb >> 8) & 0xFF)) << 8)
+ (255 - (rgb & 0xFF))
)
page_[selected].color = color
page_[0].y += 40
selected += 1
rgb = page_[selected].color
color = (
((255 - ((rgb >> 16) & 0xFF)) << 16)
+ ((255 - ((rgb >> 8) & 0xFF)) << 8)
+ (255 - (rgb & 0xFF))
)
page_[selected].color = color
while not button_a.value:
pass
if not button_a.value and selected == length_ and not done:
started = time.monotonic()
while not button_a.value:
if time.monotonic() - started > 1:
if page_ == menu:
display.root_group = group
display.refresh()
done = True
else:
page_ = menu
selected = int(page_[0].y / 40) + 1
length_ = len(page_) - 1
display.root_group = page_
submenus_ = main_menu_opts
display.refresh()
break
else:
if not done:
rgb = page_[selected].color
color = (
((255 - ((rgb >> 16) & 0xFF)) << 16)
+ ((255 - ((rgb >> 8) & 0xFF)) << 8)
+ (255 - (rgb & 0xFF))
)
page_[selected].color = color
page_[0].y = 0
selected = 1
rgb = page_[selected].color
color = (
((255 - ((rgb >> 16) & 0xFF)) << 16)
+ ((255 - ((rgb >> 8) & 0xFF)) << 8)
+ (255 - (rgb & 0xFF))
)
page_[selected].color = color
while not button_a.value:
pass
if not button_b.value:
if isinstance(submenus_[1], list):
if submenus_[0] == mode:
mode = submenus_[1][selected - 1]
submenus_[0] = mode
if mode == "PB":
pb = picker(f"{PB:05.2f}")
mode_label.text = f"{mode} {pb}"
page_[selected].text = mode_label.text
display.root_group = page_
display.refresh()
elif mode == "Par":
par = picker(f"{par:05.2f}")
mode_label.text = f"{mode} {par}"
page_[selected].text = mode_label.text
display.root_group = page_
display.refresh()
else:
mode_label.text = mode
if submenus_[0] == delay_time and len(submenus_[1]) == 5:
delay_time = submenus_[1][selected - 1]
submenus_[0] = delay_time
delay.text = f"Delay: {delay_time}"
if submenus_[0] == sensitivity_ and len(submenus_[1]) == 6:
sensitivity_ = submenus_[1][selected - 1]
submenus_[0] = sensitivity_
sens.text = f"{sensitivity_}"
for i in page_:
i.color = 0xFFFFFF
page_[selected].color = 0x00FF00
else:
page_ = submenus_[selected - 1]
submenus_ = page_opts[selected - 1]
selected = int(page_[0].y / 40) + 1
length_ = len(page_) - 1
display.root_group = page_
while not button_b.value:
pass
display.refresh()
return mode, delay_time, sensitivity_, pb, par
def label_maker(txt, grp, font, x, y, x_step=0, y_step=0, anchor=None, padding=0):
for count, t in enumerate(txt):
x_pos = x + (count * x_step)
y_pos = y + (count * y_step)
if anchor:
grp.append(
label.Label(
font,
text=t,
anchored_position=(x_pos, y_pos),
color=0xFFFFFF,
padding_top=padding,
anchor_point=anchor,
)
)
else:
grp.append(
label.Label(
font, text=t, x=x_pos, y=y_pos, color=0xFFFFFF, padding_top=padding
)
)
return grp
mode_opts = [MODE, ["Default", "PB", "Par"]]
delay_opts = [DELAY_TIME, [0, 1, 3, 5, "RNDM"]]
sensitivity_opts = [SENSITIVITY, [1, 2, 3, 4, 5, 6]]
# Number picker page
pick = displayio.Group()
pick = label_maker(
["0", "0"], pick, lato74, 40, 120, x_step=50, anchor=(0.5, 0.5), padding=8
)
pick = label_maker(
["0", "0"], pick, lato74, 150, 120, x_step=50, anchor=(0.5, 0.5), padding=8
)
pick[0].color = 0x000000
pick[0].background_color = 0xFFFFFF
dot = label.Label(
lato74,
text=".",
color=0xFFFFFF,
anchor_point=(0.5, 0.5),
anchored_position=(120, 132),
)
pick.append(dot)
# Main menu page
menu = displayio.Group()
rect = Rect(0, 0, 240, 40, fill=0xFFFFFF)
menu.append(rect)
menu = label_maker(["Mode", "Delay", "Sensitivity"], menu, arialb24, 10, 20, y_step=40)
menu[1].color = 0x000000
# Mode menu page
mode_page = displayio.Group()
select = mode_opts[1].index(MODE)
rect = Rect(0, select * 40, 240, 40, fill=0xFFFFFF)
mode_page.append(rect)
mode_page = label_maker(
["Default", f"PB {PB}", f"Par {PAR}"], mode_page, arialb24, 10, 20, y_step=40
)
mode_page[select + 1].color = 0x00FF00
# Delay menu page
delay_page = displayio.Group()
select = delay_opts[1].index(DELAY_TIME)
rect = Rect(0, select * 40, 240, 40, fill=0xFFFFFF)
delay_page.append(rect)
delay_page = label_maker(
["0s", "1s", "3s", "5s", "Random"], delay_page, arialb24, 10, 20, y_step=40
)
delay_page[select + 1].color = 0x00FF00
# Sensitivity menu page
sensitivity_page = displayio.Group()
select = sensitivity_opts[1].index(SENSITIVITY)
rect = Rect(0, select * 40, 240, 40, fill=0xFFFFFF)
sensitivity_page.append(rect)
sensitivity_page = label_maker(
["1", "2", "3", "4", "5", "6"], sensitivity_page, arialb24, 10, 20, y_step=40
)
sensitivity_page[select + 1].color = 0x00FF00
main_menu_opts = [mode_page, delay_page, sensitivity_page]
page_opts = [mode_opts, delay_opts, sensitivity_opts]
submenus = main_menu_opts
page = menu
length = len(page) - 1
mic = audiobusio.PDMIn(
board.MICROPHONE_CLOCK,
board.MICROPHONE_DATA,
sample_rate=16000,
bit_depth=16,
)
sensitivity_settings = [8000, 10000, 15000, 20000, 25000, 30000]
display.root_group = group
display.refresh()
sensitivity = sensitivity_settings[SENSITIVITY - 1]
while True:
if not button_b.value:
SHOTS = 0
samples = array.array("H", [0] * 5)
if DELAY_TIME == "RNDM":
dly = round(random.uniform(1, 10), 2)
time.sleep(dly)
else:
time.sleep(DELAY_TIME)
start = time.monotonic()
while not button_b.value:
pass
shot_list = []
simpleio.tone(board.SPEAKER, 3500, duration=0.2)
time.sleep(0.05)
if MODE == "Par":
shot_time = round(time.monotonic() - start, 2)
while shot_time < PAR:
shot_time = min(round(time.monotonic() - start, 2), PAR)
main_time.text = f"{shot_time:05.2f}"
display.refresh()
simpleio.tone(board.SPEAKER, 3500, duration=0.2)
else:
if MODE == "PB":
main_time.color = 0x00FF00
display.refresh()
while button_a.value and button_b.value:
mic.record(samples, len(samples))
magnitude = normalized_rms(samples)
if magnitude > sensitivity:
SHOTS += 1
print("SHOT")
shot_time = round(time.monotonic() - start, 2)
if len(shot_list) != 0:
shot_num.text = (
f"#{SHOTS} SPL {shot_time - shot_list[-1]:05.2f}"
)
else:
shot_num.text = f"#{SHOTS} SPL {shot_time:05.2f}"
main_time.text = f"{shot_time:05.2f}"
if MODE == "PB" and shot_time > PB:
main_time.color = 0xFF0000
shot_list.append(shot_time)
first.text = f"1st: {shot_list[0]:05.2f}"
time.sleep(DEAD_TIME_DELAY)
display.refresh()
print(
(
magnitude,
SHOTS,
)
)
gc.collect()
if not button_b.value:
show_shot_list(shot_list, display)
display.root_group = group
display.refresh()
while not button_b.value or not button_a.value:
pass
if not button_a.value:
start = time.monotonic()
while not button_a.value:
if time.monotonic() - start > 1:
MODE, DELAY_TIME, SENSITIVITY, PB, PAR = menu_mode(
MODE, DELAY_TIME, SENSITIVITY, PB, PAR, length, submenus
)
sensitivity = sensitivity_settings[SENSITIVITY - 1]
try:
with open("/settings.txt", "w") as F:
F.write(f"mode,{MODE}\n")
F.write(f"delay,{DELAY_TIME}\n")
F.write(f"par,{PAR}\n")
F.write(f"pb,{PB}\n")
F.write(f"sensitivity,{SENSITIVITY}")
except OSError as e: # Typically when the filesystem isn't writeable...
print("Filesystem is not writeable")
break
else:
main_time.color = 0xFFFFFF
main_time.text = "00.00"
shot_num.text = "#0 SPL 00.00"
first.text = "1st: 00.00"
display.refresh()
gc.collect()