Sunday, October 14, 2012

RPi.GPIO dot dash

In a previous blog, I mentionned how to get WiringPi on the Pi.

RPi.GPIO

Let's now talk about RPi.GPIO, another popular library (there is a third one that is popular which I'll cover soon) for interfacing with hardware on the Pi. We will start by installing it. One tool I always install on a machine that will be used for python, is easy_install. It is part of python-setuptools. After installing that, it is a simple task to install RPi.GPIO:



pi@raspberrypi ~ $ sudo apt-get install python-dev python-setuptools
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following extra packages will be installed:
  libexpat1-dev libssl-dev libssl-doc python-pkg-resources python2.7-dev
Suggested packages:
  python-distribute python-distribute-doc
The following NEW packages will be installed:
  libexpat1-dev libssl-dev libssl-doc python-dev python-pkg-resources
  python-setuptools python2.7-dev
0 upgraded, 7 newly installed, 0 to remove and 43 not upgraded.
Need to get 31.9 MB of archives.
After this operation, 43.5 MB of additional disk space will be used.
Do you want to continue [Y/n]? y
[lots of stuff]


Installing RPi.GPIO

[EDIT: the below should now be ok, Pypi has 0.4.1a, see:

http://pypi.python.org/pypi/RPi.GPIO/0.4.1a ]


pi@raspberrypi ~ $ sudo easy_install RPi.GPIO
Searching for RPi.GPIO
Best match: RPi.GPIO 0.3.1a
Adding RPi.GPIO 0.3.1a to easy-install.pth file

Using /usr/lib/python2.7/dist-packages
Processing dependencies for RPi.GPIO
Finished processing dependencies for RPi.GPIO


Usually, this would be ok, but the pypi repository has the older 0.3.1a version. Still, we didn't waste our time, because we will use easy_install for some other stuff. So let us get the rpi.gpio from the raspbian repository:


pi@raspberrypi ~ $ sudo apt-get install python-rpi.gpio
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following packages will be upgraded:
  python-rpi.gpio
1 upgraded, 0 newly installed, 0 to remove and 42 not upgraded.
Need to get 14.3 kB of archives.
After this operation, 18.4 kB of additional disk space will be used.
Get:1 http://archive.raspberrypi.org/debian/ wheezy/main python-rpi.gpio armhf 0.4.1a-1 [14.3 kB]
Fetched 14.3 kB in 0s (28.0 kB/s)    
(Reading database ... 66708 files and directories currently installed.)
Preparing to replace python-rpi.gpio 0.3.1a-1 (using .../python-rpi.gpio_0.4.1a-1_armhf.deb) ...
Unpacking replacement python-rpi.gpio ...
Setting up python-rpi.gpio (0.4.1a-1) ...



All right, this is better, version 0.4.1a-1. We are now going to be writing some Python code. Do you have a way to use the GPIO connector, such as a PiCobbler? If you dont, do not worry. You can make your own or buy one later. Today we will stick to what you have.

OK Computer

Let's try the library, still, using a GPIO pin that is already wired to a LED, so we can play some with the code. This is found on the Raspberry Pi schematics (page 4 is showing the OK LED with a control of STATUS_LED_N, and page 2 is showing that this is coming from the BCM2835, GPIO16).



We will thus use the OK (Activity) LED. As the name implies however, it is already in use by a process. It is triggered by activity on the SD card. Let us first disable that trigger before we try to use the LED, else we won't get the results we expect.


pi@raspberrypi ~/hardware_project/led $ sudo su
root@raspberrypi:/home/pi/hardware_project/led# echo none >/sys/class/leds/led0/trigger
root@raspberrypi:/home/pi/hardware_project/led# exit
exit
pi@raspberrypi ~/hardware_project/led $ 



In order to re-enable the activity / ok LED trigger from the SD card, once you are done with testing the code in this article, you will have to run the following:


pi@raspberrypi ~/hardware_project/led $ sudo su
root@raspberrypi:/home/pi/hardware_project/led# echo none [mmc0] >/sys/class/leds/led0/trigger
root@raspberrypi:/home/pi/hardware_project/led# exit
exit
pi@raspberrypi ~/hardware_project/led $ 



Python

Now, let's do some Python programming. We have to save the following code in a file. We will name this okled.py:


#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
OKLED - Turn on then off the OK / Activity LED.
"""
# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4

import RPi.GPIO as GPIO
from time import sleep

OKLED = 16

# setup GPIO, use Broadcom pin numbering.The only way to access pin 16
# on the BCM chip.
GPIO.setmode(GPIO.BCM)

# setup BCM pin 16 as an output
GPIO.setup(OKLED, GPIO.OUT)

# Turn on LED
GPIO.output(OKLED, GPIO.LOW)
sleep(5)

# Turn off LED
GPIO.output(OKLED, GPIO.HIGH)

# We are done, let's clean up after ourselves.
GPIO.cleanup()


Run with it

Let's run this. We have to execute with root privileges, because RPi.GPIO uses /dev/mem, and that is owned by root:root. We cant add ourselves to a mem group, because /dev/mem is owned by root group, not mem, unfortunately.


pi@raspberrypi ~/hardware_project/led $ sudo python okled.py 
pi@raspberrypi ~/hardware_project/led $ 



Yay, the OK LED came on for 5 seconds, then off. Surely we can do something better than this.

dahdah dahdahdah didahdit dididit dit

No, that's not from a song by George Kranz or Trio.

In teaching Python and mentoring programmers of all ages, I've found that using Morse code (from Samuel Morse, not Inspector Morse...) allows for a huge variety of tutorials, of problems, of material in general.

I can teach about dictionaries and list comprehensions (two obvious candidates). I can teach about timing, functions or, why not, GPIOs. There are also many potential projects that can be built around it, allowing to discuss all kinds of libraries:

  • audio decoder
  • audio encoder
  • digital signal processing
  • fourrier transforms
  • dits and dahs
  • Morse over ip
  • Prosigns
  • Q Codes
  • Morse trainer
  • QSO practive 
  • Morse in image
  • steganography
  • encryption
  • morsemail
  • sms
  • irc morsebot
  • JSON Morse
  • Web Morse
  • PDF Morse
and integrating software and hardware
  • visual Morse
  • auditive Morse
  • signal lamp
  • heliograph
  • Morse mood lamp
  • LCD Morse
  • Morse scroller
  • paper tape Morse
  • thermal printer Morse
  • Reprap 3d printer Morse
  • trainer with key
  • advanced dsp
  • keyer
  • twit-ah-morse
  • haptic receiver
  • persistence of vision
  • Morse in the sky
  • arm/disarm alarm
  • line of sight quadracopters, balloons
  • underwater transmission
  • Morse painter (trail)
And for those with the proper licenses:
  • Morse transmit / receive over the air
  • Morse from space (weather balloon)

These are just a few I can think of quickly. There are tons more possible. If you do some of these projects, please leave a comment with a link to it.  And I can use this to introduce more advanced stuff, depending on the audience. For example, trees. In this case, a dichotomic tree.

The obvious way to define a mapping of letters to Morse code would be to use a dictionary, such as:

l2m = { 'a':'.-',
    'b':'-...',

so on and so forth. There are over 70 characters that can be transmitted in Morse, so that will be a dictionary with over 70 lines. And I'm lazy, I don't like to do tedious stuff like that! (As a side note, I leave that as an exercise to the reader to do a working translator using a dictionary).

A dichotomic tree (an implicit binary tree - see also dichotomous keys) is similar to another type of tree. Everybody is familiar with a genealogical tree. I've downloaded one from about.com that was empty, and I put a few things in it to explain how this relates to Morse (just two levels to keep it simple, but we would need 6 for a complete solution):


Let's say that you have a code that is .- (that is a dot and a dash, or didah). We start at the bottom of the tree and at the first branching I have to choose. I go left on dot and right on dash (or stay there if it's the end of the stream for that character).

Since I got a dot first, I go left. Might be an E it looks like, unless the signal continues and it could be an I or an A. The signal does continue, and I receive a dash. From E, I take the right, looks like an A, and once I confirm that this is the last mark for this character, I stay in that box. An A.

So, that is cool, but what about the other way around? We have a nice fanned out binary tree, with equal branches and leafs, so we cheat a little. We start from the outer level (IANM is a level and ET is another in this example) and try to find the letter. We go on to the next lower level until we find our letter. Once found, if on a row I am on an odd space, it is a dot, and if it is even, it is a dash. But I know that A is not just dash, in fact it doesn't even start with dash, what's up with that?

What is happening is that we are building the sequence in reverse. We are starting from the leaf in the tree and trying to find our way to the trunk. Not a problem, once we have the whole sequence, we just reverse it and we get our Morse code sequence. To find the path on the next, we round to the next even number and divide by 2. This gives us our position on the next lower level. We continue until we reach the trunk.

So how does that look when put into code? We'll mix the OK LED from the previous code tidbit, but we also need to add some logic to handle the length of time the LED stays on or off, depending on:

  • a dot is 1 time unit on
  • a dash is 3 time units on
  • between marks should be 1 time unit off
  • between letters should be 3 time units off
  • between words should be 7 time units off
Adding comments and everything, we get the following:


#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
MORSE - A morse code generator for the Raspberry Pi GPIO OK LED.
EN: Morse code generated from a dichotomic tree.
FR: On génère une séquence de code Morse, a partir d'un arbre dichotomique.
ES: Generador de codigo Morse, por medio de un árbol dicotómico.
RU: Создание последовательности кода Морзе (дихотомической валом).
PT: Codigo Morse criado a partir de uma árvore dicotômica.
"""
# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4

import RPi.GPIO as GPIO
from time import sleep

__author__ = "Francois Dion"
__email__ = "francois.dion@gmail.com"
__copyright__ = "Copyright 2012, Francois Dion"

WPM = 5  # average words per minute, based on standard word PARIS
TIMEUNIT = 1.2 / WPM  # we can force a different unit, in seconds

# Activity/OK LED GPIO pin
OK_LED = 16

# We are going to be signaling on OUTPORT.
# If you are using another GPIO pin, assign it here.
OUTPORT = OK_LED

# Our famous dichotomic tree, as 6 strings to represent the 6 levels
TREE = (
    """            ?_    "  .    @   '  -        ;! (     ,    :""",
    u"54ŝ3é ð2 è+ þàĵ16=/ ç ĥ 7 ĝñ8 90",
    u"hvfüläpjbxcyzqöš",
    "surwdkgo",
    "ianm",
    "et",
)

DT = DD = '-.'


def letter2morse(letter):
    """ We take a letter and convert it to a morse code sequence.
    Since we are going down our dichotomic tree from the leaf to the trunk,
    we will end up with a reversed sequence, so we simply return the
    reverse of that ([::-1]) to get it in the right order.
    """
    if letter == ' ':  # No conversion needed, just a pause marker
        return letter
    found = False
    morse = ''
    position = 0
    for i in range(6):
        if found:
            position += position % 2
            position /= 2
            morse += DD[position % 2]
        elif letter in TREE[i]:
            position = (TREE[i].find(letter)) + 1
            morse = DT[position % 2]
            found = True
    return morse[::-1]


def morse2gpio(morse):
    """ Signal a morse letter (a morse code sequence for 1 alphanumeric).
    Also handles the pause between marks, letters and words.
    """
    for mark in morse:
        delay = TIMEUNIT
        # support for the dash as - or dah
        if mark in ('-', 'dah'):
            delay *= 3  # dash is 3 x longer than dot

        # dot or dash depending on the delay
        if mark == ' ':  # word separator
            sleep(delay * 4)  # stay off 4 + 1 + 2 below, total 7 TIMEUNITs
        else:
            GPIO.output(OUTPORT, GPIO.LOW)
        sleep(delay)
        # 1 TIMEUNIT pause between each mark
        GPIO.output(OUTPORT, GPIO.HIGH)
        sleep(TIMEUNIT)
    # end of letter pause
    sleep(TIMEUNIT * 2)  # already paused TIMEUNIT, 3 in total between letters


def main():
    """Get a string from the user and send the converted morse code sequence
    to a GPIO pin
    """

    # First, we have to setup the pin to output.
    GPIO.setmode(GPIO.BCM)
    GPIO.setwarnings(False)
    GPIO.setup(OUTPORT, GPIO.OUT)

    words = raw_input("Type sentence to send in morse code:")

    for letter in words.lower():
        morsecode = letter2morse(letter)
        print morsecode
        morse2gpio(morsecode)
    GPIO.cleanup()

if __name__ == "__main__":
    main()




We run the above after saving it into a file named morse.py:


pi@raspberrypi ~/hardware_project/led $ chmod a+x morse.py 
pi@raspberrypi ~/hardware_project/led $ sudo ./morse.py
Type sentence to send in morse code:Paris
.--.
.-
.-.
..
...
pi@raspberrypi ~/hardware_project/led $ 



Lint, clean it

One last thing. It is always good to run your code through pep8 and pylint. It will report errors, and also warnings about style and code. Pylint will even rate your code. We'll install them and run the code through it:


pi@raspberrypi ~/hardware_project/led $ sudo easy_install pep8 pylint
Searching for pep8
Best match: pep8 1.3.3
Processing pep8-1.3.3-py2.7.egg
pep8 1.3.3 is already the active version in easy-install.pth
Installing pep8 script to /usr/local/bin

Using /usr/local/lib/python2.7/dist-packages/pep8-1.3.3-py2.7.egg
Processing dependencies for pep8
Finished processing dependencies for pep8
Searching for pylint
Best match: pylint 0.26.0
Processing pylint-0.26.0-py2.7.egg
pylint 0.26.0 is already the active version in easy-install.pth
Installing pyreverse script to /usr/local/bin
Installing pylint-gui script to /usr/local/bin
Installing epylint script to /usr/local/bin
Installing pylint script to /usr/local/bin
Installing symilar script to /usr/local/bin
Installing pylint script to /usr/local/bin
Installing epylint script to /usr/local/bin
Installing pyreverse script to /usr/local/bin
Installing pylint-gui script to /usr/local/bin
Installing symilar script to /usr/local/bin

Using /usr/local/lib/python2.7/dist-packages/pylint-0.26.0-py2.7.egg
Processing dependencies for pylint
Finished processing dependencies for pylint
pi@raspberrypi ~/hardware_project/led $ 
pi@raspberrypi ~/hardware_project/led $ pep8 morse.py 
pi@raspberrypi ~/hardware_project/led $ 
pi@raspberrypi ~/hardware_project/led $ sudo pylint morse.py 
No config file found, using default configuration


Report
======
49 statements analysed.

Raw metrics
-----------

+----------+-------+------+---------+-----------+
|type      |number |%     |previous |difference |
+==========+=======+======+=========+===========+
|code      |50     |53.19 |50       |=          |
+----------+-------+------+---------+-----------+
|docstring |23     |24.47 |23       |=          |
+----------+-------+------+---------+-----------+
|comment   |8      |8.51  |8        |=          |
+----------+-------+------+---------+-----------+
|empty     |13     |13.83 |13       |=          |
+----------+-------+------+---------+-----------+



Statistics by type
------------------

+---------+-------+-----------+-----------+------------+---------+
|type     |number |old number |difference |%documented |%badname |
+=========+=======+===========+===========+============+=========+
|module   |1      |1          |=          |100.00      |0.00     |
+---------+-------+-----------+-----------+------------+---------+
|class    |0      |0          |=          |0           |0        |
+---------+-------+-----------+-----------+------------+---------+
|method   |0      |0          |=          |0           |0        |
+---------+-------+-----------+-----------+------------+---------+
|function |3      |3          |=          |100.00      |0.00     |
+---------+-------+-----------+-----------+------------+---------+



Messages by category
--------------------

+-----------+-------+---------+-----------+
|type       |number |previous |difference |
+===========+=======+=========+===========+
|convention |0      |0        |=          |
+-----------+-------+---------+-----------+
|refactor   |0      |0        |=          |
+-----------+-------+---------+-----------+
|warning    |0      |0        |=          |
+-----------+-------+---------+-----------+
|error      |0      |0        |=          |
+-----------+-------+---------+-----------+



Global evaluation
-----------------
Your code has been rated at 10.00/10 (previous run: 10.00/10)

Duplication
-----------

+-------------------------+------+---------+-----------+
|                         |now   |previous |difference |
+=========================+======+=========+===========+
|nb duplicated lines      |0     |0        |=          |
+-------------------------+------+---------+-----------+
|percent duplicated lines |0.000 |0.000    |=          |
+-------------------------+------+---------+-----------+



External dependencies
---------------------
::

    RPi 
      \-GPIO (morse)






That concludes our tutorial for the day. I hope you had fun, and don't forget to leave comments if you encounter problems, have questions, enjoyed the article or want to share what you've done with this.

3 comments:

  1. Followed the bouncing ball...
    copied from windoze-chrome browser, pasted into
    cat > morse.py
    in putty session.

    File "./morse.py", line 33
    u"54.3é ð2 è+ þà.16=/ ç . 7 .ñ8 90",
    SyntaxError: (unicode error) 'utf8' codec can't decode byte 0xe9 in position 0: unexpected end of data

    I must be missing some locale-type stuff?
    a quick inspection shows that all of the circumflexed characters get clobbered to '.'

    If some particular specific head-patting tummy-rubbing tongue-out-the-right-side-of-mouth is required, perhaps that could be preambled-in somewhere...



    Cool code/project thanks

    ReplyDelete
  2. The code is encoded as UTF-8, the line # -*- coding: utf-8 -*- allows emacs and vim to detect it as such. But simply pasting, cat'ing or similar wont work. Most editors on the Pi will support it.

    I dont have a way to upload the file right now, but either tonight or tomorrow I'll put it at a url that you can then simply get with wget from the command line. I'll post another comment here when I do.

    ReplyDelete
  3. ok, I put the code on bitbucket:
    https://bitbucket.org/fdion/raspberry-python

    and to get the file from the raspberry pi, run this (one line):

    wget https://bitbucket.org/fdion/raspberry-python/raw/edd1302a1fc7120dd5bbf668bcfe6d90348b7977/dotdash/morse.py

    ReplyDelete