Adafruit_Learning_System_Gu.../Clue_Shot_Timer/code.py
2023-03-17 15:49:28 -04:00

542 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
import microcontroller
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.show(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.show(None)
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(shots, 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(shots, shot_group)
disp.show(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):
selected = int(menu[0].y / 40) + 1
display.show(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.show(group)
display.refresh()
done = True
else:
page_ = menu
selected = int(page_[0].y / 40) + 1
length = len(page_) - 1
display.show(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.show(group)
display.refresh()
done = True
else:
page_ = menu
selected = int(page_[0].y / 40) + 1
length = len(page_) - 1
display.show(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.show(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.show(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.show(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.show(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.show(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()