a hand holding a remote control with a led strip in the background

Python Class to Control an RGB LED Strip on a Raspberry Pi

Published on:

A python class to use the LED with ease. Step-by-step tutorial. Advanced python knowledge is required.

The individual source code of the script can be found at GitHub Gist: led_controller.py. This is part of a bigger project, which is fully open source. Check out GitHub - 11Firefox11/sunrise-pialarm.

The Goal

A clear and easy way is needed to manage the LED strip. What are the goals?

Implementation

Color Properties

The @property decorator is required in python because we want to set the value of the color and also set the color of the LED. To set the LED too, the RGB pin numbers are required, RGB_PINS is declared for that. Install pigpio module, so it can interact with the pins.

from pigpio import pi
RGB_PINS = {"red": 17, "green": 22, "blue": 24}

class LedController:
    def __init__(self, r: int = 0, g: int = 0, b: int = 0):
        self._r = self._g = self._b = 0
        self.pi = pi() if not DEV_MODE else None
        self.red = r
        self.green = g
        self.blue = b

    @property
    def red(self): return self._r

    @red.setter
    def red(self, val):
        self._r = val # self._r will be our "private" variable which will store the actual value
        self.pi.set_PWM_dutycycle(RGB_PINS["red"], self._r) # apply value to raspberry pi pin

    # ... same code for green and blue with color related things replaced ...

Validate Color Values

Another decorator is required, but this time an own one. The decorator will be applied at the setters. There are 3 things we have to make sure with color values.

  1. It must be bigger or equal than 0.
  2. It must be smaller or equal than 255.
  3. It must be an integer.

Custom decorator can be created by putting a function inside a function. This is called as nested functions. The outer function gets takes the original function as an argument and returns a modified version of it.

# ...

def rgb_value_checker(func):
    def wrapper(self, val):
        if val < 0: val = 0 # 1st check
        elif val > 255: val = 255 # 2nd check
        return func(self, round(float(val))) # 3rd check
    return wrapper

class LedController:
    # ...

    @red.setter
    @rgb_value_checker # apply the decorator
    def red(self, val):
        self._r = val
        self.pi.set_PWM_dutycycle(RGB_PINS["red"], self._r)

    # ... repeat for all setters ...

Reset to Turn Off the LEDs

This one is easy, because we already developed the controllers of the colors. All we have to do is to set them to 0. I am going to use multiple assign because it is somewhat prettier and faster.

# ...

class LedController:
    # ...

    def reset(self): self.red = self.green = self.blue = 0

    # ...

Transition Generator

To generate the colors for the transition we have to use the colour module. This module however uses RGB based on brightness, so we have to convert the values to number between 0 and 1. The range_to function will generate the colors which we will have to convert back to 255 based numbers. This function also returns duplicates if steps are bigger than what is possible, so we have to take care about that too.

from colour import Color
# ...

class LedController:
    # ...

    def transition(self, r=None, g=None, b=None, steps=255):
        # use default current color if some not defined
        if r == None: r = self.red
        if g == None: g = self.green
        if b == None: b = self.blue
        transition_colors = Color(rgb=(self.red/255, self.green/255, self.blue/255)).range_to(Color(rgb=(r/255, g/255, b/255)), steps) # convert colors and run range_to
        toReturn = []
        for color in transition_colors:
            if color not in toReturn: toReturn.append([c * 255 for c in color.rgb])  # filter and convert
        return toReturn

    # ...

Developer Mode

If a LED strip is not available an alternative should be available. I thought about: HTML file and update the background, an image and update it every time. Both of these failed mainly because they were not that effective. Then I discovered the solution: a separate window which is fully blank, and the background will be updated. First I thought about tkinter, but I needed one that can be easily implemented with threading. So I found PySimpleGUI which works fine.

The window will be initialized at the __init__ function if DEV_MODE is on. It needs to run in a thread, so I can change the colors while the window is open. In an infinite loop it will sleep 10 milliseconds and update the background to the current RGB values.

DEV_MODE = True
if not DEV_MODE: from pigpio import pi # because we won't use pigpio in dev mode
if DEV_MODE:
    import PySimpleGUI as sg
    from threading import Thread
#...

class LedController:
    def __init__(self, r: int = 0, g: int = 0, b: int = 0):
        # ...
        if DEV_MODE: self.init_dev_window()

    # ...

    def init_dev_window(self):
        def gui_thread():
            try:
                window = sg.Window("Sunrise Pi Alarm Development", [[]], grab_anywhere=True, resizable=True, size=(100, 100)) # initialize the window
                while True:
                    event, values = window.Read(timeout=10) # sleep
                    window.TKroot.configure(background="#{:02x}{:02x}{:02x}".format(self.red, self.green, self.blue)) # refresh the background, it requires a HEX code
                    if event == sg.WIN_CLOSED:
                        break
                window.Close()
            except Exception as x:
                window.Close()
        gui_thread = Thread(target=gui_thread, daemon=True)
        gui_thread.start()

We are done! Test it out with GitHub Gist: pialarm-test.py.

Read more

a raspberry pi with a LED strip connected to it

How to Connect a LED Strip to a Raspberry Pi

a LED strip on top of an early sunrise view on the beach

The Idea and Details About Sunrise Pialarm