Tuesday, January 29, 2013

Applications pour téléphones mobiles avec Python

C'est une traduction de iPhone app with Python. J'ai eu une forte demande pour la version française.

Grâce a Brython


L’icône Brython GPS

 

Une vraie app pour iPhone?


Le programme lancé, on y voit un écran de démarrage, comme il se doit:

Splash (ancienne carte de Caroline du Nord)

Mais c'est un programme web, et pas une application faite avec xcode.

Le premier écran, mode manuel


Mode mise a jour automatique

J'essaie?


Il suffit d'aller sur le lien de la galerie de Brython, ici:

gallery/geo.html et sur un iPhone, on l'ajoute a l'ecran d'accueil. On peut maintenant lancer le programme comme une vraie application iOS.


J'ai déjà vu cela, non?


C'est bel et bien une application web, donc basée sur HTML et CSS, mais le code lui même, c'est écrit en Python. En plus, on utilise ici une toute nouvelle fonctionnalité de Brython, en faisant appel a un script Python externe a la page HTML (c'est une nouveauté qui date de cette fin de semaine), plutôt que d'avoir le code a même la page HTML. Cela nous permet une séparation de la présentation, de la logique et du "bling" (le CSS):

Notre HTML


<!DOCTYPE html>
<html>
    <head>
        <title>Brython test</title>
        <meta charset="iso-8859-1">
        <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1,maximum-scale=1">
        <meta name="apple-mobile-web-app-capable" content="yes">
        <script src="../brython.js"></script>
        <script type="text/python" src="navi.py"></script>
        <link rel="stylesheet" type="text/css" href="navi.css" />
        <link rel="apple-touch-icon" href="icon.png"/>
        <link rel="apple-touch-startup-image" href="splash.png">
    </head>
    <body onLoad="brython()">
        <div id="header">
            <H1>Votre position</H1>
            <div id="switch">
                  <span class="thumb"></span>
                    <input id="refr" type="checkbox" />
                </div>
            </div>
        </div>
        <div id="navarea"></div>
        <div id="maparea"></div>
    </body>
</html>

Application web pour iPhone typique, mais sans jQuery mobile ou autre module du genre. Et pas de onclick dans la page html. L'unique javascript c'est brython.js qui est l’interpréteur Brython même et l'appel a brython() par l'entremise de onload.

Le code Python n'est pas sur la page, mais on voit qu'on y fait reference par src="navi.py"

Allons donc voir ce qu'il y a dans ce fichier navi.py:

Notre Python


# globals #########################
refr = False
geo = win.navigator.geolocation
watchid = 0


# les fonctions ###########################
def navi(pos):
    xyz = pos.coords

    ul = UL(id="nav")
    ul <= LI('lat: %s' % xyz.latitude)
    ul <= LI('lon: %s' % xyz.longitude)

    mapurl = "http://maps.googleapis.com/maps/api/staticmap?markers=%f,%f&zoom=15&size=320x298&sensor=true" % (xyz.latitude, xyz.longitude)
    img = IMG(src = mapurl, id = "map")
    try:
        doc["nav"].html = ul.html  # on met a jour la liste
    except KeyError:
        doc["navarea"] <= ul  # on cree la liste
    try:
        doc["map"].src = mapurl  # on met a jour l'url de l'image
    except KeyError:
        doc["maparea"] <= img  # on cree la balise img

def nonavi(error):
    log(error)

def navirefresh(ev):
    global refr, watchid
    refr = False if refr else True
    if refr == True:
        doc["switch"].className = "switch on"
        watchid = geo.watchPosition(navi, nonavi)
    else:
        doc["switch"].className = "switch"
        geo.clearWatch(watchid)

# au demarrage ###########
if geo:
    geo.getCurrentPosition(navi, nonavi)
    doc["switch"].className = "switch"
    doc["switch"].onclick = navirefresh  # on associe un evenement onclick
else:
    alert('geolocation not supported')

On établis 2 fonctions de rappel (callback). Une si on a notre géolocalisation (navi), et une s'il y a une erreur (nonavi), et finalement, une autre fonction (navirefresh) pour s'occuper de l’événement onclick du contrôle auto refresh dans la barre de menu de l'application. Le démarrage initial se fait par un appel a geo.getCurrentPosition avec nos fonctions de rappel. Ça fonctionne assez bien comme GPS.

Notre CSS

Le CSS étant un peu long, je ne le mettrai pas sur mon blog, mais vous pouvez trouver le code sur le site brython.info ou sur googlecode: SVN repository. Le CSS pour l'interrupteur genre ios 5 a ete emprunté ici: ios-5-style-switch-control

Ce n'est que le début

Alors voila, c'est un point de depart pour faire toute sortes de choses. Un tracker pour le jogging, le velo (avec local storage et synchro par appel ajax) et bien d'autres choses. Vous pouvez désormais faire tout cela avec votre langage favori (Python, bien sur) que ce soit pour votre téléphone mobile ou tablette. Cet exemple est quelque peu spécifique au iPhone (surtout a cause du CSS), mais fonctionne sur Android aussi et peut être adapté facilement aux tablettes. Et cela ne m'a pas pris beaucoup de temps.

Et si vous avez besoin d'aide, n'oubliez pas qu'il existe une liste en francais:
forum/brython-fr

@f_dion

Monday, January 28, 2013

iPhone app with Python

Thanks to Brython


Brython GPS icon

 

Native? Not quite...


The application launches with a splashscreen, like it should

Splash page

But it is really a web application.

Tracking my progress
Automatically refreshes on movement

Can I try it?


Absolutely. Simply point your iphone to the brython gallery url:

gallery/geo.html then click add to home screen. You can now launch it like a native application.


What makes it special?


It's a web application, so of course HTML and CSS, but the code is Python. It leverages the new Brython feature to link to a remote python script, instead of having it inline with the html. This allows a clean separation of presentation, logic and eye candy:

The HTML


<!DOCTYPE html>
<html>
    <head>
        <title>Brython test</title>
        <meta charset="iso-8859-1">
        <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1,maximum-scale=1">
        <meta name="apple-mobile-web-app-capable" content="yes">
        <script src="../brython.js"></script>
        <script type="text/python" src="navi.py"></script>
        <link rel="stylesheet" type="text/css" href="navi.css" />
        <link rel="apple-touch-icon" href="icon.png"/>
        <link rel="apple-touch-startup-image" href="splash.png">
    </head>
    <body onLoad="brython(2)">
        <div id="header">
            <H1>Your position</H1>
            <div id="switch">
                  <span class="thumb"></span>
                    <input id="refr" type="checkbox" />
                </div>
            </div>
        </div>
        <div id="navarea"></div>
        <div id="maparea"></div>
    </body>
</html>

Your typical iPhone web application, except that there is no jQuery mobile or similar framework. There is no onclick. The only javascript is the brython.js file for Brython itself and the related body onload call of brython().

So let's have a look at the Python code:

The Python


# globals #########################
refr = False
geo = win.navigator.geolocation
watchid = 0


# functions ###########################
def navi(pos):
    xyz = pos.coords

    ul = UL(id="nav")
    ul <= LI('lat: %s' % xyz.latitude)
    ul <= LI('lon: %s' % xyz.longitude)

    mapurl = "http://maps.googleapis.com/maps/api/staticmap?markers=%f,%f&zoom=15&size=320x298&sensor=true" % (xyz.latitude, xyz.longitude)
    img = IMG(src = mapurl, id = "map")
    try:
        doc["nav"].html = ul.html
    except KeyError:
        doc["navarea"] <= ul
    try:
        doc["map"].src = mapurl
    except KeyError:
        doc["maparea"] <= img

def nonavi(error):
    log(error)

def navirefresh(ev):
    global refr, watchid
    refr = False if refr else True
    if refr == True:
        doc["switch"].className = "switch on"
        watchid = geo.watchPosition(navi, nonavi)
    else:
        doc["switch"].className = "switch"
        geo.clearWatch(watchid)

# the setup
if geo:
    geo.getCurrentPosition(navi, nonavi)
    doc["switch"].className = "switch"
    doc["switch"].onclick = navirefresh
else:
    alert('geolocation not supported')

We are setting up 2 callback functions. One called if we have navigation (navi) support, one if we dont (nonavi), and finally a function (navirefresh) to handle the onclick of the auto refresh switch in the title bar of the application. This works pretty decently as a quick and dirty GPS application.

The CSS

The CSS is a bit long so I'm not posting it on the blog, but you can check out the source at the brython.info site or on the SVN repository. The ios style switch CSS is borrowed from this article: ios-5-style-switch-control

The tip of the iceberg

So there you have it. You can now use your favorite language (Python, of course) to write applications for your mobile phone or tablet. This example is slightly iPhone specific (mostly due to CSS), but the same basic structure applies to a variety of mobile devices. And it took no time whatsoever to write this.

@f_dion

Sunday, January 27, 2013

Utopia Python (ou realidade?)

Utopia


Imagine um mundo onde você pode programar em Python, onde que você quer. Sim, ela existe para iPad, Android, iPhone, PC, Unix, Mac, Linux etc. Mas há uma área ...

Navegadores Web


Seria ótimo se nós não tem que usar Javascript. Nós colocamos na nossa página web, algo como:

<script type="text/python">
def lista(num):
    for i in range(num):
        print(i)

lista(10)
</script>

Ou talvez colocar o nosso código em um arquivo e fazer isso:

<script type="text/python" src="codigo.py"></script>


E ter um bom suporte para HTML5, SVG, eventos, callbacks...
Sim, é genial, sem dúvida. De qualquer forma pode parar de sonhar com uma utopia, o que!

Consola Navegador (Firefox fazer Ctrl-Shift-K, Chrome Ctrl-Shift-J, Safari Ctrl-Alt-I): Este é o código Python que foi executado.

Impossível!

É muito possível, graças Brython:

O que é isso?

Veja esta página ( http://www.brython.info/test/test_clock.html ), o você vai ver um relógio analógico. E o código fonte da página? Sim, é um programa em Python!

Sim, eu sei ... deixa sem palavras ...

@f_dion

P.S. Nós Telecharge brython.js aqui: brython/downloads e como usá-lo (english). Lista Brython em português: forum/brython-pt. Esta semana, vou publicar no meu blog um aplicativo para iPhone escrito com Brython.

Python on RaspberryPi 01

Learn Python. Starting at the beginning...
  
Note: This is a translation of the popular basic guide to Python on the Raspberry Pi: Python sur RaspberryPi 01 (in french), adapted where it made sense.

For the experts, there's not much to see in this article. For everybody else who keep seeing references to Python in your research on the Raspberry Pi, and wondering why they keep talking about a snake, then this article will be perfect for you. Or perhaps, you do know that Python is a programming language.

Welcome to our series of tutorials on the Python programming language, with a specific application to the Raspberry Pi.

Sites


Before we get too far, I want to provide you with a few basic links. The first is to python.org.

If you spoke another language, such as Spanish, Portuguese, French, Italian or Russian, I would point you to several other websites, because for other languages, individuals tend to have more complete sites than the official one (or, I should say, more in tune with the culture). But since you are reading this in English, python.org will become your primary stop.

There, you will discover regional user groups, documentation, downloads, mailing lists, a wiki and eventually, a Jobs section. You can't get enough of documentation? Then readthedocs.org

If you are the visual type, check out also pyvideo.org

Books


In english, there are lots of choices. In fact, too much. For example, on Amazon, you'll find over 1000 books on Python. Even checking out only hardcovers, you'll still end up with over 85 books!

If you live in a big city, it is a good idea to go to your local book store, and check out what books they have. As I've taught some Python to others, and recommended some books based on their personality, I've noticed that almost everybody is different. One book that I like, you might hate, and vice versa, because we are all different.

So you'll have to dig and see what book appeals to you, based on styles that vary from "Python for Kids", "Hello Python" and "Head on Python" to "Core Python Applications Programming" or even a "Python Essential Reference". There are many textbooks available too, some are assigned reading material for Python classes in colleges and universities, worth your time to check them out.

Free eBooks


I won't hold back in recommending some books in this section. Considering how much you will pay for them... the value to cost ratio is difficult to beat :)

Green Tea Press has several free ebooks. These are also available in print and eBooks from OReilly, for a fee. Among them is the classic text (or I should say the new edition of the classic text):

How to Think Like a Computer Scientist: Think Python

There is also Think Complexity

Moving on to another editor, for many years, Dive into Python was another recommended book. It is available to download for free.

In the not quite ebook, it is an online book category:
Learn Python The Hard way
A Byte of Python
Code Like a Pythonista: Idiomatic Python

If you are into games or young, or you want to help younger folks to learn Python, you might want to look at:

Invent With Python
Snake Wrangling For Kids

Have you found a free ebook that should be mentionned here? If so, please leave a comment!

Python, the program



So, Python is a programming language, but it is also an executable program. It is included with the Raspberry Pi and with most Unix type systems (Solaris, OpenIndiana, Mac OS/X and the varieties of Linux and BSDs ). It can also be installed on Windows, iPhone, iPad, Android etc. In this article, I will differentiate the python program from the language, by writing it in bold characters.

It is possible to use python in various ways or mode of operations.


First mode 

 

In an interactive way, from the command line:


pi@raspberrypi ~ $ python
Python 2.7.3rc2 (default, May  6 2012, 20:02:25) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 


The python shell is now waiting for a command, to have something to do. We will ask it to print the words "Raspberry Pi!". In the same way that I had to put quotes around the words to clearly differentiate what I want to print from the rest of the text, in Python it is required to delimit a string of characters (a sentence) with simple (') or double (") quotes on each side of this string:

pi@raspberrypi ~ $ python
Python 2.7.3rc2 (default, May  6 2012, 20:02:25) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> print "Raspberry Pi!"



To simulate this in your browser (with Brython), click python answered us:
Raspberry Pi!



If our string of characters extends beyond one line, then we will have to use the single or double quote symbol, three times on each side ("""I'm writing a lot of words, so you better be ready for me with a multiline string, since I will go on and on and on.""")


pi@raspberrypi ~ $ python
Python 2.7.3rc2 (default, May  6 2012, 20:02:25) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> print """
... Raspberry Pi!
... I continue.
... I am done!
... """

Raspberry Pi!
I continue.
I am done!



python answered us:
Raspberry Pi!
I continue.
I am done!

However, in our case, we are not done at all, we are barely starting! It is as good a time as any to mention at this point that a string that starts and ends with the triple quote ("""string of characters""") by itself, without instructions, is what is called a docstring, a string of characters (or sentence) for documentation purpose. A form of commentary, in other words.

We can also use python like a calculator:


pi@raspberrypi ~ $ python
Python 2.7.3rc2 (default, May  6 2012, 20:02:25) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 2 * 32
64


To exit the python program, we must type the commant: exit()
The word exit, followed by a pair of parenthesis, indicates that exit() is a function. We will come back to functions later on (and we will learn that print is a strange animal, that should be written print(), but that's for another time).

Second mode


If our code is quite short, there are no issues with using the interactive mode. But it might become burdensome to write the same code again and again. It's a good thing then that we can save our code in a file, whose name will end with the .py extension and that we can execute again and again.

Let's save our program that prints "Raspberry Pi!" in a file. In order to do that, we will need an editor. For the experts, I would recommend something like scribes in graphical mode and vim in console mode (text).

Since we are starting out, I would recommend instead to install geany:

pi@raspberrypi ~ $ sudo apt-get install geany
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following extra packages will be installed:
  geany-common
Suggested packages:
  doc-base
The following NEW packages will be installed:
  geany geany-common
0 upgraded, 2 newly installed, 0 to remove and 134 not upgraded.
Need to get 3,401 kB of archives.
After this operation, 8,682 kB of additional disk space will be used.
Do you want to continue [Y/n]? y
Get:1 http://mirrordirector.raspbian.org/raspbian/ wheezy/main
 geany-common all 1.22+dfsg-2 [2,336 kB]
Get:2 http://mirrordirector.raspbian.org/raspbian/ wheezy/main
 geany armhf 1.22+dfsg-2 [1,065 kB]
Fetched 3,401 kB in 6s (518 kB/s)                                              
Selecting previously unselected package geany-common.
(Reading database ... 91199 files and directories currently installed.)
Unpacking geany-common (from .../geany-common_1.22+dfsg-2_all.deb) ...
Selecting previously unselected package geany.
Unpacking geany (from .../geany_1.22+dfsg-2_armhf.deb) ...
Processing triggers for hicolor-icon-theme ...
Processing triggers for man-db ...
Processing triggers for menu ...
Processing triggers for desktop-file-utils ...
Setting up geany-common (1.22+dfsg-2) ...
Setting up geany (1.22+dfsg-2) ...
Processing triggers for menu ...
pi@raspberrypi ~ $ geany raspberry.py


We just launched our editor, with a filename of raspberry.py. We type our code in the window raspberry.py:

Geany code editor

We save, and we quite (for right now, so as to keep things simple).

How can we run our raspberry.py script? Quite easily:


pi@raspberrypi ~ $ python raspberry.py
Raspberry Pi!
pi@raspberrypi ~ $ 

This is the second mode of operation, python running a script.

Third mode


The third mode is available on Unix type computers, and as such, on the Raspberry Pi. Let's bring up our geany code editor once more:

pi@raspberrypi ~ $ geany raspberry.py 
 
We insert a new line 1


We save after adding the new line, and we quite. The line we've just added to the script tells the operating system shell which program will run this code. In our case, we specify python.

That is not all, however. We also have to change the file from a document mode, to an executable mode. We do this through the chmod command:

pi@raspberrypi ~ $ chmod +x raspberry.py

We are now ready to launch our script directly:

pi@raspberrypi ~ $ ./raspberry.py
Raspberry Pi!
pi@raspberrypi ~ $

With a little help from my friend


Wouldn't it be nice if we didn't have to leave the comfort of our editor each time we wanted to run the program after making a change? We can do this through the use of the F5 key, or through the Build->Execute menu, or even using the button with the gears (View or Execute):

Raspberry Pi! - press return to exit that screen


This concludes our first basic tutorial on Python on the Raspberry Pi. I hope that this was sufficient to get you started.

@f_dion

Friday, January 25, 2013

Brython trouve son code...

Sur le serveur


Jusqu’à maintenant, en parlant de Brython, j'ai mis l'emphase sur le fait que tout roule du cote du client, dans la page web.

Sur ce blog, par exemple, j'inclus même brython.js a même le gabarit de blogger, pour figer la version. Tout est ici.

Et bien sur, le code Python lui meme se retrouve a l'interieur de la balise script:

<script type="text/python">
#le code python va ici
print("Et pourtant elle tourne")
</script>

Mais pourquoi le titre?


C'est pour introduire une nouvelle fonctionnalité de Brython, tout juste sortie du four! On peut maintenant utiliser la syntaxe:

<script type="text/python" src="script.py"></script>

Et le Brython va aller chercher script.py (ou tout autre script python que l'on spécifie) avec un appel ajax, sur le serveur (avec certains navigateurs comme Firefox, en fait meme pas besoin de serveur, on peut faire un file open de la page web et tout fonctionne localement). Sur un serveur, comme c'est avec ajax, bien sur le script se doit d’être sur le même domaine que la page web.

On pourra ainsi séparer notre structure (.html), nos embellissements (.css) et notre logique (.py).

@f_dion

Sidekick 3

RaspberryPi the sidekick


McDonald's is currently advertising that "every hero needs a sidekick". Of course, if you've been following my blog, you already know this for a fact.

In the previous Sidekick articles (sidekick and sidekick 2), we saw how we could run a program on the Raspberry Pi, and the display would then be forwarded to another computer, through the use of X forwarding with ssh (-X).

One side effect of that mode is that the program will detect the X Windows property of the PC, and not the Raspberry Pi. For example, if your X server on your PC supports Open GL, although the Raspberry Pi supports only Open GL ES 2.0, the program will see the full Open GL stack.

It can be a good thing, but sometimes it can lead to surprises. Another limitation is the windowing.

Windowed


When running a single application, this is the best way to use the Raspberry Pi as a sidekick. But don't forget to use the X forwarding, else you get:

pi@raspberrypi ~ $ idle
Traceback (most recent call last):
  File "/usr/bin/idle", line 5, in <module>
    main()
  File "/usr/lib/python2.7/idlelib/
PyShell.py", line 1406, in main
    root = Tk(className="Idle")
  File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 1688, in __init__
    self.tk = _tkinter.create(screenName, baseName, className,
interactive, wantobjects, useTk, sync, use)
_tkinter.TclError: no display name and no $DISPLAY environment variable


If we use ssh -X (or X forwarding in putty), it works as intended:

idle main window

And if we go to file and do an open, we see the files that are on the Raspberry Pi. Let me open the quick and dirty cam server we did at the last PyHack workshop (I'm putting the code in text too)


#!/usr/bin/env python

import web

class Images:
    def GET(self):
        typemap = {
        "png":"image/png",
        "jpg":"image/jpeg",
        }
        web.header("Content-Type",typemap["png"])
        return open("fablocker.png","rb").read()

URLS = ( "/secret", Images ,
)

app = web.application(URLS, globals())

if __name__ == "__main__":
    app.run()

 This is just the web server side of things and is using web.py

idle new editor window

We now have 2 windows of programs running on the Raspberry Pi, but displaying on our computer's desktop.

But sometimes it's important to have a full desktop. So how do we do this, to see the whole Raspberry Pi desktop?

VNC


The solution is Virtual Network Computing (VNC).

There are many implementations of the protocol, and they fall into 2 camps.
  • The kind that gives you a new desktop (it is not the same as the one that is on the HDMI output)
  • and the kind that attaches to the original desktop.

 

the client


Before installing the server, let's talk about the client. On your PC, if you have a unix or linux machine, there is a chance that you have a program called vncviewer. Try it and see if you do. If not, you will have to install it. This will depend greatly on what operating system you run, as far as how to do that. If your desktop is a *nix, I'm sure you know how to use your package manager to get vncviewer.

Under Windows you can get tightvnc for windows or you can try the multiplatform (Linux, Solaris, Windows, Mac) RealVNC. Many Mac users are partial to Chicken of the VNC.

tightvnc

Tightvnc is of the first category of servers. This has the advantage that you can get a graphical desktop, even if your Raspberry Pi is on the command line, without X windows started.

First we will have to install it:

$ ssh -X pi@raspberrypi

pi@raspberrypi ~ $ sudo apt-get install tightvncserver
pi@raspberrypi ~ $ tightvncserver :1

You will require a password to access your desktops.

Password:
Verify:
Would you like to enter a view-only password (y/n)? n

New 'X' desktop is raspberrypi:1

Creating default startup script /home/pi/.vnc/xstartup
Starting applications specified in /home/pi/.vnc/xstartup
Log file is /home/pi/.vnc/raspberrypi:1.log

 And that should work, when you access your Pi remotely with a client.

It is also possible with tightvnc to specify a screen resolution that you choose, and it wont have anything to do with the Raspberry Pi screen resolution or your monitor's capability. For example, to get a 1024x768 desktop:

pi@raspberrypi ~ $ tightvncserver -geometry 1024x768 :1

Of course if tightvnc works well for you, you will want to automate the startup of the server. Stewart Watkiss posted the following startup script on elinux.org (would go under /etc/init.d as tightvncserver):

#!/bin/sh
# /etc/init.d/tightvncserver
# Customised by Stewart Watkiss
#http://www.penguintutor.com/linux/tightvnc
# Set the VNCUSER variable to the name of the user
# to start tightvncserver under
VNCUSER='pi'
eval cd ~$VNCUSER
case "$1" in
 start)
   su $VNCUSER -c '/usr/bin/tightvncserver :1'
   echo "Starting TightVNC server for $VNCUSER "
   ;;
 stop)
   pkill Xtightvnc
   echo "Tightvncserver stopped"
   ;;
 *)
   echo "Usage: /etc/init.d/tightvncserver {start|stop}"
   exit 1
   ;;
esac
exit 0

And then under .vnc, a xstartup file that contains the following:

!/bin/sh
xrdb $HOME/.Xresources
xsetroot -solid black
/usr/bin/lxsession -s LXDE &
  
And that should be it. After rebooting, it should start automatically. If you prefer x11vnc, then follow the next set of instructions instead.

x11vnc


x11vnc falls into the second category of servers. It will attach to the X server of the Raspberry Pi. As such, you have to run LXDE, you can't stay in console text mode.

To install it:

$ ssh -X pi@raspberrypi

pi@raspberrypi ~ $ sudo apt-get install x11vnc
pi@raspberrypi ~ $ x11vnc -storepasswd

You'll have to enter a password. Next we need to configure the startup with lxde:

pi@raspberrypi ~ $ cd .config
pi@raspberrypi ~ $ mkdir autostart
pi@raspberrypi ~ $ cd autostart
pi@raspberrypi ~ $ wget http://brython.heliohost.org/x11vnc.desktop

Be patient on the last step, this is getting a startup file for x11vnc from my Brython playground site. It is usually pretty busy... Reboot the Pi and you should be set.


Accessing the Pi


Once you have the server running, you run the viewer on your PC, and specify the ip address (or name if you have avahi or the like), you will be greeted with a password prompt. That is the password you defined for the server, earlier:




And then, the desktop!


1024x768 Raspbian desktop, from my PC
@f_dion

Tuesday, January 22, 2013

RaspberryPi como servidor web

Cherokee


Hacer la instalación de Cherokee Web Server en el Raspberry Pi con Raspbian es fácil, pero requiere tiempo.

wget http://cherokee-project.com/install && sudo python install
 

Y hay que esperar que la compilación se termina (mas de 30 min., me parece), con éxito (esperamos).

Dinámico

 

El Pi es pequeño en cuanto a su tamaño, pero también en cuanto a la cantidad de RAM y de core en el CPU. En cuanto a un sitio de tipo dinámico, es posible hacerlo con un Pi, pero hay que pensar en la aplicación. Como servidor personal o de departamento, es bueno.

Pero, y un blog? En mi caso, he notado que a veces hay 75+ visitantes al mismo tiempo. No es razonable esperar que un Pi puede hacerlo de manera dinámica. Por eso, vamos a ver lo que podemos esperar con Cherokee y un contenido estático.

Estático


Primero, voy a iniciar Cherokee:

$ sudo /opt/cherokee/sbin/cherokee -d

Luego voy a configurar el modo automático, pero por ahora es suficiente así.

Debemos poner una pagina como test:

$ cd /opt/cherokee/var/www
$ sudo wget http://raspberry-python.blogspot.com/2013/01/going-in-wrong-direction.html

En mi workstation, tengo apache, con ab:

$ which ab
/usr/bin/ab
$ ab
ab: wrong number of arguments
Usage: ab [options] [http[s]://]hostname[:port]/path
Options are:
    -n requests     Number of requests to perform
    -c concurrency  Number of multiple requests to make
    -t timelimit    Seconds to max. wait for responses
    -b windowsize   Size of TCP send/receive buffer, in bytes
    -p postfile     File containing data to POST. Remember also to set -T
    -u putfile      File containing data to PUT. Remember also to set -T
    -T content-type Content-type header for POSTing, eg.
                    'application/x-www-form-urlencoded'
                    Default is 'text/plain'
    -v verbosity    How much troubleshooting info to print
    -w              Print out results in HTML tables
    -i              Use HEAD instead of GET
    -x attributes   String to insert as table attributes
    -y attributes   String to insert as tr attributes
    -z attributes   String to insert as td or th attributes
    -C attribute    Add cookie, eg. 'Apache=1234. (repeatable)
    -H attribute    Add Arbitrary header line, eg. 'Accept-Encoding: gzip'
                    Inserted after all normal header lines. (repeatable)
    -A attribute    Add Basic WWW Authentication, the attributes
                    are a colon separated username and password.
    -P attribute    Add Basic Proxy Authentication, the attributes
                    are a colon separated username and password.
    -X proxy:port   Proxyserver and port number to use
    -V              Print version number and exit
    -k              Use HTTP KeepAlive feature
    -d              Do not show percentiles served table.
    -S              Do not show confidence estimators and warnings.
    -g filename     Output collected data to gnuplot format file.
    -e filename     Output CSV file with percentages served
    -r              Don't exit on socket receive errors.
    -h              Display usage information (this message)
    -Z ciphersuite  Specify SSL/TLS cipher suite (See openssl ciphers)
    -f protocol     Specify SSL/TLS protocol (SSL2, SSL3, TLS1, or ALL)


Ahora, podemos hacer una simulación de carga.

Vamos a hacer 1000 solicitudes en total (-n), y 75 al mismo tiempo (-c):

$ ab -n 1000 -c 75 -v 1 http://raspberrypi/going-in-wrong-direction.html
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking raspberrypi (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests

Server Software:        Cherokee/1.2.101
Server Hostname:        raspberrypi
Server Port:            80

Document Path:          /going-in-wrong-direction.html
Document Length:        208790 bytes

Concurrency Level:      75
Time taken for tests:   18.010 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Total transferred:      209724452 bytes
HTML transferred:       209520640 bytes
Requests per second:    55.53 [#/sec] (mean)
Time per request:       1350.741 [ms] (mean)
Time per request:       18.010 [ms] (mean, across all concurrent requests)
Transfer rate:          11372.03 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        1   96  36.0    109     128
Processing:   132 1232 266.1   1166    2738
Waiting:       16  182 153.4    126     949
Total:        143 1328 262.7   1278    2853

Percentage of the requests served within a certain time (ms)
  50%   1278
  66%   1320
  75%   1417
  80%   1479
  90%   1671
  95%   1777
  98%   1951
  99%   2060
 100%   2853 (longest request)

Aprobado


Así que, me parece que con contenido estático (quizás generado por medio de Pelican), y con poco imágenes (en el test aquí, ninguna), si, el Raspberry Pi podría servir de servidor de blog.

@f_dion

Sunday, January 20, 2013

Going in the wrong direction

$35 going once


XYZ is the new RaspberryPi. Replace XYZ by several small form computers that have hit the market in the past year (particularly those powered by ARM). That is the claim being made again and again.

Except that the Pi is small, and a lot of the contenders are bigger. Or dont have gpios. And we re not talking about embedded systems. We need an OS. And Python. But really, at the end of the day, they cost too much. How much is too much I hear you say...

We are already conditioned to a $35 price tag. A fully capable Linux computer with a fast GPU and gpios. And a DSI and CSI connector. You can bring more to the table, such as ADC, PWM, USB3, bluetooth or sata, but if the price is greater, then you are going in the wrong direction.

$35 going twice


To put things in perspective as to what $35 buys you, at a local retailer, an Arduino Uno (an embedded system, so in theory it should be cheaper) will set me back $35, and then I have to add a $40 ethernet shield if I want to get it on the network. I go to Barnes and Nobles, and I cant find a decent computer science book for that amount. So yes, the Raspberry Pi foundation set the bar high.

Yet, it is impossible to ignore that. And the fact is that while $35 got you a 256MB Pi model B some months back, it now gets you a 512MB Pi model B.

Sold for $25


Just as we got comfortable with the Raspberry Pi model B, we are about to get a $25 version, with a lower power consumption. For a lot of projects, the model B was already overkill, so the model A will really further increase the perceived value of the Raspberry Pi platform.

What's the deal?


Why does it matter? Because at that price level, we don't mind losing one in an experiment. That means that, no matter how young or old, we all have an opportunity to try something we would never have done if the Pi cost $100 or even $80, and even less so if we had to use a $1000 computer...

Let me illustrate. As a teenager, I almost cut short my career in technology. I was working with a friend on building a robot, controlled by an Apple ][ compatible. Even though it was not the real deal (it was a clone), that computer was very expensive to us (over $1000). But we had no other way. And all our knowledge came from books at the library or bulletin board systems. There was no world wide web. But hey, we found out that the game port not only had digital and analog inputs (for analog joysticks), it also had digital outputs (annunciator 0- 3):

Apple 16-pin DIP Game Port socket (on the motherboard)
  for II, II+, IIe, IIgs


                           =========
Pushbutton 3 (GS only)  9 | *     * | 8  Ground
 Gm Ctrl 1 (Stick-1 Y) 10 | *     * | 7  Gm Ctrl 2 (Stick-2 X)
 Gm Ctrl 3 (Stick-2 Y) 11 | *     * | 6  Gm Ctrl 0 (Stick-1 X)
        Annunciator 3  12 | *     * | 5  /$C040 Strobe
        Annunciator 2  13 | *     * | 4  Pushbutton 2
        Annunciator 1  14 | *     * | 3  Pushbutton 1
        Annunciator 0  15 | *     * | 2  Pushbutton 0
        No Connection  16 | *     * | 1  +5V
                           ===| |===
                               ^
                        Notch on socket
                (faces toward front of computer)

Still, we were a little concerned in hacking the expensive machine. And sure enough, during our experimenting over many weeks, we melted one of the trace on the motherboard. Smoke. Bad smell. Angry mom. Sad friend with a busted Apple ][ compatible. Well, the story does have a happy ending in that we were able to repair the computer and we both continued experimenting and going each our way in the field of technology, but I'm not sure what would have happened had we not been able to fix the computer...

That is why we need $25 computers. It wouldn't surprise me if we see $20, or even sub $20 computers... If you were thinking $60, then as I mentionned, you are going in the wrong direction.

Food for thought


Now that we've cleared this point up, makers of Raspberry Pi add-ons, you're the next contestants on the price is right.  

Are you going in the wrong direction?


@f_dion

Saturday, January 19, 2013

RPi.GPIO, l'imposteur

Un module python pour ateliers raspberrypi

L'atelier PyHack qui est présenté par PYPTUG porte sur comment faire interagir le monde virtuel de la programmation avec le monde physique, avec Python, et souvent avec l'ordinateur Raspberry Pi. Mais pas tout le monde a un Raspberry Pi.

Les ateliers, et aussi les tutoriels de ce blog, sont faits pour apprendre l’électronique et le langage de programmation Python. Les techniques Python enseignées ici, peuvent être appliquées a une grande variété de problèmes.

Et donc, ca serait drolement bien si on pouvait rouler le meme code qui est fait pour le raspberry pi, sur un portable? J'ai donc décidé de m'attaquer au probleme.

L'imposteur

J'ai donc ecris un module RPi.GPIO de remplacement pour essayer le code pour Raspberry Pi qui inclus des access aux GPIO sur des plateformes autres que le Raspberry Pi. C'est-a-dire, votre portable.


Votre portable aura une personnalité multiple

Comment?

C'est un module Python (en fait, un package RPi qui inclus un module GPIO) avec une implémentation des fonctions setmode(), setup(), cleanup(), input(), output(), prends en charge les 54 GPIO (états et directions), modes broadcom et board, fonctions vides pour les 4 set_*_event() - peut etre dans le futur j'y ajouterai la logique, et un peu de vitriol. Non, pas ca! Et un peu de logique pour les erreurs (genre, essayer d'acceder a un GPIO avant de l'assigner). Il y a meme un mode de débogage (gpio.debug = True).

Tout cela devrait etre suffisant pour rouler sur un portable du code Python fait pour le Raspberry Pi. Seul hic, pas moyen dans l’immédiat de simuler le changement d’état d'une entrée (simuler un bouton, par exemple).

On se le procure

Chez Mr. Bitbucket (bitbucket.org/fdion)

Pour cela il faut d'abord avoir l'outil Mercurial:

Sous un Linux de type debian
$ sudo apt-get install mercurial

Sous fedora
$ sudo yum install mercurial

Sous solaris / openindiana, c'est disponible par packagemanager.

Pour windows ou mac c'est ici: http://mercurial.selenic.com/

A noter que sous windows on peut aussi se procurer TortoiseHg qui ajoute un menu Mercurial (hg) quand on clique le bouton droit sur un repertoire, dans file manager: tortoisehg.bitbucket.org

Une fois que l'on a installé mercurial:

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

On se retrouve avec les ateliers PyHack. RPi.GPIO l'imposteur fait partie du deuxième atelier.

C'est sous le répertoire fablocker/PyHack/workshop02

Il y a un fichier test.py qui importe RPi.GPIO. Comme il y a un répertoire du nom de RPi et a l’intérieur, un fichier python GPIO.py, test.py va importer ce GPIO plutôt que le module système. Pour en faire l'essai, on fait:

python test.py

(RPi.GPIO l'imposteur n'a pas besoin de sudo, ce qui n'est pas le cas du module système). Sur un vrai Raspberry Pi, dans tout autre répertoire l'imposteur ne sera pas trouvé. Mais dans workshop02, si on veux rouler test.py sur les GPIO du Pi, il suffit de renommer le répertoire RPi, et ainsi Python ne le trouvera pas.

Mise a jour


Si vous avez déjà un clone du projet Fablocker, pour faire la mise a jour, il suffit de faire (sous Windows utiliser le hg workshop ou bien la ligne de commande):


pi@raspberrypi ~/bitbucket $cd fablocker
pi@raspberrypi ~/bitbucket/fablocker $ hg pull http://bitbucket.org/fdion/fablocker
real URL is https://bitbucket.org/fdion/fablocker
pulling from http://bitbucket.org/fdion/fablocker
searching for changes
adding changesets
adding manifests
adding file changes
added 2 changesets with 5 changes to 5 files
(run 'hg update' to get a working copy)
pi@raspberrypi ~/bitbucket/fablocker $ hg update
5 files updated, 0 files merged, 0 files removed, 0 files unresolved
pi@raspberrypi ~/bitbucket/fablocker $ 

Wednesday, January 16, 2013

Pi-A-Sketch code review

Python code repository

Are you guys ready to look at the entrails of the Python? We are going to review the code for the Pi-A-Sketch. Liz already published the URL for the Pi-A-Sketch code on the raspberrypi.org site, but just in case, here it is again:

piasketch.py (bitbucket)

To clone it to your local machine:

hg clone https://fdion@bitbucket.org/fdion/pi-a-sketch

Of course you have to have mercurial installed. But for the sake of our discussion, you don't need to do that right now. We are going to display the code right here, and then talk about it.

Initial thought process


Initially, I wanted a function to draw a line, and a function to draw a circle. That meant that I would have to write the guts of:

def line(x, y):
    pass

def circle(radius):
    pass

Right away I knew that I would need to do something based on Bresenham's line drawing algorithm. Horizontal lines are trivial (turn one motor), and so are vertical lines (turn the other motor). Perfect diagonals are also easy to accomplish, since it is just a matter of turning both motors at the same time, for each step. Based on the direction of the steps, 4 different diagonal directions can be done.

Yet, with Bresenham's algorithm, you can do a line of any slope. In order to save time, I was not going to do special case handling, and just do everything with his algorithm.



Bresenham's algorithm was also adapted to drawing circles. It is usually found under the name "midpoint circle algorithm".

I also knew that I would be reusing the stepper() function I had designed earlier (see PyHack step by step article), and that meant importing the RPi.GPIO module and setting up the pins. I also wanted to have the demo code in a main() function, and only execute this if the program was run directly and not imported. That would allow me to use the program as a module also.

With that in mind, all I had left was a function to move my X/Y assembly by so many steps, on an axis and a specific direction, and I already had one done for another piece of the workshop. Oh, and of course do the guts of my functions.

Overall, it left me with very few choices as to how to implement this, given all these preliminary.

Before we jump into the code, for any of this to make sense, you will have to read the PyHack step by step article, so you understand at least a little bit how stepper motors operate, and see some simpler Python code. 

And if you are just starting and dont know anything about RPi.GPIO, I'd recommend checking out the Tutorial section in the menu at the top of the site, and in particular PyHack Workshop #01 and RPi.GPIO dot dash

Code

Let's first look at the whole listing:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
#!/usr/bin/env python
""" piasketch - The Pi-A-Sketch program draws a few circles
It is also a basic library for steppers controlling an X/Y device.

The main functions are line(x,y) and circle(radius).
"""
import RPi.GPIO as gpio
import time

PINS_A = [4, 25, 24, 23]
PINS_B = [22, 21, 18, 17]
PINS = PINS_A + PINS_B
SEQA = [(4, ), (4, 25), (25, ), (25, 24), (24, ), (24, 23), (23, ), (23, 4)]
RSEQA = SEQA[::-1][1:] + SEQA[::-1][:1]
SEQB = [(22, ), (22, 21), (21, ), (21, 18), (18, ), (18, 17), (17, ), (17, 22)]
RSEQB = SEQB[::-1][1:] + SEQB[::-1][:1]

CORRECTION = 22


def setallpins():
    """
    Before we can use them, we need to set the pins for both motors
    """
    gpio.setmode(gpio.BCM)
    for pin in PINS:
        gpio.setup(pin, gpio.OUT)


def stepper(sequence, pins, delay=0.002):
    """
    One full step, based on an ordered sequence and corresponding pins
    """
    for step in sequence:
        for pin in pins:
            gpio.output(pin, gpio.HIGH if pin in step else gpio.LOW)
        time.sleep(delay)


def move(steps, axis="x"):
    """
    Move a certain number of steps on a specific axis. sign for direction
    """
    (seq, pins) = (SEQA, PINS_A) if axis == "x" else (SEQB, PINS_B)
    if steps < 0:
        steps = -steps
        seq = RSEQA if axis == "x" else RSEQB
    for _ in range(steps):
        stepper(seq, pins)


def line(x1, y1):
    """
    Bresenham's line algorithm from rosetta code (ADA/Python),
    adapted to relative positioning. It will be drawn from current
    position to x1,y1. Negative number goes in other direction.
    """
    dx = abs(x1)
    dy = abs(y1)
    x, y = 0, 0
    px, py = x, y
    sx = -1 if 0 > x1 else 1
    sy = -1 if 0 > y1 else 1
    if dx > dy:
        err = dx / 2.0
        while x != x1:
            if x - px is not 0:
                move(x - px)
            if y - py is not 0:
                move(y - py, "y")
            px, py = x, y
            err -= dy
            if err < 0:
                y += sy
                err += dx
            x += sx
    else:
        err = dy / 2.0
        while y != y1:
            if x - px is not 0:
                move(x - px)
            if y - py is not 0:
                move(y - py, "y")
            px, py = x, y
            err -= dx
            if err < 0:
                x += sx
                err += dy
            y += sy
    if x - px is not 0:
        move(x - px)
    if y - py is not 0:
        move(y - py, "y")


def _circlepoints(radius):
    """
    mid point circle drawing algorithm - basically Bresenham's.
    It was converted for use with relative. Cant use the bitmap
    approach of the original algorithm. Points are sequential
    clockwise. It works, but it is ugly. I only had a few
    minutes to write this. It needs to be cleaned up.
    """
    points = []
    segment = []
    for seg in range(8):
        x0, y0 = 0, -radius
        f = 1 - radius
        ddf_x, ddf_y = 1, -2 * radius
        x, y = 0, radius

        segment = []
        while x < y:
            if seg == 0:
                segment.append((x0 + x, y0 + y))
            elif seg == 1:
                segment.append((x0 + y, y0 + x))
            elif seg == 2:
                segment.append((x0 + y, y0 - x))
            elif seg == 3:
                segment.append((x0 + x, y0 - y))
            elif seg == 4:
                segment.append((x0 - x, y0 - y))
            elif seg == 5:
                segment.append((x0 - y, y0 - x))
            elif seg == 6:
                segment.append((x0 - y, y0 + x))
            elif seg == 7:
                segment.append((x0 - x, y0 + y))

            if f >= 0:
                y -= 1
                ddf_y += 2
                f += ddf_y
            x += 1
            ddf_x += 2
            f += ddf_x
        if seg % 2:
            points.extend(segment[::-1])
        else:
            points.extend(segment)
    return points


def circle(radius):
    """
    Draw a circle of specified radius. It is not centered. It will start
    drawing a circle at its current position, going right and down.
    """
    points = _circlepoints(radius)
    #print points
    dx, dy = 1, 1
    px, py = 0, 0
    counter = 0
    total = len(points)
    quarter = total / 4
    #print total, quarter
    for (x, y) in points:
        counter += 1
        dx, dy = x - px, y - py
        px, py = x, y
        # print dx, dy
        if dx is not 0:
            move(dx)
        if dy is not 0:
            move(dy, "y")
        if counter == quarter:
            move(-CORRECTION)
        elif counter == quarter * 2:
            move(CORRECTION, "y")
        elif counter == quarter * 3:
            move(CORRECTION)
        elif counter == total:
            move(-CORRECTION, "y")


def main():
    """
    As a standalone app, draw a few circles
    """
    setallpins()
    #line(-50, -200)
    #line(5,0)
    #circle(350)
    for radius in range(100,400,25):
        circle(radius)
    gpio.cleanup()

if __name__ == "__main__":
    main()


It's all in the details

Documentation

Documentation of any program is really important. Don't skip this step, and don't forget to document all the functions too:

1:  #!/usr/bin/env python  
2:  """ piasketch - The Pi-A-Sketch program draws a few circles  
3:  It is also a basic library for steppers controlling an X/Y device.  
4:    
5:  The main functions are line(x,y) and circle(radius).  
6:  """  

You would want to add document encoding instructions at the top, on line 2, if you had to use strings with special characters or accents (like in french, spanish or russian).

Imports

Logically the next step in a Python script is to import modules, if we need any.

7:  import RPi.GPIO as gpio  
8:  import time  

On line 7 we import RPi.GPIO but with an alias of gpio, so we don't have to fully qualify the namespace for every gpio function we call. Please use lowercase gpio. Uppercase in Python is usually for constants (yes, I know that the RPi.GPIO module name itself is not following the standard).

Line 8, we import time, as we will need time.delay() to prevent stalling the steppers (we will use that on line 37).

And speaking of importing a module, the last 2 lines of code allow us to either run this program (see the main() function) or import it as a module:
189:  if __name__ == "__main__":  
190:    main()  

Constants

In Python, all uppercase indicates a constant. This is a convention, since there is no enforcing of the type.

10:  PINS_A = [4, 25, 24, 23]  
11:  PINS_B = [22, 21, 18, 17]  
12:  PINS = PINS_A + PINS_B  
13:  SEQA = [(4, ), (4, 25), (25, ), (25, 24), (24, ), (24, 23), (23, ), (23, 4)]  
14:  RSEQA = SEQA[::-1][1:] + SEQA[::-1][:1]  
15:  SEQB = [(22, ), (22, 21), (21, ), (21, 18), (18, ), (18, 17), (17, ), (17, 22)]  
16:  RSEQB = SEQB[::-1][1:] + SEQB[::-1][:1]  
17:    
18:  CORRECTION = 22  

Line 10 defines the GPIOs for the first stepper (Broadcom GPIO nomenclature) while line 11 defines the GPIOs for the second motor. On a REV2 board, some of the GPIOs have to be adjusted. Specifically, GPIO21 is GPIO27 on a REV2. From these two lists, we create a new one combining all the pins on line 12.

Line 13 and 15 correspond to the sequences for each motors to step forward. In the previous article, the backward sequences were also spelled out like this. Here, on lines 14 and 16, we create the correct backward sequence from two reversed slices of the forward sequence. Spend a bit of time understanding this. If you've never used slices before, it would be a good idea to read the Python tutorial on lists and also this thread on stackoverflow

Finally, on line 18, we define the correction factor (to deal with hysteresis). This is the amount of stepping that has to be done to undo the slack in the Etch-A-Sketch when we reverse direction.

Preliminary GPIO operations


We are now getting into the GPIO code. In an interactive session or a main() program, setallpins() has to be called before line() and circle() can be used.

21:  def setallpins():  
22:    """  
23:    Before we can use them, we need to set the pins for both motors  
24:    """  
25:    gpio.setmode(gpio.BCM)  
26:    for pin in PINS:  
27:      gpio.setup(pin, gpio.OUT)  

Line 21 defines the function, with no arguments. It uses our global PINS constant. It would be a better idea to not use globals, but for such a small module, in a workshop setting, this is an acceptable compromise. Again, don't forget to document your code (lines 22-24)...

Line 25 sets the gpio module to Broadcom GPIO numbering (BCM). Line 26 establishes a loop that will iterate through all the elements of the list PINS, and call line 27 to set up each pin (element) as OUTput.

Basic stepper movement

Reusing the function we defined in the stepper section of PyHack:

30:  def stepper(sequence, pins, delay=0.002):  
31:    """  
32:    One full step, based on an ordered sequence and corresponding pins  
33:    """  
34:    for step in sequence:  
35:      for pin in pins:  
36:        gpio.output(pin, gpio.HIGH if pin in step else gpio.LOW)  
37:      time.sleep(delay)  

On line 30 we are defining a function, with 2 required arguments, and an optional argument: a sequence and pins, and the optional delay (will default to 0.002 seconds if not provided).

Line 34 starts iterating through the sequence, and for each step in that sequence (notice how the Python is written just like english or pseudocode?) we will iterate through all the pins in order to set them HIGH or LOW. We do that through a ternary operator on line 36:

gpio.HIGH if pin in step else gpio.LOW

In other words, if the pin I'm looking at is in that step, return a gpio.HIGH value, else return a gpio.LOW value. We then pass that as the second argument to gpio.output().

Multiple steps on one axis and one direction

The previous function does one step based on a sequence. This one leverages stepper() to do many steps.

40:  def move(steps, axis="x"):  
41:    """  
42:    Move a certain number of steps on a specific axis. sign for direction  
43:    """  
44:    (seq, pins) = (SEQA, PINS_A) if axis == "x" else (SEQB, PINS_B)  
45:    if steps < 0:  
46:      steps = -steps  
47:      seq = RSEQA if axis == "x" else RSEQB  
48:    for _ in range(steps):  
49:      stepper(seq, pins)  

On line 40 we define the function, with steps as required and by default it will happen on the X axis. if we pass "y" (really anything else but "x") it will change sequence to the one that controls the other motor.

On line 44 we build a tuple that will control motor A (x axis) or B (y axis), depending on the axis. It is again a ternary operator that is used. We are almost ready to call the stepper() function, but first, we have to figure out which direction we are going on the axis. On line 45, if we have negative steps, it means we need to reverse the sequence. We convert the negative number into a positive one on line 46 and use a reversed sequence on line 47 (RSEQA if  we are on the x axis, else we use RSEQB).
Finally, on line 48 we loop for the number of steps, calling the stepper() function for each steps.

Drawing lines

This is where we ask Bresenham for help:

On Rosetta code, you'll find an article that works on bitmaps (absolute positioning). If we look at the core itself:
        while x != x1:
            self.set(x, y)
            err -= dy
            if err < 0:
                y += sy
                err += dx
            x += sx
 
This corresponds to our lines 66, 72-76:
 
52:  def line(x1, y1):  
53:    """  
54:    Bresenham's line algorithm from rosetta code (ADA/Python),  
55:    adapted to relative positioning. It will be drawn from current  
56:    position to x1,y1. Negative number goes in other direction.  
57:    """  
58:    dx = abs(x1)  
59:    dy = abs(y1)  
60:    x, y = 0, 0  
61:    px, py = x, y  
62:    sx = -1 if 0 > x1 else 1  
63:    sy = -1 if 0 > y1 else 1  
64:    if dx > dy:  
65:      err = dx / 2.0  
66:      while x != x1:  
67:        if x - px is not 0:  
68:          move(x - px)  
69:        if y - py is not 0:  
70:          move(y - py, "y")  
71:        px, py = x, y  
72:        err -= dy  
73:        if err < 0:  
74:          y += sy  
75:          err += dx  
76:        x += sx  
77:    else:  
78:      err = dy / 2.0  
79:      while y != y1:  
80:        if x - px is not 0:  
81:          move(x - px)  
82:        if y - py is not 0:  
83:          move(y - py, "y")  
84:        px, py = x, y  
85:        err -= dx  
86:        if err < 0:  
87:          x += sx  
88:          err += dy  
89:        y += sy  
90:    if x - px is not 0:  
91:      move(x - px)  
92:    if y - py is not 0:  
93:      move(y - py, "y")  

First, I had to add support for relative positioning by adding logic to track the previous position. This is done on lines 61, 71 and 84.

What I then had to do is to add lines 67 through 70, making sure there is something to do (is not 0 condition on lines 67 and 69) and convert the x and y coordinates into a relative movement based on the previously calculated position (lines 68 and 70). Lines 80-83 and 90-93 is more of the same.

This could be refactored into a function and called at these 3 places. And that is a normal part of the coding process. The first pass, you spend only minutes on a specific area of a project, just enough to get it to the point where it can be easily read and maintained, but no more. Then later, when you are done, if you still have some time, do some R&R (refactoring and reengineering) of your code.

Creating a list of coordinates for a circle

It gets worse. Now we have to deal with drawing a circle. What I would suggest is to skip this code and go and check out the circle() function first, then come back to this.
96:  def _circlepoints(radius):  
97:    """  
98:    mid point circle drawing algorithm - basically Bresenham's.  
99:    It was converted for use with relative. Cant use the bitmap  
100:    approach of the original algorithm. Points are sequential  
101:    clockwise. It works, but it is ugly. I only had a few  
102:    minutes to write this. It needs to be cleaned up.  
103:    """  
104:    points = []  
105:    segment = []  
106:    for seg in range(8):  
107:      x0, y0 = 0, -radius  
108:      f = 1 - radius  
109:      ddf_x, ddf_y = 1, -2 * radius  
110:      x, y = 0, radius  
111:    
112:      segment = []  
113:      while x < y:  
114:        if seg == 0:  
115:          segment.append((x0 + x, y0 + y))  
116:        elif seg == 1:  
117:          segment.append((x0 + y, y0 + x))  
118:        elif seg == 2:  
119:          segment.append((x0 + y, y0 - x))  
120:        elif seg == 3:  
121:          segment.append((x0 + x, y0 - y))  
122:        elif seg == 4:  
123:          segment.append((x0 - x, y0 - y))  
124:        elif seg == 5:  
125:          segment.append((x0 - y, y0 - x))  
126:        elif seg == 6:  
127:          segment.append((x0 - y, y0 + x))  
128:        elif seg == 7:  
129:          segment.append((x0 - x, y0 + y))  
130:    
131:        if f >= 0:  
132:          y -= 1  
133:          ddf_y += 2  
134:          f += ddf_y  
135:        x += 1  
136:        ddf_x += 2  
137:        f += ddf_x  
138:      if seg % 2:  
139:        points.extend(segment[::-1])  
140:      else:  
141:        points.extend(segment)  
142:    return points 

The code here is the midpoint circle algorithm. The main issue with that algorithm is that it is very efficient and generate points in a non sequential way.

On lines 107-110 I set up the calculation initial state, before hitting a while loop.

If you look at a circle, we can easily see that at a minimum, it is made for four identical quarters. And a quarter is mirrored around each axis. So, by calculating half of a quarter circle (1/8 of a circle, an octant), we have basically calculated all the points we need, simply by flipping the signs and axis.

Symmetry



On the Rosetta code page, it is basically implemented with these lines:

        self.set(x0 + x, y0 + y, colour)
        self.set(x0 - x, y0 + y, colour)
        self.set(x0 + x, y0 - y, colour)
        self.set(x0 - x, y0 - y, colour)
        self.set(x0 + y, y0 + x, colour)
        self.set(x0 - y, y0 + x, colour)
        self.set(x0 + y, y0 - x, colour)
        self.set(x0 - y, y0 - x, colour)

I've done something similar with lines 115, 117, 119, 121, 123, 125, 127 and 129. But since I am not using a bitmap and I want an ordered list of points (clockwise), I had to loop 8 times (line 106) and add if statements ( lines 114, 116, 118 etc) in front of every calculations. It is slightly slower this way, but at least I'm not calculating these points 7 times too many. Once a segment is complete (generated by the while loop on line 113), I add that segment to my points list, but with yet another twist. If the segment is odd, it has been generated through a mirror coordinate, so it was built in reverse. So we check on line 138 if the segment modulo 2 has a remainder. If it does (odd number) then we add the segment in reverse on line 139 ( [::-1] ), else, we add it straight on line 141.

When we are done, we return the whole list, on line 142.

Drawing circle based on previous function

Now we are talking. We'll draw a circle based on a given radius. It starts like the Bresenham line drawing algorithm. That's because we are drawing tiny lines.

145:  def circle(radius):  
146:    """  
147:    Draw a circle of specified radius. It is not centered. It will start  
148:    drawing a circle at its current position, going right and down.  
149:    """  
150:    points = _circlepoints(radius)  
151:    #print points  
152:    dx, dy = 1, 1  
153:    px, py = 0, 0  
154:    counter = 0  
155:    total = len(points)  
156:    quarter = total / 4  
157:    #print total, quarter  
158:    for (x, y) in points:  
159:      counter += 1  
160:      dx, dy = x - px, y - py  
161:      px, py = x, y  
162:      # print dx, dy  
163:      if dx is not 0:  
164:        move(dx)  
165:      if dy is not 0:  
166:        move(dy, "y")  
167:      if counter == quarter:  
168:        move(-CORRECTION)  
169:      elif counter == quarter * 2:  
170:        move(CORRECTION, "y")  
171:      elif counter == quarter * 3:  
172:        move(CORRECTION)  
173:      elif counter == total:  
174:        move(-CORRECTION, "y")  

I wrote this, inspired by the line algorithm, but with a few twists. First, the line destination is based on a list of points that is calculated by another function (_circlepoints() ) called on line 150. I reset a counter on line 154 and figure out the total number of points on line 155. We will use this on lines 167-174 to correct the slop at the conclusion of each quarter of the circle, where we are changing direction. Line 158 is where I step through the points in the circle (a list of x/y tuples names points).

Within the for loop, I increment the counter on line 159, calculate the deltas on line 160, save the previous value on line 161 and then lines 163-166 I do the move() logic that I've already covered in the line() function. Since this is the 4th time I do this boilerplate code, it tells me that there is an interface mismatch between what I thought I would need when I defined the move() function, and what I really needed. move() was defined for another project, so it makes sense that there is this mismatch. A quick fix would be to modify move() to accept not just a single value, but an x/y tuple also, and inside move() to exit right away if we have a zero value, or to call itself for the x and y axis (recursion). This would get the 4 line boilerplate code back to 1 line. If you want to see the end result, follow me on twitter @f_dion as I will send an update when I refactor the code.

The demo section

We are almost there.

177:  def main():  
178:    """  
179:    As a standalone app, draw a few circles  
180:    """  
181:    setallpins()  
182:    #line(-50, -200)  
183:    #line(5,0)  
184:    #circle(350)  
185:    for radius in range(100,400,25):  
186:      circle(radius)  
187:    gpio.cleanup()  

The main() function is our main demo program. On line 181, we setup the pins for output, on line 185 I loop with a radius that will start at 100, then will go to 125, 150 etc. We simply call the circle() function with that radius. Finally, on line 187, we cleanup, so the gpios are no longer outputs.

The interactive mode


To use the module in an interactive mode:

Just make sure you qualify the functions using the module name, or the alias if you import as alias
pi@fdion-rpi ~/ $ sudo python
Python 2.7.3rc2 (default, May  6 2012, 20:02:25)
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import piasketch as p
>>> setallpins()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'setallpins' is not defined
>>> p.setallpins()
>>> p.line(25,12)
>>> p.circle(80)
>>> p.gpio.cleanup()
>>> exit()


This concludes our piasketch.py code review!

@f_dion