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
This commit is contained in:
parent
ec541811cc
commit
f2ede9c176
1 changed files with 387 additions and 305 deletions
|
|
@ -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 rainbowio import colorwheel
|
|||
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 @@ context = ssl.create_default_context()
|
|||
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)
|
||||
# Eye patterns
|
||||
EYE_OPEN = [0b10101, 0b01110, 0b10001, 0b10101, 0b10001, 0b01110, 0b00000]
|
||||
EYE_CLOSED = [0b00000, 0b00000, 0b00000, 0b11111, 0b00000, 0b00000, 0b00000]
|
||||
|
||||
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]
|
||||
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)):
|
||||
draw_pixel_flipped(matrix, x + col, y + row, color)
|
||||
self.pixel(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
|
||||
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)
|
||||
|
||||
# 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_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_text(text, color=0xFFFFFF):
|
||||
"""Draw text across both matrices with proper spacing"""
|
||||
# Clear both displays
|
||||
matrix1.fill(0x000000)
|
||||
matrix2.fill(0x000000)
|
||||
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()
|
||||
|
||||
# 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)
|
||||
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 pattern[row] & (1 << (4 - col)):
|
||||
self.pixel(matrix, x + col, y + row, color)
|
||||
|
||||
y = 1 # Vertical position
|
||||
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)
|
||||
|
||||
# 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)
|
||||
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)
|
||||
|
||||
# Draw the colon split between displays
|
||||
if len(text) >= 3 and text[2] == ':':
|
||||
draw_colon_split(y, color)
|
||||
# Initialize display handler
|
||||
display = Display(matrix1, matrix2)
|
||||
|
||||
# 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
|
||||
# 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
|
||||
|
||||
# Update both displays
|
||||
matrix1.show()
|
||||
matrix2.show()
|
||||
print("updated matrices")
|
||||
class Timer:
|
||||
"""Simple timer helper"""
|
||||
def __init__(self, interval):
|
||||
self.interval = interval
|
||||
self.last_tick = ticks_ms()
|
||||
|
||||
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}"
|
||||
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
|
||||
|
||||
while True:
|
||||
def reset(self):
|
||||
"""Reset timer"""
|
||||
self.last_tick = ticks_ms()
|
||||
|
||||
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}")
|
||||
# Initialize state
|
||||
state = State()
|
||||
|
||||
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
|
||||
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:
|
||||
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)
|
||||
last_position = position
|
||||
hour = hour_24
|
||||
is_pm = False
|
||||
return f"{hour:02}:{minute:02}", is_pm
|
||||
|
||||
# resync with NTP time server every hour
|
||||
if set_alarm == 0:
|
||||
if ticks_diff(ticks_ms(), refresh_clock) >= refresh_timer or first_run:
|
||||
def sync_time():
|
||||
"""Sync with NTP server"""
|
||||
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
|
||||
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)
|
||||
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("Some error occured, retrying! -", e)
|
||||
print(f"Error syncing time: {e}")
|
||||
return False
|
||||
|
||||
# Main loop
|
||||
while True:
|
||||
button.update()
|
||||
|
||||
# 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
|
||||
if position != last_position:
|
||||
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
|
||||
|
||||
# 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:
|
||||
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
|
||||
|
|
|
|||
Loading…
Reference in a new issue