Using XBee Radios with .NET Gadgeteer and the XBeeClient Libraries

Using the .NET Gadgeteer platform with XBee radios got a lot easier with the alpha release of XBeeClient libraries written by Paul MineauXBee radios use low-bandwidth serial protocol that is useful in many applications.  This example implements an XBee network using three XBee radios that support a surveillance device.  The .NET Gadgeteer modules include a Gyro module and Relay module from Seeed Studio, and a Camera module and SD Card module from GHI Electronics

In this scenario the gyro module is calibrated so that rotation on one axis can be monitored to send an alert when a door swings open.  When the door opens, one XBee radio sends an alert to another XBee in the network.  The second radio closes a relay circuit to turn on lights and start a timer.  Each time the timer ticks, the surveillance device takes a picture.  The Seeed relay module for .NET Gadgeteer, which turns on the lights, hosts four relays, and each of them can handle AC electric circuits up to 7 amps/ 250 volts or 16 amps/ 120 volts.  That’s enough power to illuminate an area under surveillance so brightly that it will disorient an intruder and make it easy to take pictures with the camera.

An earlier piece titled XBee Radios in .NET Gadgeteer Devices explains modem configuration and flashing XBee radios.  For other pertinent information, see Robert Faludi‘s book titled Building Wireless Sensor Networks.  The book is a tutorial using XBees in Arduino devices, but much of the information in the book is helpful when setting up XBees with .NET Gadgeteer.   The XBee radios used in this application should be flashed in the API configuration, two of them as routers and one as a coordinator (every network of XBee radios requires one coordinator).

The XBeeClient libraries are designed to make set-up and testing of XBee radios easy.  Download the Alpha 1.0.0.0 version of the libraries from CodePlex, and run the .msi installation package.  If you haven’t yet used the X-CTU tool from Digi to flash your radios, see  XBee Radios in .NET Gadgeteer Devices.  The process is not entirely painless, but we’re just getting started with these things.  In this example we’ll set up two XBee API router devices using .NET Gadgeteer XBee adapters from GHI Electronics. We’ll run the XBee coordinator on an Explorer dongle.  The InteractiveConsole PC application that is included with the XBeeClient libraries is great for testing, and in scenarios such as this, it is fine to use it as the PC component of a network of XBees.

XBee Radio number 1 and the Gyro Module Sensor Device

The device that sends the security alert when a door opens uses a GHI Electronics Spider mainboard, an XBee radio in an adapter from GHI Electronics, and a Gyro module from Seeed Studio.  The following illustration also shows a button, GHI Electronics Light Sensor module, and Display_T35.  These last two modules are not essential for operation of the device, but the Display_T35 is very useful in calibration of the gyro; you’ll have to orient the axes of the gyro and set a threshold that triggers an alert when the door on which it is mounted opens.  The Light Sensor provides useful feedback by measuring brightness when the other device in the network turns on the lights.  Dragging the GTM.Community.XBeeClient library from the toolbox adds the image of the GHI Electronics XBee adapter to the Designer surface (click to enlarge).

XBee Radio 1 and Gyro Sensor

XBee Radio 1 and Gyro Sensor

The core of this application is a little bit of code that monitors the Y axis of the gyro and sends an alert when the threshold is reached.  In this example rotation that is at least negative 50 indicates the door has been opened.  The XbeeClient object uploads the string door-open, which sends an alert to the two other XBee radios running on the same PAN ID. One of the other XBees is the coordinator on the PC dongle and the other is running on a device with a relay that turns on lights and a camera to take pictures.

    void gyro_MeasurementComplete(Gyro sender, Gyro.SensorData sensorData)
    {
        double temp = sensorData.Temperature;
        if (sensorData.Y < -50)
        {
            xBeeClient.UploadStringAsync("door-open");
        }
        // The following display code is not used after calibration of gyro.
        display_T35.SimpleGraphics.DisplayText("X: " + sensorData.X + "  Y: " + sensorData.Y +
              "  Z: " + sensorData.Z, Resources.GetFont(Resources.FontResources.small),
              GT.Color.White, 10, i);
        SetDisplay();
    }

Another interesting bit of code is that which gets the Light Sensor measurement.   This is an analog sensor.  You can use actual voltage from the Light Sensor or a percentage figure.  We use voltage as shown in the following code block.

    void timer_Tick(GT.Timer timer)
    {
        double voltage = lightSensor.ReadLightSensorVoltage();

        if (voltage > 2.9)
        {
            display_T35.SimpleGraphics.DisplayText(voltage.ToString(),
                Resources.GetFont(Resources.FontResources.small), GT.Color.Red, 10, i);

            SetDisplay();
            xBeeClient.UploadStringAsync("light-on-xbee1");
        }
        else
        {
            xBeeClient.UploadStringAsync("low-light");
        }
    }

The display code, as in the previous code, is extraneous to the working application.  When a timer ticks, this code measures the light in the area under surveillance. If the light produces greater than 2.9 volts in the sensor, the XBeeClient object uploads the string light-on-xbee1.  If the light is insufficient for taking pictures, the XBeeClient uploads the low-light message.

The complete Program.cs file of the XBeeClient_1 is shown in the following code block.

using Microsoft.SPOT;

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

using Gadgeteer.Modules.GHIElectronics;
using Gadgeteer.Modules.Seeed;
using GTM.Community;

namespace XBeeClient_1
{
    public partial class Program
    {
        uint i;
        GT.Timer timer;

        void ProgramStarted()
        {
            xBeeClient.StringReceived +=
                new GTM.Community.XBee.StringReceivedEventHandler(xBeeClient_StringReceived);

            i = 10;
            button.ButtonPressed += new Button.ButtonEventHandler(button_ButtonPressed);

            gyro.MeasurementComplete +=
                 new Gyro.MeasurementCompleteEventHandler(gyro_MeasurementComplete);

            timer = new GT.Timer(3000);
            timer.Tick += new GT.Timer.TickEventHandler(timer_Tick);
            Debug.Print("Program Started");
        }

        void gyro_MeasurementComplete(Gyro sender, Gyro.SensorData sensorData)
        {
            double temp = sensorData.Temperature;
            if (sensorData.Y < -50)
            {
                xBeeClient.UploadStringAsync("door-open");
                //display_T35.SimpleGraphics.DisplayText("Y: " + sensorData.Y,
                //     Resources.GetFont(Resources.FontResources.small), GT.Color.Yellow, 10, i);
                //SetDisplay();

            }
            //display_T35.SimpleGraphics.DisplayText("Temperature: " + temp.ToString(),
            //    Resources.GetFont(Resources.FontResources.small), GT.Color.Green, 10, i);

            //SetDisplay();

            display_T35.SimpleGraphics.DisplayText("X: " + sensorData.X + "  Y: " +
                sensorData.Y + "  Z: " + sensorData.Z,
                Resources.GetFont(Resources.FontResources.small), GT.Color.White, 10, i);

            SetDisplay();
        }

    void timer_Tick(GT.Timer timer)
    {
        double voltage = lightSensor.ReadLightSensorVoltage();

        if (voltage > 2.9)
        {
            display_T35.SimpleGraphics.DisplayText(voltage.ToString(),
                Resources.GetFont(Resources.FontResources.small), GT.Color.Red, 10, i);

            SetDisplay();
            xBeeClient.UploadStringAsync("light-on-xbee1");
        }
        else
        {
            xBeeClient.UploadStringAsync("low-light");
        }
    }

        void button_ButtonPressed(Button sender, Button.ButtonState state)
        {
            if (!button.IsLedOn)
            {
                timer.Start();
                xBeeClient.UploadStringAsync("Timer started on XBee 1");

                gyro.Calibrate();
                gyro.StartContinuousMeasurements();

                button.TurnLEDOn();
            }
            else
            {
                timer.Stop();
                xBeeClient.UploadStringAsync("Timer stopped on XBee 1");

                gyro.StopContinuousMeasurements();
                button.TurnLEDOff();
            }

        }

        void xBeeClient_StringReceived(object sender, GTM.Community.XBee.StringReceivedEventArgs e)
        {
            display_T35.SimpleGraphics.DisplayText(e.Message,
                Resources.GetFont(Resources.FontResources.small), GT.Color.Green, 10, i);

            SetDisplay();
        }

        void SetDisplay()
        {
            i += 10;
            if (i > 230)
            {
                display_T35.SimpleGraphics.Clear();
                i = 10;
            }
        }
    }
}

XBee Radio number 2, Relay Circuit, and Camera

The second .NET Gadgeteer device in this network of XBee radios includes a relay that turns on the lights and a camera that takes pictures of the area under surveillance.  An SD Card module stores images taken by the camera.  Again there is a display module that is unnecessary for the working application but useful in setting it up.  The following illustration shows the designer surface with the modules used in this device (click to enlarge).

XBee Radio 2, Relay, and Camera

XBee Radio 2, Relay, and Camera

The core of the application is the following code that starts a timer when the door-open signal is received from the gyro in the previous device.

    void xBeeClient_StringReceived(object sender, GTM.Community.XBee.StringReceivedEventArgs e)
    {
        string message = e.Message;
        oledDisplay.SimpleGraphics.DisplayText(message,
            Resources.GetFont(Resources.FontResources.small), GT.Color.Green, 10, i);
        i += 10;
        if (i > 100)
        {
            oledDisplay.SimpleGraphics.Clear();
            i = 10;
        }

        if (message.IndexOf("door-open") != -1)
        {
            relays.Relay4 = true;
            xBeeClient.UploadStringAsync("lights-on-xbee2");
            timer.Start();
        }

        if (message.IndexOf("low-light") != -1)
        {
            timer.Stop();
            relays.Relay4 = false;
        }
    }

This code sets relay number 4 to true, closing the circuit that turns on the lights.  The timer starts and runs until a low-light message is received from the other device or until the user pushes a button to stop it.  You can also stop it by sending the low-light message from the InteractiveConsole, which is described under the final heading of this article.  Each time the timer ticks, the camera takes a picture and stores it on the SD Card, as shown in the following code.

    void camera_PictureCaptured(Camera sender, GT.Picture picture)
    {
        string pathFilename = @"\Bitmaps\AlertImage_" + pictureNumber.ToString() + ".bmp";

        try
        {
            if (sDCard.IsCardMounted)
            {
                GT.StorageDevice storage = sDCard.GetStorageDevice();
                storage.WriteFile(pathFilename, picture.PictureData);

                pictureNumber++;

            }

        }
        catch (System.Exception ex)
        {
            Debug.Print("Exception: " + ex.Message.ToString() +
                "  Inner Exception: " + ex.InnerException.ToString());
        }
    }

The complete Program.cs file is shown in the following code block.

using Microsoft.SPOT;

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

using Gadgeteer.Modules.Seeed;

namespace XBeeClient_2
{
    public partial class Program
    {
        uint i;
        uint pictureNumber;
        GT.Timer timer;

        void ProgramStarted()
        {
            xBeeClient.StringReceived +=
               new GTM.Community.XBee.StringReceivedEventHandler(xBeeClient_StringReceived);

            button.ButtonPressed += new Button.ButtonEventHandler(button_ButtonPressed);

            timer = new GT.Timer(3000);
            timer.Tick += new GT.Timer.TickEventHandler(timer_Tick);

            camera.PictureCaptured += new Camera.PictureCapturedEventHandler(camera_PictureCaptured);

            i = 10;
            oledDisplay.SimpleGraphics.DisplayText("Program Started",
                Resources.GetFont(Resources.FontResources.small), GT.Color.Orange, 10, i);
            i += 10;

            Debug.Print("Program Started");
        }

        void timer_Tick(GT.Timer timer)
        {
            camera.TakePicture();
        }

        void camera_PictureCaptured(Camera sender, GT.Picture picture)
        {
            string pathFilename = @"\Bitmaps\AlertImage_" + pictureNumber.ToString() + ".bmp";

            try
            {
                if (sDCard.IsCardMounted)
                {
                    GT.StorageDevice storage = sDCard.GetStorageDevice();
                    storage.WriteFile(pathFilename, picture.PictureData);

                    pictureNumber++;

                }

            }
            catch (System.Exception ex)
            {
                Debug.Print("Exception: " + ex.Message.ToString() +
                    "  Inner Exception: " + ex.InnerException.ToString());
            }
        }

        void button_ButtonPressed(Button sender, Button.ButtonState state)
        {
            xBeeClient.UploadStringAsync("turning off relays XBee 2");
            timer.Stop();
            relays.Relay3 = false;
            relays.Relay4 = false;
        }

        void xBeeClient_StringReceived(object sender, GTM.Community.XBee.StringReceivedEventArgs e)
        {
            string message = e.Message;
            oledDisplay.SimpleGraphics.DisplayText(message,
                Resources.GetFont(Resources.FontResources.small), GT.Color.Green, 10, i);
            i += 10;
            if (i > 100)
            {
                oledDisplay.SimpleGraphics.Clear();
                i = 10;
            }

            if (message.IndexOf("door-open") != -1)
            {
                relays.Relay4 = true;
                xBeeClient.UploadStringAsync("lights-on-xbee2");
                timer.Start();
            }

            if (message.IndexOf("low-light") != -1)
            {
                timer.Stop();
                relays.Relay4 = false;
            }
        }
    }
}

The XBeeClient InteractiveConsole

The previous devices use XBee radios configured as routers.  The final element of this scenario is the XBeeClient InteractiveConsole, which uses an XBee configured as the coordinator.  The InteractiveConsole is a PC console application.  It integrates testing and control mechanisms that are invaluable in setting up XBee networks and useful in many complete application scenarios.  If you don’t need everything in the XBeeClient InteractiveConsole, you can re-use any of the code as needed.  All the source code is available on the CodePlex site.

The InteractiveConsole is about 100 lines of code, as shown following.  Running the coordinator on an Explorer dongle, you can extenisviely test the network that includes the previous devices and send the door-open or low-light strings to start and stop the lights and camera. 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO.Ports;
using System.Threading;
using System.Diagnostics;
using Gadgeteer.Modules.Community;
using System.IO;
using System.Drawing;

namespace Gadgeteer.Modules.Community.XBee.InteractiveConsole
{
    class Program
    {
        private static XBeeClient xbeeClient;

        static void Main(string[] args)
        {
            bool initialized = false;

            while (!initialized)
            {
                Console.WriteLine("Enter the COM port your XBee dongle is on.");
                string userEnteredCOMPort = Console.ReadLine();
                int comPort;
                while (!Int32.TryParse(userEnteredCOMPort, out comPort))
                {
                    Console.WriteLine("Invalid format, enter a number.");
                    userEnteredCOMPort = Console.ReadLine();
                }

                try
                {
                    xbeeClient = new XBeeClient(comPort);
                    initialized = true;
                }
                catch (IOException ioException)
                {
                    Console.WriteLine("Invalid COM Port: " + ioException.ToString());
                }
            }

            xbeeClient.RecordBytesReceivedHistory = true;
            xbeeClient.TestCompleted += new TestCompletedEventHandler(client1_TestCompleted);
            xbeeClient.StringReceived += new StringReceivedEventHandler(xbeeClient_StringReceived);
            xbeeClient.PictureReceived += new PictureReceivedEventHandler(xbeeClient_PictureReceived);

            Console.WriteLine("Enter 'history' to print the raw bytes received history to debug window");
            Console.WriteLine("Enter 'test' to run the functional test Suite'");
            Console.WriteLine("Enter 'send {your message}' to broadcast send a message, for example, 'send hello world!'");

            string message = string.Empty;
            while ((message = Console.ReadLine().ToLower()) != "exit")
            {
                int indexOfFirstSpace = message.IndexOf(' ');
                string command = indexOfFirstSpace > 0 ? message.Substring(0, indexOfFirstSpace) : message;
                switch (command)
                {
                    case "history":
                        Console.WriteLine("Printing bytes received history to debug window");
                        xbeeClient.PrintBytesReceivedHistory();
                        break;
                    case "test":
                        Console.WriteLine("Running Functional Tests...");
                        PrintTestResults(xbeeClient.RunFunctionalTests(1));
                        break;
                    case "send":
                        if (message.Length <= indexOfFirstSpace)
                        {
                            Console.WriteLine("invalid argument, send command must have some text to send");
                            continue;
                        }
                        string payload = message.Substring(indexOfFirstSpace);
                        xbeeClient.UploadStringAsync(payload);
                        break;
                    default:
                        Console.WriteLine("I don't know how to " + command);
                        break;
                }

            }

        }

        static void xbeeClient_PictureReceived(object sender, PictureReceivedEventArgs e)
        {
            Console.WriteLine("Picture received, " + e.Data.Length + " total bytes.");
            using (MemoryStream ms = new MemoryStream(e.Data, false))
            {
                Bitmap bmp = new Bitmap(ms);
                string fileName = "receivedImage.bmp";
                bmp.Save(fileName);
                Console.WriteLine(fileName);
            }
        }

        static void xbeeClient_StringReceived(object sender, StringReceivedEventArgs e)
        {
            Console.WriteLine("received string");
            Console.WriteLine(e.Message);
        }

        static void client1_TestCompleted(object sender, TestCompletedEventArgs e)
        {
            Console.WriteLine("testCompleted: " + e.Test.ToString());
        }

        private static void PrintTestResults(BaseTest[] tests)
        {
            for (int i = 0; i < tests.Length; i++)
            {
                Console.WriteLine(tests[i].ToString());
            }
        }

    }
}

All messages from the other radios are received by the InteractiveConsole and displayed in the command window.

InteractiveConsole, Command Window Output

InteractiveConsole, Command Window Output

This brief overview shows the potential of the XBeeClient libraries and introduces a few of the features of this alpha implementation.  Try it out for yourself.  There is lots more to be done.

, , , , , , ,

  1. #1 by Scott Coleman on February 8, 2013 - 2:26 PM

    Is the XBee client only for 4.1? I get the following error:
    Error 1 Cannot deploy the base assembly ‘mscorlib’, or any of his satellite assemblies, to device – USB:Gadgeteer twice. Assembly ‘mscorlib’ on the device has version 4.2.0.0, while the program is trying to deploy version 4.1.2821.0

  2. #2 by Michael Dodaro on February 9, 2013 - 11:23 AM

    Looks like that’s it. The library hasn’t been updated. The source code is there, which might help you figure out how to use the serial interface. As I recall, the problem is with the readline and writeline functions. You can parse the raw byte stream if you have the chops.
    XBee radios were discussed quite a lot on the GHI Electronics forum, and there was a community development effort. I had to punt after these couple of experiments.

  3. #3 by Paul M on February 10, 2013 - 12:33 AM

    I’m working on updating the XBee Client to 4.2, should have this fixed today.

  4. #4 by Michael Dodaro on February 10, 2013 - 8:30 AM

    Very fine! Thanks, Paul.

  5. #5 by Paul M on February 10, 2013 - 4:24 PM

    I just updated to work with 4.2, and added a hydra sample project. I didn’t test with Spider or Cerberus yet though.

Leave a comment