RGB-LED Lesson 5 – Creating a Graphical User Interface

Introduction

This lesson follows on from the previous Python lessons such as RGB-LED Lesson 1 – Creating Python Libraries (& Colours) and the RGB-LED Lesson 1 – Extra Credit Solution

The first part of this lesson does not require any hardware, as we will be looking at creating and controlling a on screen Graphical User Interface, we will then interface to the RGB-LED kit in the second part.

Part 1: Introducing the GUI

When writing software you will often need to gain input from the user, while the command line terminal can be very powerful, it is not ideal for providing friendly and intuitive interface.  Fortunately, there are many libraries which provide powerful and easy to use tools to construct custom interfaces and graphical user interfaces (GUI).

A popular option is Tkinter, which provides common controls, such as buttons, lists, drop-down boxes as you typically see in most window type applications.  Tkinter also includes basic drawing tools, so can be used quite effectively for basic games and graphics too.

Since we cover the topics here quite quickly, it is recommended that you create new files in a new directory, so you have each stage available to review.  By the end of this section you should have the following files:

newInterfaceTK.py

keyinputInterfaceTK.py

RGBInterfaceTK.py

Plus a modified version of rgbled.py

A New Window

The first step with trying something new is to do something simple.  Therefore, we shall create an application with one button.

01-tkwindow

Create a new file called newInterfaceTK.py.

Script: newInterfaceTK.py

#!/usr/bin/python

from Tkinter import * #Note Tkinter for python 2.*, tkinter for python 3+
# if you are using Python 3, comment out the previous line
# and uncomment the following line
# from tkinter import *

#Set display sizes
WINDOW_W = 500
WINDOW_H = 100
def createDisplay():
 global tk
 # create the tk window - within which
 # everything else will be built.
 tk = Tk()
 #Add a canvas area ready for drawing on
 canvas = Canvas(tk, width=WINDOW_W, height=WINDOW_H)
 canvas.pack()
 #Add an exit button
 btn = Button(tk, text="Exit", command=terminate)
 btn.pack()
 # Start the tk main-loop (this updates the tk display)
 tk.mainloop()

def terminate():
 global tk
 tk.destroy()

def main():
 createDisplay()

if __name__ == '__main__':
 main()

When you run this file, the function createDisplay() shall build up the objects which make up the application.

02-tkobjects

It will place the canvas and btn into the tk object, and the pack() command arranges them automatically and ensures the tk object is resized to fit them (there are other methods which provide more control but pack() is fine for our needs).

The button is created with the command attribute set, this creates a link of the click of the button to that particular function, in this case terminate().  The terminate() function calls tk.destroy(), which as you can probably imagine closes the window and the program will end.

Creating Simulated Lights

We shall use Tkinter to create a simple GUI for controlling our RGB-LED boards.

First we shall draw 5 squares on our canvas space, neatly spaced out (these will be our graphical versions of our LEDs).

Create a copy of newInterfaceTK.py and call it keyinputInterfaceTK.py

Script: keyinputInterfaceTK.py

Add the following values near the top of the file

#Set display sizes
BUTTON_SIZE = 100
NUM_BUTTON = 5
MARGIN = 5
WINDOW_W = MARGIN+((BUTTON_SIZE+MARGIN)*NUM_BUTTON)
WINDOW_H = (2*MARGIN)+BUTTON_SIZE

#Set colours
# R G B
BLACK = '#000000'
BRIGHTRED = '#ff0000'
RED = '#9b0000'

Adjust the createDisplay() function as follows:

def createDisplay():
  global tk
  # create the tk window - within which
  # everything else will be built.
  tk = Tk()
  #Add a canvas area ready for drawing on
  canvas = Canvas(tk, width=WINDOW_W, height=WINDOW_H, background=BLACK)
  canvas.pack()
  #Add some "lights" to the canvas
  light = []
  for i in range(0,NUM_BUTTON):
    x = MARGIN+((MARGIN+BUTTON_SIZE)*i)
    light.append(canvas.create_rectangle(x,MARGIN,
           x+BUTTON_SIZE,BUTTON_SIZE+MARGIN,fill=RED))
  #Add an exit button
  btn = Button(tk, text="Exit", command=terminate)
  btn.pack()
  # Start the tk main-loop (this updates the tk display)
  tk.mainloop()

The maths may look a little odd, but all we are doing is using a few set values to determine all the other points we need.

The size of the canvas is determined by the number of buttons and the spacing we want between them.  The position and size of the rectangle (or square in this case) is defined by providing co-ordinates for the top-left corner, and then the bottom-right (the space inbetween creating the shape).  It is good to do things this way, since it means we can adjust a few values and the remaining items “should” (if we have got it right) adjust automatically.

If you would prefer to use other colours, then you can use Green and Blue by adjusting the values (and names) accordingly:

BRIGHTGREEN    = '#00ff00'
GREEN          = '#009b00'
BRIGHTGREEN    = '#0000ff'
GREEN          = '#00009b'

When you run this code, you should get the following display:

03-tkbuttons

Adding Input

At the moment our GUI doesn’t do much and neither do the lights we have added.  We will use Tkinter to monitor for specific key presses made on our keyboard and then control the virtual lights in the display.

When we create our display, we need to let Tkinter know that we want it to call a function when certain keys are pressed (this way, our program does nothing if other keys are used since Tkinter will ignore them).  Tkinter calls this process “binding” and links a specific event to a function (just like we did with the “Exit” button previously).

Add the following to the createDisplay() function, before the call to tk.mainloop():

# Create keyboard bindings
tk.bind_all('<KeyPress-1>', handleInput)
tk.bind_all('<KeyPress-2>', handleInput)
tk.bind_all('<KeyPress-3>', handleInput)
tk.bind_all('<KeyPress-4>', handleInput)
tk.bind_all('<KeyPress-5>', handleInput)

This links the key press of the number keys 1, 2, 3, 4 and 5 to the function handleInput(event).

Make sure that canvas and light are added to the global values at the start of the createDisplay() function (we will need to change these values from the handleInput() function).

global tk, canvas, light

Create the handleInput(event) function:

def handleInput(event):
  inputkey = 0
  if event.keysym == '1':
    inputkey = 1
  elif event.keysym == '2':
    inputkey = 2
  elif event.keysym == '3':
    inputkey = 3
  elif event.keysym == '4':
    inputkey = 4
  elif event.keysym == '5':
    inputkey = 5
  #Handle the keyboard input
  if inputkey != 0:
    lightStatus[inputkey-1] = toggle(lightStatus[inputkey-1])
    canvas.itemconfig(light[inputkey-1], fill=LIGHTOFFON[lightStatus[inputkey-1]])

You will also need to add the following to the top of the file:

LIGHTOFFON=[RED,BRIGHTRED]
OFF = 0
ON = 1
colourBackground = BLACK
colourButton = RED
#Light Status
lightStatus=[OFF,OFF,OFF,OFF,OFF]

We also need to add a small utility function, toggle(value):

def toggle(value):
  if value == ON:
    value = OFF
  else:
    value = ON
return value

The new handleInput(event) function, checks to see if the keyboard symbol matches the keys we are interested in and assigns the relevant number to it.

If there is a match (inputKey is set to 1 to 5), we use the toggle(value) function to switch the state of the light (if it was ON, we change the status to OFF).  The state of the light is stored in the lightStatus[] array, so we use inputkey-1 to toggle the corresponding element in the array.

Finally, we use canvas.itemconfig() to reference the light (one of the squares we drew previously) and change the fill colour.  We use the lightStatus[] to tell us if it should be ON or OFF, and then we use that reference to determine the colour it should be (as set in LIGHTOFFON[]).

When you run this, you will see that you can press the keys and toggle the lights ON and OFF as desired.

Part 2: Using the RGB LED Kit

We now have a functioning GUI, so we can now link it to our RGB-LEDs.

Create a copy of keyinputInterfaceTK.py and call it RGBInterfaceTK.py to use for this part of the lesson.

Hardware Set-up

For details about the assumed hardware and software set-up, please see “Lesson 0″ or the RGB-LED User Manual.

Adding the RGB-LED library

First we add the rgbled library to program (ensure rgbled.py is in the same directory), add a new import to the top of the file.

You can obtain the rgbled.py file by following the previous lessons (RGB-LED Lesson 1 – Creating Python Libraries (& Colours) and the RGB-LED Lesson 1 – Extra Credit Solution)

Script: RGBInterfaceTK.py

import rgbled as RGBLED

Now if you recall from using the library previously, we will want to call RGBLED.led_setup() to ensure the pins are set as outputs and in a default state (LEDs switched off).  When we are finished, we will also call RGBLED.led_cleanup() to release the GPIO at the end of the program.

Update main() and terminate() functions:

def terminate():
  global tk
  tk.destroy()
  RGBLED.led_cleanup()

def main():
  RGBLED.led_setup()
  createDisplay()

if __name__ == '__main__':
  main()

Create ledcontrol() function:

We will create a new function ledcontrol(), which will allow us to switch a required LED ON or OFF using the .led_activate() function in the library.  To assist with testing in the next stage we will also use a DEBUG value to allow us to enable and disable debug messages in the terminal.

At the top of the file, add the following to enable our debug messages.

#Enable/Disable DEBUG
DEBUG = True

Now add the new function:

def ledcontrol(led,value):
  if value == ON:
    if DEBUG:print "ACTIVATE LED: " + str(led) + " RGB: " + str(LEDON)
    RGBLED.led_activate(led,LEDON)   #Activate LED Pin and RGB Pin
  else:
    if DEBUG:print "DEACTIVATE LED: " + str(led) + " RGB: " + str(LEDON)
    RGBLED.led_deactivate(led) #Deactivate LED Pin ONLY!

When this function is called we can provide a led number of 0 to 4, and value to set it ON or OFF.

We also need to add LEDON near the top of the file with our other values, to define what RGBLED colour we want the LED to light up when it is switched on.

LEDON=RGBLED.RGB_RED

Update the handleInput(event):

We can add a call to ledControl() at the end so that we also use the lightStatus[] to determine the state of the LEDs as well as the graphical lights.

def handleInput(event):
  inputkey = 0
  if event.keysym == '1':
    inputkey = 1
  elif event.keysym == '2':
    inputkey = 2
  elif event.keysym == '3':
    inputkey = 3
  elif event.keysym == '4':
    inputkey = 4
  elif event.keysym == '5':
    inputkey = 5
  #Handle the keyboard input
  if inputkey != 0:
    lightStatus[inputkey-1] = toggle(lightStatus[inputkey-1])
    canvas.itemconfig(light[inputkey-1], fill=LIGHTOFFON[lightStatus[inputkey-1]])
    if DEBUG:print "LED: " + str(RGBLED.LED[inputkey-1]) + " State: " + str(lightStatus[inputkey-1])
    ledcontrol(RGBLED.LED[inputkey-1],lightStatus[inputkey-1])

Found An Error?

Try running the above program, and see what happens when you start switching ON and OFF several of the LEDs at a time.  Can you spot what the problem is?

The problem is in our library file rgbled.py, have a closer look at the led_deactivate(led,colour) function.

def led_deactivate(led,colour):
  #Disable led
  led_gpiocontrol(led,LED_DISABLE)
  #Disable colour
  led_gpiocontrol(colour,RGB_DISABLE)

You will see that the code DISABLEs both, the LED Pin and the RGB Pin.  Due to the hardware multiplexing, if you DISABLE one of the RGB pins, it DISABLEs it for ALL of the LEDs (switching that colour OFF for all of them).  However, if in this case we just DISABLE the LED pin, our LED will switch OFF but will leave the others switched ON.

To do this, can update led_deactivate((led,colour) function so we can choose not to DISABLE the RGB pin if we don’t want to.

def led_deactivate(led,colour=RGB_DISABLE):
  #Disable led
  led_gpiocontrol(led,LED_DISABLE)
  #If no colour is specified, then we don't disable the RGB Pin
  #This is important if controlling multiple LEDs of the same colour
  #since we disable only the LED and not the colour.
  if (colour != RGB_DISABLE):
    #Disable colour
    led_gpiocontrol(colour,RGB_DISABLE)

This works by setting a default value for colour at the top (this automatically gets set to this value when the function is called without that parameter).  Now, if the function is called without specifying a colour it won’t disable the RGB pin.

Try running the program again, and the LEDs should now toggle, matching the graphical lights.

Completed RGBInterfaceTK.py:

#!/usr/bin/python
#RGBInterfaceTK.py

import rgbled as RGBLED
from Tkinter import * #Note Tkinter for python 2.*, tkinter for python 3+
# if you are using Python 3, comment out the previous line
# and uncomment the following line
# from tkinter import *

#Enable/Disable DEBUG
DEBUG = True

#Set display sizes
BUTTON_SIZE = 100
NUM_BUTTON = 5
MARGIN = 5
WINDOW_W = MARGIN+((BUTTON_SIZE+MARGIN)*NUM_BUTTON)
WINDOW_H = (2*MARGIN)+BUTTON_SIZE

#Set colours
#                R    G    B
BLACK        = '#000000'
BRIGHTRED    = '#ff0000'
RED          = '#9b0000'
LIGHTOFFON=[RED,BRIGHTRED]
LEDON=RGBLED.RGB_RED
OFF = 0
ON = 1
colourBackground = BLACK
colourButton = RED

#Light Status
lightStatus=[OFF,OFF,OFF,OFF,OFF]

def createDisplay():
    global tk, canvas, light
    # create the tk window - within which
    # everything else will be built.
    tk = Tk()
    #Add a canvas area ready for drawing on
    canvas = Canvas(tk, width=WINDOW_W, height=WINDOW_H, background=BLACK)
    canvas.pack()
    #Add some "lights" to the canvas
    light = []
    for i in range(0,NUM_BUTTON):
        x = MARGIN+((MARGIN+BUTTON_SIZE)*i)
        light.append(canvas.create_rectangle(x,MARGIN,
                               x+BUTTON_SIZE,BUTTON_SIZE+MARGIN,fill=RED))
    #Add an exit button
    btn = Button(tk, text="Exit", command=terminate)
    btn.pack()
    # Create keyboard bindings
    tk.bind_all('<KeyPress-1>', handleInput)
    tk.bind_all('<KeyPress-2>', handleInput)
    tk.bind_all('<KeyPress-3>', handleInput)
    tk.bind_all('<KeyPress-4>', handleInput)
    tk.bind_all('<KeyPress-5>', handleInput)
 # Start the tk main-loop (this updates the tk display)
 tk.mainloop()

def handleInput(event):
 inputkey = 0
 if event.keysym == '1':
 inputkey = 1
 elif event.keysym == '2':
 inputkey = 2
 elif event.keysym == '3':
 inputkey = 3
 elif event.keysym == '4':
 inputkey = 4
 elif event.keysym == '5':
 inputkey = 5
 #Handle the keyboard input
 if inputkey != 0:
 lightStatus[inputkey-1] = toggle(lightStatus[inputkey-1])
 canvas.itemconfig(light[inputkey-1], fill=LIGHTOFFON[lightStatus[inputkey-1]])
 if DEBUG:print "LED: " + str(RGBLED.LED[inputkey-1]) + " State: "
 + str(lightStatus[inputkey-1])
 ledcontrol(RGBLED.LED[inputkey-1],lightStatus[inputkey-1])

def toggle(value):
 if value == ON:
 value = OFF
 else:
 value = ON
 return value

def ledcontrol(led,value):
 if value == ON:
 if DEBUG:print "ACTIVATE LED: " + str(led) + " RGB: " + str(LEDON)
 RGBLED.led_activate(led,LEDON) #Activate LED Pin and RGB Pin
 else:
 if DEBUG:print "DEACTIVATE LED: " + str(led) + " RGB: " + str(LEDON)
 RGBLED.led_deactivate(led) #Deactivate LED Pin ONLY!

def terminate():
 global tk
 tk.destroy()
 RGBLED.led_cleanup()

def main():
 RGBLED.led_setup()
 createDisplay()

if __name__ == '__main__':
 main()
#End

What next?

Well you shall have to hold on for Lesson 6 which will be coming soon, where we use our GUI we have created!

Comments
  1. DL says:

    Hi,
    thanks for the tutorial although I got an error using the keyboard bindings as described:
    (tk.bind_all(”, handleInput))
    Error message:
    File “d:\python275\lib\lib-tk\Tkinter.py”, line 991, in _bind
    self.tk.call(what + (sequence, cmd))
    _tkinter.TclError: no events specified in binding

    changing the keyboard bindings to:
    tk.bind_all(”, handleInput)

    solved the problem in my case.

    Cheers
    DL

    • DL says:

      sorry little mistakes above, it should have been:
      tk.bind_all(”, handleInput) # giving the error message
      and
      tk.bind_all(”, handleInput) # solving the problem

      • DL says:

        same mistake again, which I guess is about a filter running. To avoid the error: in between the two ” should be
        ‘less-than_sign Key greater-than_sign’
        hope now it will show up in the postings😉

      • Hi DL!
        Thanks for pointing it out. I think you have found out how the error got there (wordpress is not friendly with code and likes nothing more than to remove chunks which have html like characters in).

        I’ve re-added the bits of code, hopefully this matches what you have found works. Thanks very much for letting me know, it will really help others who try it.

  2. ARW says:

    Hi, may I ask when Lesson 6 is coming? I have found these exercises very good, but you have left me hanging waiting for more!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s