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?
- Have
red
green
blue
as properties, so the colors can be changed easily. - Validate RGB values, so minus or numbers bigger than 255 can’t be set.
- Reset function to turn off the light.
- Transition generator to ease into another color.
- A developer mode, so the lights can be tested without a LED strip.
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.
- It must be bigger or equal than 0.
- It must be smaller or equal than 255.
- 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.
Previous Part
How to Connect a LED Strip to a Raspberry Pi
Next Part