Geeks With Blogs
Zak McKracken's blog Home automation, retro stuff and .NET

I’ve always been a fan of those little ARM-boards that can be programmed with C#.

So far, I was playing around with a Netduino-board in my office. I use it for automatic testing my software where I need to simulate user action like pressing a (physical) button on a device every minute. This is a very simple task which does not use the potential of this board.

So, for my house automation I use a function, which I call “shield”: The shield function will lower or raise the blinds in certain rooms if temperature rises to high. I use this in the rooms to the south and to the west. It works noticeable and is cheaper than a climate control. This is very interesting to see and guests react sometimes strange, when the house does this on its own Smiley.

To improve this function, I need some extra sensors. A good job for a FEZ Panda II board with an Ethernet shield! I use this board, because I ordered it for a project I didn’t realize some time ago.

I want to know the brightness outside, because if the temperature in the room is high, but there is no sun, lowering the blinds will not help Zwinkerndes Smiley.

Additionally I want to know the temperature on the south side of the house and (probably) expand it with a wind meter or a rain gauge in the future.

So I ordered two sensors:

- Adafruit TSl2561 lux sensor

- GHI Barometer module from .NET Gadgeteer platform

Both sensors use the I2C bus. The barometer module from GHI is intended for use with the .NET Gadgeteer platform and uses an “I”-type socket. This is actually a standardized header, but technically it’s also an I2C bus. So I cut the cable and rewired it to the FEZ Panda shield. This works without any problems so far, but of course you can’t use the GHI libraries for the sensor. You need to build your own.

I will provide the source code, when the project works (for now, the lux meter does not work).


Step 0: Connect all things together

I first connect the shield to the base FEZ Panda board (of course). For power input, I use the Vin pin which is also available on the shield.

The barometer module needs 3.3V, so do not connect it to 5V! Now I connect the SCL and SDA lines, which are available at Pin GPIO_Pin3 (SCL) and GPIO_Pin2 (SDA). In addition, one more pin needs to be connected and set to high in order to read the values from the module. I use GPIO_Pin5 for this.

The communication over the I2C bus can be completely done over the Microsoft.SPOT.Hardware.I2CDevice class.


Step 1: Build up some communication for the web server

The Class Webserver is based on GHI’s Webserver class that will work with the Wiznet W5100 Ethernet libraries. I cut a lot of things out that I do not need (file access on SD card, file upload functions).

Basically, the class sets up a web server and listens. If there’s a request, data is collected (only then, why should I collect the data, when nobody wants it and I do not want to store it on the device). Of course, you could change that behavior and poll data in a defined interval and store it on a SD card, if you want.

I need three different answers from the web server:

1. Answer for http://{FEZ_IP_ADDRESS}: This will bring up a HTML page with the measured data

2. Answer for http://{FEZ_IP_ADDRESS}/data.xml: This is used by my server software. Data is presented on a XML page that can be used without big parsing.

3. Answer for all other calls: There’s a small 404 page (just for fun)

The class looks like this:

using Microsoft.SPOT;
using GHIElectronics.NETMF.Net;
using System.Text;
using System.Threading;
using GHIElectronics.NETMF.FEZ;
using System;

namespace FezWeather
{
    public static class MyHttpServer
    {
        public static void StartServer()
        {
            Debug.Print("Starting HttpServer...");
            Thread httpThread = new Thread((new PrefixKeeper("http")).RunServerDelegate);
            httpThread.Start();
        }

        /// <summary>
        /// All this class does is keeps the prefix and provides 
        /// RunServerDelegate to run in separate thread.
        /// RunServerDelegate calls HttpServerApp.RunServer with saved prefix.
        /// </summary>
        class PrefixKeeper
        {
            /// <summary>
            /// Keeps the prefix to start server.
            /// </summary>
            private string m_prefix;

            /// <summary>
            /// Saves the prefix
            /// </summary>
            /// <param name="prefix">Prefix</param>
            internal PrefixKeeper(string prefix)
            {
                m_prefix = prefix;
            }

            /// <summary>
            /// Delegate to run server in separate thread.
            /// </summary>
            internal void RunServerDelegate()
            {
                MyHttpServer.RunServer(m_prefix);
            }
        }

        internal static void RunServer(string prefix)
        {
            HttpListener listener = new HttpListener(prefix, -1);

            listener.Start();
            Debug.Print("---o0o--- HttpListener is up and running ---o0o---");

            while (true)
            {
                HttpListenerContext context = null;

                try
                {
                    context = listener.GetContext();
                    HttpListenerRequest request = context.Request;
                    if (request.HttpMethod.ToUpper() == "GET")
                    {
                        Program.LEDAccess.StartBlinking(25, 25);

                        Program.GetAllData();
                        ProcessClientGetRequest(context);

                        Program.LEDAccess.StopBlinking();
                        Program.LEDAccess.ShutOff();
                    }
                    
                    if (context != null)
                    {
                        context.Close();
                        context = null;
                    }
                }
                catch
                {
                    if (context != null)
                    {
                        context.Close();
                        context = null;
                    }
                }
            }
        }

        private static void ProcessClientGetRequest(HttpListenerContext context)
        {
            HttpListenerResponse response = context.Response;

            Debug.Print("Query from IP: " + context.Request.RemoteEndPoint.Address.ToString() + " @ " + DateTime.Now.ToString());
            
            response.StatusCode = (int)HttpStatusCode.OK;

            string strResp = string.Empty;

            switch (context.Request.RawUrl)
            {
                case "/data.xml":
                    strResp = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\r\n";
                    strResp += "<weather>\r\n";
                    strResp += "<temperature>" + Program.Temperature + "</temperature>\r\n";
                    strResp += "<brightness>" + Program.Brightness + "</brightness>\r\n";
                    strResp += "<pressure>" + Program.Pressure + "</pressure>\r\n</weather>\r\n";

                    response.ContentType = "application/xml";
                    break;

                case "/":
                    strResp = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\"><html><head><title>FEZWeather station</title></head>";
                    strResp += "<body style=\"background-color:#007CE2; font-family:Courier; color:White\">    <h1>FEZ current weather values</h1><h3>Temperature:" + Program.Temperature + " &deg;C</h3><h3>Pressure:" + Program.Pressure + " hPa</h3><h3>Brightness:" + Program.Brightness + " lux</h3>";
                    strResp += "<br /><br />Data in XML-format? Look here: <a href=\"data.xml\">data.xml</a></body></html>";

                    response.ContentType = "text/html";
                    break;

                default:
                    strResp = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\"><html><head><title>FEZWeather station</title></head>";
                    strResp += "<body style=\"background-color:#007CE2; font-family:Courier; color:White\">    <h1>Not found</h1>    <h3>sorry...</h3>    </body></html>";

                    response.ContentType = "text/html";
                    break;

            }

            byte[] messageBody = Encoding.UTF8.GetBytes(strResp);
            response.OutputStream.Write(messageBody, 0, messageBody.Length);
        }
    }
}

To use this in my main program, I declare some static byte[] arrays to store the network information:
  static byte[] IP = { 192, 168, 0, 219 };                    //  IP address of FEZ Panda II ethernet shield
  static byte[] GW = { 192, 168, 0, 1 };                      //  Your gateway address
  static byte[] SUB = { 255, 255, 255, 0 };                   //  Your subnet mask
  static byte[] DNS = { 192, 168, 0, 1 };                     //  Your DNS
  static byte[] MAC = { 0x00, 0x26, 0x1C, 0x7B, 0x29, 0xE8 }; //  Your MAC address

Although you could use DHCP, I decided to supply a manual IP address.. So, in Main() I call:

WIZnet_W5100.Enable(SPI.SPI_module.SPI1, (Cpu.Pin)FEZ_Pin.Digital.Di10, (Cpu.Pin)FEZ_Pin.Digital.Di9, true);
NetworkInterface.EnableStaticIP(IP, SUB, GW, MAC);
NetworkInterface.EnableStaticDns(DNS);

MyHttpServer.StartServer();

That’s all to do for the web server part. The web server will query the modules if there’s a request and supply it to the user.


Step 2: Talking to the barometer module (as this seems to be easier)

First of all, I need some objects for I2C communication. I use the built-in methods, which makes it very easy to access the bus.

Most of the code is based in the Gadgeteer sample code found here: http://gadgeteer.codeplex.com/SourceControl/changeset/view/19905#157772 

The barometer module has two addresses: One for the eeprom, where some coefficients are stored, the other is the module itself, where I can obtain the values.

So, first some declarations:

public static string Temperature = "0.0";                   //  Temperature value in °C used for XML output
public static string Brightness = "1.0";                    //  LUX value used for XML output
public static string Pressure = "2.0";                      //  Pressure value in hPa used for XML output

static I2CDevice FEZ_I2C;                                   //  I2C object
static OutputPort XCLR;                                     //  XCLR line. Needed to be set HIGH when reading temp/pressure

static I2CDevice.Configuration conDevBARO;              //  Config for barometer module
static I2CDevice.Configuration conDevBAROCfg;           //  Config for barometer module eeprom

static byte BARO_ADDRESS = 0x77;                        //  Address of barometer
static byte BARO_EEPROM = 0x50;                         //  Address of barometer eeprom
            
static byte[] _readBuffer144 = new byte[18];    //  Byte Array for storing config loaded from eeprom
static Coefficients Coeff;                      //  Coeffients for calulation (loaded from eeprom)

struct Coefficients
{
     public int C1, C2, C3, C4, C5, C6, C7;
     public int A, B, C, D;
 }

In Main() I need to read the coefficients from the eeprom first, so I open address 0x50 and create some transaction to get these values. I need them later for calculation.

Coeff = new Coefficients();

// Setup barometer sensor
XCLR = new OutputPort(Cpu.Pin.GPIO_Pin5, false);
conDevBAROCfg = new I2CDevice.Configuration(BARO_EEPROM, 400);
conDevBARO = new I2CDevice.Configuration(BARO_ADDRESS, 400);
FEZ_I2C = new I2CDevice(conDevBAROCfg);

I2CDevice.I2CTransaction[] xActionsGetBaroCfg = new I2CDevice.I2CTransaction[2];
byte[] CallRegister = new byte[1] { 0x10 };
xActionsGetBaroCfg[0] = I2CDevice.CreateWriteTransaction(CallRegister);
xActionsGetBaroCfg[1] = I2CDevice.CreateReadTransaction(_readBuffer144);

FEZ_I2C.Execute(xActionsGetBaroCfg, 1000);

Coeff.C1 = (_readBuffer144[0] << 8) + _readBuffer144[1];
Coeff.C2 = (_readBuffer144[2] << 8) + _readBuffer144[3];
Coeff.C3 = (_readBuffer144[4] << 8) + _readBuffer144[5];
Coeff.C4 = (_readBuffer144[6] << 8) + _readBuffer144[7];
Coeff.C5 = (_readBuffer144[8] << 8) + _readBuffer144[9];
Coeff.C6 = (_readBuffer144[10] << 8) + _readBuffer144[11];
Coeff.C7 = (_readBuffer144[12] << 8) + _readBuffer144[13];
Coeff.A = _readBuffer144[14];
Coeff.B = _readBuffer144[15];
Coeff.C = _readBuffer144[16];
Coeff.D = _readBuffer144[17];

conDevBAROCfg = null;
FEZ_I2C.Dispose();
 
 

Remarks: Always set the Configuration objects to null and dispose the FEZ_I2C object, otherwise you can’t talk to another I2C module!

For getting the temperature and pressure value, I need to connect to address 0x77.

static void GetDataTemp()
        {
            double dUT, OFF, SENS, X;
            double P, T;
            int D1, D2;

            byte[] _readBuffer16 = new byte[2];
            byte[] ReadByte = new byte[1] { 0xFD };

            XCLR.Write(true);

            // Get raw pressure value
            I2CDevice.I2CTransaction[] xWeatherAction = new I2CDevice.I2CTransaction[1];
            byte[] GetPressure = new byte[2] { 0xFF, 0xF0 };
            xWeatherAction[0] = I2CDevice.CreateWriteTransaction(GetPressure);
            FEZ_I2C.Execute(xWeatherAction, 1000);
            Thread.Sleep(50);

            xWeatherAction = new I2CDevice.I2CTransaction[2];
            xWeatherAction[0] = I2CDevice.CreateWriteTransaction(ReadByte);
            xWeatherAction[1] = I2CDevice.CreateReadTransaction(_readBuffer16);
            
            FEZ_I2C.Execute(xWeatherAction, 1000);

            D1 = (_readBuffer16[0] << 8) | _readBuffer16[1];

            Debug.Print("RAW D1=" + D1.ToString());

            // Get raw temperature value
            _readBuffer16 = new byte[2];
            //ReadByte = new byte[1] { 0xF0 };
            xWeatherAction = new I2CDevice.I2CTransaction[1];
            byte[] GetTemp = new byte[2] { 0xFF, 0xE8 };
            xWeatherAction[0] = I2CDevice.CreateWriteTransaction(GetTemp);
            FEZ_I2C.Execute(xWeatherAction, 1000);
            Thread.Sleep(50);

            xWeatherAction = new I2CDevice.I2CTransaction[2];
            xWeatherAction[0] = I2CDevice.CreateWriteTransaction(ReadByte);
            xWeatherAction[1] = I2CDevice.CreateReadTransaction(_readBuffer16);

            FEZ_I2C.Execute(xWeatherAction, 1000);

            D2 = (_readBuffer16[0] << 8) | _readBuffer16[1];
            Debug.Print("RAW D2=" + D2.ToString());
            XCLR.Write(false);


            //////////////////////////////////////////////////////////////
            // Calculate temperature and pressure based on calibration data
            ////////////////////////////////////////////////////////////////

            // Step 1. Get temperature value.

            // D2 >= C5 dUT= D2-C5 - ((D2-C5)/2^7) * ((D2-C5)/2^7) * A / 2^C
            if (D2 >= Coeff.C5)
            {
                dUT = D2 - Coeff.C5 - ((D2 - Coeff.C5) / System.Math.Pow(2, 7) * ((D2 - Coeff.C5) / System.Math.Pow(2, 7)) * Coeff.A / System.Math.Pow(2, Coeff.C));
            }
            // D2 <  C5 dUT= D2-C5 - ((D2-C5)/2^7) * ((D2-C5)/2^7) * B / 2^C
            else
            {
                dUT = D2 - Coeff.C5 - ((D2 - Coeff.C5) / System.Math.Pow(2, 7) * ((D2 - Coeff.C5) / System.Math.Pow(2, 7)) * Coeff.B / System.Math.Pow(2, Coeff.C));
            }

            // Step 2. Calculate offset, sensitivity and final pressure value.

            // OFF=(C2+(C4-1024)*dUT/2^14)*4
            OFF = (Coeff.C2 + (Coeff.C4 - 1024) * dUT / System.Math.Pow(2, 14)) * 4;
            // SENS = C1+ C3*dUT/2^10
            SENS = Coeff.C1 + Coeff.C3 * dUT / System.Math.Pow(2, 10);
            // X= SENS * (D1-7168)/2^14 - OFF
            X = SENS * (D1 - 7168) / System.Math.Pow(2, 14) - OFF;
            // P=X*10/2^5+C7
            P = X * 10 / System.Math.Pow(2, 5) + Coeff.C7;

            // Step 3. Calculate temperature

            // T = 250 + dUT * C6 / 2 ^ 16-dUT/2^D
            T = 250 + dUT * Coeff.C6 / System.Math.Pow(2, 16) - dUT / System.Math.Pow(2, Coeff.D);

            double Temp = T / 10;
            int Press = (int)P / 10;
            
            Debug.Print(Temp.ToString("F1") + " °C");
            Debug.Print(Press.ToString() + " hPa");

            Temperature = Temp.ToString("F1");
            Pressure = Press.ToString();
        }
 
This stores the measured values in the static strings declared above. The method is called by the web server to get the current values.
Posted on Wednesday, August 1, 2012 11:25 AM Netduino and .NETMF , Home automation | Back to top


Comments on this post: Weather station with .NET - Part 1

# re: Weather station with .NET - Part 1
Requesting Gravatar...
Did you manage to have the lux sensor working?
I'll try to write my implementation but I am pretty new to the electronic/sensors world.
Left by Franco Ponticelli on May 30, 2013 6:53 AM

Your comment:
 (will show your gravatar)


Copyright © Zak McKracken | Powered by: GeeksWithBlogs.net