Part 1 of this three-part series laid the foundation for building an application targeted at the Nokia N810 Internet Tablet, including choosing a target language (Python), selecting and configuring a development environment (Eclipse with Pluthon), and building a small sample application to make sure everything worked as advertised. This installment looks at different options for helper libraries, coding with unit testing in mind, and user interface choices.
Building an application using Python is somewhat like building a bicycle using a room full of spare parts. Although some choices are completely independent, others must be made based on a particular configuration. For example, you wouldn't want to put 29-inch tires on a 26-inch frame. Choosing Python libraries to use in building an application for the N810 requires a similar approach.
The process of developing any application should start with a set of basic requirements. For this project, I chose to build a tool to track a series of global positioning system (GPS) waypoints and compute things like distance to the previous or next waypoint. Like many "user-defined" applications, this one grew out of a family cross-country trip and the notion that it would be neat to load a number of points from a map to show how far we'd travelled and how much farther we had to go. Such a tool could be used as a simple GPS logger or hiking tool.
Python is inherently an object-oriented language, because everything in Python is an object. The syntax and structure of the language make it easy to create small, easy-to-understand functions to build upon. Designing an application starts with the list of requirements and uses some type of functional decomposition to determine what code you will need to write to fulfill them.
For this application, a top-level requirement is the ability to enter and save waypoint information. To fulfill this requirement, you must have some UI elements, such as a text box, a Save button, and a way to save the information for retrieval later, which implies some type of database. Identifying the need for a database drives other code functionality, such as initializing the database the first time the application is run and, in turn, saving and retrieving information from the database.
Coding a function to create an empty database is a good example of a small, single-function routine. For this project, the code looks like Listing 1.
Listing 1. Python routine to create an empty database
def create_db(): """Creates an empty database.""" query('''CREATE TABLE 'places' ( id INTEGER PRIMARY KEY AUTOINCREMENT, lat INTEGER, lon INTEGER, comment TEXT );''')
You can see the embedded SQL statement
CREATE TABLE, with a name
places and the four fields
This type of code is easy to read and test.
Another good code design practice is to group similar functions and place them in a single file. Placing all the database code in a file named dbroutines.py makes it easier to manage and to find things later. It also gives your code repository a good, solid, and logical structure to build upon.
Determining how you'll create the UI is another one of those design decisions that you must make up front. It's common for PyGTK-based applications to use a code-based approach for building the UI: This process essentially consists of creating all the UI features through Python code. For example, the code in Listing 2 creates a list of menu items you would find on a typical application, then creates the remaining UI elements totally in code.
Listing 2. Python routine to build the basic UI
def init_ui(id='main'): """Build the UI.""" global APP APP = ui.App('GPS Logger', id=id, menu=( ui.Menu.Item(label='New', callback=on_new), ui.Menu.Item(label='Open', callback=on_open), ui.Menu.Item(label='Save', callback=on_save), ui.Menu.Item(label='Save As', callback=on_save_as), ), top=( ui.Group('dev_stat', horizontal=True, border=None, children=(ui.Label(label='Status:'), ui.Label('status', 'Ready'), ) ), ui.Group('controls', horizontal=True, border=None, children=(ui.Button('start_btn', label='Start', callback=on_start), ui.Button('stop_btn', label='Stop', callback=on_stop)) ), ), center=ui.Table('log', 'GPS Log', headers=('Time', 'Latitude', 'Longitude', 'Altitude (meters)'), types=(str, str, str, str) ) )
One quick Google search will turn up a plethora of Python library choices. In fact, you'll need to be quite specific as to what you're looking for to keep from getting an excessive number of results. For this project, I needed a few very specific libraries to make the coding task a bit easier. First and foremost was a library to talk to the GPS hardware in the N810. Next was a library that made it easy to do distance calculations from GPS coordinates. Finally, I needed a UI library to make it easy for users to enter and display information.
Many Python libraries are just wrappers for code originally written in
another language, such as
C. One such library I
found for this project wraps the Maemo version 4.0 liblocation API (see
Resources for links to more information). I
initially found an entry about this library on the
tablet talk site,
where the author posted sample code under a GNU Lesser General Public
License (LGPL) version 3 open source license. For links to this code along
with more information, see Resources.
The base Python distribution has a huge library of available routines and functions just waiting to be used. Additionally, some libraries previously distributed separately have been incorporated into the main Python project. One of these is the SQLite library for accessing SQLite databases. This library makes it essentially a no-brainer for any database functions your program may need. The latest version of Python (version 2.6.1) ships with SQLite3 and is what I used for this project.
For calculating distances between two GPS coordinates, I found geopy—a library that has been under development for several years and comes with a large number of examples (see Resources for a link). It also has interfaces to a number of geocoder tools, including Google, Yahoo!, and GeoNames, which could be useful for turning an address into a latitude-longitude pair but does require a working Internet connection to accomplish the lookup. For this application, it provides a robust distance-calculation library with options for using different methods.
Building production-ready applications requires a generous amount of testing. One of the most popular approaches to this problem in software-development circles involves the concept of unit testing. When you write code, you build it with the idea that each function will be tested as a stand-alone unit in a controlled way. Functions expecting input data are presented with values that will produce known results.
PyUnit comes built into the latest Python distributions and offers a structured method for building unit tests. PyUnit is based on Java™ JUnit technology and follows roughly the same approach (see Resources for links to JUnit information). To test code that accesses hardware, you must write a piece of code that responds in a way similar to the hardware. In unit testing language, this is called a mock object (see Resources for more information). I create two mock objects for this project to simulate the GPS device and the UI, respectively. Listing 3 shows the mock device object code.
Listing 3. Mock device unit testing code
class MockDevice: """Partially simulates gps argument given to the on_changed callback.""" @staticmethod def struct(): class value: class fix: latitude = 0.0 longitude = 0.0 altitude = 0 return value
Listing 4 shows a simple test case for the GPS code.
Listing 4. GPS test case
class TestGPS(unittest.TestCase): def setUp(self): unittest.TestCase.setUp(self) logger.init_gps() logger.ui.get_app_by_id = lambda id: MockUI() def test_changed(self): logger.on_changed(MockDevice)
Although it might seem like overkill to create unit tests for a small application like this, it really does build good programming practices to use this approach for even the smallest of projects. In fact, it's much easier to build good habits on small applications than wait for a large project and learn the hard way.
The most obvious choice for a UI framework for the N810 would be Hildon, because it was originally developed by Nokia for the Maemo operating system. It has since been taken over by the GNOME project and also selected for both the Ubuntu Mobile and Embedded Editions. Hildon is installed by default on the N810 and makes it possible to create a UI with a similar look and feel to that of existing applications.
Developing for a small form factor like the N810 requires a little extra thought when it comes to screen layout and user interaction. The N810 has a distinct advantage over previous models with the introduction of the slide-out keyboard. This addition greatly enhances the functionality for typing text. It does give the developer a little flexibility when it comes to designing a user input screen, because you don't have to make it completely finger friendly.
One of the more visually appealing UI libraries comes from the Enlightenment Project (see Resources). In its purest form, Enlightenment, or simply e, is a window manager and desktop shell for the Linux® operating system. The project also provides a number of basic building blocks for creating compelling applications, with a special version for Maemo (see Resources for a link). A number of currently available applications for Maemo based on the Enlightenment Foundation Libraries (EFL), including Carman—an application to interface your N810 to an OBD2-compatible car for displaying diagnostic codes and engine performance data—and the Canola multimedia application. Both of these applications demonstrate many cutting-edge UI features specifically targeted at small-screen devices.
The downside to using EFL is the amount of effort required to build even a simple application. While there is a Python-EFL wrapper, you still have to generate quite a bit of code and images to get your program to work. Basic concepts resemble other frameworks that separate the presentation layer from the code. You essentially use a graphical design tool like the GNU Image Manipulation Program (GIMP) to lay out your UI as you want it to look. Then, you must create an Edje Data Collections (EDC) file that describes your interface. For the purposes of this program, I decided it was more work than I wanted to put in, but the Resources section has links to more information.
Another possibility I found on the Maemo garage site (see Resources) is the easy library. This library has a number of helper functions to make it easy to access the lower-level APIs. One specific aspect of this library was the framework for rapid GUI development based on PyGTK. The UI portion is based on Eagle, an abstraction layer on top of GTK+. It offers everything needed to build a basic application.
Building the code to respond to a user's actions consists of writing code to act upon that action. These routines are typically named callback functions, as they're called back to work upon some action. Callbacks also get attached to things like timers so that when the timer expires, a specific section of code will be executed. For this application, there are callbacks for GPS functions, opening and saving data to the database, and starting and stopping the application.
Building an application using good software engineering practices is a process. It involves multiple steps that typically need to be accomplished in order. Sometimes, you have to drop back a few steps when a particular design decision doesn't make sense. The best time to find this type of issue is early on in the software design and development process. It's much easier to fix a design problem at the beginning of a project than at the end.
The final installment of this series will put it all together and look at what it takes to actually deploy an application. Hopefully, it will answer the question for every developer looking to release a polished application: When is it ready? In addition to the topics of bug reporting and fixing, I'll also cover iterative development and feature enhancements.
- Visit the official Maemo Garage site for lots of Maemo projects.
- Find everything you need to know about liblocation from the Maemo site.
- For a good overview of the Python unit testing framework, visit the PyUnit home page. For a good discussion on unit testing and Python, see Chapter 13 of Mark Pilgrim's book, Dive into Python.
- See the IBM developerWorks article, "Unit testing with mock objects" (developerWorks, November 2002), for a good discussion of this technique.
- Check out the Enlightenment Project for more information about this GUI library option.
- In the developerWorks Linux zone, find more resources for Linux developers (including developers who are new to Linux), and scan our most popular articles and tutorials.
- See all Linux tips and Linux tutorials on developerWorks.
- Stay current with developerWorks technical events and Webcasts.
Get products and technologies
- For Rob Brewer's liblocation wrapper code and other Maemo applications, see Rob Brewer's Projects page.
- Check out geopy, the geocoding library for the Python language.
- The Enlightenment Foundation produces a special edition of libraries for the Maemo operating system called EFL Maemo Edition.
- With IBM trial software, available for download directly from developerWorks, build your next development project on Linux.
- Get involved in the developerWorks community through blogs, forums, podcasts, and spaces.