Hackster is hosting Hackster Holidays, Ep. 6: Livestream & Giveaway Drawing. Watch now!Tune in to Hackster Holidays, Ep. 6 now!
craig richardson
Published © GPL3+

Pico stair lights

Using the Raspberry Pi Pico and Neopixels for animated stair lights.

IntermediateShowcase (no instructions)24 hours10,986
Pico stair lights

Things used in this project

Hardware components

Raspberry Pi Pico
Raspberry Pi Pico
×1
30 LED Neopixel strip
×1
Resistor 10k ohm
Resistor 10k ohm
×1
LDR, 5 Mohm
LDR, 5 Mohm
×1
PIR Sensor, 7 m
PIR Sensor, 7 m
×2
Power supply
×1
DC Power socket
×1
Stripboard
×1
Pre crimped wires
×6

Story

Read more

Code

stair.py

Python
Module to control intensity of light level of individual LEDs in strip
import array

class Stair:
    
    def __init__(self, numLeds, reverse):
        self.levels = array.array('f', [0.0] * numLeds)
        self.numLeds = numLeds
        self.trigger = 0
        self.dir = 1
        self.reverse = reverse
        
    def UpdateValues(self, waitTime):
        for i in range(self.trigger):
            # Choose led index, in reverse it goes from numLeds down
            idx = i
            if self.reverse > 0:
                idx = (self.numLeds - 1) - i
            # Increase or decrease the intensity level. we go to 2 as 0-1 is colour brightness and 1-2 is white intensity 
            if self.dir > 0:
                if self.levels[idx] < 2:
                    self.levels[idx] = self.levels[idx] + 0.1
                    if self.levels[idx] > 2:
                        self.levels[idx] = 2
            else:
                if self.levels[idx] > 0:
                    self.levels[idx] = self.levels[idx] - 0.1
                    if self.levels[idx] < 0:
                        self.levels[idx] = 0
                        
        myWaitTime = waitTime
        if self.trigger >= self.numLeds:
            self.trigger = self.numLeds
            done = 1
            for i in range(self.trigger):
                if self.dir > 0:
                    if self.levels[i] < 2:
                        done = 0
                        break
                else:
                    if self.levels[i] > 0:
                        done = 0
                        break
            # All values in range at either full or empty, so trigger next step
            if done == 1:
                # With botDir at 1 all leds are light, at -1 all are off
                if self.dir < 0:
                    self.trigger = 0
                else:
                    self.trigger = 1
                    myWaitTime = 14000
                self.dir = -self.dir
                
        return myWaitTime
    
    # When trigggered count up to numLeds this represents how many leds are in use
    def IncTrigger(self):
        if self.trigger > 0:
            self.trigger += 1
            if self.trigger > self.numLeds:
                self.trigger = self.numLeds
        

ledstrip.py

Python
Main code to run the stair sensor logic and update the LED strip
import time
import random
from neopixel import Neopixel
from stair import Stair
from machine import Pin

numpix = 30
blinky = Pin(25, Pin.OUT)
lightSensor = machine.ADC(27)
pirTop = Pin(21, Pin.IN, Pin.PULL_DOWN)
pirBot = Pin(20, Pin.IN, Pin.PULL_DOWN)
pixels = Neopixel(numpix, 0, 28, "GRB")
stair_time = time.ticks_ms()
blink_time = time.ticks_ms()
start_time = time.time()
stairTop = Stair(numpix, 1)
stairBot = Stair(numpix, 0)

gamma8 = [0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  1,  1,  1,
    1,  1,  1,  1,  1,  1,  1,  1,  1,  2,  2,  2,  2,  2,  2,  2,
    2,  3,  3,  3,  3,  3,  3,  3,  4,  4,  4,  4,  4,  5,  5,  5,
    5,  6,  6,  6,  6,  7,  7,  7,  7,  8,  8,  8,  9,  9,  9, 10,
   10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16,
   17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25,
   25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36,
   37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50,
   51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68,
   69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89,
   90, 92, 93, 95, 96, 98, 99,101,102,104,105,107,109,110,112,114,
  115,117,119,120,122,124,126,127,129,131,133,135,137,138,140,142,
  144,146,148,150,152,154,156,158,160,162,164,167,169,171,173,175,
  177,180,182,184,186,189,191,193,196,198,200,203,205,208,210,213,
  215,218,220,223,225,228,231,233,236,239,241,244,247,249,252,255]

yellow = (255, 255, 0)
orange = (255, 50, 0)
green = (0, 255, 0)
blue = (0, 0, 255)
red = (255, 0, 0)
pink = (255, 64, 64)
cyan = (255, 0, 255)
purple = (0, 255, 255)
white = (255,255,255)
black = (0,0,0)
colour = red
numColours = 7
picColours = [pink, yellow, purple, green, blue, red, cyan, orange]

pixels.brightness(128)
pixels.fill(green)

def GammaCorrect(grbTuple):
    g = gamma8[ int(grbTuple[0]) ]
    r = gamma8[ int(grbTuple[1]) ]
    b = gamma8[ int(grbTuple[2]) ]
    
    return (g, r, b)

def BlendToColour(toSet, levelVal):
    interp = 0.0
    # less than one fade in colour
    if levelVal < 1:
        interp = levelVal
        newCol = (colour[0] * interp, colour[1] * interp, colour[2] * interp)
    else:
    # greater than one fade from colour to white
        interp = levelVal - 1
        newCol = (colour[0] * (1.0 - interp) + white[0] * interp, colour[1] * (1.0 - interp) + white[1] * interp, colour[2] * (1.0 - interp) + white[2] * interp)
    
    pixels.set_pixel(toSet, GammaCorrect(newCol))
     
def RunStairs( currentActive ):
    global colour
    global pirTop
    global pirBot
    waitTime = 50
    topWait = 0
    botWait = 0
    
    # only update if stairs have been triggered
    if stairTop.trigger > 0:
        topWait = stairTop.UpdateValues(waitTime)

    if stairBot.trigger > 0:
        botWait = stairBot.UpdateValues(waitTime)
      
    # find out which wait is the biggest and use that  
    if topWait + botWait > 0:
        if topWait > botWait:
            waitTime = topWait
        else:
            waitTime = botWait
    
    # for each pixel get the highest level to use as blend value
    anyActive = 0
    for i in range(numpix):
        pixVal = 0
        if stairTop.levels[i] > stairBot.levels[i]:
            pixVal = stairTop.levels[i]
        else:
            pixVal = stairBot.levels[i]
        if pixVal > 0:
            anyActive += 1
        BlendToColour(i, pixVal)
    if anyActive == 0:
        colour = picColours[ random.randint(0, numColours) ]
        # Don't retrigger for a little bit
        if currentActive > 0:
            waitTime = 3000
      
    stairTop.IncTrigger()
    stairBot.IncTrigger()
    
    # check the pir sensors if less than half of the array is active
    if currentActive < (numpix / 2):
        if pirTop.value() == 1 and stairTop.trigger == 0:
            stairTop.trigger = 1
        if pirBot.value() == 1 and stairBot.trigger == 0:
            stairBot.trigger = 1    

    pixels.show()
    
    return waitTime, anyActive
    
waitTime = 0
lightLevel = 35000
averageLight = 0.0
active = 0
while True:
    # Check if it's dark enough for stair lights
    if averageLight < lightLevel or active > 0:  
        if time.ticks_diff(time.ticks_ms(), stair_time) > waitTime:
            waitTime, active = RunStairs( active )
            stair_time = time.ticks_ms()
     
    # Every second turn the light on
    if time.time() - start_time > 1:
        blink_time = time.ticks_ms()
        start_time = time.time()
        blinky.value(1)
        averageLight = averageLight + (lightSensor.read_u16() - averageLight) * 0.15
        print(averageLight)
    
    # 100 milliseconds after turning the light on, turn it off
    if time.ticks_diff(time.ticks_ms(), blink_time) > 100:
        blinky.value(0)

    

Credits

craig richardson

craig richardson

3 projects • 2 followers

Comments