It’s The Circle of Stupid

I really like my doctor. But I hate dealing with his office and staff.

He works at an office with 6 or 7 other doctors. It’s always crowded, and calling to book an appointment involves 10 minutes or more on the phone and a moderate likelihood of being disconnected.

I think the automated phone system at Hostile Medicine Ltd. reflects their attitude toward patients: making an appointment is option #7.

Let me illustrate what it’s like another way: I recently took the car in to have the tires replaced. The guy at the counter there showed me more care and compassion than the staff at Hostile Med.

When I went in for my regular physical in August I was diagnosed with diabetes. I kinda knew this was coming. My shoulder surgery a few years put me on a very sedentary trajectory and just about everyone on my father’s side of the family ended up with it in their 50s. But that’s another few blogs that will be coming in the future.

He quickly wrote a prescription, told me to lose weight, told me that there was a nutritionist on staff there at Hostile Medicine Ltd. (I had already asked if he could recommend one before he had told me the news) and then sent me on my way.

After dealing with a scheduling issue and 2 frustrating phone calls with Hostile Med’s nutritionist I decided it was time to move on. It may even be time to move on to a new primary care physician.

I have what some would call a “Cadillac” medical plan, although It’s not half as ugly or ostentatious in my opinion. (And I wish the premiums were as low as those lease prices they quote during baseball games.) I can “refer” myself to a specialist. I found one that takes my coverage and was pleased to find a reasonably well-designed website with great information about the doctors and what they do. I called and made an appointment. It was a few weeks away, but that was fine.

Nothing about this is urgent and I know what to do: get back to exercising, lose weight, and stop whining about my shoulder. But I had read conflicting information about what to eat, what the prescription I was given really does, and when to check blood my sugar. Even though my doctor had told I didn’t need to check my blood sugar, I got a device. (I like devices.)

I was directed back to the website to download intake forms in advance. Wow! Less wasted time at the first appointment! There was also a form to have my records transferred to them by my regular doctor.

The intake forms were PDF forms! Even better! Five pages of history and HIPAA bullshit was filled out and emailed very quickly.

This is where the fun starts. I received an email a few days later.

Good Afternoon,

When I printed out the New patient forms you filled out.
It printed out “BLANK.” Would you be able to fill out the new patients forms and email it back to us?

My first thought was “Why the hell would you print it out?” (Oh boy. I had no idea.)

My second was “Why is this my problem?” I downloaded their form and even filled it out using Acrobat Reader instead of one of the myriad other PDF readers to avoid issues. (I was once burned by Apple’s Preview.) I even saved and reopened to make sure the data was there before I sent it.

I opened the files again and did a print preview. The fields were empty! Something was wrong with those forms.

I replied:

When I open the file I still see all of my entries, but they will not print here either. I don’t even see them in a “print preview.”
I filled the form using Adobe Reader as instructed. Is there another app I should use?

The answer:

I would just say print out the new patient forms and just fill them out.

You can always bring them the day of your appointment.

So the solution was more work for me! I double checked their website but nope, they’re not affiliated with Hostile Med.

Curiosity finally got the best of me and I opened the form in Acrobat Pro and examined the form.

snip_20151014215257

PDFs have form fields that won’t print on purpose. Could Adobe be any worse? (Don’t answer that.) This form, which they apparently want to print, had that attribute set. On every field on every page. I set them to visible and emailed it in with a note on how I got it to work.

No acknowledgement that the problem was on their end. But I was off the hook with regards to filling out five pages again, by hand.

Finally my appointment came around and I found myself sitting in the doctor’s office answering the kind of questions and learning the kinds of things that my doctor at Hostile Med apparently didn’t have time for.

She asked me if I had filled in an intake form and I explained that I had and had emailed it in.

“Oh.” The doctor said while looking at her computer. “I guess it wasn’t printed and scanned in yet.”

The mind reels.

A Simple C# Application: Part 2

In my last post I put together a simple C# application that loads a comic book archive and lists its content to the console. Let’s load some pages now.

First we need to get the first image in the archive. Add this method to the code for the main window. As always, the complete code for this project is in GitHub.

[code language=”csharp”]
private IArchiveEntry getFirstEntry()
{
foreach (IArchiveEntry entry in archive.Entries)
{
if (!entry.IsDirectory)
{
return entry;
}
}
return null;
}
[/code]

This seems like a trivial amount of code for a method, and it’s not even the easiest way to get the first entry in the archive. If we take a look at the IArchive defintion, we see that all of the entries are available as an IEnumerable collection. We could actually do this:

[code language=”csharp”]
var entry = archive.Entries.First();
[/code]

But there’s a catch: what if the first entry is a directory? We need to skip it, since trying to load it as an image won’t work. Also, we’re going to be replacing this method soon enough and it can act as a place holder until then.

Next we need to load the image and display it. First add this method:
[code language=”csharp”]
private void displayImage(Bitmap bitmap)
{
MemoryStream memoryStream = new MemoryStream();
bitmap.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Png);

BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = memoryStream;
bitmapImage.EndInit();
ImageViewer1.Source = bitmapImage;
}
[/code]

And then update MenuItem_Click_1:
[code language=”csharp”]
private void MenuItem_Click_1(object sender, RoutedEventArgs e)
{

Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();

// Set filter for comic book archives
dlg.Filter = "CBR files (*.cbr)|*.cbr|CBZ files (*.cbz)|*.cbz|All Files (*.*)|*.*";

// Display OpenFileDialog by calling ShowDialog method
Nullable<bool> result = dlg.ShowDialog();

// Get the selected file name and log to console (for now)
if (result == true)
{
// Open document
openArchive(dlg.FileName);
fileEntry = getFirstEntry();

Bitmap bitmap = (Bitmap)Bitmap.FromStream(fileEntry.OpenEntryStream());
displayImage(bitmap);
}
}
[/code]

Build and run and open a document. You’ll see the first image, or at least most of it.

We take the fileEntry returned by getFirstEntry() and use it’s OpenEntryStream() method to create a Bitmap.

Then we pass that as an Image to displayImage(), which converts it to a BitmapImage and passes it to our ImageViewer for display.

This code is a bit convoluted. Why load one format and the convert to another?

Comic book archives tend to have very large images in them. Unless you have a remarkably high-resolution screen, when the image loaded with the code above you saw something less than half of the page. We need to resize our images to make them easier to view. For this we need a Bitmap.

What size makes sense? As tall and as wide as fits works for our purposes. So the first thing we need to the system’s screen dimension. Add this to the class definition for MainWindow:

[code language=”csharp”]
public partial class MainWindow : Window
{

IArchive archive;
IArchiveEntry fileEntry;
int maxImageHeight = (int)System.Windows.SystemParameters.PrimaryScreenHeight – 75;
[/code]

System.Windows.SystemParameters tells us the size of the primary screen.

Now two methods for figuring out our image size and then resizing the image to it:
[code language=”csharp”]
private System.Drawing.Size getNewImageSize(System.Drawing.Size oldSize)
{
Console.WriteLine("Old dimensions: " + oldSize.Width + "x" + oldSize.Height);
float ratio;
if (oldSize.Height > oldSize.Width)
{
ratio = (float)oldSize.Width / oldSize.Height;
}
else
{
ratio = (float)oldSize.Height / oldSize.Width;
}

int newWidth = (int)(maxImageHeight * ratio);
System.Drawing.Size newSize = new System.Drawing.Size(newWidth, maxImageHeight);
Console.WriteLine("New dimensions: " + newSize.Width + "x" + newSize.Height);
return newSize;
}

public static Bitmap ResizeImage(Image image, System.Drawing.Size newSize)
{

// Make a rectangle that is the new size
Rectangle destRect = new Rectangle(0, 0, newSize.Width, newSize.Height);

// Make a bitmap that is the new size
Bitmap destImage = new Bitmap(newSize.Width, newSize.Height);

// Set new image to the resolution of the original
destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);

// Create a GDI holder and use it
using (Graphics graphics = Graphics.FromImage(destImage))
{

// Set our quality options
graphics.CompositingMode = CompositingMode.SourceCopy;
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;

// Resize original image into new one
using (var wrapMode = new ImageAttributes())
{
wrapMode.SetWrapMode(WrapMode.TileFlipXY);
graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
}
}
return destImage;
}
[/code]
In getnewImageSize() we calculate the ratio of height to width (or width to height if it is a landscape page) and then use that to figure out the dimensions that best fit the screen.

Then, we resize the image. I found an old MSDN article with a resize method that required some updating.

Plug this in to our loading process. Update the event handler for MenuItem_Click_1 to the following.
[code language=”csharp”]
private void MenuItem_Click_1(object sender, RoutedEventArgs e)
{

Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();

// Set filter for comic book archives
dlg.Filter = "CBR files (*.cbr)|*.cbr|CBZ files (*.cbz)|*.cbz|All Files (*.*)|*.*";

// Display OpenFileDialog by calling ShowDialog method
Nullable<bool> result = dlg.ShowDialog();

// Get the selected file name and log to console (for now)
if (result == true)
{
// Open document
openArchive(dlg.FileName);

fileEntry = getFirstEntry();

Bitmap bitmap = loadAndResizeBitmap();
displayImage(bitmap);
}
}
[/code]

And last, tell the ImageViewer to resize itself to the size of its contents:

[code language=”csharp”]
private void displayImage(Bitmap bitmap)
{
MemoryStream memoryStream = new MemoryStream();
bitmap.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Png);

BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = memoryStream;
bitmapImage.EndInit();
ImageViewer1.Source = bitmapImage;
ImageViewer1.Height = bitmap.Height;
ImageViewer1.Width = bitmap.Width;
}
[/code]

Run the project and open a file. We see the first page, scaled to the screen!

So now let’s add the ability to see each image.

Rather than add more controls to our window, let’s use clicking on the image to “turn the page.” We can use clicking on the left side to page back and clicking on the right to page forward.

Before we set up the actual event handler, we need some machinery for opening different images in the archive.

First, refactor getFirstEntry() to getPreviousEntry() and getNextEntry():
[code language=”csharp”]
private IArchiveEntry getPreviousEntry()
{

// Don’t try to go back before the head of the collection
if (currentIndex == 0)
{
return currentEntry;
}
else
{
IArchiveEntry entry = archive.Entries.ElementAt(–currentIndex);
while (entry.IsDirectory)
{
getPreviousEntry();
}
return entry;
}
}

private IArchiveEntry getNextEntry()
{
if (currentIndex == (archive.Entries.Count() – 1))
{
return currentEntry;
}
else
{
IArchiveEntry entry = archive.Entries.ElementAt(++currentIndex);
while (entry.IsDirectory)
{
getNextEntry();
}
return entry;
}
}
[/code]

If it is isn’t obvious yet, I am sticking to the Single Responsibility Principle as much as possible here.

getPreviousEntry() and getnextEntry() are responsible for getting us the next (or previous) valid entry and also for keeping track of where we are. If we try to run pastthe beginning or end of the file, they return the right one instead. This will mean we’ll reload the current page. A more efficient algorithm would avoid this.

The way we open the load pages needs to change. Let’s move the code to open an image to its own function and generalize it a bit.
[code language=”csharp”]
private void loadPage(Direction direction)
{

if (direction == Direction.Forward)
{
currentEntry = getNextEntry();
}
else
{
currentEntry = getPreviousEntry();
}

Bitmap bitmap = loadAndResizeBitmap(currentEntry);
displayImage(bitmap);
currentWidth = bitmap.Width;
}
[/code]

So MenuItem_Click_1 is short and sweet now:

[code language=”csharp”]
private void MenuItem_Click_1(object sender, RoutedEventArgs e)
{

Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();

// Set filter for comic book archives
dlg.Filter = "CBR files (*.cbr)|*.cbr|CBZ files (*.cbz)|*.cbz|All Files (*.*)|*.*";

// Display OpenFileDialog by calling ShowDialog method
Nullable<bool> result = dlg.ShowDialog();

// Get the selected file name and log to console (for now)
if (result == true)
{
// Open document, reset page counter
currentIndex = -1;
openArchive(dlg.FileName);
loadPage(Direction.Forward);
}
}
[/code]

Again, there are more efficient (and prettier) algorithms for this. I’m aiming for simplicity.

Now we need to add a bit of state to the application too. You probably noticed two new variables in loadPage() We need to know what page we are on so we can move the the next or the previous. We also need to know how wide the current page is. Here are our declarations.

[code language=”csharp”]
IArchive archive;
IArchiveEntry currentEntry;
int currentIndex = -1;
int currentWidth = 0;

int maxImageHeight = (int)System.Windows.SystemParameters.PrimaryScreenHeight – 75;

enum Direction {
Forward,
Back
}
[/code]
So all we need is an event handler. Add it to the XAML:

[code language=”xml”]
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" x:Class="ComicViewer.MainWindow"
Title="ComicViewer" Width="Auto" Height="Auto" SizeToContent="WidthAndHeight">
<Grid>
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Header="_Open" Click="MenuItem_Click_1"/>
<MenuItem Header="_Close" Click="MenuItem_Click_2"/>
<MenuItem Header="_Save" Click="MenuItem_Click_3"/>
</MenuItem>
</Menu>
<StackPanel Orientation="Vertical">
<StackPanel >
<Image x:Name="ImageViewer1" MouseDown="Image_Click"/>
</StackPanel>
</StackPanel>
</Grid>
</Window>
[/code]

And then define it:
[code language=”csharp”]
private void Image_Click(object sender, MouseButtonEventArgs e)
{
double azimuth = e.GetPosition(ImageViewer1).X;
if (currentWidth != 0)
{
if (azimuth > (currentWidth / 2))
{
loadPage(Direction.Forward);
}
else
{
loadPage(Direction.Back);
}
}
}
[/code]

Run the code, load a file, and click on either side of the page to page forward or back.

Image_Click examines the X axis of the click event it is passed. If is it less than half of the current image width, if loads a page with Direction.Back. If it is greater, Direction.Forward. All of our bounds checking is done in getPreviousEntry() and getnextEntry(), which makes the event handler very readable.

Load the app and give it a try. You may notice one that not all archives have the images in the correct order. Better readers parse the file names and attempt to display them in the correct order. That’s beyond the scope of this project.

We need two more event handlers and we’re done.

Let’s do the easy one first:
[code language=”csharp”]
private void MenuItem_Click_2(object sender, RoutedEventArgs e)
{
archive = null;
currentEntry = null;
ImageViewer1.Source = null;
ImageViewer1.Height = 0;
ImageViewer1.Width = 0;
currentIndex = 0;
currentWidth = 0;
}
[/code]

Run the code, load a file, close it, load another.

Not a very useful menu item, but it illustrates how to reset the app to its starting point.

And finally, let’s save a file.
[code language=”csharp”]
private void MenuItem_Click_3(object sender, RoutedEventArgs e)
{
Microsoft.Win32.SaveFileDialog dlg = new Microsoft.Win32.SaveFileDialog();
dlg.FileName = Path.GetFileName(currentEntry.Key);
Nullable<bool> result = dlg.ShowDialog();
if (result == true)
{
currentEntry.WriteToFile(dlg.FileName);
}
}
[/code]
Since SaveFileDialog acts a lot like OpenFileDialog, this looks familiar.

Rather than saving the resized image, we simple use the currentEntry member that holds the current archive entry to initialize the filename in the save dialog (after stripping off any embedded directory names with Path), and also to write itself to file.

Run it and save an image.

We’re done! As mentioned above, the complete code for this project is in GitHub. Please check it out and let me know how it goes.

A Simple C# Application : Part1

I get sidetracked from time to time and enjoy play around with new things.

Or in this case, old things I just never played with before.

My “daily driver” is a gamer laptop that runs Windows 8.1. It’s primary purpose is to run Linux in VMware, as my employer does not support 100% Linux systems. I still do use the Windows “half” for a few things though, and I do like the looks of Windows 10. (Hopefully VMware will announce support for Windows 10 as a host operating system soon…)

I am also an avid comic book fan. I read most of my comics on an iPad, some with Comixology’s great reader and some in Comic Book Archive (CBR) format. You can get a lot of DRM free comics in CBR format. For example, there’s a bunch here.

As the Wikipedia article on CBR spells out in detail, comic book archive files are generally ZIP or RAR (ugh) archives of PNG or JPG images.

While I read these files on my tablet most often (I recommend Comic Zeal, which has a great web server mode for uploading comics to your device instead of the ghastly iTunes file sharing interface.) I occasionally want to manipulate a file on my laptop, usually to extract an image and share with someone.

So why not write an application for that? Visual Studio is free for personal use now, and I’ve heard from a few people that C# is a decent language to work in.

So here’s what I want to do:

  • Open a CBZ or CBR file
  • Display each image in order
  • Allow the user to save individual images to file

The code for the final application, as well as the intermediate steps for each blog post, will be available in Github. (That’s a link to the first version, covered in this post.)

Displaying a Window

For this project I used C# and Windows Presentation Foundation (WPF.) I was able to display a window and open files very quickly, in a language I never used before.

Open a new project in Visual Studio 2013. Select Visual C# in the New Project Window and then WPF Application. Give your project a name and then click OK.

The project should open in the “Visual Designer,” but if it does not open it from the View menu or with Shift-F7. You add controls to your window from the Toolbox, which should be available on the left or by pressing CTRL-ALT-X. As you add controls, the XAML in the pane below the visual editor will update.

After some playing around I ended up with this XAML:

[code language=”xml”]
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" x:Class="ComicViewer.MainWindow"
Title="ComicViewer" Width="Auto" Height="Auto" SizeToContent="WidthAndHeight">
<Grid>
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Header="_Open" Click="MenuItem_Click_1"/>
<MenuItem Header="_Close" Click="MenuItem_Click_2"/>
<MenuItem Header="_Save" Click="MenuItem_Click_3"/>
</MenuItem>
</Menu>
<StackPanel Orientation="Vertical">
<StackPanel >
<Image x:Name="ImageViewer1" />
</StackPanel>
</StackPanel>
</Grid>
</Window>
[/code]

This defines a single window named MainWindow that belongs to a class named ComicViewer. It has a menu with three options, and an ImageViewercontrol. Note that the main window’s size settings are “Auto” for both height and Width and that we have an extra SizeToContent attribute. These are not the defaults.

If I try to build and run without modifying the code associated with this window the build will fail. The app needs event handlers for the menu items. Each of the three menu items (Open, Close, and Save) have a Click attribute with a method name defined.

Press F7 to bring up the code window and add the methods defined at the bottom of this image:

[code lang=”csharp”]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace ComicViewer
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}

private void MenuItem_Click_1(object sender, RoutedEventArgs e)
{
Console.WriteLine("File open.");
}

private void MenuItem_Click_2(object sender, RoutedEventArgs e)
{
Console.WriteLine("File close.");
}

private void MenuItem_Click_3(object sender, RoutedEventArgs e)
{
Console.WriteLine("File save.");
}

}
}
[/code]

Now press start to build and run the app. There’s a very small window with the standard File menu. Select a menu option and you’ll a message in Studio’s Output window.  (You may have to open it on the bottom while the application is running.)

These console messages tell us where the action will be in this app.

GUI (Graphical User Interface) programs are event driven. They respond to events generated by the user clicking on the screen and/or pressing keys, as opposed to following a set path defined by the developer. Our only user control so far is the menu, so it is where all of our events will come from.

Opening a File

So let’s wire up opening a file:
[code lang=”csharp”]
private void MenuItem_Click_1(object sender, RoutedEventArgs e)
{

Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();

// Set filter for comic book archives
dlg.Filter = "CBR files (*.cbr)|*.cbr|CBZ files (*.cbz)|*.cbz|All Files (*.*)|*.*";

// Display OpenFileDialog by calling ShowDialog method
Nullable<bool> result = dlg.ShowDialog();

// Get the selected file name and log to console (for now)
if (result == true)
{
// Open document
string filename = dlg.FileName;
Console.WriteLine("Filename:" + filename);
}
}
[/code]

No surprises here. When the event handler MenuItem_Click_1 is called, the app instantiates a standard file open dialog.
[code lang=”csharp”]
dlg.Filter = "CBR files (*.cbr)|*.cbr|CBZ files (*.cbz)|*.cbz|All Files (*.*)|*.*";
[/code]
The file dialog’s Filter property allows us to offer sensible options for filtering the files shown in the open window.

If the user selects a file ShowDialog() returns true and we print the filename. If not, the event handler quietly returns.

Examining An Archive File

If all comics archives were zipped, we could use C#’s built-in ZipStream class. Many use RAR, which is a proprietary format and lacks native support in most environments.

Fortunately there is an easily accessible native C# library that we can add to our project that at least support RAR up to version 4. SharpCompress makes this project very simple to implement.

In Solution Explorer right-click on your project name and select Manage Nuget Packages.

Select Online in the left hand side of the Manage Nuget Packages window.

Type “SharpCompress” in the search box. Click Install on SharpCompress version .11. (The latest at the time this was published.

Until now we’ve only used classes that Visual Studio added to our declarations for us. (With the exception of the OpenFileDialog, which we declared using its fully qualified name.) Add SharpCompress to your declarations.
[code lang=”csharp”]
using SharpCompress.Archive;
[/code]
We need to add it to our packages because we’ll eventually use a few different classes from the library. In the case of OpenFileDialog we only need it from its package, so it wasn’t worth importing all of Microsoft.Win32 which is a large package.

Now let’s use SharpCompress to read the archive’s table of contents:

[code lang=”csharp”]
private void openArchive(String filename)
{
var archive = ArchiveFactory.Open(filename);
foreach (IArchiveEntry entry in archive.Entries)
{
if (!entry.IsDirectory)
{
Console.WriteLine(entry.Key);
}
}
}
[/code]

As I said, SharpCompress makes this project very simple.

Before you can test this, you need an archive. There’s a couple in the Github archive for this project, or you can download one here.

When we select a comic archive we see a list of files in the archive printed to the output window.

So what happens if a user bypasses our file select filters and tries to open a file that is not an archive? Try it.

ArchiveFactory throws an exception and our program screeches to a halt. We need to protect ourselves from this.
[code lang=”csharp”]
private void openArchive(String filename)
{
try
{
var archive = ArchiveFactory.Open(filename);
foreach (IArchiveEntry entry in archive.Entries)
{
if (!entry.IsDirectory)
{
Console.WriteLine(entry.Key);
}
}
}
catch (InvalidOperationException ioe)
{
Console.WriteLine("Error opening file " + ioe.Message);
MessageBox.Show("Error opening file. Please try another.");

}
}
[/code]

If we catch the exception we can return from the file open event handler and give the user another chance. We also display a message telling them what happened.

We covered a lot this time. Next time we’ll display some images. See you then!

Don’t Be Afraid to Point Out The Obvious

We moved into a new house last month. If any of you have bought a house before, you know that it ends up kicking off a whole host of projects.

This weekend I retrofit five fluorescent light fixtures in the new house to take LED “bulbs.”  This involves some simple wiring — once you figure it out. Figuring it out was the hard part for me.

I spent 8 years in the Army as a second level support Radar repairman. This required almost a year of electronics training. After I left the Army I spent 2 years working in electronics, including a stint helping design microprocessor-driven manufacturing systems. (We call them “robots” now.)

I couldn’t figure these instructions out:

instructions

Finally I did some Internet searches and found this sentence.  (In a forum that I can’t link to directly.)

Short the ballast out, and remove the starters.

This involved clipping the wires on either side of a now-unnecessary component inside the fixture and connecting them too each other, and then removing the starters for each bulb.

It was obvious. Why didn’t the instructions just say that?

In a previous life I was a dog trainer. I had a mildly popular blog (no point in linking to it, I took it down.) At one point I wrote a series of posts that reviewed episodes of a dog training TV show that many trainers find objectionable. I decided to take the approach of actually reviewing the show, both good and bad, rather than what had been the standard approach of only discussing the bad.

As I watched the show I noticed that while many of the things that the TV trainer had his students doing were unnecessary or even outright undesirable he was really good at getting them to do them.  If he wanted them to hold the leash tight (ugh) they held it tight. If he wanted them to make eye contact, they made eye contact. I don’t mean he was a master of persuasion — I mean he was (is) an excellent coach.

This was something I was struggling with and was ultimately one of the reason I left training. I paid close attention to how he handled students and for a very long time had a post-it on my desk at home:

Don’t be afraid to point out the obvious.

When he coached a student he would point out, in very direct terms, what was happening, what was going wrong (in his opinion), and what was going well. When he needed them to do something he explained it and repeated it if necessary. I had a problem with this. When I stated the obvious I felt like I sounded like an idiot (after all I was belaboring the obvious!) or a nag.

Now “obvious” and “simple” are not the same thing. We developers can have a tendency to get bogged down in details and oftentimes a nice dose of simplicity to the forehead can be exactly what the doctor ordered, but that’s a different story.

What’s obvious to you might not be obvious to someone else. Don’t be afraid to add it to your documentation. Don’t be afraid to add a comment to your code.

What inspired this post was some documentation I read today that, while providing excellent (and actually quite simple) configuration instructions for some new software I needed to use,  never told me why I would ever need to configure it that why. Why I found out it was….obvious.

Raspberry Pi and Gamepad Programming Part 3: Adding a Gun Turret

Subtitling this one “Adding a Gun Turret” seems almost like click bait, doesn’t it? But yes, that’s exactly what we’re doing. One of Dexter Industries’ sample projects is attaching the Dream Cheeky Thunder Cannon to the GoPiGo. I’m going to show you how to control it with the same gamepad that also controls the robot’s movement.

In part two of this series we connected the gamepad events to the GoPiGo movement commands. We used the evdev module to identify the button that was pressed and then moved (or stopped) the GoPiGo accordingly.

But the gamepad has more controls than the seven we have used so far. What about the joysticks? We’ll use the directional pad (which sends joystick events) to control the cannon.

Dream-Cheeky-Ltd-908-desktop-gadgets-computer-accessories-PACK

Interfacing with the Cannon

The Thunder Cannon does not ship with any kind of API. However the “Office Cannon” project does have Python code in it for controlling the thunder cannon.  I modified the script in the project and made a small module for controlling the cannon. I have to give credit where credit is due: the Dexter Industries crew’s work made this possible. I just moved the code to a module and shifted a few things around.

We can’t use evdev to control the cannon : evdev is designed for reading from devices, not writing to them. Even if we did try to use it, the messages in evdev are not what the Cannon expects. The pyusb module allows us to see devices on USB and write messages to them.

The Raspbian Linux run on most Raspberry Pis is, of course, based on Debian Linux. Debian is very conservative when it comes to packages, and if you use apt-get to install pyusb you will get an older version that does include all of the features we need. I used Dexter’s modified image which already has pyusb installed. If you wish to use your own, follow the instructions for installing from Github here. (All things considered, taking the time to setup an SD Card with Dexter’s image is worth the effort.)

The office cannon script from the GoPiGo samples has some commands that can be sent to the cannon. I reused them, plus added a few constants of my own. I’ll explain my additions in a bit.

# Protocol command bytes
DOWN = 0x01
UP = 0x02
LEFT = 0x04
RIGHT = 0x08
FIRE = 0x10
STOP = 0x20
PARK = 0x30
LED = 0x31

But before we can send a command we need to “find” the device:

    DEVICE = usb.core.find(idVendor=0x2123, idProduct=0x1010)

    if DEVICE is None:
        DEVICE = usb.core.find(idVendor=0x0a81, idProduct=0x0701)
        if DEVICE is None:
            raise ValueError('Missile device not found')
        else:
            DEVICE_TYPE = "Original"
    else:
        DEVICE_TYPE = "Thunder"

Find() finds USB devices by probing for the vendor and product ids it is passed and returns an object (or None). In setup() we search for either a Thunder Cannon or an older model. We set DEVICE_TYPE as a flag to tell us which one we ended up with.

Once we have a device, we can send commands to it:

# Send command to the office cannon
def __cmd(cmd):
    if "Thunder" == dev_type:
        dev.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, cmd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
    elif "Original" == dev_type:
        dev.ctrl_transfer(0x21, 0x09, 0x0200, 0, [cmd])


# Send command to control the LED on the office cannon
def __led(cmd):
    if "Thunder" == dev_type:
        dev.ctrl_transfer(0x21, 0x09, 0, 0, [0x03, cmd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
    elif "Original" == dev_type:
        print("There is no LED on this device")

These 2 “hidden” methods send messages, or “transfers” to use USB lingo, to the cannon.  We see where the DEVICE_TYPE flag is used here. The older model cannon didn’t have an LED on it and also expects a different command syntax.

I hid these methods from other modules by prefixing them with two underscores.The reason for this is to simplify the interface for the user, as we’ll see below.

The public interface to the module is here:

def run_command(command, value):
    if command == RIGHT or command == LEFT or command == UP or command == DOWN:
        __move(command, value)
    elif command == PARK:
        # Move to bottom-left
        __move(DOWN, 2000)
        __move(LEFT, 8000)
    elif command == LED:
        if value == 0:
            __led(0x00)
        else:
            __led(0x01)
    elif command == FIRE:
        # Stabilize prior to the shot, then allow for reload time after.
        time.sleep(0.5)
        __cmd(FIRE)
        time.sleep(4.5)
    else:
        print "Error: Unknown command: '%s'" % command

This function accepts one of the constants defined above, along with an integer value. The function using the constant to figure out which internal (“hidden”) function to call. And how the integer value is used.

  • At the top we see that the directional constants are simply passed on to __move().
  • PARK is expanded into a __move DOWN and then LEFT.
  • LED turns the LED on or off.
  • FIRE pauses for half a second and the fires the cannon.

__move adds duration to the cannon movements:

# Send command to move the office cannon
def __move(cmd, duration_ms):
    __cmd(cmd)
    time.sleep(duration_ms / 1000.0)
    __cmd(STOP)

So when a direction is sent, the integer argument is how long to move in that direction.

With an LED we expect 0 or 1 to turn it on or off.

With PARK and FIRE the integer is ignored. Not the most elegant API, but it’s good enough for this quick exercise.

Connecting the Gamepad to the Cannon

Joystick events in evdev are quite different from buttons. Buttons send us a simple up or down event. Joystick controllers send events that contain coordinates corresponding to a position on a screen.  If we wanted to use the actual joystick controllers we would need to simplify these into UP-DOWN-RIGHT-LEFT commands. However the directional pad does that for us. So here are our controls, including controlling to GoPiGo.

Turrent controls

We need to process the directional pad events in our evdev read loop:

    for event in gamepad.read_loop():
        if event.type == ecodes.EV_ABS:
            absevent = categorize(event)
            print ecodes.bytype[absevent.event.type][absevent.event.code], absevent.event.value
            if ecodes.bytype[absevent.event.type][absevent.event.code] == 'ABS_HAT0X':
                if absevent.event.value == -1:
                    next_move = thunder.LEFT
                    event_time = absevent.event.timestamp()
                elif absevent.event.value == 1:
                    next_move = thunder.RIGHT
                    event_time = absevent.event.timestamp()
            if ecodes.bytype[absevent.event.type][absevent.event.code] == 'ABS_HAT0Y':
                if absevent.event.value == -1:
                    next_move = thunder.UP
                    event_time = absevent.event.timestamp()
                elif absevent.event.value == 1:
                    next_move = thunder.DOWN
                    event_time = absevent.event.timestamp()
        elif event.type == ecodes.EV_SYN:
            synevent = categorize(event)
            print synevent
            syntime = synevent.event.timestamp()
            move_duration = (syntime - event_time) * 1000
            print move_duration
            thunder.run_command(next_move, move_duration)

There’s a lot packed in here.

When we press the directional pad we receive an ABS event. These events, similar to button events, have a timestamp. They also have a code (similar to the button codes we’re seen before) and a value. With an actual joystick this value corresponds to a position on an axis. With the pad we simply get 1 or -1.

So when we receive the event we check for horizontal (ABS_HAT0X) or vertical (ABS_HAT0Y) and then we save the direction and the time.

We save the time because when the directional pad is released we then receive a SYN event. This event is similar to the button UP events we saw in part 2.  If we subtract our saved time from this we have how may milliseconds the button was held.  We use this as our duration for the command.

With this code we have very simple control over the cannon. How long we hold a directional button roughly governs how far the turret moves, and we can fire with the left hand joystick button. The script sends a “Park” command on startup.

Here’s the whole thing. (Of course you can get it on Github too!)

#!/usr/bin/python

import thunder_control as thunder
from evdev import InputDevice, categorize, ecodes, KeyEvent
import os
from gopigo import *

model_b_plus = True
next_move = "down"
event_time = 0
speed = 100
gamepad = InputDevice('/dev/input/event0')

thunder.setup()

# Enable USB to supply up to 1.2A on model B+
if model_b_plus:
    os.system("gpio -g write 38 0")
    os.system("gpio -g mode 38 out")
    os.system("gpio -g write 38 1")

try:
    thunder.run_command(thunder.PARK, 1000)
    print "Parked"
    for event in gamepad.read_loop():
        if event.type == ecodes.EV_ABS:
            absevent = categorize(event)
            print ecodes.bytype[absevent.event.type][absevent.event.code], absevent.event.value
            if ecodes.bytype[absevent.event.type][absevent.event.code] == 'ABS_HAT0X':
                if absevent.event.value == -1:
                    next_move = thunder.LEFT
                    event_time = absevent.event.timestamp()
                elif absevent.event.value == 1:
                    next_move = thunder.RIGHT
                    event_time = absevent.event.timestamp()
            if ecodes.bytype[absevent.event.type][absevent.event.code] == 'ABS_HAT0Y':
                if absevent.event.value == -1:
                    next_move = thunder.UP
                    event_time = absevent.event.timestamp()
                elif absevent.event.value == 1:
                    next_move = thunder.DOWN
                    event_time = absevent.event.timestamp()
        elif event.type == ecodes.EV_SYN:
            synevent = categorize(event)
            print synevent
            syntime = synevent.event.timestamp()
            move_duration = (syntime - event_time) * 1000
            print move_duration
            thunder.run_command(next_move, move_duration)
        elif event.type == ecodes.EV_KEY:
            keyevent = categorize(event)
            if keyevent.keystate == KeyEvent.key_down:
                if keyevent.keycode[0] == 'BTN_A':
                    print "Back"
                    bwd()
                elif keyevent.keycode == 'BTN_Y':
                    print "Forward"
                    fwd()
                elif keyevent.keycode == 'BTN_B':
                    print "Right"
                    right()
                elif keyevent.keycode == 'BTN_X':
                    print "Left"
                    left()
                elif keyevent.keycode == 'BTN_THUMBR':
                    print "Stop"
                    stop()
                elif keyevent.keycode == 'BTN_THUMBL':
                    thunder.run_command(thunder.FIRE, 250)
                elif keyevent.keycode == 'BTN_TR':
                    print "Faster"
                    speed += 20
                    if speed > 255:
                        speed = 255
                    set_speed(speed)
                elif keyevent.keycode == 'BTN_TL':
                    print "Slower"
                    speed -= 20
                    if speed < 50:
                        speed = 50
                    set_speed(speed)

except KeyboardInterrupt:
    # Disable high current mode on USB before exiting
    if model_b_plus:
        os.system("gpio -g write 38 0")

At the top of the script you see some extra code (also from the original Dexter Industries script) where, if we are running on a Pi B+, we enable high current mode for the Cannon. In my experience the Cannon uses a lot of power.

The main loops is also wrapped in a try/except block that makes sure we disable high current if the script is interrupted.

That’s it for now. I apologize for the delay in posting this. There’s more coming soon, but before I return to the GoPiGo they’ll be a brief interlude with Raspberry Pi, MAME, and analog controls.

Raspberry Pi and Gamepad Programming Part 2: Controlling the GoPiGo

Welcome to part 2 of my series on working with programming the GoPiGo  and a Gamepad controller with Python.

In part 1 I talked about what a gamepad “looks” like to a Raspberry Pi and how the excellent evdev package makes it easy to read and process information from it. I finished the post with a script that reads buttons on the gamepad and prints the direction it would send the GoPiGo in.

The final script from part 1 and all of the scripts from this part (and all future posts) can be found on github.

Let’s pick up where we left off and add code for the GoPiGo.

#!/usr/bin/python

from evdev import InputDevice, categorize, ecodes, KeyEvent
from gopigo import *

gamepad = InputDevice('/dev/input/event0')

for event in gamepad.read_loop():
    if event.type == ecodes.EV_KEY:
        keyevent = categorize(event)
    if keyevent.keystate == KeyEvent.key_down:
        if keyevent.keycode[0] == 'BTN_A':
            print "Back"
            bwd()
        elif keyevent.keycode == 'BTN_Y':
            print "Forward"
            fwd()
        elif keyevent.keycode == 'BTN_B':
            print "Right"
            right()
        elif keyevent.keycode == 'BTN_X':
            print "Left"
            left()
        elif keyevent.keycode == 'BTN_THUMBR' or keyevent.keycode == 'BTN_THUMBR':
            print "Stop"
            stop()

This script looks familiar. The only changes are an import for the gopigo  module, and then calls to it for each button press. I also added a “stop” command at the end. It’s activated by the gamepad’s joystick buttons.

Stop Buttons

Try running the script.

pi@raspberrypi ~/gopigo-tutorial/part2 $ ./gamepad.py
Traceback (most recent call last):
 File "./gamepad.py", line 17, in 
 from gopigo import *
 File "build/bdist.linux-armv6l/egg/gopigo.py", line 30, in 
IOError: [Errno 13] Permission denied

Oops. We need root access to run anything that accesses the Gopigo device. Run the script with sudo.

pi@raspberrypi ~/gopigo-tutorial/part2 $ sudo ./gamepad.py
Back
Stop

Everything works now. Why is that? Let’s look at line 30 of gopigo.py. as it is called out in the error message.

I am running the Raspbian image from Dexter Industries, so there’s a copy of the GoPiGo scripts in the gopigo folder in the pi user’s home folder. You can also find the scripts here on github.

if rev == 2 or rev == 3:
    bus = smbus.SMBus(1) #

Smbus is a Python package that allows us to communicate with devices connected to Pi’s General Purpose In Out (GPIO) connection. When you assembled your GoPiGo you connected this to the circuit board on the robot. Here is a (slightly blurry) picture of a Pi with the GPIO highlighted with a red rectangle.

gpio

This GPIO has a file interface similar to the gamepad:

pi@raspberrypi ~ $ cd /dev
pi@raspberrypi /dev $ ls -l i2c-1
crw-rw---T 1 root i2c 89, 1 Jun 7 14:33 i2c-1

And here is the gamepad we saw earlier in part1.

pi@raspberrypi /dev $ ls -l input/event0
crw-rw---T+ 1 root input 13, 64 Jun 7 14:33 input/event0

Why does one require sudo and not the other? Let’s look closely at the listings side-by-side:

crw-rw—T 1 root i2c 89, 1 Jun 7 14:33 i2c-1

crw-rw—T+ 1 root input 13, 64 Jun 7 14:33 nput/event0

The first column shows the UNIX file bits, which contain information about the file type and permission. The first letter in both listings , ‘c’, indicates a “character special” file. In other words, a device we read and/or write bytes from.

The next 9 letters indicate the permissions for the file’s owner, the file’s group, and then for everyone else (“world”). Each user has 3 settings: r – read. w – write, and x – eXecute. Character special files are not executable they are not programs.

These files have the same permissions: we see ‘rw’ for both owner and group.

So what’s the difference? The answer is a couple of columns later, in red. After we see the file’s size (1) we see the owner “root,” then the group. For the i2c-1 it’s “i2c” and for the gamepad it is “input.”

What groups does pi belong to? Run the id command.

pi@raspberrypi /dev $ id
uid=1000(pi) gid=1000(pi) groups=1000(pi),4(adm),20(dialout),24(cdrom),27(sudo),29(audio),44(video),46(plugdev),60(games),100(users),105(netdev),999(input),1002(spi),1003(gpio)

There it is! We don’t see “i2c” in that long list of groups! We could remove the need for root access by adding pi to the i2c group.

But I have been off on this tangent for long enough. Let’s make the GoPiGo do something cool before I wrap this post up.

What we’re missing is a way to control the GoPiGo’s speed with the gamepad. But this requires a bit more code since speed is not “on” or “off” like a direction: a real speed control has “faster” or “slower.” We need 2 buttons and a way to track what our current speed is.

I want to use these 2 buttons:

Speed Buttons

So let’s write a quick script to identify the keycode for each button.

from select import select
from evdev import InputDevice, categorize, ecodes, KeyEvent
gamepad = InputDevice('/dev/input/event0')

for event in gamepad.read_loop():
    if event.type == ecodes.EV_KEY:
        keyevent = categorize(event)
        if keyevent.keystate == KeyEvent.key_down:
            print keyevent.keycode

Let’s run that and press the buttons we want to use.

pi@raspberrypi ~/gopigo-tutorial/part2 $ ./show_buttons.py
BTN_TR
BTN_TL

And we have the code for the two buttons on the front of the controller now. Let’s put it all together.

#!/usr/bin/python

from evdev import InputDevice, categorize, ecodes, KeyEvent
from gopigo import *

gamepad = InputDevice('/dev/input/event0')

speed=50
set_speed(speed)
for event in gamepad.read_loop():
    if event.type == ecodes.EV_KEY:
        keyevent = categorize(event)
    if keyevent.keystate == KeyEvent.key_down:
        if keyevent.keycode[0] == 'BTN_A':
            print "Back"
            bwd()
        elif keyevent.keycode == 'BTN_Y':
            print "Forward"
            fwd()
        elif keyevent.keycode == 'BTN_B':
            print "Right"
            right()
        elif keyevent.keycode == 'BTN_X':
            print "Left"
            left()
        elif keyevent.keycode == 'BTN_THUMBR' or keyevent.keycode == 'BTN_THUMBR':
            print "Stop"
            stop()  elif keyevent.keycode == 'BTN_TR':
        elif keyevent.keycode == 'BTN_TR':
            print "Faster"
            speed += 50
            if speed > 550:
               speed = 550
            set_speed(speed)
        elif keyevent.keycode == 'BTN_TL':
            print "Slower"
            speed -= 50
            if speed < 50:
                speed = 50
            set_speed(speed)

The GoPiGo module has functions for increasing and decreasing speed, but I wanted a little bit more control over how high or low we set the speed.

At the top speed  is set to a starting value of 50.

When each speed button is pushed it is incremented or decremented and then checked against a minimum or maximum setting. We use these checks to avoid setting speed to zero or a negative value to to avoid setting it too high. (I have no idea if there really is a too high, it ‘s just an example.)

Go ahead and play with the settings to suit your taste.

That’s it for this week, but we’re not done. Next week we’re going to get a little, er, flashy and then we’ll take a look at how to read joysticks instead of just buttons.  We need something to aim those joysticks with.,,

gun

Raspberry Pi and Gamepad Programming Part 1: Reading the Device

gopigo with gamepad

A friend of mine recently asked if I could help out with a Raspberry Pi project he is working on. Pi was one of those things I had read about and always wanted to take a look at — and here was my excuse!

Moreover, Python, the language most Raspberry Pi tools are written in, is something else I had been meaning to take a look at.

Of course playing around with Raspberry Pi very quickly led to this:

gopigo with gamepad
A GoPiGo and a Logitech Gamepad

On the left is a GoPiGo (affiliate link), while on the right is a Logitech F710 wireless gamepad. These 2 devices do not work together “out of the box.” The GoPiGo has tutorials and sample scripts for working with a keyboard or mouse, but not for a gamepad. Here’s a chance to learn some basics and have a little fun!

Much of what makes working with the Raspberry Pi easy is that it runs Linux. (It runs other operating systems, but we’ll stick with Linux here.) As a matter of fact, this first tutorial will work on Linux running on pretty much any hardware.

Prerequisites

For this tutorial you need a Raspberry Pi (or a computer running Linux, but you will need a GoPiGo for the next article in this series) and a gamepad. I am using a Logitech Gamepad F710 (affiliate link,) which lists for $40 right now. I picked mine up at a local Target for $25. Other USB gamepads should work similarly to the F710, including wired models. (But wireless will work a lot better once you get your robot moving.)

You need to be comfortable with a Linux command line, as well as having as basic knowledge of Python. Nothing fancy: just the ability to enter a program in your favorite text editor and run it.

Reading from Device Files

In order to use the gamepad we need to get information from it into our program. Linux, like most UNIX-like operating systems, makes this easy via device files. When the gamepad (or it’s receiver in the case of a wireless device) is connected a file appears that can be read from.

Without the joystick or USB receiver connected, open a shell on your system and list the contents of /dev/input (click on the image for a larger version if you cannot read it):

no_device

On my system I see only a default file named mice, as I do not have an actual mouse or keyboard connected.

Now connect the gamepad or its USB receiver and wait a few seconds (especially if you are using a Pi B+ or Original) for your system to notice the device:

with_device

On my system we see 2 new files and 2 new folders. The event0 and js0 devices correspond the my gamepad. The by-id and by-path folders provide an alternative method to access the devices, as well as additional information about the device.

Since these devices are files, we can treat them as such. Let’s see what’s “in” them. Opening them in an editor will not work well, but we can use good old cat to have some fun.

Cat the event0 (or eventN if you had one before you connected the new gamepad) and then press a button on the gamepad:

cat_device

The image above is for a single button press. That’s a lot of characters! And most of them seem garbled.  But we do see that the gamepad device file is just like any other file that can be read.

Reading the Gamepad

We’re going to use the evdev package to interpret the input from the gamepad. I’ll explain what this package does as we go. The package has not been installed on any of the Pi distributions I have worked with, so let’s start by installing it with pip:

% sudo pip install evdev

Pip will do it’s thing and after a bit (it can take a while on a slower Pi) the package is installed.

Now let’s open the gamepad and print some information about it. For these exercises I am using idle but a command line session with Python or running these lines from a script will work too. (Again, click on the image for a larger version.)

print_device

The first line imports a few things from the evdev package: InputDevice, categorize, and ecodes.

The second line creates an InputDevice by passing the path to the gamepad device file.

And the we finally print this new device object to standard output and see some interesting information about it.

While the mechanics of reading from the gamepad device are as simple as any other file, this quick 3 line program demonstrates how evdev makes it even simpler. The package has a termendous of code built that for managing devices like a joystick or gamepad.

Let’s use evdev to read some buttons. Start with the same import statement and create an InputDevice like we did above, but now replace printing the device with the 2 lines below. Then press a button on your gamepad.

Basic Read Loop

Instead of the garbled characters we saw earlier, we see something that is at least readable.

Before we discuss the events, let’s talk about this line:

for event in gamepad.read_loop()

This illustrates another feature of the evdev package. The package provides a simple for loop that reads the device and creates events for us. Without this feature our code would look something like this:

from evdev import InputDevice
from select import select
gamepad = InputDevice('/dev/input/event0')
 while True:
     r,w,x = select([gamepad], [], [])
     for event in gamepad.read():
         print(event)

Evdev eliminates the outer While loop, as well as the call to select. In terms of lines of code, the difference is negligible, however the read_loop() version is a little easier to read and we’ll stick with it.

(In an application that needs to monitor more than one input device, such as a mouse and a keyboard, read_loop() won’t work, as it only lets us read one device. Select is excellent for reading and writing from multiple devices.)

Let’s identify what’s actually being pressed on the gamepad:

Categorize Events

Now for every event we examine the type value and see if it is a button. finally we are using the ecodes we imported.

If it is a button, we use categorize to return a more specific type of event to us and print it. If it is not, we simply print a message.

Here we see where evdev saves us a lot of work. In addition to reading the events a gamepad sends to our app, it also categorizes them for us too.

key event at 1433298890.180640, 304 (['BTN_A', 'BTN_GAMEPAD']), down

Note two things about this event info: we see the button I pressed (‘A’) and we see “down.” After a mysterious “not a button” event we then see the same button going up.

We know a lot about how to read the gamepad at this point. Let’s write a script that we could use to control the robot using the A-B-X-Y buttons on the gamepad. Enter the following into a text editor and save it.

#!/usr/bin/python

from evdev import InputDevice, categorize, ecodes, KeyEvent
gamepad = InputDevice('/dev/input/event0')

for event in gamepad.read_loop():
    if event.type == ecodes.EV_KEY:
        keyevent = categorize(event)
        if keyevent.keystate == KeyEvent.key_down:
            if keyevent.keycode[0] == 'BTN_A':
                print "Back"
            elif keyevent.keycode == 'BTN_Y':
                print "Forward"
            elif keyevent.keycode == 'BTN_B':
                print "Right"
            elif keyevent.keycode == 'BTN_X':
                print "Left"

I saved this as gamepad.py.

chmod +x gamepad.py
./gamepad.py

Run the script and press a few buttons.

As we receive each event we first check to see if it is a button. If it is, we check to see that the button is down (it’s been pressed) and then finally we check to see if it is one of the four buttons we currently care about.

That’s it for this week. Next week we’ll add code to make the GoPiGo respond to the buttons, and we’ll also a some more controls.

See you then! And feel free to leave a comment below.