.NET Gadgeteer-powered RoombaCam Prototype Complete

A while ago, I got the .NET Gadgeteer FEZ Starter Kit from GHI Electronics. For some reason I never got around to doing anything with it aside from that very basic first project. The other day, though, I got my copy of Getting Started with .NET Gadgeteer, and after reading through a couple of the projects in the book, I was intrigued to do one of my own. So I came up with the idea to build a camera connected to a web server that I could attach to my Roomba. The camera would take pictures periodically and make them available on a simple website so I could check on the Roomba working online.

The prototype

Below are the components from the started kit I used to build the prototype. Of course there is the camera that is triggered by a timer. The current picture is kept in memory, so when a page request comes in through the web server on the Ethernet module, it can be included in the page served. Each picture is also saved under the current timestamp to an SD card, so eventually I could have a page showing a history of everything my Roomba has seen. For testing purposes, I have also included a button that, just like the timer, triggers the picture taking code. For good measure, I also threw in the multicolor LED that displays a simple status (starting, ready, error) even though it won’t be needed in the final version as that is supposed to run unattended.

I build and wrote the code (see below) for all of this within a few hours lat one night last week. Knowing C# this was super easy. The components are really simple to use, and even though some .NET classes are not as full featured in the Micro Framework as they are in the full framework (e.g. the TimeSpan class is missing a couple of convenient factory methods), these limitations are very easy to work around.

RoombaCam Prototype Components

Next step: going mobile

There is one obvious problem with the prototype above: it uses Ethernet and relies on my development PC for power. A post on the .NET Gadgeteer on MSDN helped me solve the latter problem: a battery pack with a Mini-USB connector that plugs into the USB Client module. I couldn’t find any of the battery packs mentioned in that article here in Germany, but I did find a different one on Amazon that seems to work just as well. I don’t know yet what kind of battery life I will get with it, though. I hope it will be a couple of hours, because its probably 3 hours between the time a leave for work (turning the device on) and the Roomba finishing its sweep of my apartment.

For wireless network connectivity, I think I will get the WiFi RS21 Module. It shares the NetworkModule base class with the Ethernet module, so code-changes will be minimal when I swap the two. Unfortunately, the module is currently out-of-stock at GHI Electronics’ German distributor, so I’ll have to wait a while. I guess I could order it directly from GHI in the U.S., but last time I did this, it took the post office forever to the ship the things and factoring in shipping&handling, taxes and import duties it was kind of pricey as well.

Another module I want to get is the compass module, because I think it would be really cool to know the Roomba’s orientation as well. I think I could also use this to detect when Roomba starts and stops moving, so I can start and stop the picture taking accordingly. I don’t know, maybe a gyroscope would better for this, but the gyro module is kind of expensive as well.

I also need a board or something to mount all theses modules to. Right now, everything is spread out on my desk, but for the final version I will need something I can attach safely to the Roomba so things stay in place as it moves around and bumps into things (which it seems to do a lot recently, I don’t know why the obstacle detection seems to have deteriorated).

Where the magic happens

Below is the main program code (Program.cs) to power my RoombaCam. I know still need to refine and refactor some things, which I will do when I get the missing modules. Once that is done, I think I will post the full project source to GitHub or something. After reading Joel Spolsky’s Mercurial tutorial HgInit a wile ago, I really wanted to check out distributed version control, so maybe this is a good opportunity to do so. So stay tuned…

[03-July-2016 Update] Here is the source code.

using System;
using Microsoft.SPOT;
using Microsoft.SPOT.Presentation;
using Microsoft.SPOT.Presentation.Controls;
using Microsoft.SPOT.Presentation.Media;

using GT = Gadgeteer;
using GTM = Gadgeteer.Modules;
using Gadgeteer.Modules.GHIElectronics;

namespace RoombaCam
{
    public partial class Program
    {
        void ProgramStarted()
        {
            _sdCard.SDCardMounted += (sender, e) => CheckStatus();
            _sdCard.SDCardUnmounted += (sender) => CheckStatus();
            if ((_sdCard.IsCardInserted) && (!_sdCard.IsCardMounted))
            {
                _sdCard.MountSDCard();
            }

            var pictureTakingInterval = new TimeSpan(0, 0, 30);
            _pictureTakingIntervalInSeconds = pictureTakingInterval.Seconds.ToString();
            new System.Threading.Timer(TakePicture, null, new TimeSpan(0, 0, 1), pictureTakingInterval);
            _camera.PictureCaptured += PictureCaptured;

            _ethernet.NetworkUp += StartWebServer;
            _ethernet.NetworkDown += StopWebServer;
            _ethernet.UseDHCP();

            _button.ButtonPressed += (sender, e) => TakePicture(null);

            CheckStatus();
        }

        //
        // General
        //

        private void Log(string message)
        {
            Debug.Print(DateTime.Now.ToString("hh:MM:ss: ") + message);
        }

        private void CheckStatus()
        {
            if ((_sdCard.IsCardMounted) && (_ethernet.IsNetworkUp))
            {
                _led.TurnGreen();
            }
            else
            {
                _led.TurnRed();
            }
        }

        //
        // Camera
        //

        private string _pictureTakingIntervalInSeconds;
        private GT.Picture _picture;
        private bool _captureInProgress;
        private DateTime _pictureTaken;

        private void TakePicture(object state)
        {
            if (_captureInProgress)
            {
                Log("Picture is already being taken, not taking another one right now");
            }
            else
            {
                if (!_camera.CameraReady)
                {
                    Log("Camera not ready, no picture was taken");
                }
                else
                {
                    Log("Taking a picture...");
                    _pictureTaken = DateTime.Now;
                    _captureInProgress = true;
                    _camera.TakePicture();
                }
            }
        }

        void PictureCaptured(Camera sender, GT.Picture picture)
        {
            _picture = picture;
            _captureInProgress = false;
            if (_sdCard.IsCardMounted)
            {
                string fileName = _sdCard.GetStorageDevice().RootDirectory + "\\RoombaCam_" + _pictureTaken.ToString("yyyy-MM-dd_hh-mm-ss") + ".bmp";
                Log("Saving picture as " + fileName + "...");
                System.IO.File.WriteAllBytes(fileName, picture.PictureData);
                Log("Picture saved");
            }
        }

        //
        // Web server responses
        //

        private bool _isWebServerRunning;

        private void StartWebServer(object sender, GTM.Module.NetworkModule.NetworkState networkState)
        {
            Log("Network is up, starting web server...");
            _ethernet.StartLocalServer(80);
            _ethernet.SetupWebEvent("RoombaCam.htm").WebEventReceived += ShowPage;
            _ethernet.SetupWebEvent("CurrentPicture.bmp").WebEventReceived += ShowImage;
            CheckStatus();
            Log("Web server started");
            _isWebServerRunning = true;
        }

        private void StopWebServer(object sender, GTM.Module.NetworkModule.NetworkState networkState)
        {
            if (_isWebServerRunning)
            {
                Log("Network is down, stopping web server...");
                _ethernet.StopLocalServer();
                CheckStatus();
                Log("Web server stopped");
            }
        }

        private void ShowImage(string path, GTM.Module.NetworkModule.HttpMethod method, GTM.Module.NetworkModule.Responder responder)
        {
            Log("HTTP request for \"" + path + "\"");
            responder.Respond(_picture);
        }

        private void ShowPage(string path, GTM.Module.NetworkModule.HttpMethod method, GTM.Module.NetworkModule.Responder responder)
        {
            Log("HTTP request for \"" + path + "\"");
            string page =
@"
<!DOCTYPE html>
<html>
  <head>
    <title>RoombaCam</title>
    <meta http-equiv=""refresh"" content=""" + _pictureTakingIntervalInSeconds + @""">
  </head>
  <body>
    <h1>RoombaCam</h1>
    <p>This is what Roomba was looking at at " + _pictureTaken.ToString("dd MMM yyyy, hh:mm:ss") + @". Page refreshes every " + _pictureTakingIntervalInSeconds + @" seconds.</p>
    <p><img src=""CurrentPicture.bmp"" alt=""The world as Roomba sees it""/></p>
  </body>
</html>";

            responder.Respond(System.Text.UTF8Encoding.UTF8.GetBytes(page), "text/html");
        }
    }
}
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s