adding led matrix clock code
CircuitPython code and wav files for the led matrix clock project
This commit is contained in:
parent
a87f6abb0b
commit
02768500ad
3 changed files with 385 additions and 0 deletions
385
LED_Matrix_Clock/code.py
Normal file
385
LED_Matrix_Clock/code.py
Normal file
|
|
@ -0,0 +1,385 @@
|
|||
# SPDX-FileCopyrightText: 2025 Liz Clark for Adafruit Industries
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
'''LED Matrix Alarm Clock'''
|
||||
import os
|
||||
import ssl
|
||||
import time
|
||||
import random
|
||||
import wifi
|
||||
import socketpool
|
||||
import microcontroller
|
||||
import board
|
||||
import audiocore
|
||||
import audiobusio
|
||||
import audiomixer
|
||||
import adafruit_is31fl3741
|
||||
from adafruit_is31fl3741.adafruit_rgbmatrixqt import Adafruit_RGBMatrixQT
|
||||
import adafruit_ntp
|
||||
from adafruit_ticks import ticks_ms, ticks_add, ticks_diff
|
||||
from rainbowio import colorwheel
|
||||
from adafruit_seesaw import digitalio, rotaryio, seesaw
|
||||
from adafruit_debouncer import Button
|
||||
|
||||
timezone = -4 # your timezone offset
|
||||
alarm_hour = 14 # hour is 24 hour for alarm to denote am/pm
|
||||
alarm_min = 11 # minutes
|
||||
alarm_volume = 1 # float 0.0 to 1.0
|
||||
hour_12 = True # 12 hour or 24 hour time
|
||||
BRIGHTNESS = 128 # led brightness (0-255)
|
||||
|
||||
# I2S pins for Audio BFF
|
||||
DATA = board.A0
|
||||
LRCLK = board.A1
|
||||
BCLK = board.A2
|
||||
|
||||
# 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')}")
|
||||
|
||||
context = ssl.create_default_context()
|
||||
pool = socketpool.SocketPool(wifi.radio)
|
||||
ntp = adafruit_ntp.NTP(pool, tz_offset=timezone, cache_seconds=3600)
|
||||
|
||||
# Initialize I2C
|
||||
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()
|
||||
|
||||
audio = audiobusio.I2SOut(BCLK, LRCLK, DATA)
|
||||
wavs = []
|
||||
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,
|
||||
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
|
||||
|
||||
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
|
||||
encoder = rotaryio.IncrementalEncoder(seesaw)
|
||||
last_position = 0
|
||||
|
||||
# Simple 5x7 font bitmap patterns for digits 0-9
|
||||
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
|
||||
]
|
||||
}
|
||||
|
||||
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]
|
||||
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}"
|
||||
|
||||
while True:
|
||||
|
||||
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:
|
||||
# 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)
|
||||
|
||||
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)
|
||||
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
|
||||
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)
|
||||
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:
|
||||
print("alarm!")
|
||||
# grab a new wav file from the wavs list
|
||||
wave = open_audio()
|
||||
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)
|
||||
|
||||
# loop alarm wav
|
||||
if active_alarm:
|
||||
mixer.voice[0].play(wave, loop=True)
|
||||
BIN
LED_Matrix_Clock/nice-alarm.wav
Normal file
BIN
LED_Matrix_Clock/nice-alarm.wav
Normal file
Binary file not shown.
BIN
LED_Matrix_Clock/square-alarm.wav
Normal file
BIN
LED_Matrix_Clock/square-alarm.wav
Normal file
Binary file not shown.
Loading…
Reference in a new issue