paint-brush
[DIY] Use Python to Control the Pins of a Raspberry Pi to Light Up LEDsby@avcourt
5,896 reads
5,896 reads

[DIY] Use Python to Control the Pins of a Raspberry Pi to Light Up LEDs

by Andrew VaillancourtSeptember 18th, 2019
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Using Python to Control the Pins of a Raspberry Pi to Light Up LEDs, we'll just be turning on a few LEDs. The main concepts covered here could easily be extended to trigger more complex events. This is a fun physical computing project that would be great for teaching kids the basics of Python programming while giving them the satisfaction of seeing their code change things in the real world. This tutorial will cover:Installing Raspbian on a Pi,Configuring remote access to a Pi via SSH and setting up a Python development environment on the Pi.Writing a simple Python program to control the GPIO pins of the Pi to turn LEDs on/off.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - [DIY] Use Python to Control the Pins of a Raspberry Pi to Light Up LEDs
Andrew Vaillancourt HackerNoon profile picture

Want to learn how to control the GPIO (general-purpose input/output) pins of a Raspberry Pi with Python? Well, you're in the right place.

In this project we'll just be turning on a few LEDs, but the main concepts covered here could easily be extended to trigger more complex events.

This is a fun physical computing project that would be great for teaching kids the basics of Python programming while giving them the satisfaction of seeing their code change things in the real world!

Project Overview

This tutorial will cover:

  • Installing Raspbian on a Raspberry Pi
  • Configuring remote access to a Raspberry Pi via SSH
  • Setting up a Python development environment on a Raspberry Pi
  • Setting up and connecting a breadboard circuit with LEDs to the Pi
  • Writing a simple Python program to control the GPIO pins of the Pi to turn LEDs on/off

For this project you will need:

  • Raspberry Pi
  • micro-SD card
  • 9 - LEDs (preferably multicolored: 3xR, 1xG, 2xB, 3xY in this example)
  • 9 - 1k resistors (anything over 100Ω should be fine)
  • breadboard
  • 10 - GPIO pin connecting cables

There are many kits available on Amazon for under $20.

If you already have access (either remotely, or via a desktop environment) to a Raspberry Pi, you can skip ahead to the Setting Up Your Development Environment section. If not, read on.

Note: terminal commands may be prefixed by a

localmachine:~$
or
pi:~$
prompt in parts where it may not be clear which machine we are logged into. These prefixes are not meant to be typed and only serve as a reminder of which machine we should be logged into at that point.

Installing Raspbian

The first step is to set up a Raspberry Pi with a headless version of Raspbian and enable SSH access. If you want to attach a monitor to your Pi and develop locally on it, download the desktop version.

Download your choice of Raspbian here and unzip it.

The next step is to write the Raspbian image onto a micro-SD card. I'm going to do this from the Linux command-line, but you can use a GUI disk writer like Etcher if you'd like.

Note :

dd
is often not -so-jokingly referred to as ‘Disk Destroyer’ because if you type even one wrong character in a
dd
command, you can instantly and permanently wipe out an entire drive of valuable data. Consider yourself warned!

Before you plug in the SD card to your computer open a terminal and run:

df -h -x squashfs -x tmpfs

You should see output similar to this:

Filesystem      Size  Used Avail Use% Mounted on
udev             16G     0   16G   0% /dev
/dev/sda1       440G  364G   53G  88% /
/dev/sdc        1.8T  548G  1.2T  32% /media/backups

This is listing the file systems on our machine.

Now plug in the SD card into your laptop or desktop and run it again.

df -h -x squashfs -x tmpfs

You should see a new device:

Filesystem      Size  Used Avail Use% Mounted on
udev             16G     0   16G   0% /dev
/dev/sda1       440G  364G   53G  88% /
/dev/sdc        1.8T  548G  1.2T  32% /media/backups
/dev/sdf        16G     0    6GB   6% /media/sandisk-SD

This is the device name of our SD card. In this case

sdf
.

Now run

dd
, specifying the path to your extracted Raspbian image after the
if=
flag:

dd if=/path/to/2019-07-10-raspbian-buster-lite.img of=/dev/sdf bs=1m status=progress

Wait for dd to finish and you will have a Raspbian image written to your SD card with a

boot
and
rootfs
partition.

Enabling Remote Login to the Pi with SSH

Now that we have Raspbian on our micro-SD card, before we install it in the Raspberry Pi, we need to add an empty file named

ssh
(without any extension) to the
boot
partition of the SD card. This will enable us to connect to the Raspberry Pi via SSH from a computer on our network.

If you are using a desktop version of Raspbian and plan to develop directly on the Pi you can skip to the next section (Setting Up Your Development Environment).

I will assume you are using a Mac or Linux machine. If you are using Windows, see this page for using SSH from Windows.

Most Linux distros should already have an SSH client like OpenSSH installed and enabled. You can check by typing

ssh
in a terminal. You should see output similar to the following:

username@host:~$ ssh

usage: ssh [-1246AaCfGgKkMNnqsTtVvXxYy] [-b bind_address] [-c cipher_spec]
[-D [bind_address:]port] [-E log_file] [-e escape_char]
[-F configfile] [-I pkcs11] [-i identity_file]
[-J [user@]host[:port]] [-L address] [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port] [-Q query_option] [-R address] [-S ctl_path] [-W host:port] [-w local_tun[:remote_tun]]
[user@]hostname [command]

If the command is not recognized, it should give you some suggestions on how to install it. On a Debian based Linux distribution, like Ubuntu or Raspbian, you can use

APT
(Advanced Package Tool) to install OpenSSH:

sudo apt install openssh-server

Enable the ssh service:

sudo systemctl enable ssh

Start the ssh service:

sudo systemctl start ssh

Now that we've verified we have an SSH client running on our local machine, put the SD card in the Raspberry Pi, connect it your network and power it up. It's easier to just use an ethernet cable, but if you will be connecting your Pi to the network via Wi-Fi, see this guide.

We'll need to find the private IP address of our Pi. This can be done by pinging the default hostname of the Pi which is

raspberrypi
.

ping raspberrypi

And you should see output similar to:

PING raspberrypi.isp.provider.net (10.0.0.14) 56(84) bytes of data.
64 bytes from raspberrypi.isp.provider.net (10.0.0.14): icmp_seq=1 ttl=64 time=0.407 ms
64 bytes from raspberrypi.isp.provider.net (10.0.0.14): icmp_seq=2 ttl=64 time=0.373 ms
64 bytes from raspberrypi.isp.provider.net (10.0.0.14): icmp_seq=3 ttl=64 time=0.350 ms
64 bytes from raspberrypi.isp.provider.net (10.0.0.14): icmp_seq=4 ttl=64 time=0.474 ms
64 bytes from raspberrypi.isp.provider.net (10.0.0.14): icmp_seq=5 ttl=64 time=0.359 ms
64 bytes from raspberrypi.isp.provider.net (10.0.0.14): icmp_seq=6 ttl=64 time=0.512 ms
64 bytes from raspberrypi.isp.provider.net (10.0.0.14): icmp_seq=7 ttl=64 time=0.389 ms
^C
--- raspberrypi.isp.provider.net ping statistics ---
7 packets transmitted, 7 received, 0% packet loss, time 6125ms
rtt min/avg/max/mdev = 0.350/0.409/0.512/0.057 ms

If the

raspberrypi
hostname can't be resolved by your machine, you can find out your local network's information and scan it for connected devices using
ifconfig
and
nmap
. You check out an article I wrote on how to do that here.

From the output of the

ping
command we can see that the IP address of the Raspberry Pi is
10.0.0.14
. So let's try logging into the Pi as the default user
pi
from our local machine using ssh:

ssh pi@raspberrypi

Or using the IP address we discovered above:

ssh pi@10.0.0.14

You should be greeted with something like this if this is the first time connecting to your Pi:

The authenticity of host 'raspberrypi (10.0.0.14)' can't be established.
ECDSA key fingerprint is SHA256:qeHIC464zGhEecXmkEiqADemCp96/HnJ76ZJOF8sc8Y.
Are you sure you want to continue connecting (yes/no)?

Type

y
and you'll be greeted with a prompt asking for your password. Use the default password
raspberry
.

After entering the password you will be greeted with some system information about your Raspberry Pi and will be prompted to change the default password. So let's do that by typing

passwd
:

pi@raspberrypi:~$ passwd

After setting up a new password, let's logout and try logging back in with our new password:

localmachine:~$ ssh pi@raspberrypi

Okay, we’ve verified that everything’s working, but it’d be nice if we didn’t have to type in the username and hostname (or worse; the IP address) every time we wanted to log into our Pi). I definitely have a hard time remembering IP addresses, usernames, and hostnames.

Let's log back out of our Pi and edit the ssh

config
file on our local machine so we can log in using one easy to remember command.

You should have a

.ssh
directory in the home directory of your local machine. If not, you can create one. Let's change into that directory:

localmachine:~$ cd ~/.ssh

If there's not a file called

config
in the
.ssh
directory, create one and add the following lines:

Host pi
    Hostname raspberrypi # or ip addr e.g. 10.0.0.14
    User pi

Save the file.

You should now be able to log into the Raspberry Pi by typing

ssh pi
:

localmachine:~$ ssh pi

Okay, so that's easier than typing in the username and hostname/IP address every time we log in. However, we still need to type in a password each time we connect to our Pi which can get a little annoying after a while.

To enable remote access to our Raspberry Pi without having to type in a password, let's now set up some SSH keys.

Make sure you're in the

.ssh
directory of your local machine and type:

ssh-keygen -f pi

You will be prompted with:

Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 

Leave the passphrase empty. This password is for added security. The whole point of setting up these keys in this case is so we don't have to use a password to login to our Raspberry Pi. Just hit

enter
to leave it blank.

Don't worry using an SSH key without a passphrase is still much more secure than using only a password without an SSH key provided you have disabled root and password login. With password and root logins disabled, an attacker would need access to the private key on your local machine to gain access to the Raspberry Pi. If you wish to disable root login and password logins after we generate our SSH keys, see this article on how to do that.

The ssh-agent will now generate a pair of private and public keys.

Your identification has been saved in pi.
Your public key has been saved in pi.pub.
The key fingerprint is:
SHA256:AK1zJarZrhBYsU3ertvfsa448wI+ZVjrSwgcs6BJjVfrt/M user@localmachine
The key's randomart image is:
+---[RSA 2048]----+
|  . +o. . .      |
|   = +.o.+       |
|  o +o=o+ +      |
|.. .+..+ * =.    |
|o  +oo  S +o..   |
| .o .    +oo+    |
|.  .     .E=+    |
| =  .     o.oo   |
|  ..       ..o.  |
+----[SHA256]-----+

The contents of your

.ssh
directory on your local machine should now look something like:

authorized_keys  known_hosts   config  pi.pub  pi   

In the last command,

ssh-keygen
generated a private and public key pair. We'll now need to copy the public key
pi.pub
from our local machine to the
authorized_keys
file on our Raspberry Pi. You can do this manually by copying the text contents of
pi.pub
and pasting it into the Pi's
authorized_keys
file. However, the
ssh-agent
, installed with
OpenSSH-server
, has a tool that makes this a breeze.

Just type the following command in a terminal from within the

.ssh
directory on your local machine:

ssh-copy-id -i pi.pub pi

You'll see the following output:

/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "pi.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'pi'"
and check to make sure that only the key(s) you wanted were added.

So, let's try logging in:

ssh pi

Right on. We can now quickly and easily log into our Raspberry Pi with one easy-to-remember command. We can also transfer files between our local machine and the Raspberry Pi in the same manner using

sftp
:

localmachine:~$ sftp pi
Connected to pi.
sftp> put source_code.py

See video if there's any confusion. It covers these steps in detail.


Setting Up Your Development Environment

Now that we have SSH access to our Raspberry Pi, it's time to set up our Python development environment.

Let's start by logging into our Pi and updating our packages.

If you're logging in remotely via SSH:

localmachine:~$ ssh pi

or, if you're developing directly on the Raspberry Pi with a desktop version of Raspbian with a monitor, just open a terminal.

Python 3 should already be installed on the Raspberry Pi, but you can check by running:

python3 -V

If it's not installed, run:

sudo apt install python3

We’re going to want to use the Python package manager

pip
to install Python libraries that aren't part of the standard library. It can be installed with:

sudo apt install python3-pip

I have a public GitHub repository set up with all the code examples and schematics used in this tutorial. You can find it here. Let's install

git
so you can clone the repository to your machine if you'd like.

sudo apt install git

You can run

git clone https://github.com/avcourt/restful-pi
to copy the contents of my repository to your machine. It contains the schematics of the LED circuit above in the
img/
directory. It also contains the source code of the Python program
pin_controller.py
that we will begin to write shortly.

The repository is also home to a REST API back-end written using the Python micro-web framework Flask. This Flask app (called

app.py)
allows you to control the pins of a Raspberry Pi over the internet by making HTTP requests to a Flask server running on a Raspberry Pi but will not be covered in this article.

I prefer to use

vim
for text editing remotely, but pick whichever text editor you prefer:

sudo apt install vim

We’ll also need a Python library to enable us to control the GPIO pins of the Raspberry Pi using Python. Generally, it's good practice to use a Python virtual environment to handle all your Python dependencies, but for simplicity, we're just going install all of our packages globally. I would suggest looking up a tool like Pipenv if virtual environments are new to you.

Let's use

pip
to install the Python library
RPi.GPIO
:

pip3 install RPi.GPIO

That should be all the software we need to start writing a Python program to control the pins of our Raspberry Pi. Let's move on to setting up our LED circuit using the breadboard.

Breadboard Set Up

The code example that will follow this section uses the following configuration for pins and LEDs:

{"pin_num": 23, "color": "red",}
{"pin_num": 24, "color": "yellow"},
{"pin_num": 25, "color": "blue"},
{"pin_num": 22, "color": "red"},
{"pin_num": 12, "color": "yellow"},
{"pin_num": 16, "color": "blue"},
{"pin_num": 20, "color": "red"},
{"pin_num": 21, "color": "green"},
{"pin_num": 13, "color": "yellow"}

These pin numbers refer to the GPIO pin numbering, not the generic pin numbering:

If you set up your breadboard using the pin configuration shown above, your circuit should look like this:

Python Program to Turn LEDs On and Off

Now that our breadboard circuit is set up, let's start writing some Python code to light up some of these LEDs!

We'll start by logging into our Raspberry Pi and making a directory to put our LED Python program in:

pi:~$ mkdir led_pgms
pi:~$ cd led_pgms

Next, open your favorite text editor and create a file called

pin_controller.py
.

We'll start by importing

RPi.GPIO
and creating a list of pins using the configuration shown up above in the schematic:

import RPi.GPIO as GPIO

pins = [{'pin_num': 23, 'color': 'red'},
        {'pin_num': 24, 'color': 'yellow'},
        {'pin_num': 25, 'color': 'blue'},
        {'pin_num': 22, 'color': 'red'},
        {'pin_num': 12, 'color': 'yellow'},
        {'pin_num': 16, 'color': 'blue'},
        {'pin_num': 20, 'color': 'red'},
        {'pin_num': 21, 'color': 'green'},
        {'pin_num': 13, 'color': 'yellow'}]

If your circuit varies when compared to the schematic up above, either in LED color or the GPIO pin numbers that are connected to the breadboard, this

pins
list is the place to change it.

Next, let's tell

RPi.GPIO
to use the GPIO pin numbering format, not the generic pin numbering:

GPIO.setmode(GPIO.BCM)  # use GPIO numbering, not generic

And finally let's set up all our pins as output power pins and initialize their state to

LOW
(off):

for pin in pins:
    GPIO.setup(pin['pin_num'], GPIO.OUT, initial=GPIO.LOW)

The above code snippet loops through our list of pins (a 'pin' being a Python dictionary containing 2 keys: the pin number and led color associated with that GPIO pin), and gets the integer pin value stored in each pin dictionary and sets it as an output pin so we can turn the power to that GPIO pin on or off.

So far our program should like this:

import RPi.GPIO as GPIO

pins = [{'pin_num': 23, 'color': 'red'},
        {'pin_num': 24, 'color': 'yellow'},
        {'pin_num': 25, 'color': 'blue'},
        {'pin_num': 22, 'color': 'red'},
        {'pin_num': 12, 'color': 'yellow'},
        {'pin_num': 16, 'color': 'blue'},
        {'pin_num': 20, 'color': 'red'},
        {'pin_num': 21, 'color': 'green'},
        {'pin_num': 13, 'color': 'yellow'}]

GPIO.setmode(GPIO.BCM)  # use GPIO numbering, not generic


# setup all pins based on above configuration
for pin in pins:
    GPIO.setup(pin['pin_num'], GPIO.OUT, initial=GPIO.LOW)

Now that we have all the set up taken care of, let's write a function that will turn a specific color of LEDs on or off:

def toggle_color(color: str, state: str):
    pin_nums = [pin['pin_num'] for pin in pins if pin['color'] == color]
    for pin_num in pin_nums:
        if state == 'on':
            GPIO.output(pin_num, GPIO.HIGH)
        elif state == 'off':
            GPIO.output(pin_num, GPIO.LOW)

The line:

pin_nums = [pin['pin_num'] for pin in pins if pin['color'] == color]

uses a Python list comprehension to create a list of the GPIO pin numbers that power an LED of the function parameter

color
. So if we call
toggle_color('red, 'on')
, this statement will loop through the
pins
list and collect the pin numbers that are associated with a red LED. In this case if
color='red'
,
pin_nums
=
[23, 22, 20]
.

List comprehensions are a concise way of creating lists; a bit of syntactic sugar in Python.They are considered a "Pythonic" way of creating lists while programming in Python. The above code snippet is logically equivalent to:

    pin_nums = []
    for pin in pins:
        if pin['color'] == color:
            pin_nums.append(pin['pin_num'])

Let's save this file as

pin_controller.py
and open an interactive Python shell while in the same directory as
pin_controller.py
to test out our newly created
toggle_color
function:

pi:~$ python3

You will be greeted with an interactive command prompt:

Python 3.7.3 (default, Apr  3 2019, 05:39:12) 
[GCC 8.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 

The Python Interactive Interpreter allows us to run commands and have them executed immediately. Very useful for experimenting and debugging.

Let's start by importing the code we just wrote:

>>> import pin_controller

This will execute each line of the code contained in

pin_controller.py
.

We can now test out the

toggle_color()
function we just wrote:

>>> pin_controller.toggle_color('red', 'on')

Hopefully, if we set our breadboard circuit up correctly, the 3 red LEDs on the board should have turned on. Lets turn them off:

>>> pin_controller.toggle_color('red', 'off')

Cool! Let's exit out of the Python shell by typing

exit()
and write a couple more functions.

Open up

pin_controller.py
again and let's add two functions that turn all the pins on or off:

def all_on():
    for pin in pins:
        GPIO.output(pin['pin_num'], GPIO.HIGH)


def all_off():
    for pin in pins:
        GPIO.output(pin['pin_num'], GPIO.LOW)

Save the file and test the functions out again in a new interactive Python shell:

python3
Python 3.7.3 (default, Apr  3 2019, 05:39:12) 
[GCC 8.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pin_controller
>>>

Turn all LEDs on:

>>> pin_controller.all_on()

Right on! All the LEDs just lit up at once! Let's turn them all off now:

>>> pin_controller.all_off()

Don't worry if you get a warning about a GPIO channel already being in use. This warning is being shown because we didn't set the output state to off for all the active GPIO pins before our program terminated. You can add the following line to your program to suppress these warning if you'd like:

GPIO.setmode(GPIO.BCM)  # use GPIO numbering, not generic
## add the following line vvv
GPIO.setwarnings(False) # suppress pin channel warnings

Okay so that's kinda cool, but we can probably write a more interesting function. Let's write a function that uses the

all_on()
and
all_off()
functions we just wrote to make a strobe effect. We'll want to use the
time.sleep()
function so we'll need to import
time
from the Python standard library. Add the following to the top of
pin_controller.py
:

import time

Now we'll write a function that turns all the LEDs on and then off in a regular period called

strobe_reg()
. Think about how you might write this function yourself before looking below:

def strobe_reg(period=0.5):
    while True:
        all_on()
        time.sleep(period)
        all_off()
        time.sleep(period)

This function simply creates an infinite loop and turns all the LEDs on or off every half a second. This function also uses a default argument called

period
. If
strobe_reg()
is called without an argument, the period value used in the
sleep()
function defaults to 0.5 seconds, however, if an argument is provided to the function that value will be used as the period:

e.g.

strobe_reg(0.1)
would have a faster strobe effect

Let's add the

strobe_reg()
function to
pin_controller.py
and create one more similar function. This function will have some randomization to it provided by the
random
module in the Python standard library. Add
import random
to the top of
pin_controller.py
and then add this function below
strobe_reg():

def strobe_rand(min_time=0, max_time=1.2):
    while True:
        all_on()
        time.sleep(random.uniform(min_time, max_time))
        all_off()
        time.sleep(random.uniform(min_time, max_time))

strobe_rand()
uses default values again to provide upper and lower ranges to the
random.uniform()
method.
random.uniform(a,b)
return a random float in the range [a,b] for a more "random" strobe effect.

Let's save

pin_controller.py
and try out the new functions again in the Python shell:

python3
Python 3.7.3 (default, Apr  3 2019, 05:39:12) 
[GCC 8.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pin_controller
>>> pin_controller.strobe_reg()

Note: since

strobe_reg()
relies on an infinite loop, you'll have to type
ctrl+c
to kill the last command and return control to the Python shell. This will apply to all of our functions that use an infinite loop.

Hit

ctrl+c
to stop
strobe_reg()
:

^CTraceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/pi/restful-pi/pin_controller.py", line 63, in strobe_reg
    time.sleep(period)
KeyboardInterrupt

and let's try

strobe_reg()
with a shorter period:

>>> pin_controller.strobe_reg(0.1)

You can start to see how you can get quite creative with your own "lightshow" functions.

I'll give you 3 more functions to try out, but then I want you to create your own.

Add the following code snippet to the bottom of

pin_controller.py
:

def wave_reg(period=0.1):
    while True:
        for pin in pins:
            GPIO.output(pin['pin_num'], GPIO.HIGH)
            time.sleep(period)

        for pin in reversed(pins):
            GPIO.output(pin['pin_num'], GPIO.LOW)
            time.sleep(period)


def wave_rand(min_time=0, max_time=0.4):
    while True:
        period = random.uniform(min_time, max_time)
        for pin in pins:
            GPIO.output(pin['pin_num'], GPIO.HIGH)
            time.sleep(period)

        period = random.uniform(min_time, max_time)
        for pin in reversed(pins):
            GPIO.output(pin['pin_num'], GPIO.LOW)
            time.sleep(period)


def wave_rand_ex(min_time=0, max_time=0.4):
    while True:
        for pin in pins:
            GPIO.output(pin['pin_num'], GPIO.HIGH)
            time.sleep(random.uniform(min_time, max_time))

        for pin in reversed(pins):
            GPIO.output(pin['pin_num'], GPIO.LOW)
            time.sleep(random.uniform(min_time, max_time))

These wave functions use the

reversed()
method to reverse the pin list and create a sort of up-and-down wave effect. If you were to remove one of the loops it would simply turn on the lights in one direction only. Try it out! If you were using a large list it would probably be a good idea to create the reversed pin list before the while loop so we'd only have to call the reversed() method once, but in this program, the performance increase is probably negligible.

Your

pin_controller.py
program should now look something like:

import RPi.GPIO as GPIO
import random
import time

pins = [{'pin_num': 23, 'color': 'red'},
        {'pin_num': 24, 'color': 'yellow'},
        {'pin_num': 25, 'color': 'blue'},
        {'pin_num': 22, 'color': 'red'},
        {'pin_num': 12, 'color': 'yellow'},
        {'pin_num': 16, 'color': 'blue'},
        {'pin_num': 20, 'color': 'red'},
        {'pin_num': 21, 'color': 'green'},
        {'pin_num': 13, 'color': 'yellow'}]

GPIO.setmode(GPIO.BCM)  # use GPIO numbering, not generic
GPIO.setwarnings(False)

# setup all pins based on above configuration
for pin in pins:
    GPIO.setup(pin['pin_num'], GPIO.OUT, initial=GPIO.LOW)


def toggle_color(color: str, state: str):
    pin_nums = [pin['pin_num'] for pin in pins if pin['color'] == color]
    for pin_num in pin_nums:
        if state == 'on':
            GPIO.output(pin_num, GPIO.HIGH)
        elif state == 'off':
            GPIO.output(pin_num, GPIO.LOW)


def color_on(color: str):
    toggle_color(color, 'on')


def color_off(color: str):
    toggle_color(color, 'off')


def all_on():
    for pin in pins:
        GPIO.output(pin['pin_num'], GPIO.HIGH)


def all_off():
    for pin in pins:
        GPIO.output(pin['pin_num'], GPIO.LOW)


def strobe_reg(period=0.5):
    while True:
        all_on()
        time.sleep(period)
        all_off()
        time.sleep(period)


def strobe_rand(min_time=0, max_time=1.2):
    while True:
        all_on()
        time.sleep(random.uniform(min_time, max_time))
        all_off()
        time.sleep(random.uniform(min_time, max_time))


def wave_reg(period=0.1):
    while True:
        for pin in pins:
            GPIO.output(pin['pin_num'], GPIO.HIGH)
            time.sleep(period)

        for pin in reversed(pins):
            GPIO.output(pin['pin_num'], GPIO.LOW)
            time.sleep(period)


def wave_rand(min_time=0, max_time=0.4):
    while True:
        period = random.uniform(min_time, max_time)
        for pin in pins:
            GPIO.output(pin['pin_num'], GPIO.HIGH)
            time.sleep(period)

        period = random.uniform(min_time, max_time)
        for pin in reversed(pins):
            GPIO.output(pin['pin_num'], GPIO.LOW)
            time.sleep(period)


def wave_rand_ex(min_time=0, max_time=0.4):
    while True:
        for pin in pins:
            GPIO.output(pin['pin_num'], GPIO.HIGH)
            time.sleep(random.uniform(min_time, max_time))

        for pin in reversed(pins):
            GPIO.output(pin['pin_num'], GPIO.LOW)
            time.sleep(random.uniform(min_time, max_time))

Experiment in the Python Interactive Interpreter by using different values for the functions that take timing parameters or try changing the source code a bit and see what happens. After that, write your own "lightshow" functions. It'd be great if you shared them down in the comments below!

Hope you learned something and had some fun while you were at it!

a short video going over the the initial set up and testing some of these functions in the Python shell. All the code can also be found on my GitHub profile.

Thanks for reading and don't forget to leave a comment with any lightshow functions you're proud of!

You can follow me on Twitter @avcourt for similar content. Happy coding!