Monday, July 1, 2013

Updating Tkinter applications at an interval - Clock demo

Python version: 2.7.5
Tkinter version: 8.5.2

 Tkinter includes a method after() that provides the ability to call a method after a certain amount of time.  Here is a simple demo of how to use that method to update a clock.  The delay variable in after is in mili-seconds (1/100 th second).  By updating every 50 I am able to show the clock down the the second with relative precision.

from ttk import Frame, Label
from Tkinter import StringVar
from time import strftime

class Clock(Frame):
    def __init__(self):
        Frame.__init__(self)
        self.master.title("Simple Clock")
        self.pack()

        self.time = StringVar()

        Label(self, textvariable=self.time, width=30).pack(padx=10, pady=15)

        self.update_time()

    def update_time(self):
        self.time.set(strftime('%m/%d/%Y - %H:%M:%S'))
        self.after(50, self.update_time)

if __name__ == '__main__':
    Clock().mainloop()

Monday, June 24, 2013

Walking directories in Python in search of files.

Python version: 2.7.5
Tkinter version: 8.5.2
Source: file_walker.pyw

Brief:
This tutorial shows how to walk directory trees using python.  A simple Tkinter interface is used to and directory paths are displayed with files are found within that directory.  These directory paths could then be copied and pasted into the location bar of explorer to find these files.

1. Walk directory trees in search of files

Python provides a simple interface for dealing with file systems within the os module.  Of note is the os.getcwd() and os.walk().  getcwd returns the current working directory.  If you are using IDLE this is probably your python root directory.  os.walk() returns a turple that can be gone through using a for loop.  The code below loops through the current working directory and prints out root directory paths that contain files.

import os

for root, dirs, files in os.walk(os.getcwd()):
    if files:
        print root

2. Build a simple Tkinter interface

Tkinter is the included GUI library.  The documentation included with Tkinter in the Python documentation is real lacking.  I've found one of the best references on Tkinter, although outdated, to be the book Python and Tkinter Programming by John E Grayson Ph.D.  There is also a tutorial online at http://www.tkdocs.com/tutorial/ if you are using Python 3.x.  Most of what I've learned has been through the above book, reading the source code in the python library, trial and error, and searching the internet.  Tkinter is relatively easy to use and full featured, there just isn't one good source of documentation on it.

Of note is the tkFileDialog module that provides access to the standard open file / directory dialog boxes within your operating system.

Also, when using Tkinter always remember to rename your .py file to .pyw to make it open in windowed mode without the annoying command window.

version = '0.1'

import os
from Tkinter import BOTH,LEFT,TOP,END,StringVar
from ttk import Frame, Entry, Button, Label
from ScrolledText import ScrolledText
from tkFileDialog import askdirectory

class FileWalkerWindow(Frame):
    def __init__(self):
        Frame.__init__(self)
        self.pack(expand=True, fill=BOTH)
        self.master.title("File Walker v" + version)
        self.master.iconname("File Walker")

        self.dir = StringVar() #tkinter does not work with standard python variables
        self.dir.set(os.getcwd()) # set to current working directory
       
        description = "This program walks directories looking " \
        + "for files.  A directory path is outputed each time a file is " \
        + "found.  These directory paths can be coppied into explorer to " \
        + "view files."
       
        row1 = Frame(self)
        Label(row1, text="Root Directory:").pack(side=LEFT, pady=10)
        self.dir_ent = Entry(row1, width=80, textvariable=self.dir)
        self.dir_ent.pack(side=LEFT)
        Button(row1, text="Browse", width=10, command=self.browse).pack(side=LEFT, padx=5)
        row1.pack(side=TOP, ipadx=15)

        row2 = Frame(self)
        btn = Button(row2, text="Find Files", command=self.walk, width=15)
        btn.pack(side=LEFT, padx=5, pady=10)
        row2.pack(side=TOP)

        self.output = ScrolledText(self, height=15, state="normal",
                                   padx=10, pady=10,
                                   wrap='word')
        self.output.insert(END,description)
        self.output.pack(side=LEFT, fill=BOTH, expand=True, padx=5, pady=5)


        self.bind('<Key-Return>', self.walk) #bind enter press to walk

    def browse(self):
        dirpath = askdirectory(parent=self, title="Select Root Directory")
        self.dir.set(dirpath)

    def walk(self):
        self.output.delete(1.0, END)
        for root, dirs, files in os.walk(self.dir.get()):
            if files:
                self.output.insert(END, root+'\n')


if __name__ == '__main__':
    FileWalkerWindow().mainloop()

KMZ/KML file parsing with Python

Python version: 2.7.5
Source: kmz_parser.py

Brief:
This totorial describes a method for writing a python script to extracting coordinate and label information from kmz/kml files then exporting to a csv file.

1. Unzip the KMZ and extract doc.kml

Keyhole Markup Language (KMZ) files are google earth files that can contain points and lines and shapes from google earth. They are simply zipped archives. Inside they contain a plain text xml file doc.kml. To look at this file rename your .kmz to .zip, extract it and open doc.kml. 

Python provides many nice built in libaries, the first we are going to use is zipfile

from zipfile import ZipFile

filename = 'test.kmz'

kmz = ZipFile(filename, 'r')
kml = kmz.open('doc.kml', 'r')


This opens the doc.kml file as a standard file for reading.  You can now parse the file.

2. Examine the KML file to determine the type of information you want and how it's stored.

IDLE (Python included editor) is a good editor for viewing kml files.  For extracting the names of items and their grids we need to look at three tags, <Placemark>, <name> and <coordinates>.  <Placemark> tags surround each item, inside they have a <name> and <coordinates> tag.  Of note is the parser we are going to use calls tags "Elements".

3. Write a SAX handler

Simple API for XML (SAX) allows us to parse XML files.  Python naturally has a built in library for this, xml.sax.  To make this work we use ineritance and create our own custom xlm.sax.handler.ContentHandler class.  To understand how the SAX parser will work, when we feed it our file and our ContentHandler object it will call the methods within our ContentHandler object at certian times.  To make sure that it knows the names of our functions we are required to create an child class of the ContentHandler class that contains dummy methods for all these different events.  By overriding the functionality of these functions we can make the parser do our work when it reaches each of these events.  The documentation on the ContentHandler base class in the python documentation has the names of these methods and a description of when they are called.  The ones we are interested in are these:
  • __init__(self)
    • constructor, called when the object is created
  • startElement(self, name, attributes)
    • called at start elements (i.e. '<Placemark>', and <name>, etc.)
  • characters(self, data)
    • called at text between elements
  • endElement(self, name)
    • called at end elements (i.e. '</Placemark>', and <name>, etc.)

The data we are going to capture will be stored in a nested dictionary object.  Each Placemark's <name> attribute data will be a key maped to a second dictionary object.  Inside this object each Element name will become a key mapped to the data contained in that Element.  This will allow us to extract all the data contained within each placemark, including the 'coordinates' attribute.  See code below:

import xml.sax, xml.sax.handler
class PlacemarkHandler(xml.sax.handler.ContentHandler):
    def __init__(self):
        self.inName = False # handle XML parser events
        self.inPlacemark = False
        self.mapping = {}
        self.buffer = ""
        self.name_tag = ""
       
    def startElement(self, name, attributes):
        if name == "Placemark": # on start Placemark tag
            self.inPlacemark = True
            self.buffer = ""

        if self.inPlacemark:
            if name == "name": # on start title tag
                self.inName = True # save name text to follow
           
    def characters(self, data):
        if self.inPlacemark: # on text within tag
            self.buffer += data # save text if in title
           

    def endElement(self, name):
        self.buffer = self.buffer.strip('\n\t')
       
        if name == "Placemark":
            self.inPlacemark = False
            self.name_tag = "" #clear current name
       
        elif name == "name" and self.inPlacemark:
            self.inName = False # on end title tag           
            self.name_tag = self.buffer.strip()
            self.mapping[self.name_tag] = {}

        elif self.inPlacemark:
            if name in self.mapping[self.name_tag]:
                self.mapping[self.name_tag][name] += self.buffer
            else:
                self.mapping[self.name_tag][name] = self.buffer

        self.buffer = ""

4. Create a Parser, set the Handler, and parse the file.

To parse the file we need to create a parser object, set it's handler object to an instance of the custom object we created, execute the parse function on the file, and close the file.  After this our mapping dictionary is ready to be used.
parser = xml.sax.make_parser()
handler = PlacemarkHandler()
parser.setContentHandler(handler)
parser.parse(kml)
kmz.close()

5. Build the CSV table for output

The mapping created cointains a great amount of data that we don't need, however there is one thing of note within there.  Points contain the tag <LookAt>, lines contain <LineString>, and shapes contain <Polygon>.  By testing for these values we are able to sort our output table so all the points will be together, then the lines, then the polygons.  Below is a function to build the table:
def build_table(mapping):
    sep = ','
       
    output = 'Name' + sep + 'Coordinates\n'
    points = ''
    lines = ''
    shapes = ''
    for key in mapping:
        coord_str = mapping[key]['coordinates'] + sep
       
        if 'LookAt' in mapping[key]: #points
            points += key + sep + coord_str + "\n"
        elif 'LineString' in mapping[key]: #lines
            lines += key + sep + coord_str + "\n"
        else: #shapes
            shapes += key + sep + coord_str + "\n"
    output += points + lines + shapes
    return output

6. Save the new file, output the data.

outstr = build_table(handler.mapping)
out_filename = filename[:-3] + "csv" #output filename same as input plus .csv
f = open(out_filename, "w")
f.write(outstr)
f.close()
print outstr

Tuesday, January 15, 2013

HDMI to VGA on Acer G235H


OS: Occidentalis v0.2
Hardware: Raspberry Pi revision B, HDMI to VGA (AdaFruit), Acer G235H
Reference: RPiconfig

According to newegg.com where I purchased the monitor the max resolution is 1920x1080 on this monitor.  However with the hdmi to vga connection the max resolution I was able to achieve is 1680x1050.

When you look at the pi's SD card there is a file config.txt.  When on the pi it is located in the /boot/ folder.

Through trail and error I found these to be the needed settings in config.txt:

hdmi_group=2
hdmi_mode=58
hdmi_drive=2

If you go back to using the pi on a hdtv you will need to comment out the first two lines and it will automatically work as before.

Monday, December 31, 2012

Raspberry Pi UART with PySerial

OS: Occidentalis v0.2
Hardware: Raspberry Pi Revision B with Cobbler
Setup: Serial loopback (connect RX and TX pins on GPIO pins)

Linux attempts to treat all devices as file system like devices, the UART that is available on the GPIO pins is located at:

/dev/ttyAMA0

Configure the operating system:

Occidentalis comes pre-configured to allow you to console into the Raspberry Pi using the external UART. If you intend to use the UART for your own software you will have to disable this functionality.

Below is a summary of This Post

First backup the two files you are going to edit with:

sudo cp /boot/cmdline.txt /boot/cmdline.txt.bak
sudo cp /etc/inittab /etc/inittab.bak


Then use your favorite editor to remove these two settings from /boot/cmdline.txt:

console=ttyAMA0,115200 kgdboc=ttyAMA0,115200

Then comment out the line that mentions ttyAMA0 in /etc/inittab. (place a # at the start of the line.

#T0:23:respawn:/sbin/getty -L ttyAMA0 11520 vt100

Install PySerial
Reference: http://pyserial.sourceforge.net/index.html

PySerial is a python library for interfacing with serial interfaces, it does not come standard with Occidentalis. You can download it at pyserial-2.6.tar.gz. To extract the file and install use the following commands:

mkdir pyserial-2.6
tar -zxvf pyserial-2.6.tar.gz pyserial-2.6
cd pyserial-2.6
python setup.py install

Using PySerial
Reference: http://pyserial.sourceforge.net/index.html

Here is a simple script that tests the serial connection in loopback. Note that you need to wait for a bit in between sending characters and receiving them.  This is because the command to send serial characters uses interrupts and does not wait for the output to be put on the bus before it returns.  If you print the input and output strings you will see that the last character gets dropped once the output sting is longer than can be sent in the given delay.  On mine it fails when writing strings of characters longer than 46 with a 0.5 second delay.


from serial import Serial
import time

serialPort = Serial("/dev/ttyAMA0", 9600, timeout=2)
if (serialPort.isOpen() == False):
    serialPort.open()

outStr = ''
inStr = ''

serialPort.flushInput()
serialPort.flushOutput()

for i, a in enumerate(range(33, 126)):
    outStr += chr(a)

    serialPort.write(outStr)
    time.sleep(0.05)
    inStr = serialPort.read(serialPort.inWaiting())

    #print "inStr =  " + inStr
    #print "outStr = " + outStr

    if(inStr == outStr):
        print "WORKED! for length of %d" % (i+1)
    else:
        print "failed"

serialPort.close()