I had an idea for this project for a long time and finally I found some time to implement it. The idea is simple, light up staircase when we step on it! We also decided to go a bit further and also removed annoying wooden plank between floors, repainted wall, and renovated the wood (small cracks, fainted color, etc.)

You can find video of the renovation process and the result on YouTube:

YouTube

Here is the text version of it

Wall and Staircase Renovation

First we've removed annoying wooden plank between floors that builders left because there were too lazy to make a nice wall between floors.

removing wooden plankNothing crazy, all you need for this is a hammer, old chisel, screwdriver and a bit of patience.

applying wall putty

After cleaning the huge hole between floors I have applied Knauf Stucprimer and then I used Knauf Rotband and applied the first layer of wall putty and covered the huge (2-3cm deep) hole between floors. Do not mix too much putty in the single run, it dies quite fast. After applying, let it dry for at least one day (two is better).

removing old carpetWhile the first layer of a wall putty was drying I have removed the old carpet from stairs. I've used old putty knife and a mix of soap and water clean the glue from the paint.

cutting channel for RGB light

Then was the hardest part of the whole project, cutting channels for the RGB lights. I wanted to hide them inside the stair so when you walk up you will not see the LED strips. So I used multitool from Bosh and a 1 cm thick wooden plank to make a first cut (1 cm deep). Then I clamped another wooden plank, so in total it was two centimeters, and made another cut (also approximately 1 cm deep)

chisel the wood

Then I used a chisel and manually removed all wood from the channel. At the end I got a 1x1 cm channel that is 90 cm long, just enough to fit RGB LED strip.

second layer wall putty

For the second layer of a wall putty I used Gyproc Rifino Top, it has smaller grains and can be used as final layer. Let it dry for another day.

sanding a wall

For the third layer (the final one) I used Knauf Finishpasta, that is already pre-mixed and has the smallest possible grain. After that I sanded the wall with a 240 sandpaper.sanding stairs

 

Then I have sanded stairs with 80 and then with 120 sandpaper and removed old sealant.

painting wall

Before painting the wall, I have applied a wall primer (Alabastine voorstrijk grijs) and then applied two layers of a wall paint from Histor.

paint staircase paint stairs

I have applied two layers of a special anti slippery paint to the stairs from Sigma, Sigma S2U Nova Traplak Soft Satin.

Then I started working on the RGB LED part of the project

 

RGB Lights and Motion Sensors

This is the scheme I used to wire the LED lights:

raspberry pi rgb led connectionComponents:

soldering wires to rgb led

First I cut 5m long strips in a 85-90 centimeters blocks. Some of them are a bit longer since on the turns stairs are also longer. Then I soldered wires and started assembling them.

glue RGB LED strip

I've used universal glue to mount them under the stairs.

test RGB LED lights

Do not forget to test every 2-3 stairs, to be sure everting is working as expected.

Then I have mounded two motion sensors, one at the bottom and another at the top of the staircase. I mounded them under the stair (I have drilled a hole to get wires through (I forgot to film it)

connecting wires

Them I've assembled everything under the stairs and connected all wires.

Time to code

code RGB LED logic

I have used rpi_ws281x python library to control LEDs and wrote a simple logic to turn on LED strip if motion is detected:

import time
import argparse
import colorsys

from rpi_ws281x import ws, Color, Adafruit_NeoPixel
import RPi.GPIO as GPIO

# LED strip configuration:

# Bottom - from top to bottom
# 75 75 75 75 75 75 | 75 90 105 85 80                                                                             │
LED_1_COUNT = 177        # Number of LED pixels.                                                        │
LED_1_PIN = 18          # GPIO pin connected to the pixels (must support PWM! GPIO 13 and 18 on RPi 3). │
LED_1_FREQ_HZ = 800000  # LED signal frequency in hertz (usually 800khz)                                │
LED_1_DMA = 10          # DMA channel to use for generating signal (Between 1 and 14)                   │
LED_1_BRIGHTNESS = 255  # Set to 0 for darkest and 255 for brightest                                    │
LED_1_INVERT = False    # True to invert the signal (when using NPN transistor level shift)             │
LED_1_CHANNEL = 0       # 0 or 1

# Top - From bottom to top
# 75 80 95 95 90 85
LED_2_COUNT = 104        # Number of LED pixels.                                                        │
LED_2_PIN = 13          # GPIO pin connected to the pixels (must support PWM! GPIO 13 or 18 on RPi 3).  │
LED_2_FREQ_HZ = 800000  # LED signal frequency in hertz (usually 800khz)                                │
LED_2_DMA = 10          # DMA channel to use for generating signal (Between 1 and 14)                   │
LED_2_BRIGHTNESS = 255  # Set to 0 for darkest and 255 for brightest                                    │
LED_2_INVERT = False    # True to invert the signal (when using NPN transistor level shift)             │
LED_2_CHANNEL = 1       # 0 or 1                                                                        │
LED_STRIP_TYPE = ws.WS2811_STRIP_BRG

# from bottom to top
leds_per_stair = [ 16, 17, 21, 18, 15, 15, 15, 15, 15, 15, 15, 15, 16, 19, 19, 18, 17 ]

def hsv2rgb(h,s,v):
    return tuple(round(i * 255) for i in colorsys.hsv_to_rgb(h,s,v))

def colorWipe(strip1, strip2, led_indexes, color, wait_ms=1):
    """Wipe color across display a pixel at a time."""
    for stair in led_indexes:
        for i in range(len(stair) - 1):
            stair[0].setPixelColor(stair[i + 1], color)
            stair[0].show()
            time.sleep(wait_ms/1000)


def wheel(pos):
    """Generate rainbow colors across 0-255 positions."""
    if pos < 85:
        return Color(pos * 3, 255 - pos * 3, 0)
    elif pos < 170: pos -= 85 return Color(255 - pos * 3, 0, pos * 3) else: pos -= 170 return Color(0, pos * 3, 255 - pos * 3) def rainbowCycle(strip1, strip2, wait_ms=20, iterations=5): """Draw rainbow that uniformly distributes itself across all pixels.""" totalPixels = strip1.numPixels() + strip2.numPixels() for j in range(256 * iterations): for i in range(totalPixels): if i >= strip1.numPixels():
                strip2.setPixelColor(strip2.numPixels() - (i - strip1.numPixels()) - 1, wheel(
                    (int(i * 256 / totalPixels) + j) & 255))
            else:
                strip1.setPixelColor(i, wheel(
                    (int(i * 256 / totalPixels) + j) & 255))
        strip1.show()
        strip2.show()
        time.sleep(wait_ms / 1000.0)

def slowOnBottomToTop(led_indexes):
    """Draw rainbow that uniformly distributes itself across all pixels."""
    for stair in led_indexes:
        for value in range(4):
            for i in range(len(stair) - 1):
                color = hsv2rgb(0.05, 1, value/10.0)
                stair[0].setPixelColor(stair[i+1], Color(color[0], color[1], color[2]))
            stair[0].show()
            time.sleep(5/1000)

def slowOnTopToBottom(led_indexes):
    for stair in reversed(led_indexes):
        for value in range(4):
            for i in range(len(stair) - 1):
                color = hsv2rgb(0.05, 1, value/10.0)
                stair[0].setPixelColor(stair[i+1], Color(color[0], color[1], color[2]))
            stair[0].show()
            time.sleep(5/1000)

def rainbowPerStair(strip1, strip2, led_indexes, wait_ms=20, iterations=50):
    """Draw rainbow that uniformly distributes itself across pixels of each stair."""
    for j in range(256 * iterations):
        for stair in led_indexes:
            for i in range(len(stair) - 1):
                stair[0].setPixelColor(stair[i+1], wheel((int(i * 256 / (len(stair) - 1)) + j) & 255))
        strip1.show()
        strip2.show()
        time.sleep(wait_ms / 1000.0)

def map_leds(strip1, strip2):
    led_indexes = []
    indexes_used = 0
    for i in range(len(leds_per_stair)):
        if i < 11:
            led_indexes.append([strip1])
            for j in range(leds_per_stair[i]):
                led_indexes[i].append(176 - indexes_used)
                indexes_used = indexes_used + 1
            # bottom part

            if indexes_used == 177:
                indexes_used = 0
        else:
            led_indexes.append([strip2])
            for j in range(leds_per_stair[i]):
                led_indexes[i].append(indexes_used)
                indexes_used = indexes_used + 1
    return led_indexes

if __name__ == '__main__':
    # Process arguments
    parser = argparse.ArgumentParser()
    parser.add_argument('-c', '--clear', action='store_true', help='clear the display on exit')
    args = parser.parse_args()

    # Create NeoPixel objects with appropriate configuration for each strip.                            │
    strip1 = Adafruit_NeoPixel(LED_1_COUNT, LED_1_PIN, LED_1_FREQ_HZ, LED_1_DMA, LED_1_INVERT, LED_1_BRIGHTNESS, LED_1_CHANNEL, LED_STRIP_TYPE)

    strip2 = Adafruit_NeoPixel(LED_2_COUNT, LED_2_PIN, LED_2_FREQ_HZ, LED_2_DMA, LED_2_INVERT, LED_2_BRIGHTNESS, LED_2_CHANNEL, LED_STRIP_TYPE)

    led_indexes= map_leds(strip1, strip2)

    # Intialize the library (must be called once before other functions).                               │
    strip1.begin()
    strip2.begin()

    GPIO.setwarnings(False)
    GPIO.setmode(GPIO.BOARD)
    GPIO.setup(11, GPIO.IN)
    GPIO.setup(13, GPIO.IN)

    print('Press Ctrl-C to quit.')

    #rainbowCycle(strip1, strip2)
#    rainbowPerStair(strip1, strip2, led_indexes)
    try:
        while True:
            top = GPIO.input(11)
            bottom = GPIO.input(13)
            if bottom == 1:
                slowOnBottomToTop(led_indexes)
                time.sleep(15)
                colorWipe(strip1, strip2, led_indexes, Color(0, 0, 0), 5)
            elif top == 1:
                slowOnTopToBottom(led_indexes)
                time.sleep(15)
                colorWipe(strip1, strip2, led_indexes, Color(0, 0, 0), 5)

    except KeyboardInterrupt:
        if args.clear:
            colorWipe(strip1, strip2, led_indexes, Color(0, 0, 0), 2)

And the final result, RGB rainbow mode:

RGB-LED-Staircase-1 RGB-LED-Staircase-catRGB-LED-Staircase-black-catAnd normal mode:

RGB-LED-Staircase-2

If you have any questions, feel free to ask in comments!