Roomba + Netbook = Telepresence Video Chat Robot

I recently built a robot based on a roomba and a netbook based on the telepresence video chat robot created by Johnny Lee for his blog.  For my design, I took a metal grate and drilled some holes into it and then formed a shape that would position the laptop higher off the ground than just placing it on the roomba.  You can see what it looks like in the following picture:

The software that Johnny Lee wrote, works fine as he authored it but I thought it would be fun to add support for moving the robot using an Xbox controller so I made a few changes to his code.  You can download my modified version of the Video Chat Robot code here so that you can see the minor changes I made to enable this.  To give you an idea of how the changes work, it helps to describe how the software that controls the robot.

The project consists of a couple C# source files:

  • Form1.Designer.CS - The Windows Forms application for the User Interface for controlling and connecting to the robot.
  • Form1.cs - The code behind for the Windows Form UI.  If Visual Studio starts this form in design view, you can press CTRL+ALT+0 to see the actual code.
  • iRobotCreate.cs - The wrapper for the opcodes that are sent to the robot.
  • UPnP.cs - UPnP wrapper for enabling remote access when the application is running behind a firewall.

 The way that the robot movements work from Form1.cs is a list of commands to send to the roomba wrapper through the following loop using a virtual joystick.  The following image shows the Virtual Joystick:

The following code shows how the input from the virtual joystick is turned into wheel velocities:

     void UpdateJoystickAndDriveCommand(MouseEventArgs e)    {      updating = true;      try      {        mousePoint.X = e.X;        mousePoint.Y = e.Y;        Point center = new Point(joyBitmap.Width / 2, joyBitmap.Height / 2);        joyGraphics.Clear(Color.White);        int size = 10;        int dx = (e.X - center.X)/2;        int dy = -1*(center.Y - e.Y);        float mag = (float)Math.Sqrt(dx * dx + dy * dy);        float maxRad = 200;        float scale = maxRad / mag;        if (scale < 1)        {          dx = (int)(dx * scale);          dy = (int)(dy * scale);        }         Point newE = new Point(center.X + 2*dx,center.Y + dy);         joyGraphics.DrawEllipse(new Pen(Color.Red), newE.X - size / 2, newE.Y - size / 2, size, size);        joyGraphics.DrawLine(new Pen(Color.Red), center, newE);        pictureBox1.Image = joyBitmap;         float maxVel = 400;        dx = (int)(dx*maxVel/ maxRad);        dy = (int)(dy*maxVel / maxRad);         int left = (dy - dx);        int right = (dy + dx);         robot.DriveDirect(left, right);        lblWheelVelocity.Invoke((MethodInvoker)(() => lblWheelVelocity.Text = "WheelVelocity: " + left + " " + right));         if ((Environment.TickCount - lastNetworkMessageTimestamp) > 50)        {          lastNetworkMessageTimestamp = Environment.TickCount;          if (udpClientSender != null)          {            if (udpClientSender.Client.Connected)            {              lblWheelVelocityR.Invoke((MethodInvoker)(() => lblWheelVelocityR.Text = "WheelVelocity: " + left + " " + right));               Byte[] sendBytes = Encoding.ASCII.GetBytes(mousePoint.X + " " + mousePoint.Y);              udpClientSender.Send(sendBytes, sendBytes.Length);              lastSentNetworkMessageTimestamp = System.Environment.TickCount;            }          }        }      }      catch (Exception x)      {        Console.Out.WriteLine(x);      }      updating = false;    }

The mouse event arguments are used in this method to calculate the velocity for each wheel and then spin the wheels accordingly. There are some complexities in the algorithm because it works over the network as well as locally, but the simplicty of using this function by preprocessing mouse messages seemed like a logical point for adding joystick controls to the robot.

To enable joystick support, I used the XNA framework (SDK) and then read this short article on using a joystick on Windows with the XNA sdk.  I added a new method, UpdateControllerState which is set up using a timer in Windows.  The following code shows my implementation of UpdateControllerState and the code for setting up the variables used:

     private void UpdateControllerState(object sender, EventArgs e)    {      //Get the new gamepad state and save the old state.      this.gamePadState = Microsoft.Xna.Framework.Input.GamePad.GetState(Microsoft.Xna.Framework.PlayerIndex.One);       float fx = this.gamePadState.ThumbSticks.Left.X;      float fy = this.gamePadState.ThumbSticks.Left.Y;         {          int x = (int)((joyBitmap.Width / 2) + (100*fx));          int y = (int)((joyBitmap.Height / 2) - (100*fy));           MouseEventArgs me = new MouseEventArgs(MouseButtons.Left, 0, x, y, 0);          UpdateJoystickAndDriveCommand(me);        }        }

 I also added some variables to Form1.cs and also added some code to set up the timer in the InitializeComponent part of the designer generated code (probably a little hacky, but it works):

 private int x1Position;  private int y1Position;  private Microsoft.Xna.Framework.Input.GamePadState gamePadState;  private System.Windows.Forms.Timer controllerTimer;        /// <summary>        /// Required method for Designer support - do not modify        /// the contents of this method with the code editor.        /// </summary>        private void InitializeComponent()        {   (...)             this.components = new System.ComponentModel.Container();            this.controllerTimer = new System.Windows.Forms.Timer(this.components);            this.controllerTimer.Interval = 500;            this.controllerTimer.Tick += new System.EventHandler(this.UpdateControllerState);            this.controllerTimer.Start(); 

Now, when the application starts, joystick commands from a connected Xbox controller will send drive commands to the Robot.