From f2ede9c176ade9eec8932adfb9b6218ceb6a4730 Mon Sep 17 00:00:00 2001 From: Liz Date: Fri, 6 Jun 2025 12:39:18 -0400 Subject: [PATCH 1/3] adding extra features to matrix clock project new features for matrix clock project * "blinking eyes" every 30 seconds * scrolling text during alarm * scrolling text to denote if alarm is enabled --- LED_Matrix_Clock/code.py | 708 ++++++++++++++++++++++----------------- 1 file changed, 395 insertions(+), 313 deletions(-) diff --git a/LED_Matrix_Clock/code.py b/LED_Matrix_Clock/code.py index 1c2e200de..f109e91ee 100644 --- a/LED_Matrix_Clock/code.py +++ b/LED_Matrix_Clock/code.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2025 Liz Clark for Adafruit Industries # SPDX-License-Identifier: MIT -'''LED Matrix Alarm Clock''' +'''LED Matrix Alarm Clock with Scrolling Wake Up Text and Winking Eyes''' import os import ssl import time @@ -21,20 +21,22 @@ from adafruit_seesaw import digitalio, rotaryio, seesaw from adafruit_debouncer import Button -timezone = -4 # your timezone offset -alarm_hour = 12 # hour is 24 hour for alarm to denote am/pm -alarm_min = 00 # minutes -alarm_volume = 1 # float 0.0 to 1.0 -hour_12 = True # 12 hour or 24 hour time +# Configuration +timezone = -4 +alarm_hour = 11 +alarm_min = 36 +alarm_volume = .2 +hour_12 = True no_alarm_plz = False -BRIGHTNESS = 128 # led brightness (0-255) +BRIGHTNESS_DAY = 200 +BRIGHTNESS_NIGHT = 50 # I2S pins for Audio BFF DATA = board.A0 -LRCLK = board.A1 -BCLK = board.A2 +LRCLK = board.A2 +BCLK = board.A3 -# connect to WIFI +# Connect to WIFI wifi.radio.connect(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")) print(f"Connected to {os.getenv('CIRCUITPY_WIFI_SSID')}") @@ -42,345 +44,425 @@ pool = socketpool.SocketPool(wifi.radio) ntp = adafruit_ntp.NTP(pool, tz_offset=timezone, cache_seconds=3600) -# Initialize I2C +# Initialize I2C and displays i2c = board.STEMMA_I2C() - -# Initialize both matrix displays matrix1 = Adafruit_RGBMatrixQT(i2c, address=0x30, 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) -wavs = [] -for filename in os.listdir('/'): - if filename.lower().endswith('.wav') and not filename.startswith('.'): - wavs.append("/"+filename) +wavs = ["/"+f for f in os.listdir('/') if f.lower().endswith('.wav') and not f.startswith('.')] mixer = audiomixer.Mixer(voice_count=1, sample_rate=22050, channel_count=1, bits_per_sample=16, samples_signed=True, buffer_size=32768) mixer.voice[0].level = alarm_volume -wav_filename = wavs[random.randint(0, (len(wavs))-1)] -wav_file = open(wav_filename, "rb") audio.play(mixer) def open_audio(): - n = wavs[random.randint(0, (len(wavs))-1)] - f = open(n, "rb") - w = audiocore.WaveFile(f) - return w + """Open a random WAV file""" + filename = random.choice(wavs) + return audiocore.WaveFile(open(filename, "rb")) + +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.pin_mode(24, seesaw.INPUT_PULLUP) -ss_pin = digitalio.DigitalIO(seesaw, 24) -button = Button(ss_pin, long_duration_ms=1000) - -button_held = False +button = Button(digitalio.DigitalIO(seesaw, 24), long_duration_ms=1000) encoder = rotaryio.IncrementalEncoder(seesaw) last_position = 0 -# Simple 5x7 font bitmap patterns for digits 0-9 +# Font definitions FONT_5X7 = { - '0': [ - 0b01110, - 0b10001, - 0b10011, - 0b10101, - 0b11001, - 0b10001, - 0b01110 - ], - '1': [ - 0b00100, - 0b01100, - 0b00100, - 0b00100, - 0b00100, - 0b00100, - 0b01110 - ], - '2': [ - 0b01110, - 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 - ] + '0': [0b01110, 0b10001, 0b10011, 0b10101, 0b11001, 0b10001, 0b01110], + '1': [0b00100, 0b01100, 0b00100, 0b00100, 0b00100, 0b00100, 0b01110], + '2': [0b01110, 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], + 'W': [0b10001, 0b10001, 0b10001, 0b10101, 0b10101, 0b11011, 0b10001], + 'A': [0b01110, 0b10001, 0b10001, 0b11111, 0b10001, 0b10001, 0b10001], + 'K': [0b10001, 0b10010, 0b10100, 0b11000, 0b10100, 0b10010, 0b10001], + 'E': [0b11111, 0b10000, 0b10000, 0b11110, 0b10000, 0b10000, 0b11111], + 'U': [0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b01110], + 'P': [0b11110, 0b10001, 0b10001, 0b11110, 0b10000, 0b10000, 0b10000], + 'O': [0b01110, 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b01110], + 'N': [0b10001, 0b11001, 0b10101, 0b10101, 0b10011, 0b10001, 0b10001], + 'F': [0b11111, 0b10000, 0b10000, 0b11110, 0b10000, 0b10000, 0b10000] } -def draw_pixel_flipped(matrix, x, y, color): - """Draw a pixel with 180-degree rotation""" - flipped_x = 12 - x - 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): - """Draw a character at position x,y on the specified matrix (flipped)""" - if char in FONT_5X7: - bitmap = FONT_5X7[char] +# Eye patterns +EYE_OPEN = [0b10101, 0b01110, 0b10001, 0b10101, 0b10001, 0b01110, 0b00000] +EYE_CLOSED = [0b00000, 0b00000, 0b00000, 0b11111, 0b00000, 0b00000, 0b00000] + +class Display: + """Handle all display operations""" + def __init__(self, m1, m2): + 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): + """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 col in range(5): - if bitmap[row] & (1 << (4 - col)): - draw_pixel_flipped(matrix, x + col, y + row, color) - -def draw_colon_split(y, color): - """Draw a split colon with 2x2 dots between the displays""" - # Top dot - left half on matrix1, right half on matrix2 - draw_pixel_flipped(matrix1, 12, y+1, color) # Top-left - draw_pixel_flipped(matrix1, 12, y + 2, color) # Bottom-left - draw_pixel_flipped(matrix2, 0, y+1, color) # Top-right - draw_pixel_flipped(matrix2, 0, y + 2, color) # Bottom-right - - # Bottom dot - left half on matrix1, right half on matrix2 - draw_pixel_flipped(matrix1, 12, y + 4, color) # Top-left - draw_pixel_flipped(matrix1, 12, y + 5, color) # Bottom-left - draw_pixel_flipped(matrix2, 0, y + 4, color) # Top-right - draw_pixel_flipped(matrix2, 0, y + 5, color) # Bottom-right - -def draw_text(text, color=0xFFFFFF): - """Draw text across both matrices with proper spacing""" - # Clear both displays - matrix1.fill(0x000000) - matrix2.fill(0x000000) - - # For "12:00" layout with spacing: - # "1" at x=0 on matrix1 (5 pixels wide) - # "2" at x=6 on matrix1 (5 pixels wide, leaving 1-2 pixels space before colon) - # ":" split between matrix1 and matrix2 - # "0" at x=2 on matrix2 (leaving 1-2 pixels space after colon) - # "0" at x=8 on matrix2 (5 pixels wide) - - y = 1 # Vertical position - - # Draw first two digits on matrix1 - if len(text) >= 2: - draw_char(matrix1, text[0], 0, y, color) # First digit at x=0 - draw_char(matrix1, text[1], 6, y, color) # Second digit at x=6 (leaves space for colon) - - # Draw the colon split between displays - if len(text) >= 3 and text[2] == ':': - draw_colon_split(y, color) - - # Draw last two digits on matrix2 - if len(text) >= 5: - 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 - matrix1.show() - matrix2.show() - print("updated matrices") - -refresh_clock = ticks_ms() -refresh_timer = 3600 * 1000 -clock_clock = ticks_ms() -clock_timer = 1000 -first_run = True -new_time = False -color_value = 0 -COLOR = colorwheel(0) -time_str = "00:00" -set_alarm = 0 -active_alarm = False -alarm = f"{alarm_hour:02}:{alarm_min:02}" + if pattern[row] & (1 << (4 - col)): + self.pixel(matrix, x + col, y + row, color) -while True: + def wink_animation(self, color): + """Perform winking animation""" + # Sequence: open -> left wink -> open -> right wink -> open + sequences = [ + (EYE_OPEN, EYE_OPEN), + (EYE_CLOSED, EYE_OPEN), + (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) + + def blink_time(self, time_str, color, is_pm=False, count=3): + """Blink time display for mode changes""" + for _ in range(count): + self.clear() + self.show() + time.sleep(0.2) + self.draw_time(time_str, color, is_pm) + time.sleep(0.2) + +# Initialize display handler +display = Display(matrix1, matrix2) + +# State variables +class State: + """Track all state variables""" + def __init__(self): + self.color_value = 0 + 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 + +class Timer: + """Simple timer helper""" + def __init__(self, interval): + self.interval = interval + self.last_tick = ticks_ms() + def check(self): + """Check if timer has elapsed""" + if ticks_diff(ticks_ms(), self.last_tick) >= self.interval: + self.last_tick = ticks_add(self.last_tick, self.interval) + return True + return False + + def reset(self): + """Reset timer""" + self.last_tick = ticks_ms() + +# Initialize state +state = State() + +def format_time_display(hour_24, minute, use_12hr=True): + """Format time for display with AM/PM detection""" + if use_12hr: + 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 + +def sync_time(): + """Sync with NTP server""" + try: + print("Getting time from internet!") + now = ntp.datetime + state.am_pm_hour = now.tm_hour + state.mins = now.tm_min + state.seconds = now.tm_sec + state.time_str, state.is_pm = format_time_display(state.am_pm_hour, state.mins, hour_12) + update_brightness(state.am_pm_hour) + if not state.active_alarm and not state.showing_status: + 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: button.update() + + # Handle button presses 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: + 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() - 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: + 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"alarms off? {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 if position != last_position: - if position > last_position: - # when setting alarm, rotate through hours/minutes - # when not, change color for LEDs - if set_alarm == 0: - color_value = (color_value + 5) % 255 - elif set_alarm == 1: - alarm_hour = (alarm_hour + 1) % 24 - elif set_alarm == 2: - alarm_min = (alarm_min + 1) % 60 - else: - if set_alarm == 0: - color_value = (color_value - 5) % 255 - elif set_alarm == 1: - alarm_hour = (alarm_hour - 1) % 24 - elif set_alarm == 2: - alarm_min = (alarm_min - 1) % 60 - alarm = 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) + delta = 1 if position > last_position else -1 + if state.set_alarm == 0: + # Change color + state.color_value = (state.color_value + delta * 5) % 255 + state.color = colorwheel(state.color_value) + display.draw_time(state.time_str, state.color, state.is_pm) + elif state.set_alarm == 1: + # Change hour + alarm_hour = (alarm_hour + delta) % 24 + state.alarm_is_pm = alarm_hour >= 12 if hour_12 else False + hour_str, _ = format_time_display(alarm_hour, 0, hour_12) + display.draw_time(hour_str[:2] + ": ", state.color, state.alarm_is_pm) + elif state.set_alarm == 2: + # Change minute + alarm_min = (alarm_min + delta) % 60 + display.draw_time(f" :{alarm_min:02}", state.color, state.alarm_is_pm) + state.alarm_str = f"{alarm_hour:02}:{alarm_min:02}" last_position = position - # resync with NTP time server every hour - if set_alarm == 0: - if ticks_diff(ticks_ms(), refresh_clock) >= refresh_timer or first_run: - try: - print("Getting time from internet!") - now = ntp.datetime - print(now) - total_seconds = time.mktime(now) - first_run = False - am_pm_hour = now.tm_hour - if hour_12: - hours = am_pm_hour % 12 - if hours == 0: - hours = 12 + # Handle alarm status display + if state.showing_status: + if state.alarm_status_timer.check(): + status_text = "OFF " if no_alarm_plz else "ON " + display.draw_scrolling_text(status_text, state.scroll_offset, state.color) + text_width = 4*6 if no_alarm_plz else 3*6 + state.scroll_offset += 1 + # Reset when text has completely scrolled off + if state.scroll_offset > text_width + 18: + state.scroll_offset = 0 + state.showing_status = False + if state.set_alarm == 0 and not state.active_alarm: + display.draw_time(state.time_str, state.color, state.is_pm) + + # 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: - hours = am_pm_hour - time_str = f"{hours:02}:{now.tm_min:02}" - print(time_str) - mins = now.tm_min - seconds = now.tm_sec - draw_text(time_str, COLOR) - refresh_clock = ticks_add(refresh_clock, refresh_timer) - except Exception as e: # pylint: disable=broad-except - print("Some error occured, retrying! -", e) + display.draw_time(f" :{alarm_min:02}", state.color, state.alarm_is_pm) + else: + # Only clear display during the "off" part of blink + display.clear() + display.show() + + # Normal mode operations + else: # state.set_alarm == 0 + # 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) microcontroller.reset() - # keep time locally between NTP server syncs - if ticks_diff(ticks_ms(), clock_clock) >= clock_timer: - seconds += 1 - # print(seconds) - if seconds > 59: - mins += 1 - seconds = 0 - new_time = True - if mins > 59: - am_pm_hour += 1 - mins = 0 - new_time = True - if hour_12: - hours = am_pm_hour % 12 - if hours == 0: - hours = 12 - else: - hours = am_pm_hour - 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 + # Local timekeeping + if state.clock_timer.check(): + state.seconds += 1 + if state.seconds > 59: + state.seconds = 0 + state.mins += 1 + if state.mins > 59: + state.mins = 0 + state.am_pm_hour = (state.am_pm_hour + 1) % 24 + update_brightness(state.am_pm_hour) + # Update display + state.time_str, state.is_pm = format_time_display(state.am_pm_hour, + state.mins, hour_12) + if not state.active_alarm and not state.showing_status: + display.draw_time(state.time_str, state.color, state.is_pm) + # Check alarm + if f"{state.am_pm_hour:02}:{state.mins:02}" == state.alarm_str and not no_alarm_plz: + print("ALARM!") wave = open_audio() mixer.voice[0].play(wave, loop=True) - active_alarm = True - if active_alarm: - # blink the clock characters - 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) + state.active_alarm = True + state.alarm_start_time = ticks_ms() + state.scroll_offset = 0 From 5a8650418c5b9a1cb0faa667b7dc312d65b79738 Mon Sep 17 00:00:00 2001 From: Liz Date: Fri, 6 Jun 2025 12:40:19 -0400 Subject: [PATCH 2/3] fix pins for audio bff --- LED_Matrix_Clock/code.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LED_Matrix_Clock/code.py b/LED_Matrix_Clock/code.py index f109e91ee..4a664f8c7 100644 --- a/LED_Matrix_Clock/code.py +++ b/LED_Matrix_Clock/code.py @@ -33,8 +33,8 @@ # I2S pins for Audio BFF DATA = board.A0 -LRCLK = board.A2 -BCLK = board.A3 +LRCLK = board.A1 +BCLK = board.A2 # Connect to WIFI wifi.radio.connect(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")) From f3c46a45078b6f0bd5104ed5f692f00c48cce280 Mon Sep 17 00:00:00 2001 From: Liz Date: Fri, 6 Jun 2025 12:53:17 -0400 Subject: [PATCH 3/3] lint --- LED_Matrix_Clock/code.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LED_Matrix_Clock/code.py b/LED_Matrix_Clock/code.py index 4a664f8c7..3244383fe 100644 --- a/LED_Matrix_Clock/code.py +++ b/LED_Matrix_Clock/code.py @@ -128,7 +128,7 @@ def show(self): self.matrix1.show() self.matrix2.show() - def pixel(self, matrix, x, y, color): + 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: