This simple clock runs on a Raspberry Pi Pico-W, similar to the Clock with Buttons script. Instead of a display, I use a standard battery powered clock to show the time. The Pico-W connects to the internet to keep the time in sync, so that it can chime the correct hour (or on demand with a signal from Clock with Buttons).
It also have two neopixels that cycle slowly through the rainbow, and flash white when the chime strikes
The single chime is from a set of outdoor chimes, which was used in one of my first projects, a set of Music Chimes that played songs or was playable through a web interface.
All of the components are mounted on a small painting canvas, with an identical canvas on the back separated my M3 mounting headers to hide and protect the wiring.
Parts
The links below go to the sites where I ordered the parts for this project, though you can find these parts at various websites as well.
- Raspberry Pi Pico-W
- ALITOVE 5mm WS2812B Individually Addressable RGB LED (WS2812B)
- Micro Servos
- Glockenspeil Mallet
Project Notes
The Raspberry Pi Pico board features a dual core RP2040 processor, with 5V USB Power. The timer running the clock runs on one core thread, while managing the LED color cycle runs simultaneously in a seperate thread on the other core.
The MicroPython code
- The secrets.py import contains three lines to store my Access Point's SSID, Password, and Static IP:
SSID = "My_SSID" PW = "My_Password" StaticIP = "My_Static_IP_Address"
- When the Wifi module attempts to connect, it should connect on the first try. However if the device was already connected prior to rebooting, it may need a second attempt before the router accepts the connection. My code automatically reboots if it doesn't successfully connect after 15 seconds.
- The LEDs employ the LED Driver, WS2812.py.
- To calibrate the hammer, this short snippet allows you to try different duty cycles. I settled on 5600/8800 to give the hammer a greater distance to travel, for astetic purposes. Originally I aligned the hammer straight down, but it caused my servo to jitter.
By making the start position higher up the right hand side, the weight of the hammer applies enough pressure to stablize the servo.
from machine import Pin, PWM, Timer import utime led = Pin("LED", Pin.OUT) led.on() utime.sleep(1) # create Clime class class Chime: def __init__(self,name,servo_pin): self.name = name self.pin = PWM(Pin(servo_pin)) self.pin.freq(50) self.pin.duty_u16(8800) @classmethod def strike(self, key, pin, pause = 1000): print(f"************************\n* Striking *********\n") pin.duty_u16(5700) utime.sleep_ms(250) print("* Retracting Mallet - pause: %4d \n************************" % (pause)) pin.duty_u16(8800) utime.sleep_ms(pause) return do0 = Chime("do0", 2) do0.strike("do0",do0.pin)
import gc
from machine import Pin, PWM, Timer
import network
#import framebuf
import utime
import secrets
import urequests
import socket
from ws2812 import WS2812
import urandom
import _thread
import ubinascii
gc.enable()
ws = WS2812(machine.Pin(0),2)
led = Pin("LED", Pin.OUT)
led.on()
utime.sleep(1)
#Create Timer
timer=Timer()
hourAP=0
old_time_stamp = 0
time_stamp = 0
## secrets
SSID = secrets.SSID
PW = secrets.PW
#setup WLAN as station
#TEST: find mac address of Pico
mac = ubinascii.hexlify(network.WLAN().config('mac'),':').decode()
print("MAC:" + mac)
#28:cd:c1:07:f9:cf 28-CD-C1-07-F9-CF 10.3.107.4
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
# connect to AP
wlan.connect(ssid=SSID,key=PW)
print('Waiting for connection')
busy_meter = 0
while not wlan.isconnected():
led.toggle()
busy_meter += 1
utime.sleep(0.5)
if busy_meter == 16: machine.reset()
status = wlan.ifconfig()
print('connection to', secrets.SSID,'succesfull established!', sep=' ')
print('IP-adress: ' + status[0])
led.on()
utime.sleep(2)
# get time
def get_time():
global time_stamp
global zonemod
print("\n\n2. Querying the current time:")
old_time_stamp = time_stamp
if wlan.isconnected():
r = urequests.get("http://worldtimeapi.org/api/timezone/America/New_York") # Server that returns the current EST time.
print(r.json())
rt_year, rt_month, rt_day, rt_hour, rt_mins, rt_secs = int(r.json()['datetime'][0:4]), int(r.json()['datetime'][5:7]), int(r.json()['datetime'][8:10]), int(r.json()['datetime'][11:13]), int(r.json()['datetime'][14:16]), int(r.json()['datetime'][17:19])
rt_weekday, rt_yearday, zonemod = int(r.json()['day_of_week']), int(r.json()['day_of_year']), r.json()['abbreviation']
print(rt_year, rt_month, rt_day, rt_hour, rt_mins, rt_secs, rt_weekday, rt_yearday, zonemod)
rt_time_tuple = tuple((rt_year,rt_month,rt_day,rt_hour,rt_mins,rt_secs,rt_weekday,rt_yearday))
print("rt_time_tuple", rt_time_tuple)
print("=====")
time_stamp = utime.mktime(rt_time_tuple)
time=utime.localtime(time_stamp)
return time_stamp
else:
print("error")
#time_stamp = old_time_stamp
machine.reset()
def clock(t):
global time_stamp
global zonemod
global lcd_update
global hourAP
time=utime.localtime(time_stamp)
# print(time, time_stamp)
if time[3] in [0,12]:
hourAP = 12
else:
hourAP = time[3] % 12
# if time[3] >= 12:
# AMPM = "PM"
# else:
# AMPM = "AM"
time_stamp += 1
# print(time[3],time[4],time[5]) #, gc.mem_free(), gc.mem_alloc())
if (time[4] == 0) and (time[5] == 0): #m,s
for i in range(0, hourAP):
do0.strike("do0",do0.pin)
# if (time[4] == 30) and (time[5] == 0):
# get_time()
gc.collect()
# create Clime class
class Chime:
def __init__(self,name,servo_pin):
self.name = name
self.pin = PWM(Pin(servo_pin))
self.pin.freq(50)
self.pin.duty_u16(8800)
@classmethod
def strike(self, key, pin, pause = 1000):
print(f"************************\n* Striking *********\n")
pin.duty_u16(5600)
ws.write_all([255,255,255])
utime.sleep_ms(250)
print("* Retracting Mallet - pause: %4d \n************************" % (pause))
pin.duty_u16(8800)
utime.sleep_ms(pause)
return
do0 = Chime("do0", 2)
#ws = WS2812(machine.Pin(0),2)
ws.write_all([0,0,0])
print(ws[0])
ws.write()
def flowing_light():
while True:
current_color = ws[0]
colors = [[255,0,0],[0,0,255],[0,255,0],[0,255,255],[255,0,255],[255,255,0],[255,255,255]]
next_color = urandom.choice(colors)
# print("Current Color:", current_color[0], current_color[1], current_color[2])
# print("Next Color:", next_color[0], next_color[1], next_color[2])
dRed, dGreen, dBlue = 0, 0, 0
dRed, dGreen, dBlue = abs(current_color[0] - next_color[0]), abs(current_color[1] - next_color[1]), abs(current_color[2] - next_color[2])
# print(dRed, dGreen, dBlue)
while dRed or dGreen or dBlue:
if dRed != 0:
if current_color[0] > next_color[0]:
current_color[0] -= 1
else:
current_color[0] += 1
dRed -= 1
if dGreen != 0:
if current_color[1] > next_color[1]:
current_color[1] -= 1
else:
current_color[1] += 1
dGreen -= 1
if dBlue != 0:
if current_color[2] > next_color[2]:
current_color[2] -= 1
else:
current_color[2] += 1
dBlue -= 1
# for i in range(1,0,-1):
ws[1] = ws[0]
ws[0] = [current_color[0],current_color[1],current_color[2]]
# print(ws[0], dRed, dGreen, dBlue)
ws.write()
utime.sleep_ms(10)
time_stamp = get_time()
_thread.start_new_thread(flowing_light, ())
timer.init(freq=1,mode=Timer.PERIODIC,callback=clock)
do0.strike("do0",do0.pin)
while True:
pass
# do0.strike("do0",do0.pin)
# utime.sleep(3)
# print("ok")
# try:
# cl, addr = s.accept()
# request = cl.recv(1024)
# request = str(request)
# start_index = request.find('"command": ') + len('"command": ')
# end_index = request.find('}', start_index)
# ring_count = int(request[start_index:end_index])
# print(ring_count)
# for i in range(0,ring_count):
# do0.strike("do0",do0.pin)
# # Send a response back to the sender
# response = "Chime Count Received"
# cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
# cl.send(response)
# cl.close()
#
#
# except OSError as e:
# cl.close()
# print('connection closed')
WS2812.py (LED Driver)
import array, time
import rp2
from rp2 import PIO, StateMachine, asm_pio
@asm_pio(sideset_init=PIO.OUT_LOW, out_shiftdir=PIO.SHIFT_LEFT, autopull=True, pull_thresh=24)
def ws2812():
T1 = 2
T2 = 5
T3 = 3
label("bitloop")
out(x, 1).side(0)[T3 - 1]
jmp(not_x, "do_zero").side(1)[T1 - 1]
jmp("bitloop").side(1)[T2 - 1]
label("do_zero")
nop().side(0)[T2 - 1]
class WS2812():
def __init__(self, pin, num):
# Configure the number of WS2812 LEDs.
self.led_nums = num
self.pin = pin
self.sm = StateMachine(0, ws2812, freq=8000000, sideset_base=self.pin)
# Start the StateMachine, it will wait for data on its FIFO.
self.sm.active(1)
self.buf = array.array("I", [0 for _ in range(self.led_nums)])
def write(self):
self.sm.put(self.buf, 8)
def write_all(self, value):
for i in range(self.led_nums):
self.__setitem__(i, value)
self.write()
def list_to_hex(self, color):
if isinstance(color, list) and len(color) == 3:
c = (color[0] << 8) + (color[1] << 16) + (color[2])
return c
elif isinstance(color, int):
value = (color & 0xFF0000)>>8 | (color & 0x00FF00)<<8 | (color & 0x0000FF)
return value
else:
raise ValueError("Color must be 24-bit RGB hex or list of 3 8-bit RGB")
def hex_to_list(self, color):
if isinstance(color, list) and len(color) == 3:
return color
elif isinstance(color, int):
r = color >> 8 & 0xFF
g = color >> 16 & 0xFF
b = color >> 0 & 0xFF
return [r, g, b]
else:
raise ValueError("Color must be 24-bit RGB hex or list of 3 8-bit RGB")
def __getitem__(self, i):
return self.hex_to_list(self.buf[i])
def __setitem__(self, i, value):
value = self.list_to_hex(value)
self.buf[i] = value