Monday, July 6, 2015

Hummingbird tutorial II: Visual C# .NetMf 4.2 and Zigbee series 2

Overview

1.   Connect to VivaPlanet

This next step is a bit more complex and requires some C# and visual studio with the Gagetter software from GHI Installed.  This will take some time...persist it will be worth it in the end...There are a lot of great tutorials out there on how to get this installed on your machine so I won't go into that here. I spent some time getting all the right references so I've taken a screen shot of all the references I'm using in this script.This is a two part post. This post covers the initial libaries, setup and some of the XBee packet handling.

Figure 1: References to be used in Visual Studio

Now we’ll start the teardown of the code.  I’m going to put my commentary in order of how it appears within the code. So to really understand what’s going on you’ll need to read through it at least twice. I'm taking a XBee packet and writing it's ADC values to a file. Then I'm reading this packet from the file to the serial output. I'm doing this every 5 seconds. There are several of debug points in this script and I've left them in. You can literally use this code and get some real time data analytics from your sensor system. You can even send pictures. But this is more complex and may be in a different project all together.

2.   Getting the right headers.

First we need to make sure we have all the system essential headers. This will be a multi-threaded application so we need System.Threading and we’ll have a serial port output so we’ll also need the System.Io libraries.


using System;
using System.Collections;
using System.Threading;
using System.Text;
using System.Security.Cryptography ;
using System.IO; //for series 2
using System.IO.Ports;
Next we’ll need to add some Microsoft libraries to support the hardware. The most important are Microsoft.SPOT and SPOT.hardware. The rest don’t need to be there yet. There will be functionality in the future for these libraries so I chose to leave them.

using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using Microsoft.SPOT.Presentation;
using Microsoft.SPOT.Presentation.Controls;
using Microsoft.SPOT.Presentation.Media;
using Microsoft.SPOT.Presentation.Shapes;
using Microsoft.SPOT.Touch;
using Microsoft.SPOT.IO;
Lastly the API’s need to be added. This includes GHI’s libraries, some xbee libraries and the netmf toolbox library. I spent a lot of time finding these libraries so please use them!

using GHIElectronics.Gadgeteer;
using Gadgeteer.Networking;
using GT = Gadgeteer;
using GTM = Gadgeteer.Modules;
using Gadgeteer.Modules.OpenSource;
using Gadgeteer.Modules.GHIElectronics;
using GHI.OSHW.Hardware;
using NETMF.OpenSource.XBee.Api;
using NETMF.OpenSource.XBee.Api.Common;
using Toolbox.NETMF;
using NETMF.OpenSource.XBee.Api.Zigbee;
using NETMF.OpenSource.XBee.Util;
The main program namespace I call VivaPlanetEmbedded. This namespace handles all of the datarouting on the embedded system. Right now it has Xbee functionality and serial out functionality. In the future we’ll add some Ethernet and camera functionality as well.
namespace VivaPlanetEmbedded
{
The first class is a manager class of sorts. It takes a packet listener and makes it publicly available to other classes. This was really a key learning for me to add this class. It works very well and makes the code stable.
    public class IOSampleDecodedEventArgs : EventArgs
    {
        public string DecodedPacket { get; set; }

        public IOSampleDecodedEventArgs(string myString)
        {
            this.DecodedPacket = myString;
        }
    }
The IOSampleListener class handles the incoming Xbee packets. I am using two XBees in my system but I’m sure it can take more.  Let’s walk through this class.
public class IoSampleListener : IPacketListener
    {
        public bool Finished { get { return false; } }
        public XBeeResponse[] Packets;
        UsbSerial usbSerial = new UsbSerial(1);
        static bool flash = true;
        public event EventHandler IOSampleDecoded;
Here we initialize the variables for the class. I have a ‘finished’ flag to make sure that I know when the packets are done.  The UsbSerial variable is for debug. It doesn’t have to be here but it sure helps. We need an public event handler and a packet variable to put the packets somewhere. Keep in mind most of these datatypes come from the XBee API at Github. There is some documentation there but some of this I had to learn myself and ask others.
        public void ReportPacket(string packet)
        {
            EventHandler handler = IOSampleDecoded;
            if (handler != null)
            {
                handler(this, new IOSampleDecodedEventArgs(packet));               
            }
        }
This method passes the received string to the even handler to do something with it. The string is formatted as an xbee packet which makes it really nice. I don’t have to do any fancy parsing.

        public void ProcessPacket(XBeeResponse packet)
        {
            if (packet is IoSampleResponse)
            {
                IoSampleResponse myPackets = ProcessIoSample(packet as IoSampleResponse);

            }
        }

        public IoSampleResponse ProcessIoSample(IoSampleResponse packet)
        {
            DateTime time = new DateTime(2007, 06, 21, 00, 00, 00);
            Utility.SetLocalTime(time);
            try
            {
                for (int ii = 0; ii < 5; ii++)
                {  
               //String format: Address HasData DataId SensorType TimeValue
                    string s = "^" + packet.SourceAddress.Address.ToString() + "^" + "true" + "^" + "0" + "^" + ii.ToString() + "^" + time.TimeOfDay.ToString() + "^" + packet.Analog[ii].ToString() + "\r\n";

                    ReportPacket(s);           
                }
            }
            catch (Exception e) //Catch any exceptions
            {
                Debug.Print(e.ToString()); //print the error to the debug ouput
            }
            return packet;
        }
This method is the meat-and-potatoes of the Xbee portion. We format the string and the ‘report’ the packet to the ReportPacket Class. This enables other classes within this namespace to access the Xbee data.

        private void DebugPrint(string str)
        {
            flash = !flash;
            Debug.Print(str + " \r\n");
            try
            {
                usbSerial.SerialLine.Write(str + " \r\n");
            }
            catch (Exception e)
            {
                Debug.Print("USB Not enabled" + " \r\n");
                Debug.Print(e.ToString() + " \r\n");
            }
        }
        This method is just a debug script. It is very helpful.
        public XBeeResponse[] GetPackets(int timeout)
        {
            throw new NotImplementedException();
        }
    }
Below we can see the main portion of the .NetMF class. First it is important to note there is a lot of autogenerated code in .Netmf. So this class is only a partial class. The other portion of it is hidden.
    public partial class Program
    {
        public const string DeviceSerialNumer = "17564321";
        private string _root; //volume
        bool flash = true;
        bool newPackets = false;

        //set up the Xbee                    
        const string serialPortName = "COM1";
        const Cpu.Pin resetPin = GHI.Hardware.FEZCerb.Pin.PB0;
        const Cpu.Pin sleepPin = GHI.Hardware.FEZCerb.Pin.PC13;
        XBee xBee = new XBee(serialPortName, resetPin, sleepPin) { DebugPrintEnabled = true };       
There are some more variables declared here up front. The most important part here is setting up the Xbee. This code affects the onboard Xbee on the FEZCerbuino Bee. It enables the reset and sleep functions as well as the serial functions.  I have a lot of comments in this next portion that describe what the code is doing so I’m going to rely on them until the next method.

        // This method is run when the mainboard is powered up or reset.  
        void ProgramStarted()
        {
            Debug.Print("Viva Planet!");

            try //both usbSerial.Configure and usbSerial.Open()have different exceptions but it is rare that they will triggered in this specific code
            {
                //mount the SD card
                GHI.OSHW.Hardware.StorageDev.MountSD();

                //configure the usbSerial port
                usbSerial.Configure(9600, GT.Interfaces.Serial.SerialParity.None, GT.Interfaces.Serial.SerialStopBits.One, 8);

                //if the port is not open then open it
                if (!usbSerial.SerialLine.IsOpen)
                {
                    usbSerial.SerialLine.Open();
                    DebugPrint("opened the serial port");
                }

                if (VolumeInfo.GetVolumes()[0].IsFormatted)//check to make sure the SD card is formatted
                {
                    _root = VolumeInfo.GetVolumes()[0].RootDirectory;
                    DebugPrint("SD Card is formatted: " + _root);
                }
                else
                {
                    DebugPrint("SD Card is not formatted"); //print the error to the debug output
                }             
            }
            catch (Exception e) //Catch any exceptions
            {
                DebugPrint(e.ToString()); //print the error to the debug output
            }
           
            //Gets the command data from the intertubes. This is interrupt driven.            usbSerial.SerialLine.DataReceived += new GT.Interfaces.Serial.DataReceivedEventHandler(SerialLine_DataReceived);

            //Implement the interface member
            IoSampleListener sample = new IoSampleListener();  //created 1x only
            sample.IOSampleDecoded += sample_IOSampleDecoded;  //every time the event is raised (report packet)

            //add the listener
            xBee.Api.AddPacketListener(sample);

            // initialize the loop timer and send a file to the server
            GT.Timer timer = new GT.Timer(5000);

            //set the interrupt
            timer.Tick += new GT.Timer.TickEventHandler(timer_Tick);
            timer.Start(); 
        }

        //this method is an event handler that decodes the packet within this class
        private void sample_IOSampleDecoded(object sender, EventArgs e)
        {
            IOSampleDecodedEventArgs eventargs = (IOSampleDecodedEventArgs)e;
            string packet = eventargs.DecodedPacket;
            writeToFile(packet); //write the packet to file
            newPackets = true;
        }

        private void writeToFile(string packet)
        {           
            //initialize variables
            string fileName = Path.Combine(_root, "file.txt");
            Stream stream;
            try
            {
                //write it to file
                if (File.Exists(fileName))
                {
                    stream = File.OpenWrite(fileName);
                    stream.Position = stream.Length;
                }
                else
                {
                    stream = File.Create(fileName);
                }
                using (var writer = new StreamWriter(stream))
                {
                    writer.WriteLine(packet);
                    DebugPrint("wrote:  " + packet);
                }
                stream.Dispose();
            }
            catch (Exception e)
            {
                DebugPrint(e.ToString());
            }
        }

This is the timer event handler. It fires at the designated time and allows the code to run in an endless loop on the embedded device. It is better than using a while loop for a lot of different reasons.

        private void timer_Tick(GT.Timer timer)
        {
               DebugPrint("tick");
               if (newPackets)
               {
                   //populate the device struct with the measurements
                   DeviceReport device = readPacketData();
                   //Send the data
                   NotifyServiceQueue(device, null);
                   newPackets = false;
               }
               else
               {
                   DebugPrint("no new packets");
               }
        }
 Stay tuned for the next update. We'll finish going over the code and show some working output.

No comments:

Post a Comment