Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 |
This project consists of any number of external sensors (in this case Volatile Organic Compounds (VOC) and temperature and humidity). These communicate over 802.15.4 (Zigbee) using an XBee module. The information is aggregated at a gateway device that can send alerts to a phone number over SMS.
Gateway - Program.cs
C#This is the entry point for the Gateway program. It creates the Xbee and GSM radio objects and starts the Xbee monitoring
using System;
using System.Net;
using System.IO.Ports;
using System.Net.Sockets;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware;
using SecretLabs.NETMF.Hardware.Netduino;
namespace AirQualityOverXBeeGateway
{
public class Program
{
private static readonly byte[] c_Self_Address = new byte[] { (byte)'0', (byte)'1' }; //the oposite of the sensor device
private static readonly byte[] c_Target_Address = new byte[] { (byte)'0', (byte)'2' };
static SeeedStudioGPRS gsmRadio;
static XBee xbeeRadio;
public static void Main()
{
ReadXBeeInput input = null;
xbeeRadio = new XBee(Serial.COM2, 9600, c_Self_Address, c_Target_Address, false, Cpu.Pin.GPIO_NONE);
gsmRadio = new SeeedStudioGPRS(Serial.COM1, 19200, System.IO.Ports.Parity.None, 8,
System.IO.Ports.StopBits.One);
gsmRadio.SendSMS("+14252137640", " Air Quality Monitoring started");
input = new ReadXBeeInput(xbeeRadio, gsmRadio);
input.Start();
Thread.Sleep(Timeout.Infinite);
}
}
}
XBee.cs
C#This is the Xbee class that is used by both the gateway and the sensor to set up the network and communicate over the Xbee radio
using System;
using System.Runtime.CompilerServices;
using System.IO.Ports;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware;
using SecretLabs.NETMF.Hardware.Netduino;
namespace AirQualityOverXBeeGateway
{
public delegate void DataReadingCallback(XBee radio);
public class XBee : IDisposable
{
public class RadioException : Exception
{
}
//--//
static readonly int c_powerOnTime = 100; // ms
//--//
static readonly byte[] c_InitMessage = new byte[] { (byte)'+', (byte)'+', (byte)'+' };
static readonly byte[] c_BaudRate = new byte[] { (byte)'A', (byte)'T', (byte)'B', (byte)'D', (byte)13 };
static readonly byte[] c_GetIDMessage = new byte[] { (byte)'A', (byte)'T', (byte)'I', (byte)'D', (byte)13 };
static readonly byte[] c_EndMessage = new byte[] { (byte)'A', (byte)'T', (byte)'C', (byte)'N', (byte)13 };
//--//
bool _verbose = true;
//--//
SerialPort _serial;
OutputPort _power;
DataReadingCallback _subscribers;
//--//
public XBee(string port, int baud, byte[] self, byte[] target, bool coordinator, Cpu.Pin powerPin)
{
int bytesRead;
// 4.1 int bytesSent;
byte[] readBuffer = new byte[32];
if (self.Length != 2 || target.Length != 2)
{
throw new ArgumentException();
}
_serial = new SerialPort(port, baud);
if (powerPin != Cpu.Pin.GPIO_NONE) _power = new OutputPort(powerPin, false);
Powered = true;
_serial.Open();
//Send out an 'END' message to terminate any previous command sessions left open
// 4.1 bytesSent = _serial.Write(c_EndMessage, 0, c_EndMessage.Length);
_serial.Write(c_EndMessage, 0, c_EndMessage.Length);
Thread.Sleep(1000);
bytesRead = _serial.Read(readBuffer, 0, _serial.BytesToRead);
Thread.Sleep(1000);
//4.1 bytesSent = _serial.Write(c_InitMessage, 0, c_InitMessage.Length);
_serial.Write(c_InitMessage, 0, c_InitMessage.Length);
Thread.Sleep(3000);
bytesRead = _serial.Read(readBuffer, 0, _serial.BytesToRead);
//checking that the init message actually went through
byte[] initMessage = new byte[bytesRead];
Array.Copy(readBuffer, initMessage, bytesRead);
string message = new string(System.Text.UTF8Encoding.UTF8.GetChars(initMessage));
if (message.IndexOf(new string(new char[] { (char)79, (char)75, (char)13 }), 0, message.Length) == -1)
{
throw new RadioException();
}
//setup the XBee
XBeeRestore();
XBeeConfigure(self, target, coordinator);
while (!XBeeNodeDetect())
{
if (_verbose)
Debug.Print("No Node found");
Thread.Sleep(1000);
}
Debug.Print("Node Detected");
Thread.Sleep(1000);
//4.1 bytesSent = _serial.Write(c_EndMessage, 0, c_EndMessage.Length);
_serial.Write(c_EndMessage, 0, c_EndMessage.Length);
Thread.Sleep(1000);
Debug.Print("Return from End Message");
bytesRead = _serial.Read(readBuffer, 0, _serial.BytesToRead);
for (int i = 0; i < bytesRead; i++)
{
Debug.Print(readBuffer[i].ToString());
}
Powered = false;
_serial.DataReceived += new SerialDataReceivedEventHandler(DataAvailable);
}
public void Dispose()
{
_serial.Dispose();
if (_power != null)
_power.Dispose();
}
public bool Powered
{
get
{
return _power != null && _power.Read();
}
set
{
if (_power != null)
{
lock (this)
{
// we power on the radio all the times a user requests it...
if (value == true)
{
// do not power up twice
if (_power.Read() == false)
{
_power.Write(true);
Thread.Sleep(c_powerOnTime); // give it a little time to wake up
}
}
//.. but we power it off only if no handler is registered for notifications
else
{
if (_subscribers == null)
{
_power.Write(false);
}
}
}
}
}
}
public event DataReadingCallback NotifyReading
{
[MethodImplAttribute(MethodImplOptions.Synchronized)]
add
{
DataReadingCallback callbacksOld = _subscribers;
DataReadingCallback callbacksNew = (DataReadingCallback)Delegate.Combine(callbacksOld, value);
try
{
_subscribers = callbacksNew;
if (callbacksNew != null)
{
if (callbacksOld == null)
{
Powered = true;
}
if (callbacksNew.Equals(value) == false)
{
callbacksNew = new DataReadingCallback(MultiCastCase);
}
}
}
catch
{
_subscribers = callbacksOld;
if (callbacksOld == null)
{
Powered = false;
}
throw;
}
}
[MethodImplAttribute(MethodImplOptions.Synchronized)]
remove
{
DataReadingCallback callbacksOld = _subscribers;
DataReadingCallback callbacksNew = (DataReadingCallback)Delegate.Remove(callbacksOld, value);
try
{
_subscribers = (DataReadingCallback)callbacksNew;
if (callbacksNew == null && callbacksOld != null)
{
Powered = false;
}
}
catch
{
_subscribers = callbacksOld;
throw;
}
}
}
public void Send(byte[] buffer)
{
try
{
Powered = true;
_serial.Write(buffer, 0, buffer.Length);
}
finally
{
lock (this)
{
if (_subscribers == null)
{
Powered = false;
}
}
}
}
public byte[] Receive()
{
const int maxBufferLength = 32;
byte[] buffer = new byte[maxBufferLength];
int read = 0;
while (true)
{
int available = _serial.BytesToRead;
if (read >= maxBufferLength) break;
if (available <= 0)
{
{
int sleep = 10000; while (sleep-- > 0) ;
}
available = _serial.BytesToRead;
if (available <= 0) break;
}
int count = _serial.Read(buffer, read, available);
read += count;
}
if (read > 0)
{
byte[] result = new byte[read];
Array.Copy(buffer, result, read);
return result;
}
return null;
}
public int BytesToRead
{
get
{
return _serial.BytesToRead;
}
}
//--//
private void DataAvailable(object sender, SerialDataReceivedEventArgs e)
{
if (_subscribers != null)
{
_subscribers(this);
}
}
private bool IsOK(byte[] readBuffer)
{
if (readBuffer[0] == 79 && readBuffer[1] == 75 && readBuffer[2] == 13)
return (true);
else
return (false);
}
private void XBeeRestore()
{
XBeeRestore(false);
}
private void XBeeRestore(bool throwOnError)
{
int bytesRead;
//4.1 int bytesSent;
byte[] readBuffer = new byte[1000];
byte[] RestoreMessage = new byte[] { (byte)'A', (byte)'T', (byte)'R', (byte)'E', (byte)13 };
Thread.Sleep(1000);
// 4.1 bytesSent = _serial.Write(RestoreMessage, 0, RestoreMessage.Length);
_serial.Write(RestoreMessage, 0, RestoreMessage.Length);
Thread.Sleep(1000);
bytesRead = _serial.Read(readBuffer, 0, _serial.BytesToRead);
if (throwOnError)
{
if (bytesRead != 3 || !IsOK(readBuffer))
throw new RadioException();
}
}
private bool XBeeNodeDetect()
{
int bytesRead;
// 4.1 int bytesSent;
byte[] readBuffer = new byte[1000];
byte[] NodeDetectMessage = new byte[] { (byte)'A', (byte)'T', (byte)'N', (byte)'D', (byte)13 };
byte[] GetNetworkSettingsMessage = new byte[] { (byte)'A', (byte)'T', (byte)'A', (byte)'I', (byte)13 };
Thread.Sleep(1000);
// 4.1 bytesSent = _serial.Write(NodeDetectMessage, 0, NodeDetectMessage.Length);
_serial.Write(NodeDetectMessage, 0, NodeDetectMessage.Length);
Thread.Sleep(1000);
bytesRead = _serial.Read(readBuffer, 0, _serial.BytesToRead);
if (_verbose)
{
for (int i = 0; i < bytesRead; i++)
{
Debug.Print(readBuffer[i].ToString());
}
}
if (bytesRead < 2)
{
if (_verbose)
{
// 4.1 bytesSent = _serial.Write(GetNetworkSettingsMessage, 0, GetNetworkSettingsMessage.Length);
_serial.Write(GetNetworkSettingsMessage, 0, GetNetworkSettingsMessage.Length);
Debug.Print("NetworkSettings Retrieved ");
Thread.Sleep(1000);
Debug.Print("NetworkSettings Retrieved ");
bytesRead = _serial.Read(readBuffer, 0, _serial.BytesToRead);
for (int i = 0; i < bytesRead; i++)
{
Debug.Print(readBuffer[i].ToString());
}
}
return (false);
}
else
return (true);
}
private void XBeeConfigure(byte[] self, byte[] target, bool coordinator)
{
XBeeConfigure(self, target, coordinator, false);
}
private void XBeeConfigure(byte[] self, byte[] target, bool coordinator, bool throwOnError)
{
byte[] SetCoordinatorMessage;
byte[] SetNodeMessage = new byte[] { (byte)'A', (byte)'T', (byte)'M', (byte)'Y', self[0], self[1], (byte)13 };
byte[] SetTargetLMessage = new byte[] { (byte)'A', (byte)'T', (byte)'D', (byte)'L', target[0], target[1], (byte)13 };
byte[] SetIDMessage = new byte[] { (byte)'A', (byte)'T', (byte)'I', (byte)'D', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)13 };
byte[] GetNodeMessage = new byte[] { (byte)'A', (byte)'T', (byte)'M', (byte)'Y', (byte)13 };
byte[] SetTargetHMessage = new byte[] { (byte)'A', (byte)'T', (byte)'D', (byte)'H', (byte)'0', (byte)13 };
byte[] GetTargetHMessage = new byte[] { (byte)'A', (byte)'T', (byte)'D', (byte)'H', (byte)13 };
byte[] GetTargetLMessage = new byte[] { (byte)'A', (byte)'T', (byte)'D', (byte)'L', (byte)13 };
if (coordinator)
{
SetCoordinatorMessage = new byte[] { (byte)'A', (byte)'T', (byte)'C', (byte)'E', (byte)1, (byte)13 };
}
else
{
SetCoordinatorMessage = new byte[] { (byte)'A', (byte)'T', (byte)'C', (byte)'E', (byte)0, (byte)13 };
}
//byte[] WriteMessage = new byte[] { (byte)'A', (byte)'T', (byte)'W', (byte)'R', (byte)13 }; //not used - radio set up at the start of every session
int bytesRead;
// 4.1 int bytesSent;
byte[] readBuffer = new byte[1000];
// set this devices as the coordinator
// 4.1 bytesSent = _serial.Write(SetCoordinatorMessage, 0, SetCoordinatorMessage.Length);
_serial.Write(SetCoordinatorMessage, 0, SetCoordinatorMessage.Length);
Thread.Sleep(1000);
bytesRead = _serial.Read(readBuffer, 0, _serial.BytesToRead); // Clear the response
Thread.Sleep(1000);
//set the network ID to "1234"
// 4.1 bytesSent = _serial.Write(SetIDMessage, 0, SetIDMessage.Length);
_serial.Write(SetIDMessage, 0, SetIDMessage.Length);
Thread.Sleep(1000);
bytesRead = _serial.Read(readBuffer, 0, _serial.BytesToRead);
if (throwOnError)
{
if (bytesRead != 3 || !IsOK(readBuffer))
throw new RadioException();
}
Thread.Sleep(1000);
if (_verbose)
{
// get the ID back to make sure that it was set
// 4.1 bytesSent = _serial.Write(c_GetIDMessage, 0, c_GetIDMessage.Length);
_serial.Write(c_GetIDMessage, 0, c_GetIDMessage.Length);
Debug.Print("ID Retreived : ");
Thread.Sleep(1000);
bytesRead = _serial.Read(readBuffer, 0, _serial.BytesToRead);
for (int i = 0; i < bytesRead; i++)
{
Debug.Print(readBuffer[i].ToString());
}
}
//set the Node ID to "10"
// 4.1 bytesSent = _serial.Write(SetNodeMessage, 0, SetNodeMessage.Length);
_serial.Write(SetNodeMessage, 0, SetNodeMessage.Length);
Thread.Sleep(1000);
bytesRead = _serial.Read(readBuffer, 0, _serial.BytesToRead);
if (throwOnError)
{
if (bytesRead != 3 || !IsOK(readBuffer))
throw new RadioException();
}
if (_verbose)
{
// 4.1 bytesSent = _serial.Write(GetNodeMessage, 0, GetNodeMessage.Length);
_serial.Write(GetNodeMessage, 0, GetNodeMessage.Length);
Debug.Print("Node Retrieved : ");
Thread.Sleep(1000);
bytesRead = _serial.Read(readBuffer, 0, _serial.BytesToRead);
for (int i = 0; i < bytesRead; i++)
{
Debug.Print(readBuffer[i].ToString());
}
}
// set the target high address to '0'
// 4.1 bytesSent = _serial.Write(SetTargetHMessage, 0, SetTargetHMessage.Length);
_serial.Write(SetTargetHMessage, 0, SetTargetHMessage.Length);
Thread.Sleep(1000);
bytesRead = _serial.Read(readBuffer, 0, _serial.BytesToRead);
if (throwOnError)
{
if (bytesRead != 3 || !IsOK(readBuffer))
throw new RadioException();
}
if (_verbose)
{
//get the target node that was set
// 4.1bytesSent = _serial.Write(GetTargetHMessage, 0, GetTargetHMessage.Length);
_serial.Write(GetTargetHMessage, 0, GetTargetHMessage.Length);
Debug.Print("Target H Retrieved ");
Thread.Sleep(1000);
bytesRead = _serial.Read(readBuffer, 0, _serial.BytesToRead);
for (int i = 0; i < bytesRead; i++)
{
Debug.Print(readBuffer[i].ToString());
}
}
//set target low address to '01'
// 4.1 bytesSent = _serial.Write(SetTargetLMessage, 0, SetTargetLMessage.Length);
_serial.Write(SetTargetLMessage, 0, SetTargetLMessage.Length);
Thread.Sleep(1000);
bytesRead = _serial.Read(readBuffer, 0, _serial.BytesToRead);
if (throwOnError)
{
if (bytesRead != 3 || !IsOK(readBuffer))
throw new RadioException();
}
if (_verbose)
{
// 4.1 bytesSent = _serial.Write(GetTargetLMessage, 0, GetTargetLMessage.Length);
_serial.Write(GetTargetLMessage, 0, GetTargetLMessage.Length);
Debug.Print("Target L Retrieved ");
Thread.Sleep(1000);
bytesRead = _serial.Read(readBuffer, 0, _serial.BytesToRead);
for (int i = 0; i < bytesRead; i++)
{
Debug.Print(readBuffer[i].ToString());
}
}
}
private void MultiCastCase(XBee radio)
{
DataReadingCallback callbacks = _subscribers;
if (callbacks != null)
{
callbacks(this);
}
}
}
}
Gateway - ReadXBeeInput.cs
C#This class includes and ISR that monitors for any traffic from the remote sensors over 802.15.4 and triggers an SMS message if the alert criteria are met
using System;
using System.Threading;
using Microsoft.SPOT;
using System.IO.Ports;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware;
using SecretLabs.NETMF.Hardware.Netduino;
using System.Collections;
namespace AirQualityOverXBeeGateway
{
public class ReadXBeeInput : IDisposable
{
private XBee _xbeeRadio;
SeeedStudioGPRS _gsmRadio;
#region //Constructors and Destructors
public ReadXBeeInput(XBee xbeeRadio, SeeedStudioGPRS gsmRadio)
{
_xbeeRadio = xbeeRadio;
_gsmRadio = gsmRadio;
}
public void Dispose()
{
try
{
Stop();
}
finally
{
_xbeeRadio.Dispose();
}
}
#endregion //constructors and destructors
#region //public Methods
public void Start()
{
_xbeeRadio.NotifyReading += new DataReadingCallback(OnRadioActivity);
}
public void Stop()
{
_xbeeRadio.NotifyReading -= new DataReadingCallback(OnRadioActivity);
}
#endregion //public Methods
#region Private Methods
private void OnRadioActivity(XBee radio)
{
byte[] buffer = radio.Receive();
if (buffer != null)
{
ProcessRadioActivity(buffer);
}
}
private void ProcessRadioActivity(byte[] airQuality)
{
if (airQuality == null)
return;
Debug.Print("Sending SMS");
_gsmRadio.SendSMS("+14252137640", " Air Quality Alert!!");
Thread.Sleep(2000);
}
#endregion Private Methods
}
}
SeeedStudioGPRS.cs
C#This is the code that interfaces with the GPRS radio to form and send the SMS message.
using System;
using System.Threading;
using System.Text;
using System.IO.Ports;
using Microsoft.SPOT;
using SecretLabs.NETMF.Hardware.Netduino;
namespace AirQualityOverXBeeGateway
{
public class SeeedStudioGPRS
{
static SerialPort serialPort;
static string awaitingResponseString = "";
static StringBuilder output = new StringBuilder();
//changed parity from Odd to None
public SeeedStudioGPRS(string portName, int baudRate = 19200, Parity parity = Parity.None, int dataBits = 8, StopBits stopBits = StopBits.One)
{
serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits);
serialPort.ReadTimeout = -1;
serialPort.WriteTimeout = 10;
serialPort.Handshake = Handshake.XOnXOff;
serialPort.Open();
serialPort.DataReceived += new SerialDataReceivedEventHandler(serialPort_DataReceived);
}
private void GPRSWrite(string line)
{
System.Text.UTF8Encoding encoder = new System.Text.UTF8Encoding();
byte[] bytesToSend = encoder.GetBytes(line);
serialPort.Write(bytesToSend, 0, bytesToSend.Length);
serialPort.Flush();
}
public void GPRSWriteLine(string line)
{
GPRSWrite(line + "\r");
}
public void SendSMS(string msisdn, string message)
{
//PrintLine("");
GPRSWriteLine("AT+CMGF=1");
Thread.Sleep(500);
GPRSWriteLine("AT+CMGS=\"" + msisdn + "\"");
Thread.Sleep(500);
GPRSWriteLine(message);
Thread.Sleep(500);
//PrintLine("\0x1A");
//Thread.Sleep(500);
PrintEnd();
Thread.Sleep(500);
//Debug.Print("SMS Sent!");
}
static byte[] readbuff(byte[] inputBytes)
{
for (int i = 0; i < inputBytes.Length; i++)
{
if (inputBytes[i] == 230)
{
inputBytes[i] = 32;
}
}
return inputBytes;
}
private void PrintEnd()
{
byte[] bytesToSend = new byte[1];
bytesToSend[0] = 26;
serialPort.Write(bytesToSend, 0, 1);
Thread.Sleep(200);
}
static void serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
// Check if Chars are received
if (e.EventType == SerialData.Chars)
{
// Create new buffer
byte[] ReadBuffer = new byte[serialPort.BytesToRead];
// Read bytes from buffer
serialPort.Read(ReadBuffer, 0, ReadBuffer.Length);
if (ReadBuffer.Length > 0 && (ReadBuffer[ReadBuffer.Length - 1] == 10 || ReadBuffer[ReadBuffer.Length - 1] == 0))
{
// New line or terminated string.
//output.Append(System.Text.Encoding.UTF8.GetChars(ReadBuffer));
output.Append(GetUTF8StringFrombytes(ReadBuffer));
if (!awaitingResponseString.Equals("") && output.ToString().IndexOf(awaitingResponseString) > -1)
{
Debug.Print("Response Matched : " + output.ToString());
awaitingResponseString = "";
}
else
{
if (output.ToString().IndexOf("CONNECT OK") > -1)
{
Debug.Print("CONNECTED !!!!!!!!!!!");
}
Debug.Print("Recieved : " + output.ToString());
}
output.Clear();
}
else
{
try
{
//output.Append(UTF8Encoding.UTF8.GetChars(readbuff(ReadBuffer)));
//output.Append(System.Text.Encoding.UTF8.GetChars(ReadBuffer));
output.Append(GetUTF8StringFrombytes(ReadBuffer));
Debug.Print("Recieved : " + output.ToString());
}
catch (Exception ecx)
{
Debug.Print("Cannot parse : " + ecx.StackTrace);
}
}
}
}
private static string GetUTF8StringFrombytes(byte[] byteVal)
{
byte[] btOne = new byte[1];
StringBuilder sb = new StringBuilder("");
char uniChar;
for (int i = 0; i < byteVal.Length; i++)
{
btOne[0] = byteVal[i];
if (btOne[0] > 127)
{
uniChar = Convert.ToChar(btOne[0]);
sb.Append(uniChar);
}
else
sb.Append(new string(Encoding.UTF8.GetChars(btOne)));
}
return sb.ToString();
}
}
}
Sensor - Program.cs
C#This is the Main for the sensor side application. It starts up the sensor object and goes to sleep
using System;
using System.IO;
using System.IO.Ports;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware;
using SecretLabs.NETMF.Hardware.Netduino;
namespace AirQualityOverXbeeSensor
{
public class Program
{
//private static byte[] c_Read_Sensors_Message = new byte[] { (byte)'T', (byte)'E', (byte)'M', (byte)'P', (byte)13 }; not used
private static readonly byte[] c_Self_Address = new byte[] { (byte)'0', (byte)'2' };
private static readonly byte[] c_Target_Address = new byte[] { (byte)'0', (byte)'1' };
//--//
static AutoResetEvent _exit = new AutoResetEvent(false);
//--//
public static void Main()
{
SensorAsClient _sensorClient = null;
XBee radio = new XBee(Serial.COM2, 9600, c_Self_Address, c_Target_Address, false, Cpu.Pin.GPIO_NONE);
try
{
_sensorClient = new SensorAsClient(radio);
_sensorClient.Start(5000);
_exit.WaitOne();
}
finally
{
_sensorClient.Dispose();
}
}
}
}
SensorAsClient.cs
C#This is the sensor class that creates instances of the individual sensors and outputs messages to the Xbee radio (802.15.4)
using System;
using System.Threading;
using System.IO.Ports;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware;
using SecretLabs.NETMF.Hardware.Netduino;
namespace AirQualityOverXbeeSensor
{
public class SensorAsClient : IDisposable
{
private SHT15 _th_Sensor;
private MQ_4 _co_Sensor;
private XBee _radio;
private Timer _measurementTimer;
private OutputPort _onBoardLED;
private double oldAirQuality = 4000;
private AnalogInput channel2;
//Constructors and Destructors
public SensorAsClient(XBee radio)
{
_radio = radio;
_co_Sensor = new MQ_4(Cpu.AnalogChannel.ANALOG_0);
_th_Sensor = new SHT15();
_onBoardLED = new OutputPort(Pins.ONBOARD_LED, false);
}
public void Dispose()
{
Stop();
_th_Sensor.Dispose();
_co_Sensor.Dispose();
}
public void Start(int samplingTime)
{
_measurementTimer = new Timer(TimerCallback, null, 10, samplingTime);
}
public void Stop()
{
_measurementTimer.Dispose();
}
public double GetTemperature()
{
return _th_Sensor.GetTemperature(TemperatureUnits.Fahrenheit);
}
public double GetHumidity()
{
return _th_Sensor.GetHumidity();
}
public double GetAirQuality()
{
return _co_Sensor.GetMQ4Data();
}
private void TimerCallback(object o)
{
_onBoardLED.Write(true);
double temperature = GetTemperature();
double humidity = GetHumidity();
double airQuality = GetAirQuality();
Debug.Print("AirQuality = " + airQuality.ToString("F2"));
if (airQuality - oldAirQuality > 1000)
{
//_radio.Send(new byte[] { (byte)temperature, (byte)humidity, (byte)airQuality });
_radio.Send(new byte[] { (byte)airQuality });
}
oldAirQuality = airQuality;
_onBoardLED.Write(false);
}
}
}
MQ-4.cs
C#This is the class for the Volatile Compounds (methane) sensor. MQ-4 is the carrier board for the sensor that the Netduino actually connects to.
using System;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware;
using SecretLabs.NETMF.Hardware.Netduino;
namespace AirQualityOverXbeeSensor
{
class MQ_4
{
private AnalogInput _mq4In;
private Timer _sensorReadyTimer;
private bool _sensorReady = false;
//properties
public bool sensorReady
{
get { return _sensorReady; }
}
#region Constructors/Destructors
public MQ_4(Cpu.AnalogChannel inputChannel)
{
//4.1 _mq4In = new AnalogInput(inputPin);
_mq4In = new AnalogInput(inputChannel);
_sensorReadyTimer = new Timer(new TimerCallback(_sensorReadyCallback), null, 20000, 20000);
}
public void Dispose()
{
_mq4In.Dispose();
_sensorReadyTimer.Dispose();
}
#endregion
#region Public Intefaces
public double GetMQ4Data()
{
if (sensorReady)
return _mq4In.ReadRaw();
else
return 0.0;
}
void _sensorReadyCallback(object o)
{
_sensorReady = true;
_sensorReadyTimer.Dispose();
}
#endregion
}
}
using System;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware;
using SecretLabs.NETMF.Hardware.Netduino;
namespace AirQualityOverXbeeSensor
{
public enum SensorMode
{
Tempurature, RelativeHumidity
}
public enum TemperatureUnits
{
Celsius, Fahrenheit
}
public class SensorException : Exception
{
public SensorException(string message)
{
}
}
public class SHT15
{
OutputPort _sck;
TristatePort _data;
//00000011 = Temperature
bool[] _temperatureCommand = new bool[] { false, false, false, false, false, false, true, true };
//00000101 = Humidity
bool[] _humidityCommand = new bool[] { false, false, false, false, false, true, false, true };
#region constructors/destructors
public SHT15()
{
Thread.Sleep(11); // After power up the sensor needs 11 ms to get to Sleep State - no commands before then
_sck = new OutputPort(Pins.GPIO_PIN_D11, false);
_data = new TristatePort(Pins.GPIO_PIN_D10, false, true, Port.ResistorMode.Disabled);
}
public void Dispose()
{
_sck.Dispose();
_data.Dispose();
}
#endregion
#region public communication methods
public double GetTemperature(TemperatureUnits units)
{
try
{
double temperature;
int rawTemp = this.GetRawData(SensorMode.Tempurature);
temperature = rawTemp * 0.01 - 39.65;
if (units == TemperatureUnits.Celsius)
{
return temperature;
}
else
{
return temperature * 9 / 5 + 32;
}
}
catch (SensorException exception)
{
Debug.Print(exception.ToString());
ConnectionReset();
return 0;
}
}
public double GetHumidity()
{
try
{
int rawHumidity = this.GetRawData(SensorMode.RelativeHumidity);
return -2.0468 + .0367 * rawHumidity + -0.0000015955 * rawHumidity * rawHumidity;
}
catch (SensorException exception)
{
Debug.Print(exception.ToString());
ConnectionReset();
return 0;
}
}
#endregion
#region private communication methods
private void TransmissionStart()
{
//To initiate a transmission, a Transmission Start Sequence has to be issued
// It consistes of a lowering of the DATA line while the SCK is high
//followed by a low pulse on SCK and raising DATA again while SCK is still high
//set Tristate to output
if (!_data.Active)
{
_data.Active = true;
}
//set initial states
_data.Write(true);
Thread.Sleep(100);
_sck.Write(false);
Thread.Sleep(100);
//start sequence
_sck.Write(true);
Thread.Sleep(100);
_data.Write(false);
Thread.Sleep(100);
_sck.Write(false);
Thread.Sleep(100);
_sck.Write(true);
Thread.Sleep(100);
_data.Write(true);
Thread.Sleep(100);
_sck.Write(false);
Thread.Sleep(100);
}
private void Measure(SensorMode sensor)
{
// The commands consist of three address bits (only '000' is supported) and 5 command bits
// set Tristate to Output
if (!_data.Active)
{
_data.Active = true;
}
// output command bits
//address + tempurature command is '00000011'
if (sensor == SensorMode.Tempurature)
{
OutputBits(_temperatureCommand);
}
//address + RelativeHimidity command is '00000101'
else if (sensor == SensorMode.RelativeHumidity)
{
OutputBits(_humidityCommand);
}
//get ACK
// set tristate to input
_data.Active = false;
_sck.Write(true);
Thread.Sleep(100);
//ACK is DATA low
if (_data.Read())
{
throw new SensorException(" No ACK to command");
}
_sck.Write(false);
Thread.Sleep(100);
}
private void WaitForData()
{
// After issuing a measurement command, the controller has to wait for the measurement to complete.
// The sensor signals completionby pulling the data line low and enters Idle mode.
// The controller must wait for this Data Ready signal before restarting SCK and reading out the data
// set Tristate to Output
if (!_data.Active)
{
_data.Active = true;
}
// set data line high
_data.Write(true);
Thread.Sleep(100);
// set Tristate to input
_data.Active = false;
// wait for Data ReadySignal
int i = 40; // The max delay for 14 bit measurements is 320 ms
while (i > 0)
{
if (!_data.Read()) // DATA low means that the data is ready
{
break;
}
Thread.Sleep(10);
i--;
}
if (i <= 0)
{
throw new SensorException(" Timed out in Data Ready");
}
}
private int ReadRawData()
{
//The default measurement resolution is 14bit (temperature) and 12bit (humidity)
//two bytes of measurement data and one byte of CRC data will then be transmitted.
//The micro controller must acknowledge each byte by pulling the data line low.
//All values are MSB first, right justified
int myReturn = 0;
// Set Tristate to input
if (_data.Active)
{
_data.Active = false;
}
//toggle SCK and read first byte into _myReturn
for (int i = 15; i > 7; i--)
{
_sck.Write(true);
if (_data.Read())
{
myReturn |= 1 << i;
}
_sck.Write(false);
}
// acknowledge first byte
// set Tristate to Output
_data.Active = true;
_data.Write(true);
Thread.Sleep(100);
_data.Write(false);
Thread.Sleep(100);
_sck.Write(true);
Thread.Sleep(100);
_sck.Write(false);
Thread.Sleep(100);
//set Tristate to Input
_data.Active = false;
//start SCK and read second byte into _myReturn
for (int i = 7; i >= 0; i--)
{
_sck.Write(true);
Thread.Sleep(100);
if (_data.Read())
{
myReturn |= 1 << i;
}
_sck.Write(false);
Thread.Sleep(100);
}
// ACK and keep DATA high to skip CRC
// set Tristate to Output
_data.Active = true;
_data.Write(true);
Thread.Sleep(100);
_sck.Write(true);
Thread.Sleep(100);
_sck.Write(false);
Thread.Sleep(100);
//Debug.Print("Raw Data = " + myReturn.ToString());
return myReturn;
}
private void ConnectionReset()
{
//The following sequence will reset the serial interface. While leaving DATA high
// toggle SCK 9 or more times. This must be followed by a Transmission Start Sequence
// the transmission start is in all of the 'Get..' commands so it is not repeated here.
//Set Tristate to Output
if (!_data.Active)
{
_data.Active = true;
}
_data.Write(true);
Thread.Sleep(100);
for (int i = 0; i < 10; i++)
{
_sck.Write(true);
Thread.Sleep(100);
_sck.Write(false);
Thread.Sleep(100);
}
}
private void OutputBits(bool[] bits)
{
for (int i = 0; i < bits.Length; i++)
{
_data.Write(bits[i]);
_sck.Write(true);
Thread.Sleep(100);
_sck.Write(false);
Thread.Sleep(100);
}
}
private int GetRawData(SensorMode sensor)
{
this.TransmissionStart();
//trigger measurement
this.Measure(sensor);
this.WaitForData();
return (this.ReadRawData());
}
#endregion
}
}
Comments