Merge pull request #3054 from adafruit/led_clock

updating matrix clock code
This commit is contained in:
Liz 2025-06-06 13:02:55 -04:00 committed by GitHub
commit 2b0f7263de
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,7 +1,7 @@
# SPDX-FileCopyrightText: 2025 Liz Clark for Adafruit Industries # SPDX-FileCopyrightText: 2025 Liz Clark for Adafruit Industries
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
'''LED Matrix Alarm Clock''' '''LED Matrix Alarm Clock with Scrolling Wake Up Text and Winking Eyes'''
import os import os
import ssl import ssl
import time import time
@ -21,20 +21,22 @@ from rainbowio import colorwheel
from adafruit_seesaw import digitalio, rotaryio, seesaw from adafruit_seesaw import digitalio, rotaryio, seesaw
from adafruit_debouncer import Button from adafruit_debouncer import Button
timezone = -4 # your timezone offset # Configuration
alarm_hour = 12 # hour is 24 hour for alarm to denote am/pm timezone = -4
alarm_min = 00 # minutes alarm_hour = 11
alarm_volume = 1 # float 0.0 to 1.0 alarm_min = 36
hour_12 = True # 12 hour or 24 hour time alarm_volume = .2
hour_12 = True
no_alarm_plz = False no_alarm_plz = False
BRIGHTNESS = 128 # led brightness (0-255) BRIGHTNESS_DAY = 200
BRIGHTNESS_NIGHT = 50
# I2S pins for Audio BFF # I2S pins for Audio BFF
DATA = board.A0 DATA = board.A0
LRCLK = board.A1 LRCLK = board.A1
BCLK = board.A2 BCLK = board.A2
# connect to WIFI # Connect to WIFI
wifi.radio.connect(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")) wifi.radio.connect(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD"))
print(f"Connected to {os.getenv('CIRCUITPY_WIFI_SSID')}") print(f"Connected to {os.getenv('CIRCUITPY_WIFI_SSID')}")
@ -42,345 +44,425 @@ context = ssl.create_default_context()
pool = socketpool.SocketPool(wifi.radio) pool = socketpool.SocketPool(wifi.radio)
ntp = adafruit_ntp.NTP(pool, tz_offset=timezone, cache_seconds=3600) ntp = adafruit_ntp.NTP(pool, tz_offset=timezone, cache_seconds=3600)
# Initialize I2C # Initialize I2C and displays
i2c = board.STEMMA_I2C() i2c = board.STEMMA_I2C()
# Initialize both matrix displays
matrix1 = Adafruit_RGBMatrixQT(i2c, address=0x30, allocate=adafruit_is31fl3741.PREFER_BUFFER) matrix1 = Adafruit_RGBMatrixQT(i2c, address=0x30, allocate=adafruit_is31fl3741.PREFER_BUFFER)
matrix2 = Adafruit_RGBMatrixQT(i2c, address=0x31, allocate=adafruit_is31fl3741.PREFER_BUFFER) matrix2 = Adafruit_RGBMatrixQT(i2c, address=0x31, allocate=adafruit_is31fl3741.PREFER_BUFFER)
matrix1.global_current = 0x05
matrix2.global_current = 0x05
matrix1.set_led_scaling(BRIGHTNESS)
matrix2.set_led_scaling(BRIGHTNESS)
matrix1.enable = True
matrix2.enable = True
matrix1.fill(0x000000)
matrix2.fill(0x000000)
matrix1.show()
matrix2.show()
# Configure displays
for m in [matrix1, matrix2]:
m.global_current = 0x05
m.set_led_scaling(BRIGHTNESS_DAY)
m.enable = True
m.fill(0x000000)
m.show()
# Audio setup
audio = audiobusio.I2SOut(BCLK, LRCLK, DATA) audio = audiobusio.I2SOut(BCLK, LRCLK, DATA)
wavs = [] wavs = ["/"+f for f in os.listdir('/') if f.lower().endswith('.wav') and not f.startswith('.')]
for filename in os.listdir('/'):
if filename.lower().endswith('.wav') and not filename.startswith('.'):
wavs.append("/"+filename)
mixer = audiomixer.Mixer(voice_count=1, sample_rate=22050, channel_count=1, mixer = audiomixer.Mixer(voice_count=1, sample_rate=22050, channel_count=1,
bits_per_sample=16, samples_signed=True, buffer_size=32768) bits_per_sample=16, samples_signed=True, buffer_size=32768)
mixer.voice[0].level = alarm_volume mixer.voice[0].level = alarm_volume
wav_filename = wavs[random.randint(0, (len(wavs))-1)]
wav_file = open(wav_filename, "rb")
audio.play(mixer) audio.play(mixer)
def open_audio(): def open_audio():
n = wavs[random.randint(0, (len(wavs))-1)] """Open a random WAV file"""
f = open(n, "rb") filename = random.choice(wavs)
w = audiocore.WaveFile(f) return audiocore.WaveFile(open(filename, "rb"))
return w
def update_brightness(hour_24):
"""Update LED brightness based on time of day"""
brightness = BRIGHTNESS_NIGHT if (hour_24 >= 20 or hour_24 < 7) else BRIGHTNESS_DAY
matrix1.set_led_scaling(brightness)
matrix2.set_led_scaling(brightness)
return brightness
# Seesaw setup for encoder and button
seesaw = seesaw.Seesaw(i2c, addr=0x36) seesaw = seesaw.Seesaw(i2c, addr=0x36)
seesaw.pin_mode(24, seesaw.INPUT_PULLUP) seesaw.pin_mode(24, seesaw.INPUT_PULLUP)
ss_pin = digitalio.DigitalIO(seesaw, 24) button = Button(digitalio.DigitalIO(seesaw, 24), long_duration_ms=1000)
button = Button(ss_pin, long_duration_ms=1000)
button_held = False
encoder = rotaryio.IncrementalEncoder(seesaw) encoder = rotaryio.IncrementalEncoder(seesaw)
last_position = 0 last_position = 0
# Simple 5x7 font bitmap patterns for digits 0-9 # Font definitions
FONT_5X7 = { FONT_5X7 = {
'0': [ '0': [0b01110, 0b10001, 0b10011, 0b10101, 0b11001, 0b10001, 0b01110],
0b01110, '1': [0b00100, 0b01100, 0b00100, 0b00100, 0b00100, 0b00100, 0b01110],
0b10001, '2': [0b01110, 0b10001, 0b00001, 0b00010, 0b00100, 0b01000, 0b11111],
0b10011, '3': [0b11111, 0b00010, 0b00100, 0b00010, 0b00001, 0b10001, 0b01110],
0b10101, '4': [0b00010, 0b00110, 0b01010, 0b10010, 0b11111, 0b00010, 0b00010],
0b11001, '5': [0b11111, 0b10000, 0b11110, 0b00001, 0b00001, 0b10001, 0b01110],
0b10001, '6': [0b00110, 0b01000, 0b10000, 0b11110, 0b10001, 0b10001, 0b01110],
0b01110 '7': [0b11111, 0b00001, 0b00010, 0b00100, 0b01000, 0b01000, 0b01000],
], '8': [0b01110, 0b10001, 0b10001, 0b01110, 0b10001, 0b10001, 0b01110],
'1': [ '9': [0b01110, 0b10001, 0b10001, 0b01111, 0b00001, 0b00010, 0b01100],
0b00100, ' ': [0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000],
0b01100, 'W': [0b10001, 0b10001, 0b10001, 0b10101, 0b10101, 0b11011, 0b10001],
0b00100, 'A': [0b01110, 0b10001, 0b10001, 0b11111, 0b10001, 0b10001, 0b10001],
0b00100, 'K': [0b10001, 0b10010, 0b10100, 0b11000, 0b10100, 0b10010, 0b10001],
0b00100, 'E': [0b11111, 0b10000, 0b10000, 0b11110, 0b10000, 0b10000, 0b11111],
0b00100, 'U': [0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b01110],
0b01110 'P': [0b11110, 0b10001, 0b10001, 0b11110, 0b10000, 0b10000, 0b10000],
], 'O': [0b01110, 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b01110],
'2': [ 'N': [0b10001, 0b11001, 0b10101, 0b10101, 0b10011, 0b10001, 0b10001],
0b01110, 'F': [0b11111, 0b10000, 0b10000, 0b11110, 0b10000, 0b10000, 0b10000]
0b10001,
0b00001,
0b00010,
0b00100,
0b01000,
0b11111
],
'3': [
0b11111,
0b00010,
0b00100,
0b00010,
0b00001,
0b10001,
0b01110
],
'4': [
0b00010,
0b00110,
0b01010,
0b10010,
0b11111,
0b00010,
0b00010
],
'5': [
0b11111,
0b10000,
0b11110,
0b00001,
0b00001,
0b10001,
0b01110
],
'6': [
0b00110,
0b01000,
0b10000,
0b11110,
0b10001,
0b10001,
0b01110
],
'7': [
0b11111,
0b00001,
0b00010,
0b00100,
0b01000,
0b01000,
0b01000
],
'8': [
0b01110,
0b10001,
0b10001,
0b01110,
0b10001,
0b10001,
0b01110
],
'9': [
0b01110,
0b10001,
0b10001,
0b01111,
0b00001,
0b00010,
0b01100
],
' ': [
0b00000,
0b00000,
0b00000,
0b00000,
0b00000,
0b00000,
0b00000
]
} }
def draw_pixel_flipped(matrix, x, y, color): # Eye patterns
"""Draw a pixel with 180-degree rotation""" EYE_OPEN = [0b10101, 0b01110, 0b10001, 0b10101, 0b10001, 0b01110, 0b00000]
flipped_x = 12 - x EYE_CLOSED = [0b00000, 0b00000, 0b00000, 0b11111, 0b00000, 0b00000, 0b00000]
flipped_y = 8 - y
if 0 <= flipped_x < 13 and 0 <= flipped_y < 9:
matrix.pixel(flipped_x, flipped_y, color)
def draw_char(matrix, char, x, y, color): class Display:
"""Draw a character at position x,y on the specified matrix (flipped)""" """Handle all display operations"""
if char in FONT_5X7: def __init__(self, m1, m2):
bitmap = FONT_5X7[char] self.matrix1 = m1
self.matrix2 = m2
def clear(self):
"""Clear both displays"""
self.matrix1.fill(0x000000)
self.matrix2.fill(0x000000)
def show(self):
"""Update both displays"""
self.matrix1.show()
self.matrix2.show()
def pixel(self, matrix, x, y, color): # pylint: disable=no-self-use
"""Draw a pixel with 180-degree rotation"""
fx, fy = 12 - x, 8 - y
if 0 <= fx < 13 and 0 <= fy < 9:
matrix.pixel(fx, fy, color)
def draw_char(self, matrix, char, x, y, color):
"""Draw a character at position x,y"""
if char.upper() in FONT_5X7:
bitmap = FONT_5X7[char.upper()]
for row in range(7):
for col in range(5):
if bitmap[row] & (1 << (4 - col)):
self.pixel(matrix, x + col, y + row, color)
def draw_colon(self, y, color, is_pm=False):
"""Draw colon split between displays with optional PM indicator"""
# Two dots for the colon
for dy in [(1, 2), (4, 5)]:
for offset in dy:
self.pixel(self.matrix1, 12, y + offset, color)
self.pixel(self.matrix2, 0, y + offset, color)
# PM indicator dot
if is_pm:
self.pixel(self.matrix1, 12, y + 6, color)
self.pixel(self.matrix2, 0, y + 6, color)
def draw_time(self, time_str, color, is_pm=False):
"""Draw time display across both matrices"""
self.clear()
y = 1
# Draw digits
if len(time_str) >= 5:
self.draw_char(self.matrix1, time_str[0], 0, y, color)
self.draw_char(self.matrix1, time_str[1], 6, y, color)
self.draw_colon(y, color, is_pm)
self.draw_char(self.matrix2, time_str[3], 2, y, color)
self.draw_char(self.matrix2, time_str[4], 8, y, color)
self.show()
def draw_scrolling_text(self, text, offset, color):
"""Draw scrolling text across both matrices"""
self.clear()
char_width = 6
total_width = 26
# Calculate position for smooth scrolling
y = 1
for i, char in enumerate(text):
# Start from right edge and move left
char_x = total_width - offset + (i * char_width)
# Draw character if any part is visible
if -6 < char_x < total_width:
if char_x < 13: # On matrix1
self.draw_char(self.matrix1, char, char_x, y, color)
else: # On matrix2
self.draw_char(self.matrix2, char, char_x - 13, y, color)
self.show()
def draw_eye(self, matrix, pattern, color):
"""Draw eye pattern centered on matrix"""
x, y = 4, 1 # Center position
for row in range(7): for row in range(7):
for col in range(5): for col in range(5):
if bitmap[row] & (1 << (4 - col)): if pattern[row] & (1 << (4 - col)):
draw_pixel_flipped(matrix, x + col, y + row, color) self.pixel(matrix, x + col, y + row, color)
def draw_colon_split(y, color): def wink_animation(self, color):
"""Draw a split colon with 2x2 dots between the displays""" """Perform winking animation"""
# Top dot - left half on matrix1, right half on matrix2 # Sequence: open -> left wink -> open -> right wink -> open
draw_pixel_flipped(matrix1, 12, y+1, color) # Top-left sequences = [
draw_pixel_flipped(matrix1, 12, y + 2, color) # Bottom-left (EYE_OPEN, EYE_OPEN),
draw_pixel_flipped(matrix2, 0, y+1, color) # Top-right (EYE_CLOSED, EYE_OPEN),
draw_pixel_flipped(matrix2, 0, y + 2, color) # Bottom-right (EYE_OPEN, EYE_OPEN),
(EYE_OPEN, EYE_CLOSED),
(EYE_OPEN, EYE_OPEN)
]
for left_eye, right_eye in sequences:
self.clear()
self.draw_eye(self.matrix1, left_eye, color)
self.draw_eye(self.matrix2, right_eye, color)
self.show()
time.sleep(0.3)
# Bottom dot - left half on matrix1, right half on matrix2 def blink_time(self, time_str, color, is_pm=False, count=3):
draw_pixel_flipped(matrix1, 12, y + 4, color) # Top-left """Blink time display for mode changes"""
draw_pixel_flipped(matrix1, 12, y + 5, color) # Bottom-left for _ in range(count):
draw_pixel_flipped(matrix2, 0, y + 4, color) # Top-right self.clear()
draw_pixel_flipped(matrix2, 0, y + 5, color) # Bottom-right self.show()
time.sleep(0.2)
self.draw_time(time_str, color, is_pm)
time.sleep(0.2)
def draw_text(text, color=0xFFFFFF): # Initialize display handler
"""Draw text across both matrices with proper spacing""" display = Display(matrix1, matrix2)
# Clear both displays
matrix1.fill(0x000000)
matrix2.fill(0x000000)
# For "12:00" layout with spacing: # State variables
# "1" at x=0 on matrix1 (5 pixels wide) class State:
# "2" at x=6 on matrix1 (5 pixels wide, leaving 1-2 pixels space before colon) """Track all state variables"""
# ":" split between matrix1 and matrix2 def __init__(self):
# "0" at x=2 on matrix2 (leaving 1-2 pixels space after colon) self.color_value = 0
# "0" at x=8 on matrix2 (5 pixels wide) self.color = colorwheel(0)
self.is_pm = False
self.alarm_is_pm = False
self.time_str = "00:00"
self.set_alarm = 0
self.active_alarm = False
self.alarm_str = f"{alarm_hour:02}:{alarm_min:02}"
self.current_brightness = BRIGHTNESS_DAY
# Timers
self.refresh_timer = Timer(3600000) # 1 hour
self.clock_timer = Timer(1000) # 1 second
self.wink_timer = Timer(30000) # 30 seconds
self.scroll_timer = Timer(80) # Scroll speed
self.blink_timer = Timer(500) # Blink speed
self.alarm_status_timer = Timer(100) # Status scroll
# Display state
self.scroll_offset = 0
self.blink_state = True
self.showing_status = False
self.status_start_time = 0
self.alarm_start_time = 0
# Time tracking
self.first_run = True
self.seconds = 0
self.mins = 0
self.am_pm_hour = 0
y = 1 # Vertical position class Timer:
"""Simple timer helper"""
def __init__(self, interval):
self.interval = interval
self.last_tick = ticks_ms()
# Draw first two digits on matrix1 def check(self):
if len(text) >= 2: """Check if timer has elapsed"""
draw_char(matrix1, text[0], 0, y, color) # First digit at x=0 if ticks_diff(ticks_ms(), self.last_tick) >= self.interval:
draw_char(matrix1, text[1], 6, y, color) # Second digit at x=6 (leaves space for colon) self.last_tick = ticks_add(self.last_tick, self.interval)
return True
return False
# Draw the colon split between displays def reset(self):
if len(text) >= 3 and text[2] == ':': """Reset timer"""
draw_colon_split(y, color) self.last_tick = ticks_ms()
# Draw last two digits on matrix2 # Initialize state
if len(text) >= 5: state = State()
draw_char(matrix2, text[3], 2, y, color) # Third digit at x=2 (leaves space after colon)
draw_char(matrix2, text[4], 8, y, color) # Fourth digit at x=8
# Update both displays def format_time_display(hour_24, minute, use_12hr=True):
matrix1.show() """Format time for display with AM/PM detection"""
matrix2.show() if use_12hr:
print("updated matrices") hour = hour_24 % 12
if hour == 0:
hour = 12
is_pm = hour_24 >= 12
else:
hour = hour_24
is_pm = False
return f"{hour:02}:{minute:02}", is_pm
refresh_clock = ticks_ms() def sync_time():
refresh_timer = 3600 * 1000 """Sync with NTP server"""
clock_clock = ticks_ms() try:
clock_timer = 1000 print("Getting time from internet!")
first_run = True now = ntp.datetime
new_time = False state.am_pm_hour = now.tm_hour
color_value = 0 state.mins = now.tm_min
COLOR = colorwheel(0) state.seconds = now.tm_sec
time_str = "00:00" state.time_str, state.is_pm = format_time_display(state.am_pm_hour, state.mins, hour_12)
set_alarm = 0 update_brightness(state.am_pm_hour)
active_alarm = False if not state.active_alarm and not state.showing_status:
alarm = f"{alarm_hour:02}:{alarm_min:02}" display.draw_time(state.time_str, state.color, state.is_pm)
print(f"Time: {state.time_str}")
state.first_run = False
return True
except Exception as e: # pylint: disable=broad-except
print(f"Error syncing time: {e}")
return False
# Main loop
while True: while True:
button.update() button.update()
if button.long_press:
# long press to set alarm & turn off alarm
if set_alarm == 0 and not active_alarm:
set_alarm = 1
draw_text(f"{alarm_hour:02}: ", COLOR)
if active_alarm:
mixer.voice[0].stop()
active_alarm = False
BRIGHTNESS = 128
matrix1.set_led_scaling(BRIGHTNESS)
matrix2.set_led_scaling(BRIGHTNESS)
if button.short_count == 1:
# short press to set hour and minute
set_alarm = (set_alarm + 1) % 3
if set_alarm == 0:
draw_text(time_str, COLOR)
elif set_alarm == 2:
draw_text(f" :{alarm_min:02}", COLOR)
if button.short_count == 3:
no_alarm_plz = not no_alarm_plz
print(f"alarms off? {no_alarm_plz}")
# Handle button presses
if button.long_press:
if state.set_alarm == 0 and not state.active_alarm:
# Enter alarm setting mode
state.blink_timer.reset()
state.set_alarm = 1
state.alarm_is_pm = alarm_hour >= 12 if hour_12 else False
hour_str, _ = format_time_display(alarm_hour, 0, hour_12)
display.blink_time(hour_str[:2] + ": ", state.color, state.alarm_is_pm)
# Draw the alarm hour after blinking to keep it displayed
display.draw_time(hour_str[:2] + ": ", state.color, state.alarm_is_pm)
elif state.active_alarm:
# Stop alarm
mixer.voice[0].stop()
state.active_alarm = False
update_brightness(state.am_pm_hour)
state.scroll_offset = 0
# Immediately redraw the current time
display.draw_time(state.time_str, state.color, state.is_pm)
print("Alarm silenced")
if button.short_count == 1: # Changed from == 1 to >= 1 for better detection
# Cycle through alarm setting modes
state.set_alarm = (state.set_alarm + 1) % 3
if state.set_alarm == 0:
# Exiting alarm setting mode - redraw current time
state.wink_timer.reset()
display.draw_time(state.time_str, state.color, state.is_pm)
elif state.set_alarm == 1:
# Entering hour setting
hour_str, _ = format_time_display(alarm_hour, 0, hour_12)
display.draw_time(hour_str[:2] + ": ", state.color, state.alarm_is_pm)
# Reset timer to prevent immediate blinking
elif state.set_alarm == 2:
# Entering minute setting
display.blink_time(f" :{alarm_min:02}", state.color, state.alarm_is_pm)
# Draw the minutes after blinking to keep them displayed
display.draw_time(f" :{alarm_min:02}", state.color, state.alarm_is_pm)
# Reset timer to prevent immediate blinking
if button.short_count == 3: # Changed for better detection
# Toggle alarm on/off
no_alarm_plz = not no_alarm_plz
print(f"Alarm disabled: {no_alarm_plz}")
state.showing_status = True
state.status_start_time = ticks_ms()
state.scroll_offset = 0
# Handle encoder (your existing code)
position = -encoder.position position = -encoder.position
if position != last_position: if position != last_position:
if position > last_position: delta = 1 if position > last_position else -1
# when setting alarm, rotate through hours/minutes if state.set_alarm == 0:
# when not, change color for LEDs # Change color
if set_alarm == 0: state.color_value = (state.color_value + delta * 5) % 255
color_value = (color_value + 5) % 255 state.color = colorwheel(state.color_value)
elif set_alarm == 1: display.draw_time(state.time_str, state.color, state.is_pm)
alarm_hour = (alarm_hour + 1) % 24 elif state.set_alarm == 1:
elif set_alarm == 2: # Change hour
alarm_min = (alarm_min + 1) % 60 alarm_hour = (alarm_hour + delta) % 24
else: state.alarm_is_pm = alarm_hour >= 12 if hour_12 else False
if set_alarm == 0: hour_str, _ = format_time_display(alarm_hour, 0, hour_12)
color_value = (color_value - 5) % 255 display.draw_time(hour_str[:2] + ": ", state.color, state.alarm_is_pm)
elif set_alarm == 1: elif state.set_alarm == 2:
alarm_hour = (alarm_hour - 1) % 24 # Change minute
elif set_alarm == 2: alarm_min = (alarm_min + delta) % 60
alarm_min = (alarm_min - 1) % 60 display.draw_time(f" :{alarm_min:02}", state.color, state.alarm_is_pm)
alarm = f"{alarm_hour:02}:{alarm_min:02}" state.alarm_str = f"{alarm_hour:02}:{alarm_min:02}"
COLOR = colorwheel(color_value)
if set_alarm == 0:
draw_text(time_str, COLOR)
elif set_alarm == 1:
draw_text(f"{alarm_hour:02}: ", COLOR)
elif set_alarm == 2:
draw_text(f" :{alarm_min:02}", COLOR)
last_position = position last_position = position
# resync with NTP time server every hour # Handle alarm status display
if set_alarm == 0: if state.showing_status:
if ticks_diff(ticks_ms(), refresh_clock) >= refresh_timer or first_run: if state.alarm_status_timer.check():
try: status_text = "OFF " if no_alarm_plz else "ON "
print("Getting time from internet!") display.draw_scrolling_text(status_text, state.scroll_offset, state.color)
now = ntp.datetime text_width = 4*6 if no_alarm_plz else 3*6
print(now) state.scroll_offset += 1
total_seconds = time.mktime(now) # Reset when text has completely scrolled off
first_run = False if state.scroll_offset > text_width + 18:
am_pm_hour = now.tm_hour state.scroll_offset = 0
if hour_12: state.showing_status = False
hours = am_pm_hour % 12 if state.set_alarm == 0 and not state.active_alarm:
if hours == 0: display.draw_time(state.time_str, state.color, state.is_pm)
hours = 12
# Handle active alarm scrolling
if state.active_alarm:
# Auto-silence alarm after 1 minute
if ticks_diff(ticks_ms(), state.alarm_start_time) >= 60000:
mixer.voice[0].stop()
state.active_alarm = False
update_brightness(state.am_pm_hour)
state.scroll_offset = 0
display.draw_time(state.time_str, state.color, state.is_pm)
print("Alarm auto-silenced")
elif state.scroll_timer.check():
display.draw_scrolling_text("WAKE UP ", state.scroll_offset, state.color)
text_width = 8 * 6 # "WAKE UP " is 8 characters
state.scroll_offset += 1
# Reset when text has completely scrolled off
if state.scroll_offset > text_width + 26:
state.scroll_offset = 0
# Handle alarm setting mode blinking
elif state.set_alarm > 0:
# Only blink if enough time has passed since mode change
if state.blink_timer.check():
state.blink_state = not state.blink_state
if state.blink_state:
# Redraw during the "on" part of blink
if state.set_alarm == 1:
hour_str, _ = format_time_display(alarm_hour, 0, hour_12)
display.draw_time(hour_str[:2] + ": ", state.color, state.alarm_is_pm)
else: else:
hours = am_pm_hour display.draw_time(f" :{alarm_min:02}", state.color, state.alarm_is_pm)
time_str = f"{hours:02}:{now.tm_min:02}" else:
print(time_str) # Only clear display during the "off" part of blink
mins = now.tm_min display.clear()
seconds = now.tm_sec display.show()
draw_text(time_str, COLOR)
refresh_clock = ticks_add(refresh_clock, refresh_timer) # Normal mode operations
except Exception as e: # pylint: disable=broad-except else: # state.set_alarm == 0
print("Some error occured, retrying! -", e) # Winking animation
if not state.active_alarm and not state.showing_status and state.wink_timer.check():
print("Winking!")
display.wink_animation(state.color)
display.draw_time(state.time_str, state.color, state.is_pm)
# Time sync
if state.refresh_timer.check() or state.first_run:
if not sync_time():
time.sleep(10) time.sleep(10)
microcontroller.reset() microcontroller.reset()
# keep time locally between NTP server syncs # Local timekeeping
if ticks_diff(ticks_ms(), clock_clock) >= clock_timer: if state.clock_timer.check():
seconds += 1 state.seconds += 1
# print(seconds) if state.seconds > 59:
if seconds > 59: state.seconds = 0
mins += 1 state.mins += 1
seconds = 0 if state.mins > 59:
new_time = True state.mins = 0
if mins > 59: state.am_pm_hour = (state.am_pm_hour + 1) % 24
am_pm_hour += 1 update_brightness(state.am_pm_hour)
mins = 0 # Update display
new_time = True state.time_str, state.is_pm = format_time_display(state.am_pm_hour,
if hour_12: state.mins, hour_12)
hours = am_pm_hour % 12 if not state.active_alarm and not state.showing_status:
if hours == 0: display.draw_time(state.time_str, state.color, state.is_pm)
hours = 12 # Check alarm
else: if f"{state.am_pm_hour:02}:{state.mins:02}" == state.alarm_str and not no_alarm_plz:
hours = am_pm_hour print("ALARM!")
if new_time:
time_str = f"{hours:02}:{mins:02}"
new_time = False
print(time_str)
draw_text(time_str, COLOR)
if f"{am_pm_hour:02}:{mins:02}" == alarm and not no_alarm_plz:
print("alarm!")
# grab a new wav file from the wavs list
wave = open_audio() wave = open_audio()
mixer.voice[0].play(wave, loop=True) mixer.voice[0].play(wave, loop=True)
active_alarm = True state.active_alarm = True
if active_alarm: state.alarm_start_time = ticks_ms()
# blink the clock characters state.scroll_offset = 0
if BRIGHTNESS:
BRIGHTNESS = 0
else:
BRIGHTNESS = 128
matrix1.set_led_scaling(BRIGHTNESS)
matrix2.set_led_scaling(BRIGHTNESS)
clock_clock = ticks_add(clock_clock, clock_timer)