Colin Miller
Published © Apache-2.0

Methane Detection System

Multiple discrete VOC sensors send alerts over 802.15.4 to gateway which sends SMS to phone

IntermediateFull instructions provided1,086
Methane Detection System

Things used in this project

Hardware components

Netduino3
Wilderness Labs (Netduino) Netduino3
×1
Seeed Studio GPRS Shield
×1
SparkFun ProtoShield Kit
SparkFun ProtoShield Kit
×1
Xbee Series 1
×1
SparkFun XBee Shield
SparkFun XBee Shield
×1
Methane CNG sensor
×1
Pololu MQ Gas Sensor carrier
×1

Story

Read more

Code

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
    }
}

SHT15.cs

Curly
This class contains the interfaces to the humidity and temperature sensor.
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


    }
}

Credits

Colin Miller

Colin Miller

2 projects • 5 followers

Comments