Skip navigation

The Finalizer

Building a .NET Robot

CoverStory

LANGUAGES: C#

ASP.NET VERSIONS: 2.0

 

The Finalizer

Building a .NET Robot

 

By Brian Peek and Jonathan Goodyear

 

In December of 2004, Jonathan Goodyear was watching a BattleBot competition on television with his four-year-old son, CJ, who asked his dad if he could make one for him. At first Jonathan thought, No way! Those things are way too complex. The more Jonathan thought about it, though, the more he started to ask himself, Why not? In fact, building a robot powered by the Microsoft .NET Framework would be a perfect case study to prove that .NET was capable of doing so much more than boring business applications. A few days later, a proposal for the project was submitted to Microsoft. The Microsoft Regional Director (RD) Program (of which Jonathan is a member) loved the idea and provided some seed capital (which did not go nearly as far as anticipated). The Finalizer project was born.

 

Of course, we had absolutely no idea what we were getting into. Jonathan had built some high-performance remote control cars as a teenager, but nothing that even came close to the magnitude of the journey upon which we were about to embark. Luckily, as a consulting company, we had a whole team of developers ready to help on this seemingly foolish endeavor.

 

We initially tried to build the robot hardware ourselves, which was a complete disaster. After a couple of weeks, we ended up with something that resembled a demented motorized circus tricycle. As a result of this tangent to our progress, Jonathan is probably the only software developer who owns an arc welder. We then heard from Microsoft that the excitement surrounding the project was mounting and that they wanted to unveil The Finalizer during one of the keynotes at Tech Ed 2005. That was less than three months away!

 

We were in serious trouble, and for no good reason. After all, our goal was not to show our mechanical engineering prowess (which we obviously had none). Our goal was to show that it was possible to control machinery with .NET code. So we called in the expertise of Zack Bieber at The Machine Lab in Los Angeles. Zack has built some of the most famous BattleBots, and worked with us to design and build The Finalizer.

 

When The Finalizer arrived at our office in Orlando, FL, its control mechanism was a standard two-stick multi-function remote control; no different from what you might see controlling a model helicopter or any other BattleBot. Our goal was to strip out this standard control mechanism and replace it with a .NET-powered control mechanism.

 

There were three major parts to the system that we needed to address and build: the robot s onboard processor/brain, the controller/monitor, and the communications layer (see Figure 1).

 


Figure 1: A diagram showing the entire architecture.

 

This article will discuss how we tackled each of these parts, including the factors and alternatives that we considered, as well as some of the issues that we encountered and surmounted.

 

The Onboard Processor/Brain

When building the control infrastructure for the robot, we attempted to use off-the-shelf components as much as possible. Originally, the robot was to be controlled with a .netCPU, a microcontroller running a stripped-down version of the CLR known as TinyCLR. You may recognize the TinyCLR as the engine that powers MSN Direct watches that were popular for a brief time. This microcontroller would connect directly to the servos on the I/O pins available on the chip, and would communicate with the host PC via a radio frequency (RF) link. However, mid-way through development of the software using .netCPU, the manufacturer discontinued the product and subsequently went out of business. The inability to get support from the manufacturer, coupled with the .netCPU microcontroller s flakey performance, required a redesign of The Finalizer s brain.

 

After researching alternative solutions, we decided to control the robot using a Windows Mobile-powered PDA and selected the HP iPAQ hx4705 PDA with a built-in WiFi card. We also added a Quatech DSCF-100 CompactFlash serial adapter card to the PDA, which breaks out to a standard 9-pin serial port. This serial port is then plugged into a Lynxmotion SSC-32 32-channel servo controller, which accepts input on a serial interface and translates that input into Pulse Width Modulation (PWM) output to each servo.

 

The PDA inside The Finalizer runs an application written using Visual Studio 2005 and the .NET Compact Framework 2.0. When this application is launched, it actively polls the laptop for the listening socket and connects. Once connected, it uses asynchronous socket communication to handle the incoming data.

 

Asynchronous sockets are non-blocking sockets. That is, they will not halt the application waiting for a connection or data to arrive. Instead, the methods involved allow the developer to specify delegates to be called when certain events occur. For example, with The Finalizer, we set up a delegate to read data from the socket into a FinalizerPacket in two parts. First, we have the socket classes call a delegate when exactly enough bytes are available to reconstruct the packet header. When the delegate is called, we inspect the header, ensure it is valid, and create a new FinalizerPacket with a data buffer of the size specified in the header into which to deserialize the rest of the data. A second asynchronous event is then set up to notify the application when the exact number of bytes in the rest of the packet is available so we can flesh out the rest of the FinalizerPacket and process it accordingly. This is demonstrated in the simplified code shown in Listing One, along with a partial implementation of the FinalizerPacket class.

 

When a full FinalizerPacket packet arrives, the application deserializes the data portion of the packet, a byte array, back into a ControllerState object and acts appropriately. If the sticks have been moved or any of the buttons have been pressed, the commands are sent to the servo controller for action.

 

The servo controller accepts a string in a specific format passed to its serial port to act upon. The data from the ControllerState object contains the controller s state information and is transformed into the string required to activate (or deactivate) the servos accordingly. Luckily, the new .NET Compact Framework 2.0 contains a SerialPort class that very easily handles sending and receiving data across any serial port. Opening and using a serial port is as simple as the following code listing:

 

// constructor takes the serial port to open,

// and the baud-rate

 SerialPort serial = new SerialPort("COM1", 19200);

 serial.Open(); // open the port

 serial.Write("hello world");  // write a string to the port

 serial.Close(); // close the port

 

Obviously, this new class was invaluable to us because it saved us the process of writing and debugging a wrapper for the Win32 API serial port and I/O calls.

 

One of the biggest obstacles in developing the software for The Finalizer was that the lead developer, Brian Peek, lived in upstate New York, while the rest of the team, and the robot itself, resided in and around Orlando. Because it would be impossible to ship the several-hundred-pound robot back and forth between New York and Orlando during development, we decided to maintain two sets of mirrored development environments, one in each location. Brian bought several hobby servos from Radio Shack to mimic the servos and motors used in the real robot for testing the servo controls while the robot was permanently housed in Orlando. The entire PDA application was developed in New York using the PDA, a servo controller, two hobby servos, and a few 9-volt batteries. This approach worked remarkably well, and is a great way to cost-effectively try out some of the concepts in this article.

 

When the robot finally arrived in Orlando, their PDA was hooked up to the servo controller, the servo controller was hooked up to the servos of the robot, and testing began. The on-screen PDA interface consists of a series of sliders and buttons (see Figure 2). While we had documentation on the workings of the servos in the robot, this interface allowed the team in Orlando to drive each servo individually to determine the direction and speed to turn each one to make the robot work. The manual interface has also been an invaluable debugging tool to ensure the robot is working correctly without needing to set up the host PC for communication.

 


Figure 2: The PDA interface.

 

All the servos in The Finalizer use a PWM control system. For rotational servos (which the robot uses exclusively), this means that the servo spins at a certain speed and in a certain direction based on an electrical pulse being sent to it every 20 milliseconds. The width of this pulse, that is, the length of time the pulse is sent, determines the servo behavior, and must be between 1ms and 2ms. A pulse width of 1.5ms stops the servo. A pulse width of 1.0ms will turn it one direction, while a pulse width of 2.0ms will turn it the opposite direction. Values in between these will control the speed at which the servo turns. With the servo control information determined, we could proceed with building the control infrastructure for the robot.

 

The Controller/Monitor

On the PC side of the fence, we have an Xbox controller hooked up to a laptop PC with built-in WiFi, running a custom Windows Forms application developed in Visual Studio 2005 and .NET 2.0. When the application starts, a thread is launched that creates a listening socket, then waits for a connection from the robot s PDA, as shown in Figure 3.

 

// create a new listening socket

Socket listener = new Socket(AddressFamily.InterNetwork,

 SocketType.Stream, ProtocolType.Tcp);

// create an endpoint accepting any client connecting

// on port 1234

IPEndPoint end = new IPEndPoint(IPAddress.Any, 1234);

// bind the listener to that endpoint

listener.Bind(end);

// only allow 1 connection

listener.Listen(1);

// wait for the robot...this will block until

// we get a connection

mSocket = listener.Accept();

Figure 3: Create a listening socket and wait for a connection.

 

Ideally, this situation would be reversed, with the controlling PC connecting to the PDA. While testing, however, we found that the PDA, in order to preserve power, shut down the network card at will, even if there was a socket listening for a connection. Because we didn t want the PDA to shut down the listening socket before we could connect from the controlling laptop, we decided the easiest fix would be to reverse the process.

 

Once a successful connection between the robot and the PC is established, the main application loop begins. Using DirectInput, a library in the DirectX suite of libraries that allows one to easily poll the keyboard, mouse, or game controllers in real-time, we are able to poll the state of the Xbox controller. The controller is plugged into the PC using a custom adapter cable (available online), which converts the Xbox plug into a standard USB plug. This, coupled with a DirectInput driver written for the controller, turns the Xbox controller into a standard PC game controller usable with DirectInput.

 

After some initial testing, we arrived at our final control scheme. The left analog stick controls the forward and backward movement of the robot; the right analog stick controls the left and right motion of the robot. The left and right triggers control the saw spinning and hammer. The D-Pad controls the lifter up and down, as well as the saw arm extension in and out. And finally, the X, B, and A buttons control the lights inside the robot, allowing it to glow blue, red, or not at all, respectively.

 

To maintain a responsive control system, the application architecture is set up much like that of a video game, as shown in Figure 4.

 

while (mRun)

{

 // get joystick state from DirectInput and pack it

 // into a ControllerState object

 ControllerState cs = GetJoystickState();

 // send that object off to the bot

 // in a FinalizerPacket object

mNetwork.Send(new FinalizerPacket(

 PacketType.ControllerState, cs.ToBytes()).ToBytes());

 // update the user interface

 UpdateUI(cs);

 // sleep for 100ms

  Thread.Sleep(100);

}

Figure 4: The application architecture maintains a responsive control system.

 

Instead of waiting for messages and events from the control system, we use DirectInput to poll the controller in a tight loop, reading its state every 100 milliseconds. After determining the state the controller is in, we package that information into a custom ControllerState class. That object is then serialized into a byte array and passed to a FinalizerPacket class. This class wraps that information with a packet header that contains the type of packet being transmitted and some size information. The resulting FinalizerPacket is once again serialized into a byte array and then passed to the robot via the socket connection instantiated earlier. Next, the user interface is updated.

 

Our main PC dashboard interface contains several Dundas Gauge for .NET controls (see Figure 5). These controls display the robot s current (approximate) speed, the saw blade RPMs, and the current rotation of the robot. These values are determined simply by determining how far the triggers on the controller have been pressed, doing some quick math, and presenting it in the gauge control.

 


Figure 5: The PC interface.

 

Finally, the application sleeps for 100 milliseconds and starts the process again.

 

The Communications Layer

Early in our planning stages, we considered using Web services to transmit commands from the host PC to the robot. However, after examining the size of the required Web service request and response, we knew it would be impossible to send, process, and receive that much data and at the same time keep the robot responsive to controls on the host side. Web services may be acceptable in a business application where turnaround time is 1 or 2 seconds, but when attempting to send data to the robot every 100 milliseconds, Web services are not a good architecture.

 

The PC is connected to the laptop via a standard 802.11g WiFi connection with a Linksys WRT54G access point/router in between for good measure. The two devices could be connected in ad hoc mode, but to gain some extra distance and power, we connected both the PDA and laptop to the access point to act as a repeater.

 

To ensure a secure connection between the robot and the PC, and to further ensure that no devices other than The Finalizer and the laptop are connected, WiFi security is used. Specifically, broadcasting of the SSID of the access point is disabled, and both devices are connected to the access point using WEP encryption with MAC filtering enabled, only allowing connections from the laptop and the PDA.

 

Extra Feature

Outside all of this, the robot is equipped with a small camera that transmits its data to a receiver via an RF link. This receiver outputs video using a composite video jack. To display this video on our PC dashboard application, we purchased a Dazzle Digital Video Creator 90 video capture device that has a composite input. The receiver is attached to the video capture device, which is in turn connected to the PC via USB. The PC application uses the Windows Media Encoder 9 SDK to grab the feed directly from the capture device to display on the dashboard application.

 

Safety First

In an attempt to prevent the robot from dismembering us or anyone else around it, there are fail-safes built into the system. First and foremost, the PDA application contains a watchdog timer. When the laptop connects to the PDA, the application immediately starts a periodic timer that attempts to call a method every n milliseconds. Upon receipt of a packet of data, the watchdog timeout period is reset back to n milliseconds. So, if no data were to arrive for a time period greater than that n milliseconds, the watchdog method would actually be called, which resets all servos to their centered positions, effectively stopping the robot in its tracks.

 

Additionally, The Finalizer itself has manual failsafe switches on the outside of its case. These switches allow one to stop the robot in its tracks, or to disable portions of the robot during testing or performances where certain weapons will not be used.

 

Post Mortem

Overall, operation of The Finalizer has gone relatively smoothly, but a few obstacles were encountered along the way. One of the first issues we found was the sloppiness of the analog sticks on the Xbox controller. There is no true center for an analog stick, because the stick at a dead center position will still float a bit in all directions. We compensate for this by allowing the configuration of a dead zone, where no movement information is sent to the robot unless the stick is moved outside this dead zone in any direction.

 

The scariest obstacle of all came on the day The Finalizer was shown to the world live, on-stage at Tech Ed 2005. As stated earlier, the communication between the laptop and the PDA inside the robot is handled over straight 802.11g WiFi. We had left the access point s WiFi channel on the default of 6 and had never seen any issue with interference. However, on the day of the show, after several thousand developers streamed into the auditorium, all with their trusty laptops in hand, the connection between the robot and the PDA died a few minutes before showtime. After attempting to reconnect unsuccessfully several times, a quick reconfiguration of the access point to a non-standard WiFi channel allowed the two to maintain their connection to each other, and the performance went off without a hitch. Since Tech Ed 2005, The Finalizer has appeared at numerous internal and external Microsoft events, such as the 2005 Professional Developer Conference (PDC) in Los Angeles and the Visual Studio 2005 Launch Event in San Francisco, as well as at user groups and code camps.

 

You can download the full source code for The Finalizer at http://www.finalizer.net. The software is split up into three separate projects: one for the PC, one for the PDA, and a common project containing code that is shared by both the PDA and the PC. The code in this common project mainly consists of the socket layer and the ControllerState class. The code builds to an assembly that is copied to both the PDA and the PC for execution. Because the code is very generic and uses classes and methods found in both the full framework and the compact framework, we can use the output assembly on both devices without modification.

 

As previously mentioned, you don t need to build your own BattleBot to try out the concepts presented in this article and accompanying code. The Finalizer cost over $25,000 in hardware alone. You can put together a fully functioning test environment for as little as $500, or even less if you already have a PocketPC device with a CompactFlash slot. So go ahead and expand your horizons and build your own .NET-powered machine. We guarantee your kids will think it is way cool.

 

Jonathan Goodyear is president of ASPSoft (http://www.aspsoft.com), an Internet consulting firm based in Orlando, FL. Jonathan is Microsoft Regional Director for Florida, a Microsoft Certified Solution Developer (MCSD), and co-author of Debugging ASP.NET (New Riders). Jonathan also is a contributing editor for asp.netPRO. E-mail him at mailto:[email protected] or through his angryCoder eZine at http://www.angryCoder.com.

 

Brian Peek is a recognized .NET expert with more than three years experience developing .NET solutions, and more than six years of professional developing experience using Microsoft technologies and platforms. Along with .NET and its associated languages, Brian is particularly skilled in the languages of C/C++ and assembly language for a variety of CPUs, and a variety of technologies, including Web development (ASP, JavaScript, HTML, XML, etc.), document imaging, and GIS. Brian has a strong background in developing applications for the health-care industry, as well as developing solutions for portable devices, such as tablet PCs and PDAs. He co-authored Debugging ASP.NET (New Riders). Contact Brian at mailto:[email protected].

 

Begin Listing One

public class FinalizerPacket

{

 public static int HEADER_MARKER = 0x1234;

   // eh, it works

 public static int HEADER_SIZE = 9;

   // marker (4) + data size (4) + packet type (1)

 private PacketType mType;

 private byte[] mData;

 private int mDataSize;

 // create an empty packet of a specific data size

 public FinalizerPacket(PacketType type, int size)

 {

   mType = type;

   mData = new byte[size];

   mDataSize = size;

 }

 // ...

 // properties for the above member variables

 // ...

 // serialize the entire packet into a byte array

 // to pass across the socket

 public byte[] ToBytes()

 {

   MemoryStream ms = new MemoryStream();

   BinaryWriter bw = new BinaryWriter(ms);

   // write each field, in order, to the BinaryWriter

   bw.Write(HEADER_MARKER);

   bw.Write(mData.Length);

   bw.Write((byte)mType);

   bw.Write(mData);

   // convert the stream to an Array

   byte[] buff = ms.ToArray();

   bw.Close();

   ms.Close();

   // return the array

   return buff;

 }

}

// read a header's worth of data

mSocket.BeginReceive(mBuffer, 0, FinalizerPacket.HEADER_SIZE,

 SocketFlags.None, new AsyncCallback(OnHeaderData), null);

// if we got a header's worth of data, process it

private void OnHeaderData(IAsyncResult ar)

{

 int received = mSocket.EndReceive(ar);

 if(received > 0)

 {

   // track how much data we've gotten so far

   mCurrentSize += received;

   // try to process the header of the packet

   if(ProcessHeader())

   {

     // we have a valid header

     mCurrentSize = 0;

     // receive again, this time pulling the data

     // portion of the packet

     mSocket.BeginReceive(mBuffer, 0, mPacket.DataSize,

      SocketFlags.None, new AsyncCallback(OnReceivedData),

       null);

   }

 }

 else

 {

   // we've been disconnected...handle it

 }

}

// make sure the header is valid

private bool ProcessHeader()

{

 // if we've received a header's worth of data

 if (mCurrentSize >= FinalizerPacket.HEADER_SIZE)

 {

   // grab the packet marker and make sure it's valid

   int header = BitConverter.ToInt32(mBuffer, 0);

   // make sure it's the start of the packet

   if (header == FinalizerPacket.HEADER_MARKER)

   {

     // grab the size of the data portion of the packet

     int size = BitConverter.ToInt32(mBuffer, 4);

     // grab the packet type and create a new

     // FinalizerPacket of that type and size

     PacketType pt = (PacketType)mBuffer[8];

     mPacket = new FinalizerPacket(pt, size);

     return true;

   }

 }

 else

 {

   // we've received the incorrect amount of data...

   // handle it

   return false;

 }

}

// received the data portion of the packet

private void OnReceivedData(IAsyncResult ar)

{

 // end the current receive and get the length

 // of data received

 int received = mSocket.EndReceive(ar);

 // if we've received some data and didn't disconnect

 if(received > 0)

 {

   // track the current amount of data received

   mCurrentSize += received;

   // process the data

   ProcessData();

   // setup to received another packet, header first

   mSocket.BeginReceive(mBuffer, 0,

    FinalizerPacket.HEADER_SIZE, SocketFlags.None,

    new AsyncCallback(OnHeaderData), null);

   // reset it all

   mCurrentSize = 0;

   mPacket = null;

 }

 else

 {

   // we've been disconnected....handle it

 }

}

private bool ProcessData()

{

 // if we've received the proper data amount

 if(mCurrentSize > 0 && mCurrentSize >= mPacket.DataSize)

 {

   // copy off our data to the current packet

   Array.Copy(mBuffer, 0, mPacket.Data, 0, mPacket.DataSize);

   // pass it to the specifid delegate for processing

   if(OnDataReceived != null)

     OnDataReceived(mPacket);

   return true;

 }

 else

 {

   // we got some other size of data...handle it

   return false;

 }

}

End Listing One

 

Build Your Own Bot

Visit these links for more information:

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish