Compare commits

...

4 Commits

2 changed files with 213 additions and 7 deletions

View File

@ -1,6 +1,36 @@
import time import time
import board import board
import neopixel import neopixel
import adafruit_datetime as datetime
from adafruit_seesaw import seesaw, rotaryio, digitalio
import busio
####
# i2c bus setup
SDA = board.GP0
SCL = board.GP1
i2c = busio.I2C(SCL, SDA)
# END i2c bus setup
####
####
# Rotary Encoder setup
seesaw = seesaw.Seesaw(i2c, 0x36)
seesaw.pin_mode(24, seesaw.INPUT_PULLUP)
button = digitalio.DigitalIO(seesaw, 24)
button_held = False
encoder = rotaryio.IncrementalEncoder(seesaw)
last_position = -1
# END Rotary Encoder setup
####
#### ####
@ -34,6 +64,13 @@ pixels.show()
# END Neopixel setup # END Neopixel setup
#### ####
# Use datetime.timedelta to convert an int of seconds to a
# string with the format MM:SS
def prettytime(seconds):
return str(datetime.timedelta(seconds=abs(seconds)))[2:]
# Set the color on a single neopixel based on colormode and # Set the color on a single neopixel based on colormode and
# whether yellowtime or redtime has been reached. Calling # whether yellowtime or redtime has been reached. Calling
# logic should iterate over every neopixel ID that should # logic should iterate over every neopixel ID that should
@ -44,7 +81,8 @@ pixels.show()
# red and yellow are both set to True depends on how the # red and yellow are both set to True depends on how the
# colormode is configured. # colormode is configured.
def colorizer(pxnum, colormode="fill", yellow=False, red=False): def colorizer(pxnum, colormode="fill", yellow=False, red=False):
# Every pixel from lowest to currently highest # Fill every pixel from lowest to currently highest with
# the current color.
if colormode == "fill": if colormode == "fill":
if red: if red:
pixels[pxnum] = RED pixels[pxnum] = RED
@ -52,6 +90,8 @@ def colorizer(pxnum, colormode="fill", yellow=False, red=False):
pixels[pxnum] = YELLOW pixels[pxnum] = YELLOW
else: else:
pixels[pxnum] = GREEN pixels[pxnum] = GREEN
# Only fill the next pixel with the current color if it's
# currently BLANK
elif colormode == "candybar": elif colormode == "candybar":
if pixels[pxnum] == BLANK: if pixels[pxnum] == BLANK:
if red: if red:
@ -66,6 +106,7 @@ def colorizer(pxnum, colormode="fill", yellow=False, red=False):
# Invalid colormodes end up here # Invalid colormodes end up here
raise Exception("Invalid colormode: " + colormode) raise Exception("Invalid colormode: " + colormode)
# Count down from the given total seconds, using the chosen # Count down from the given total seconds, using the chosen
# colormode (how the colors are filled into each pixel), # colormode (how the colors are filled into each pixel),
# and the given yellowtime (seconds before timer has elapsed # and the given yellowtime (seconds before timer has elapsed
@ -111,7 +152,11 @@ def countdown(
# Do update stuff # Do update stuff
# Calculate the current position # Calculate the current position.
# Takes the percentage of time elapsed, multiplied with
# the total numbers of pixels, and rounded to the nearest
# decimal. This results in a number of pixels proportional
# to the elapsed time
current_position = round(num_pixels * ((seconds - current_time) / seconds)) current_position = round(num_pixels * ((seconds - current_time) / seconds))
# Catch a couple of special cases # Catch a couple of special cases
@ -123,7 +168,7 @@ def countdown(
# If current_position calls for *all* # If current_position calls for *all*
# pixels to be lit, and the timer # pixels to be lit, and the timer
# hasn't expired yet, don't do anything. # hasn't expired yet, don't do anything.
# This should delay the last pixel from # This will delay the last pixel from
# lighting until the timer has fully elapsed # lighting until the timer has fully elapsed
pass pass
else: else:
@ -131,6 +176,10 @@ def countdown(
# based on the elapsed time # based on the elapsed time
for pixel in range(current_position): for pixel in range(current_position):
# Set pixel color stuff # Set pixel color stuff
# If current_time has gone negative, don't
# change any pixels, just keep counting for
# user feedback
if current_time < 0: if current_time < 0:
pass pass
elif current_time <= redtime: elif current_time <= redtime:
@ -140,20 +189,144 @@ def countdown(
else: else:
colorizer(pixel, colormode) colorizer(pixel, colormode)
# Display the result IRL # All the pixels have now been set based on the
# specified colormode, now display the result IRL.
pixels.show() pixels.show()
# Increment the elapsed time variable # Increment the elapsed time variable
current_time -= update_interval current_time -= update_interval
# Massage the current_time seconds count into human-readable minutes:seconds # Add a negative sign to the output when current_time is negative.
display_time = str(datetime.timedelta(seconds=abs(current_time)))[3:] # prettytime() puts the given value through abs() because the way
# datetime.timedelta() represents negative values is kind of a PITA
# to deal with.
if current_time < 0: if current_time < 0:
display_time_sign = "-" display_time_sign = "-"
else: else:
display_time_sign = " " display_time_sign = " "
print("current time: " + display_time_sign + display_time) # Give the user feedback
# (this string will eventually go to a ssd1306 OLED display via
# displayio, but just put it on the terminal output for now)
print("current time: " + display_time_sign + prettytime(current_time))
# If the button is currently being pressed
if not button.value:
# We are paused
pause = True
# There's no long-press option before pausing, so button should
# no longer be down.
button_held = False
print("Timer Paused")
# Keep looping here as long as we're paused
while pause:
# If the button is being pressed
if not button.value and not button_held:
# The button is being held down
button_held = True
# Record when the button started being pressed
button_held_timer = time.monotonic()
# Keep looping while the button is down
while not button.value:
# Continually check if button_hold_delay has elapsed
# while the button is still down
if time.monotonic() - button_held_timer > button_hold_delay:
# Button should no longer be down
button_held = False
# Reset the long-press timer as a debounce method
button_held_timer = time.monotonic()
# No longer in pause mode
pause = False
# Give the user feedback
print("Timer Reset")
# Hang around for half a second for debounce
time.sleep(0.5)
# Return from countdown() back to the main loop
return
# If the button is not being pressed and button_held is True.
# I don't understand why, but it gets hung up here sometimes,
# requiring the user to press the button multiple times to
# resume the timer again...
if button.value and button_held:
# Flip it back to False
button_held = False
# If the button is being short-pressed when it previously wasn't
# being pressed, and we are paused.
if not button.value and not button_held and pause:
# Flip it back to False
button_held = False
# Exiting pause
pause = False
# User Feedback
print("Timer Resumed")
# Hang around for half a second for debounce
time.sleep(0.5)
# Hard-coded initial value. (will replace with stored value later)
set_time_orig = 120
set_time = set_time_orig
# How many seconds should be added to or subtracted from set_time
# for every encoder click
set_time_step = 60
# How long is a long-press in seconds
button_hold_delay = 2
# Main loop
while True:
# Negate the position to make clockwise rotation positive
position = -encoder.position
# If the encoder position has changed since last iteration
if position != last_position:
# If last_position is set to -1, assume it's just been
# initialized, so don't adjust anything
if last_position == -1:
pass
# Clockwise turn increases set_time by set_time_step
elif position > last_position:
set_time += set_time_step
# Counter-clockwise turn decreases set_time by set_time_step
# only until 0
elif set_time > 0:
set_time -= set_time_step
# Update the position tracker
last_position = position
# User feedback
print("Current set time: " + prettytime(set_time))
# If the button is being pressed, and button_held is False
if not button.value and not button_held:
# Button is being pressed
button_held = True
# Capture the current monotonic clock time to later detect a long-press
button_held_timer = time.monotonic()
# Keep looping while the button is down
while not button.value:
# Continually check if button_hold_delay has elapsed
# while the button is still down
if time.monotonic() - button_held_timer > button_hold_delay:
# Reset the set_time to the value of set_time_orig
# (eventually, set_time_orig will be read from persistent config)
set_time = set_time_orig
# Give the user feedback
print("Time reset to: " + prettytime(set_time))
# Button should no longer be down
button_held = False
# Reset the long-press timer as a debounce method
button_held_timer = time.monotonic()
# If the button is not being pressed, and it previously was being pressed
if button.value and button_held:
# Button is no longer being pressed
button_held = False
# Start the countdown using the configured set_time
countdown(set_time)
# Once the timer has been reset, re-init last_position.
# In effect, this will display the set_time to the user again
last_position = -1
# Turn off all pixels
pixels.fill(BLANK)
pixels.show()

View File

@ -0,0 +1,33 @@
# Ref: https://learn.adafruit.com/adafruit-i2c-qt-rotary-encoder/python-circuitpython
import board
from adafruit_seesaw import seesaw, rotaryio, digitalio
import busio
SDA = board.GP0
SCL = board.GP1
i2c = busio.I2C(SCL, SDA)
seesaw = seesaw.Seesaw(i2c, 0x36)
seesaw.pin_mode(24, seesaw.INPUT_PULLUP)
button = digitalio.DigitalIO(seesaw, 24)
button_held = False
encoder = rotaryio.IncrementalEncoder(seesaw)
last_position = None
while True:
# negate the position to make clockwise rotation positive
position = -encoder.position
if position != last_position:
last_position = position
print("Position: {}".format(position))
if not button.value and not button_held:
button_held = True
print("Button pressed")
if button.value and button_held:
button_held = False
print("Button released")