Sunday, November 11, 2012

PyHack Workshop #01

Yesterday, PYPTUG held its first PyHack workshop at Fablocker in Winston Salem, NC. The Raspberry Pi was briefly introduced along with some background information on the foundation.


Environments

David Mitchell presented iPython, the iPython notebook (uber cool, look into it if you dont know what it is) and the Adafruit WebIDE, along with how to install this (which some set it up during the workshop) and some demo. This was quite interesting, and David's presentation is available here.

"Fab"oard Deux (2)

You might have seen already "Fab"oard Un (1). This version features a larger cutting board, a raspberry pi in a "Fab"ulous Pi Case, with a GertBoard (not used in this workshop) and a homebrew board.

"Fab"oard Deux (2)

GPIOs


Many wanted to know how to use the GPIOs with Python. After providing a quick reference card (in Raspberry Pi appropriate colors...), we started reviewing code.





The workshop source code is hosted on bitbucket.org, using mercurial (hg). You will need to install it on your Raspberry Pi before you can get the code and use it:

$ sudo apt-get install mercurial

To get the code for the workshops, go into the directory where you will want to store your python code and then issue the following command:

$ hg clone https://bitbucket.org/fdion/fablocker

Let's review what we did for Workshop #01. Armed with the GPIO quickref:




Flash 

And we went on to talk about GPIO outputs. The simplest, LEDs:
$ cd fablocker/PyHack/workshop01
$ cd flash
$ ls
flash.py  okled.py

The okled.py is about the simplest way to get something going with GPIOs on the Raspberry Pi. It will turn on the OK LED for 5 seconds, then turn it off. I would suggest disabling the OS process that is already using it before using this code. I review it and explain how to disable the OS process on the RPi.GPIO dot dash article.

The code for flash.py:

1:  #!/usr/bin/env python  
2:  """ Setting up two pins for output """  
3:  import RPi.GPIO as gpio  
4:  import time  
5:  PINR = 0 # note, this should be 2 on a V2 RPi  
6:  PING = 1 # note, this should be 3 on a V2 RPi  
7:  gpio.setmode(gpio.BCM) # broadcom mode  
8:  gpio.setup(PINR, gpio.OUT) # set up red LED pin to OUTput  
9:  gpio.setup(PING, gpio.OUT) # set up green LED pin to OUTput  
10:  #Make the two LED flash on and off forever  
11:  while True:  
12:    gpio.output(PINR, gpio.HIGH)  
13:    gpio.output(PING, gpio.LOW)  
14:    time.sleep(1)  
15:    gpio.output(PINR, gpio.LOW)  
16:    gpio.output(PING, gpio.HIGH)  
17:    time.sleep(1)  

flash.py goes a little further, in that it will flash 2 LEDs, in alternance. One red LED and one green LED, on GPIO0 and GPIO1 (pins 3 and 5 on the P1 header on the Raspberry Pi). This is for version 1.0 of the Pi. For version 2.0, use flash2.py which uses GPIO2 and GPIO3 instead.

To run these, you will need to have RPi.GPIO installed. Again, I'll refer you to the RPi.GPIO dot dash article, to install RPi.GPIO. Then to execute:

$ sudo ./flash.py

There is no need to do sudo python flash.py, because the scripts are executable (chmod +x) and the first line is #!/usr/bin/env python.

The code is by no means optimal. It was designed to provide a gentle introduction to Python and GPIOs. I brought up lists as a way to make the code tighter, and this is demonstrated in the next section.

Buttons


From output to LEDs, we graduated to input with buttons.

$ cd ../button
$ ls
button.py  quiz.py  quiz2.py  quiz3.py    quiz4.py

If we look at the code of button.py (line numbers added to help following):

1:  #!/usr/bin/env python  
2:  """button.py - simplest testing of a gpio input"""  
3:    
4:  import time  
5:  import RPi.GPIO as gpio  
6:    
7:  BUTTON = 22 # header pin 22, GPIO25  
8:    
9:  gpio.setmode(gpio.BOARD) # reference by header pin number  
10:  gpio.setup(BUTTON, gpio.IN) # set pin as INput  
11:    
12:  not_pressed = True  
13:  print("Press button to exit")  
14:  while not_pressed:  
15:    not_pressed = gpio.input(BUTTON)  
16:    time.sleep(.2)  
17:    
18:  print("Goodbye")  
19:  gpio.cleanup() # reset pins to default  
20:    

Line 1 is to allow executing the script directly, line 2 is documentation. Line 4 imports the time module, which we'll use for the sleep function.

Line 5 is key to using GPIOs. We import this library, as gpio, so we wont have to type RPi.GPIO.function, we can use instead gpio.function.

Line 7, we decide to use GPIO25 / header pin 22 for the button and assign that to a constant named BUTTON. We are using BOARD mode (line 9), that is why we use a pin number instead of a GPIO number (like we did with the okled.py).

Line 10 sets up the pin as an input.

Line 12, we default not_pressed to True so the while loop (line 14) will be done at least once. On line 13 we are simply printing a statement to the console.

Inside the loop (that starts at line 14), we check on line 15 the status of the pin 22 and assign that to not_pressed. We chose this convenient way of loop control because gpio.input can return either True or False. True means the reading is high (3V3), which is the default, because we wired the pin with a pullup resistor (10KOhm) to 3v3, while the switch is connected between the pin and the ground (add a 1KOhm resistor in series with the switch if there is a possibility of seting the pin as an output and pressing the button at the same time, something that would fry the pin circuitry on the Pi). If the button is pressed, the pin sees 0 volts (ground) and thus has a value of False.

We conclude the loop with a delay of 0.2 seconds. If not_pressed is set to False (because we pressed the button) then the loop exits. We bid farewell by printing Goodbye to the console.

Last step, line 19, we do housekeeping of the GPIO.

Moving on to quiz.py:

1:  #!/usr/bin/env python  
2:  """ quiz.py - reading 4 different inputs """  
3:    
4:  import time  
5:  import RPi.GPIO as gpio  
6:    
7:  gpio.setmode(gpio.BCM) # broadcom mode  
8:  gpio.setup(22, gpio.IN) # set GPIO22 as INput  
9:  gpio.setup(23, gpio.IN) # set GPIO23 as INput  
10:  gpio.setup(24, gpio.IN) # set GPIO24 as INput  
11:  gpio.setup(25, gpio.IN) # set GPIO25 as INput  
12:    
13:  print("question?")  
14:  while True:  
15:    mybutton1 = gpio.input(22) # read value of GPIO22  
16:    mybutton2 = gpio.input(23) # read value of GPIO23  
17:    mybutton3 = gpio.input(24) # read value of GPIO24  
18:    mybutton4 = gpio.input(25) # read value of GPIO25  
19:    print mybutton1, mybutton2, mybutton3, mybutton4  
20:    time.sleep(.2)  
21:    

This is a similar concept, but this time we read 4 different pins, for 4 different push buttons:

PVC game controllers
Up to line 7, the code is similar. On line 7 we use BCM mode instead of board, so we can use the consecutive GPIO numbers (22 to 25).
Lines 8 to 11 set up the pins as inputs.

The while loop starts at 14 and lines 15 to 18 read the status of the 4 pins. We then print these statuses to the screen on line 19 and sleep for 0.2s on line 20 before continuing on with the while loop.

There is a lot of repetition going on here. Surely there is a better way to do this in Python? Yes.

Let's have a look at quiz2.py:

1:  #!/usr/bin/env python  
2:  """ quiz2.py - improving quiz with a tuple """  
3:    
4:  import time  
5:  import RPi.GPIO as gpio  
6:    
7:  PINS = (22, 23, 24, 25) # list of pins as tuple  
8:  gpio.setmode(gpio.BCM) # broadcom mode, by GPIO  
9:  for pin in PINS:  
10:    gpio.setup(pin, gpio.IN) # set GPIO pin as INput  
11:    
12:  print("question?")  
13:  while True:  
14:    mybuttons = [] # reset the list to be empty  
15:    for pin in PINS:  
16:      mybuttons.append(gpio.input(pin)) # read value of pin input  
17:    print(mybuttons) # print the whole list, the statuses of all pins  
18:    time.sleep(.2)  

That is both shorter and more easily read. We establish a list of pins on line 7. A list in Python is defined with []. Here we define this list with () because it is really an immutable list (a constant array in some other programming languages parlance). In Python, this is called a tuple. A list or a tuple can be iterated, and that is what we are about to do:

On line 9 we start a for loop, where the pin variable is assigned from PINS. We then use this assignment on line 10 to set up the pins as inputs.

Similarly, on lines 14 through 16 we do a for loop process and read the pin statuses and append them to a list. We then print the values at once on line 17.

Could we improve this a little further more? Sure. Are there other ways to do this? Again, yes. quiz3.py:

1:  #!/usr/bin/env python  
2:  """ quiz3.py - even better with list comprehensions """  
3:    
4:  import time  
5:  import RPi.GPIO as gpio  
6:    
7:  PINS = range(22, 26) # [22, 23, 24, 25] - same as tuple, no advantage  
8:    
9:  gpio.setmode(gpio.BCM) # broadcom mode, by GPIO  
10:  for pin in PINS:  
11:    gpio.setup(pin, gpio.IN) # set pins as INput  
12:  print("question?")  
13:  while True:  
14:    mybuttons = [gpio.input(pin) for pin in PINS] # list comprehension  
15:    print(mybuttons)  
16:    time.sleep(.2) 

Since the GPIOs are consecutives, 22, 23, 24 and 25, we can leverage some of the functional programming aspects of Python. On line 7, we assign PINS as a range of numbers that starts at 22 and ends less than 26 (25). I mention this simply for you to investigate functional programming with Python, since here, we dont gain any advantage compared to using a tuple. In fact, it is harder to read.

What about lines 14 to 16 in the previous code (quiz2.py), is that the Python way? No, not really. When it comes to Python and lists that are programmatically generated, the majority of the time, you'll want to use a list comprehension. Basically, you can generate the list in one line. If we read the new line 14, we are assigning to mybuttons a list that will comprise of the result of gpio.input(pin) where the value of pin is sequentially fetched from PINS (22,23,24 and 25).

This is barely touching the tip of the list comprehension iceberg.

We are forgetting an important safeguard in our code, a way to do the GPIO housekeeping after we exit with Control-C. Plus, as our code is growing, we want to put it inside a function.

Let me demonstrate both concepts with the next variation of quiz, quiz4.py:


1:  #!/usr/bin/env python  
2:  """ quiz4.py - added handling of ctrl-c and print only when pressed """  
3:  import time  
4:  import RPi.GPIO as gpio  
5:    
6:  PINS = (22, 23, 24, 25) # list of pins as tuple  
7:    
8:    
9:  def main():  
10:    """ our main program """  
11:    gpio.setmode(gpio.BCM) # broadcom mode, by GPIO  
12:    for pin in PINS:  
13:      gpio.setup(pin, gpio.IN) # set pins as INput  
14:    print("question?")  
15:    while True:  
16:      mybuttons = [gpio.input(pin) for pin in PINS] # list comprehension  
17:      if False in mybuttons: # at least one button was pressed  
18:        print(mybuttons)  
19:      time.sleep(.2)  
20:    
21:  if __name__ == "__main__":  
22:    try:  
23:      main()  
24:    except KeyboardInterrupt:  
25:      gpio.cleanup()  
26:    

Lines 1 to 6 should be very familiar by now.

Starting on line 9, we now define a function called main(), to hold our code. By default this is evaluated as a function definition and is not executed. In order to execute it we need lines 21 and 23 which basically state that if this script is executed as itself, and not as an import into another script, then we want to run or execute the function main(). FYI, functions in Python are first class objects, like classes, like everything else (even strings and numbers).

Going back to our definition of our main() function, on line 10 we document it. Lines 11 through 19 are the same in functionality as we had before. But if we hit Control-C to interrupt the program while in this code, something interesting happens, an exception is trapped (by the code on line 24) and execution jumps to line 25, does a cleanup and exits.

The reason the KeyboardInterrupt exception was trapped, is because we put a try: statement on like 22, before calling our main() function. Any error raised inside of main() will be caught by the except statement. In our case however, we are trapping only the KeyboardInterrupt (which is what is raised when you hit Control-C).

So, I think it is a good recap of what was discussed in the main part of the workshop. We still have to talk about the PiQuizMachine, but this is already quite long, so we will have to do that in another article, tomorrow.

No comments: