-
1Step 1
Step 1: Building the sensor node
A functional test can be easily constructed with 2 XBees and a BJT Oscillator (more details on how to do this are here: http://meca-labs.blogspot.com/2014/05/fun-with-xbe...) The sensor node is the easiest node to assemble it is shown in Figure 2 below. We hacked into a solar powered light and added a moisture sensor to the bottom. Then we ran a wire up to the top and stored the XBee (series 2) and power system. The XBee has 4 ADC converters and we used one of these to poll the moisture sensor and send the information to the coordinator node. We just used XTCU to program the sensor node.
Figure 2: Functional example of 2 XBees sending ADC data from a BJT Oscillator to a coordinator node.
Figure 3: Sensor node deconstructed
Figure 4: Sensor node in action
-
2Step 2
Step 2: The coordinator node
The coordinator node is also the same solar power set but it consists of a GHI CerbuinoBee an XBee (series 2) and some sort of way to connect to the internet. We've demonstrated this project with a GHI GPRS module, shown in figure 5, and a USB-Serial Module. Using the Serial interface in C# is really simple and that's what we'll focus on here. Getting the GPRS working is tricky and is another project in itself.
Figure 5: Coordinator node with a SIM900 GPRS Module.
-
3Step 3
Step 3: 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.
Figure 6: References to be used in Visual Studio
Here is the full script. There is a lot going on here but for the most part it is obvious. 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.
using System; using System.Collections; using System.Threading; using System.Text; using System.Security.Cryptography ; using http://System.IO; //for series 2 using http://System.IO.Ports; using Toolbox.NETMF; 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 GHIElectronics.Gadgeteer; using Gadgeteer.Networking; using GT = Gadgeteer; using GTM = Gadgeteer.Modules; using Gadgeteer.Modules.OpenSource; using Gadgeteer.Modules.GHIElectronics; //using Gadgeteer.Modules.DLSys; //using Gadgeteer.Modules.Mekalogic; using GHI.OSHW.Hardware; using NETMF.OpenSource.XBee.Api; using NETMF.OpenSource.XBee.Api.Common; using NETMF.OpenSource.XBee.Api.Zigbee; using NETMF.OpenSource.XBee.Util; using http://Microsoft.SPOT.IO; //This program will write data to the SD Card for the webClient to access namespace VivaPlanetEmbedded { public class IOSampleDecodedEventArgs : EventArgs { public string DecodedPacket { get; set; } public IOSampleDecodedEventArgs(string myString) { this.DecodedPacket = myString; } } 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; public void ReportPacket(string packet) { EventHandler handler = IOSampleDecoded; if (handler != null) { handler(this, new IOSampleDecodedEventArgs(packet)); } } 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++) { //working this logic here because this is the closest point to the raw data. Ideally this would be fastest? //String format: Address HasData DataId SensorType Time Value 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; } 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"); } } public XBeeResponse[] GetPackets(int timeout) { throw new NotImplementedException(); } } 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 }; // 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 ouput } } catch (Exception e) //Catch any exceptions { DebugPrint(e.ToString()); //print the error to the debug ouput } //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()); } } private void timer_Tick(GT.Timer timer) { // Make sure the file exists // Load the file into a string 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"); } } private DeviceReport readPacketData() { //initialize variables string fileName = Path.Combine(_root, "file.txt"); Stream stream = null; DeviceReport device = new DeviceReport(); //this needs to be global try { //read it to file if (File.Exists(fileName)) { stream = File.OpenRead(fileName); stream.Position = stream.Length; using (var reader = new StreamReader(stream)) { string packet; while ((packet = reader.ReadLine()) != null) { device = parseVivaPacket(packet); DebugPrint("read: " + device.Address); } } stream.Dispose(); } else { DebugPrint("The filename does not exist"); } } catch (Exception e) { DebugPrint(e.ToString()); } return device; throw new NotImplementedException(); } private DeviceReport parseVivaPacket(string packet) { Sensors deviceSensors = new Sensors(); DeviceReport device = new DeviceReport(); //this needs to be global char[] charSeparator = new char[] {'^'}; string[] result; result = packet.Split(charSeparator, 1); device.Address = result[0]; //the actual address device.HasData = (result[1] == "true"); //if true then true else false device.DataId = result[2]; //not sure how to use this one...default it to 0 switch (result[3]) //sort the sensor type { case "0": //TEMP deviceSensors.Time = result[5]; deviceSensors.Value = result[6]; deviceSensors.SensorType = "TEMP"; device.DeviceSensors[0] = deviceSensors; break; case "1": //LIGHT deviceSensors.Time = result[5]; deviceSensors.Value = result[6]; deviceSensors.SensorType = "LIGHT"; device.DeviceSensors[1] = deviceSensors; break; case "2": //PH deviceSensors.Time = result[5]; deviceSensors.Value = result[6]; deviceSensors.SensorType = "PH"; device.DeviceSensors[2] = deviceSensors; break; case "3": //HUMI deviceSensors.Time = result[5]; deviceSensors.Value = result[6]; deviceSensors.SensorType = "HUMI"; device.DeviceSensors[3] = deviceSensors; break; default: //other unknown sensor deviceSensors.Time = result[5]; deviceSensors.Value = result[6]; deviceSensors.SensorType = "other"; device.DeviceSensors[4] = deviceSensors; break; } return device; } //This is here for debug usage void SerialLine_DataReceived(GT.Interfaces.Serial sender, http://System.IO.Ports.SerialData data) { //initialize variables Thread.Sleep(500); //let all the data get there. int NumberOfBytesToRead = usbSerial.SerialLine.BytesToRead; byte[] readInputBuffer = new byte[NumberOfBytesToRead]; //get sensor data usbSerial.SerialLine.Read(readInputBuffer, 0, NumberOfBytesToRead); // From byte array to string char[] c = Encoding.UTF8.GetChars(readInputBuffer, 0, NumberOfBytesToRead); string s = new string(c); DebugPrint("wrote: " + s); } private void DebugPrint(string str) { flash = !flash; Mainboard.SetDebugLED(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"); } } void NotifyServiceQueue (DeviceReport device, DataUploadResponse Dataresponse = null) { DebugPrint("Sending notification to service bus queue"); DebugPrint(device.Address); DebugPrint(device.HasData.ToString()); DebugPrint(createToken("https://vivaplanetbusservicedev.servicebus.windows...", "DevicePolicy", "zxuC2mrAN0o9lRlZmex/KhwSiF6uSr/Fxe/0lAS52G4=")); /* need an ethernet connection for this http://System.Net.WebRequest sbRequest = http://System.Net.WebRequest.Create (@"https://vivaplanetbusservicedev.servicebus.windows.net/hummingbirdqueue/messages"); var headers = sbRequest.Headers; sbRequest.Method = "POST"; using (var sbMessageStream = sbRequest.GetRequestStream ()) { //string body = JsonConvert.SerializeObject (device); //var bytes = Encoding.UTF8.GetBytes (body); //sbMessageStream.Write (bytes, 0, bytes.Length); // headers.Add ("Authorization", createToken ("https://vivaplanetbusservicedev.servicebus.windows.net/hummingbirdqueue/messages", "DevicePolicy", "zxuC2mrAN0o9lRlZmex/KhwSiF6uSr/Fxe/0lAS52G4=")); } try { DebugPrint("Sending notification for " + DeviceSerialNumer); using (var response = sbRequest.GetResponse ()) { DebugPrint("Sucessfully Sent"); } } catch (Exception e) { DebugPrint("Couldn't post to service bus -" + e); } * */ } private string createToken (string resourceUri, string keyName, string key) { TimeSpan sinceEpoch = DateTime.UtcNow - new DateTime (1970, 1, 1); var expiry = sinceEpoch.Seconds.ToString(); //EXPIRES in 1h string stringToSign = Tools.RawUrlEncode(resourceUri) + "\n" + expiry; System.Security.Cryptography.X509Certificates.X509Certificate hmac = new System.Security.Cryptography.X509Certificates.X509Certificate(Encoding.UTF8.GetBytes(key)); var signature = hmac.GetHashCode(); var sasToken = System.Globalization.CultureInfo.CurrentUICulture + "SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}" + Tools.RawUrlEncode(resourceUri) + Tools.RawUrlEncode(signature.ToString()); return sasToken; } public class DataUploadResponse { public string sasUrl { get; set; } public string DataId { get; set; } public string expiry { get; set; } } public class DeviceReport { public string Address { get; set; } public string DataUrl { get; set; } public bool HasData { get; set; } public string DataId { get; set; } public Sensors[] DeviceSensors { get; set; } //temp, light ext... } public class Sensors { public string SensorType { get; set; } public string Time { get; set; } public string Value { get; set; } } } }
Next we connect with the serial port and pull the packets from file.txt to the computer. This script opens up a serial connection to the cerbuinoBee and reads in all the sensor data that was written and parses it. It then sends this data to the VivaPlanet server. This is much easier to set up than the .netMF side there are better and worse ways to implement this handshake and I encourage you to find a better way than this.
using System; using System.Text; using http://System.Net; using System.Web; using http://System.IO; using Newtonsoft.Json; using System.Security.Cryptography; using System.Globalization; using http://System.IO.Ports; using System.Diagnostics; using System.Threading; namespace HummingBirdClient { class MainClass { public const string DeviceSerialNumer = "17564321"; //added for serial communication static SerialPort p = new SerialPort("COM3", 9600, Parity.None, 8, StopBits.One); static string sbuffer = string.Empty; static byte i = 0; public static void Main (string[] args) { bool res = getSerialData(); } static int planetCount = 0; static bool getSerialData() //fire off the event { string[] names = SerialPort.GetPortNames(); Console.WriteLine("Serial ports:"); foreach (string name in names) { Console.WriteLine(name); } Console.WriteLine("Using COM3"); planetCount++; if (p.IsOpen) { p.Open(); string data_ = "Viva Planet!" + planetCount.ToString() + "\r"; Console.WriteLine("Writing data: {0}", data_); p.Write(data_); p.DataReceived += new SerialDataReceivedEventHandler(p_DataReceived); ConsoleKeyInfo cki; Console.WriteLine("Getting ready to send the data"); DeviceReport device = parseVivaPacket(p.ReadLine()); NotifyServiceQueue(device, null); Console.WriteLine("I am done sending the data"); Console.WriteLine("type any key to send another data transfer"); Console.WriteLine("Press the Escape (Esc) key to quit: \n"); cki = Console.ReadKey(); if (cki.Key != ConsoleKey.Escape)//yes { Console.WriteLine("Sending more data"); getSerialData(); } else { p.Close(); } } else { string data_ = "Viva Planet!" + planetCount.ToString() + "\r"; Console.WriteLine("Writing data: {0}", data_); p.Write(data_); p.DataReceived += new SerialDataReceivedEventHandler(p_DataReceived); ConsoleKeyInfo cki; Console.WriteLine("Getting ready to send the data"); DeviceReport device = parseVivaPacket(p.ReadLine()); NotifyServiceQueue(device, null); Console.WriteLine("I am done sending the data"); Console.WriteLine("type any key to send another data transfer"); Console.WriteLine("Press the Escape (Esc) key to quit: \n"); cki = Console.ReadKey(); if (cki.Key != ConsoleKey.Escape)//yes { Console.WriteLine("Sending more data"); getSerialData(); } else { p.Close(); } } return true; } public static DeviceReport parseVivaPacket(string packet) { Sensors deviceSensors = new Sensors(); DeviceReport device = new DeviceReport(); //this needs to be global char[] charSeparator = new char[] { '^' }; string[] result; result = packet.Split(charSeparator, 1); device.Address = result[0]; //the actual address device.HasData = (result[1] == "true"); //if true then true else false device.DataId = result[2]; //not sure how to use this one...default it to 0 switch (result[3]) //sort the sensor type { case "0": //TEMP deviceSensors.Time = result[5]; deviceSensors.Value = result[6]; deviceSensors.SensorType = "TEMP"; device.DeviceSensors[0] = deviceSensors; break; case "1": //LIGHT deviceSensors.Time = result[5]; deviceSensors.Value = result[6]; deviceSensors.SensorType = "LIGHT"; device.DeviceSensors[1] = deviceSensors; break; case "2": //PH deviceSensors.Time = result[5]; deviceSensors.Value = result[6]; deviceSensors.SensorType = "PH"; device.DeviceSensors[2] = deviceSensors; break; case "3": //HUMI deviceSensors.Time = result[5]; deviceSensors.Value = result[6]; deviceSensors.SensorType = "HUMI"; device.DeviceSensors[3] = deviceSensors; break; default: //other unknown sensor deviceSensors.Time = result[5]; deviceSensors.Value = result[6]; deviceSensors.SensorType = "other"; device.DeviceSensors[4] = deviceSensors; break; } return device; } private static void p_DataReceived(object sender, SerialDataReceivedEventArgs e) { Thread.Sleep(500); sbuffer += (sender as SerialPort).ReadExisting(); Console.WriteLine(sbuffer); WriteOutputToTextFile(sbuffer); sbuffer = string.Empty; } static void WriteOutputToTextFile(string _data) { string FolderName = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);//set destination as your desktop using (StreamWriter SW = new StreamWriter(FolderName + "\\test.txt", true)) //true makes it append to the file instead of overwrite { SW.WriteLine(_data); SW.Close(); } } static void NotifyServiceQueue(DeviceReport device, DataUploadResponse Dataresponse = null) { // need an ethernet connection for this http://System.Net.WebRequest sbRequest = http://System.Net.WebRequest.Create (@"https://vivaplanetbusservicedev.servicebus.windows..."); var headers = sbRequest.Headers; sbRequest.Method = "POST"; using (var sbMessageStream = sbRequest.GetRequestStream ()) { //string body = JsonConvert.SerializeObject (device); //var bytes = Encoding.UTF8.GetBytes (body); //sbMessageStream.Write (bytes, 0, bytes.Length); // headers.Add ("Authorization", createToken ("https://vivaplanetbusservicedev.servicebus.windows.net/hummingbirdqueue/messages", "DevicePolicy", "zxuC2mrAN0o9lRlZmex/KhwSiF6uSr/Fxe/0lAS52G4=")); } try { Console.WriteLine("Sending notification for " + DeviceSerialNumer); using (var response = sbRequest.GetResponse ()) { Console.WriteLine("Sucessfully Sent"); } } catch (Exception e) { Console.WriteLine("Couldn't post to service bus -" + e); } } private string createToken(string resourceUri, string keyName, string key) { TimeSpan sinceEpoch = DateTime.UtcNow - new DateTime(1970, 1, 1); var expiry = sinceEpoch.Seconds.ToString(); //EXPIRES in 1h string stringToSign = HttpUtility.UrlEncode(resourceUri) + "\n" + expiry; System.Security.Cryptography.X509Certificates.X509Certificate hmac = new System.Security.Cryptography.X509Certificates.X509Certificate(Encoding.UTF8.GetBytes(key)); var signature = hmac.GetHashCode(); var sasToken = System.Globalization.CultureInfo.CurrentUICulture + "SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}" + HttpUtility.UrlEncode(resourceUri) + HttpUtility.UrlEncode(signature.ToString()); return sasToken; } } public class DataUploadResponse { public string sasUrl { get; set; } public string DataId { get; set; } public string expiry { get; set; } } public class DeviceReport { public string Address { get; set; } public string DataUrl { get; set; } public bool HasData { get; set; } public string DataId { get; set; } public Sensors[] DeviceSensors { get; set; } //temp, light ext... } public class Sensors { public string SensorType { get; set; } public string Time { get; set; } public string Value { get; set; } } }
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.