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.

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

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:

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:

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:

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:

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!)

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.

Proudly powered by WordPress | Theme: Baskerville 2 by Anders Noren.

Up ↑