LED Stairway Lighting

Stairway Lighting

For those late nights in my office when I’m staring at SQL/Python scripts all day, walking back to the bedroom at night is a task done completely in the dark. Since we recently encountered a 3.5’ snake in the house, I’m a little hesitant to continue walking around blindly.

This was a quick weekend project that not only adds a boost to our quality of life score, but these lights can be customized based on a holiday or event. I’m thinking about doing a movie theater chasing lights theme automatically for Family Movie & Video Game Night™. It’ll add an extra special flair for the kids when it’s time to grab popcorn and snacks. [edit: added and updated below]

Stairway Lighting

The build only took about an hour from start to finish. I soldered a Raspberry Pi Pico W to a string of LEDs on leftover aluminum channels from the Tron Lights project. The only difference is I’m not using WLED to control them. Other than grabbing the current network time from the built-in Wi-Fi, it’s a set-it-and-forget-it with no third party app or data harvesting company in the middle.

I did, however, install an open source desktop app called Thonny that takes care of the more tedious aspects of dealing with an RP2040. I’d highly recommend you do the same.

It’s recommended to power the LEDs directly to a 5v power supply, but if you’re not going full white (255,255,255) brightness for a long distance, there won’t be any drops in current that cause your lights to dim or lose color. Also, make sure you solder your LEDs to the following pinout if you’re using the same led_pin as the script below:

  • GP0 (Pi Pin 1) > LED Data Pin
  • VBUS (Pi Pin 40) > LED Power
  • GND (Pi Pin 38) > LED Ground

Note: The neopixel library is included by default in the Pico firmware, but not the 2040 firmware.

main.py

	import network
	import ntptime
	import utime
	from machine import Pin, Timer
	import neopixel

	ssid = 'WIFI'
	password = 'PASSWORD'

	NUM_LEDS = 230
	led_pin = Pin(0, Pin.OUT)
	strip = neopixel.NeoPixel(led_pin, NUM_LEDS)
	seconds_delay = 600

	def set_led_color(strip, r, g, b):
		for i in range(NUM_LEDS):
			strip[i] = (r, g, b)
		strip.write()

	def connect_to_wifi(ssid, password):
		wlan = network.WLAN(network.STA_IF)
		wlan.active(True)
		wlan.connect(ssid, password)
		print('Connecting to network...', end='')

		while not wlan.isconnected():
			print('.', end='')
			utime.sleep(1)
		print('\nConnected to', ssid)
		print('IP address:', wlan.ifconfig()[0])

	def get_ntp_time():
		try:
			ntptime.settime()
			current_time = utime.time()
			current_time += -7 * 3600
			adjusted_time = utime.localtime(current_time)
			print("Current time:", adjusted_time)
			return adjusted_time
		except Exception as e:
			print('Failed to get NTP time:', e)
			return None

	def wheel(pos):
		if pos < 85:
			return (pos * 3, 255 - pos * 3, 0)
		elif pos < 170:
			pos -= 85
			return (255 - pos * 3, 0, pos * 3)
		else:
			pos -= 170
			return (0, pos * 3, 255 - pos * 3)

	def wave_effect(strip, wait_ms=50, duration_s=600):
		start_time = utime.time()
		while utime.time() - start_time < duration_s:
			for j in range(256):
				for i in range(NUM_LEDS):
					strip[i] = wheel((i + j) & 255)
				strip.write()
				utime.sleep_ms(wait_ms)  # Speed

	def main():
		connect_to_wifi(ssid, password)

		while True:
			current_time = get_ntp_time()
			if current_time is None:
				utime.sleep(60)  # Retry in a minute if NTP time fetch failed
				continue

			current_hour = current_time[3]
			current_minute = current_time[4]
			current_day = current_time[6]

			# Saturday nights between 18:00 and 21:00
			if current_day == 5 and 18 <= current_hour < 21:
				wave_effect(strip)
			elif current_hour >= 19 or current_hour <= 7:
				set_led_color(strip, 7, 7, 7)  # White at very low brightness
				utime.sleep(seconds_delay)
			else:
				set_led_color(strip, 0, 0, 0)
				utime.sleep(seconds_delay)

	if __name__ == '__main__':
		main()

Sources

 

  • 28September2024
    • Updated the Movie Night effect to a slow, colorful wave.
  • 31July2024
    • Addressed issue where chase_effect() would stop until next refresh.
    • Updated chase_effect() to be dodger blue.
  • 14July2024
    • Lowered default animation speed.
    • Increased default chase_effect() timing to 10 minutes before escaping.
  • 11July2024
    • Fixed an infinite loop in chase_effect().
    • Changed the chase_effect() colors to orange.
  • 04July2024
    • Added network retry.
    • Added blue chase lights for Movie & Game Night™.
  • 30June2024
    • Removed extra get_ntp_time().
  • 29June2024
    • Got tired of doing the math to convert from UTC > PDT. Added adjustment.
    • Lowered brightness to `7` to be a faint glow and not a security light.
Previous: Yosemite Next: Furiosa