Click here to Skip to main content
15,884,099 members
Articles / Programming Languages / C#

Connecting a Davis Instruments Vantage Weather Station to the Internet

Rate me:
Please Sign up or sign in to vote.
4.67/5 (5 votes)
27 Aug 2007CPOL7 min read 64.4K   1.1K   24  
This article will describe how to connect a Davis Intruments Vantage weather station to the Internet using .NET 2.0 and C# as well as a few pieces of necessary hardware.
<!--------------------------------------------------------------------------->  
<!--                           INTRODUCTION                                

 The Code Project article submission template (HTML version)

Using this template will help us post your article sooner. To use, just 
follow the 3 easy steps below:
 
     1. Fill in the article description details
     2. Add links to your images and downloads
     3. Include the main article text

That's all there is to it! All formatting will be done by our submission
scripts and style sheets. 

-->  
<!--------------------------------------------------------------------------->  
<!--                        IGNORE THIS SECTION                            -->
<html>
<head>
<title>The Code Project</title>
<Style>
BODY, P, TD { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 10pt }
H2,H3,H4,H5 { color: #ff9900; font-weight: bold; }
H2 { font-size: 13pt; }
H3 { font-size: 12pt; }
H4 { font-size: 10pt; color: black; }
PRE { BACKGROUND-COLOR: #FBEDBB; FONT-FAMILY: "Courier New", Courier, mono; WHITE-SPACE: pre; }
CODE { COLOR: #990000; FONT-FAMILY: "Courier New", Courier, mono; }
</style>
<link rel="stylesheet" type=text/css href="http://www.codeproject.com/styles/global.css">
</head>
<body bgcolor="#FFFFFF" color=#000000>
<!--------------------------------------------------------------------------->  


<!-------------------------------     STEP 1      --------------------------->
<!--  Fill in the details (CodeProject will reformat this section for you) -->

<pre>
Title:       Connecting a Davis Instruments Vantage Weatherstation to the Internet
Author:      Will Herman 
Email:       will@herman.com
Member ID:   3626180
Language:    C# 2.0
Platform:    Windows, .NET 2.0
Technology:  TCP, serial
Level:       Intermediate
Description: This article will describe how to connect a Davis Intruments Vantage weatherstation to the Internet using .Net 2.0 and C# as well as a few pieces of necessary hardware.  
Section      C# Programming
SubSection   Suggest a subsection...
</pre>

<!-------------------------------     STEP 2      --------------------------->
<!--  Include download and sample image information.                       --> 

<ul class=download>
<li><a href="weatherlink.zip">Download source - 57 Kb</a></li>
</ul>

<p><img src="weatherlink.jpg" alt="Basic windows form for testing both serial and network connections" width=321 height=305></p>


<!-------------------------------     STEP 3      --------------------------->
<!--  Add the article text. Please use simple formatting (<h2>, <p> etc)   --> 

<h2>Introduction</h2>

<p><a href="http://www.davisnet.com/weather/">Davis Instruments</a> is one of the leading developers of personal weather stations (I'm not affiliated, just a customer).  
I've used one of their VantagePro wireless weatherstations for years now and have really liked it.  It's only drawback was that I couldn't get the information 
from the device when I wasn't looking directly at the console of the unit.  Davis sells a piece of hardware that allows a serial connection to the weatherstation 
console and an associated Windows program that reads the data from the console.  It, however, requires that you have a computer directly connected to the 
console running at all times to get the data from the weather station.  I wanted to be able to get the weather information from anywhere on the internet 
without requiring the use of a physically connected computer running at all times.  
</p>
<p>Doing this required learning a few things and acquiring a few others.  Some hardware, aside from the Davis weather station is required.  First, I needed to 
purchase <a href="http://www.davisnet.com/weather/products/weather_product.asp?pnum=06510SER">Davis' WeatherLink serial port connector</a>.  As mentioned 
above, this allowed me to get access to the weather station console.  Next, I had to find a way to translate serial port signals to TCP packets bi-directionally.  
There are many products that do this and, if you're handy with some basic hardware development, it's probably not too hard to build such a device yourself.  
Being lazy, I decided for an off-the-shelf soloution called the <a href="http://www.siteplayer.com/telnet/index.html">SitePlayer</a> from NetMedia (about $100).
</p> 
<p>Then, moving onto the software (that's why you're reading this in the first place, right?), I couldn't find any guidance on interfacing to the weather station 
through its serial port, let alone over the network, through any of my searches.  Davis does provide an SDK, but it only contains some very old C and VB code 
that had nothing to do with .Net development.  I found very little value in them.  The documentation is also very weak.  As such, I had to figure out how to 
establish an appropriate serial connection, then how to decode the data that the console returned to me.  Once I had established a working serial link between 
the console and an attached computer, I then had to extend this to sending and recieving the data using a TCP socket.  In the end, the solutions are rather simple, although 
they took me a while to figure out.</p>
<p>Warning: while I used to develop a lot of software (many years ago), I have been away from it for some time and I'm pretty rusty.  Also, I'm fairly new to 
.Net and C# so I'm sure that at the very least, I've missed some basic optimizations of the code.  I've tested the code extensively, though, and you should 
be able to use it as a solid starting point for your project.</p>
<p>Note: Davis has talked about delivering an integrated device to make their VantagePro II weather station available over the network for some time now.  As of this
writing, it's still not released.  Since I'm impatient and want full control anyway, writing my own seemed like the right way to go.</p>

<h2>Background</h2>

<p>As with any project that communicates with the serial port, there are two basic ways of exchanging data - synchronously and asynchronously.  .Net provides 
callbacks that are easily accessible to deal with data asynchronously, but I found the system confusing for a project like this where the type and size 
of data being sent and received is fairly well known.  So, I chose to do averything synchronously - polling for data.  This required that I watch for timing issues - you'll see 
these in the code.  Given that I'm dealing with serial ports and TCP sockets, a lot of error checking needs to happen.  It wouldn't be surprising to lose 
a connection in the middle of a transmission so the code needs to handle that.
</p>
<p>There are many commands to that result in the Vantage weather station sending data about current and historic weather as well as the staus of the station, 
console and its sensors.  I've tried to simplifiy the code here by showing only what I believe is the most important command - the "LOOP" command - which returns 
95 bytes of information about the current weather and status of the station.  Processing the LOOP command is also the most complex of the commands so it should 
be relatively straightforward to implement other commands given the template here.
</p>
<p>While I've only tested this code on a VantagePro weather station, I have followed the conventions required to make it work on a newer, VantagePro II station 
as well.
</p>

<h2>Using the code</h2>

<p>The code is setup as follows:</p>
<ul>
<li>Open the serial or TCP port</li>
<li>Wake up the weather station - it sleeps between commands</li>
<li>Send a command</li>
<li>Parse the returned data</li>
</ul>
<p>I initially built the code to talk with the weather station directly through the serial port.  This let me debug the communication with the fewest variables 
and let me get an idea about what had to happen between the SitePlayer and the console. Once I got that working, I extended the program to also get data over a 
network, including the Internet.  To help out as many people as possible, I've included the code for both serial and network connections here.
</p>
<p>The cornerstone of the program is the <code>WeatherLoopData</code> class.  Once passed the byte array retrieved from the weather station, this class parses the 
array into its constituent parts and makes them available for use.  There's a fair amount of manipulation that has to take place in order to get the data into 
the correct form.  The class also contains procedures to format the data for output and to translate some of the numeric data into easier to read strings.  
For brevity, I've only included the procedure from the class that does the heavy lifting here.  The full class is in the included source code.</p>

<pre lang=CS>
// The WeatherLoopData class extracts and stores the weather data from the array of bytes returned from the Vantage weather station
// The array is generated from the return of the LOOP command.
//
public class WeatherLoopData
{
    // Load - dissassembles the byte array passed in and loads it into local data that the accessors can use.
    // Actual data is in the format to the right of the assignments - I convert it to make it easier to use
    // When bytes have to be assembled into 2-byte, 16-bit numbers, I convert two bytes from the array into 
    // an Int16 (16-bit integer).  When a single byte is all that's needed, I just convert it to an Int32.
    // In the end, all integers are cast to Int32 for return.
    public void Load(Byte[] loopByteArray)
    {
        int hours,
            minutes;
        string timeString;
        DateTime currTime;

        barTrend = Convert.ToInt32((sbyte)loopByteArray[3]);                    // Sbyte - signed byte
        barometer = (float)(BitConverter.ToInt16(loopByteArray, 7)) / 1000;     // Uint16
        insideTemp = (float)(BitConverter.ToInt16(loopByteArray, 9)) / 10;      // Uint16
        insideHumidity = Convert.ToInt32(loopByteArray[11]);                    // Byte - unsigned byte
        outsideTemp = (float)(BitConverter.ToInt16(loopByteArray, 12)) / 10;    // Uint16
        outsideHumidity = Convert.ToInt32(loopByteArray[33]);                   // Byte - unsigned byte
        windDirection = BitConverter.ToInt16(loopByteArray, 16);                // Uint16
        currWindSpeed = Convert.ToInt32(loopByteArray[14]);                     // Byte - unsigned byte
        avgWindSpeed = Convert.ToInt32(loopByteArray[15]);                      // Byte - unsigned byte
        dayRain = (float)(BitConverter.ToInt16(loopByteArray, 50)) / 100;       // Uint16

        // get the current date and time
        currTime = DateTime.Now;

        // Time from the Vatnage is all in 24-hour format.  I move it into a string so I can manipulate it 
        // more easily.
        timeString = BitConverter.ToInt16(loopByteArray, 91).ToString();    // Uint16
        // Exract hours and minutes and convert them to integers - required by Datetime
        hours = Convert.ToInt32(timeString.Substring(0, timeString.Length - 2));
        minutes = Convert.ToInt32(timeString.Substring(timeString.Length - 2, 2));
        // Create a new Datetime instance - use surrent year, month and day
        sunRise = new DateTime(currTime.Year, currTime.Month, currTime.Day, hours, minutes, 0);

        timeString = BitConverter.ToInt16(loopByteArray, 93).ToString();    // Uint16
        hours = Convert.ToInt32(timeString.Substring(0, timeString.Length - 2));
        minutes = Convert.ToInt32(timeString.Substring(timeString.Length - 2, 2));
        sunSet = new DateTime(currTime.Year, currTime.Month, currTime.Day, hours, minutes, 0); ;
    }
}
</pre>

<p>With this class in hand, the rest of the job is about establishing a solid serial or network connection, sending a command and getting the data back 
from the weather station.</p>
<p>Opening the serial port is easy, the trick in communicating with the Vantage, though, is that DTR (Data Terminal Ready) must be set high (true).  This little 
tidbit took me a while to figure out.  It seems obvious to me now, but I had assumed that true would be the default.</p>
<pre lang=cs>
// Open the serial port for communication
private SerialPort Open_Serial_Port()
{
    try
    {
        SerialPort thePort = new SerialPort("COM1", 19200, Parity.None, 8, StopBits.One);

        // This establishes an event handler for serial comm errors
        thePort.ErrorReceived += new SerialErrorReceivedEventHandler(SerialPort_ErrorReceived);

        // Set a timeout just in case there's a big problem and nothing is being received.  The rest of the code should
        // take care of most problems.  The following line can be used if no timeout is desired:
        // thePort.ReadTimeout = SerialPort.InfiniteTimeout;
        thePort.ReadTimeout = 2500;
        thePort.WriteTimeout = 2500;

        // Set Data Terminal Ready to true - can't transmit without DTR turned on
        thePort.DtrEnable = true;

        thePort.Open();

        return (thePort);
    }
    catch (Exception ex)
    {
        Show_Message(ex.ToString());
        return (null);
    }
}</pre>
<p>Opening the TCP socket is shockingly simple once you get the right syntax.  I use port 23 because the SitePlayer defaults to talking over a standard telnet port.  
Below is the try block for dealing with the TCP port opening.  The rest of the code in the procedure is the same as for the serial port.</p>
<pre lang=cs>
// Open a TCP socket.  Most operations will work on the underlying stream from the port which aren't completely 
// implemented in .Net 2.x
try
{
    // Creating the new TCP socket effectively opens it - specify IP address or domain name and port
    TcpClient sock = new TcpClient("xxx.xxx.xxx.xxx", 23);
    // Set the timeout of the underlying stream
    // WARNING: several of the methods on the underlying stream object are not implemented in .Net 2.x
    sock.GetStream().ReadTimeout = 2500;

    return sock;
}
</pre>

<p>With the port open, we need to go out and get the data.  Since the streams for a serial connection and TCP socket need to be handled differently, there are some 
differences in the way I needed to do this as seen in the code.  The weather station returns an array of bytes.  As is shown, most of the code is about error checking 
and timing.  We need to make sure that we've received all the data that we are expecting and that our connection remains active during the transmission.  Sometimes, 
the console will respond with a \n\r (new line), acknowledging the command.  Other times it won't.</p>

<pre lang=cs>
// Retrieve_Command retrieves data from the Vantage weather station using the specified command
private byte[] Retrieve_Serial_Command(SerialPort thePort, string commandString, int returnLength)
{
    bool Found_ACK = false;
    int ACK = 6,        // ASCII 6
        passCount = 1,
        maxPasses = 4;
    int currChar;

    try
    {
        // Clean out the input (receive) buffer just in case something showed up in it
        thePort.DiscardInBuffer();
        // . . . and clean out the output buffer while we're at it for good measure
        thePort.DiscardOutBuffer();

        // Try the command until we get a clean ACKnowledge from the Vantage.  We count the number of passes since
        // a timeout will never occur reading from the sockets buffer.  If we try a bunch of times (maxPasses) and
        // we get nothing back, we assume that the connection is busted
        while (!Found_ACK && passCount < maxPasses)
        {
            thePort.WriteLine(commandString);
            // I'm using the LOOP command as the baseline here because many its parameters are a superset of
            // those for other commands.  The most important part of this is that the LOOP command is iterative
            // and the station waits 2 seconds between its responses.  Although it's not clear from the documentation, 
            // I'm assuming that the first packet isn't sent for 2 seconds.  In any event, the conservative nature
            // of waiting this amount of time probably makes sense to deal with serial IO in this manner anyway.
            System.Threading.Thread.Sleep(2000);

            // Wait for the Vantage to acknowledge the the receipt of the command - sometimes we get a '\n\r'
            // in the buffer first or nor response is given.  If all else fails, try again.
            while (thePort.BytesToRead > 0 && !Found_ACK)
            {
                // Read the current character
                currChar = thePort.ReadChar();
                if (currChar == ACK)
                    Found_ACK = true;
            }

            passCount += 1;
        }

        // We've tried a bunch of times and have heard nothing back from the port (nothing's in the buffer).  Let's 
        // bounce outta here
        if (passCount == maxPasses)
            return (null);
        else
        {
            // Allocate a byte array to hold the return data that we care about - up to, but not including the '\n'
            // Size the array according to the data passed to the procedure
            byte[] loopString = new byte[returnLength];

            // Wait until the buffer is full - we've received returnLength characters from the LOOP response, 
            // including the final '\n' 
            while (thePort.BytesToRead <= loopString.Length)
            {
                // Wait a short period to let more data load into the buffer
                System.Threading.Thread.Sleep(200);
            }

            // Read the first returnLength bytes of the buffer into the array
            thePort.Read(loopString, 0, returnLength);

            return loopString;
        }
    }
    catch (Exception ex)
    {
        Show_Message(ex.ToString());
        return null;
    }
}
</pre>
<p>
Again, because the way we need to deal with the data stream is different between serial and TCP connections, the code for the TCP connection is slightly different.  
For example, there is no implemented method on the stream for finding out the number of bytes waiting in the buffer.  We replace it with a method that tells us 
simply if data is available.</p>
<pre lang=cs>
// Retrieve_Command retrieves data from the Vantage weather station using the specified command
private byte[] Retrieve_Telnet_Command(TcpClient thePort, string commandString, int returnLength)
{
    bool Found_ACK = false;
    int currChar,
        ACK = 6,        // ASCII 6
        passCount = 1,
        maxPasses = 4;
    string termCommand;

    try
    {
        // Set a local variable so that it's easier to work with the stream underlying the TCP socket
        NetworkStream theStream = thePort.GetStream();

        // Try the command until we get a clean ACKnowledge from the Vantage.  We count the number of passes since
        // a timeout will never occur reading from the sockets buffer.  If we try a bunch of times (maxPasses) and
        // we get nothing back, we assume that the connection is busted
        while (!Found_ACK && passCount < maxPasses)
        {
            termCommand = commandString + "\n";
            // Convert the command string to an ASCII byte array - required for the .Write method - and send
            theStream.Write(Encoding.ASCII.GetBytes(termCommand), 0, termCommand.Length);
            // According to the Davis documentation, the LOOP command sends its response every 2 seconds.  It's
            // not clear if there is a 2-second delay for the first response.  My trials have show that this can
            // move faster, but still needs some delay.
            System.Threading.Thread.Sleep(500);

            // Wait for the Vantage to acknowledge the the receipt of the command - sometimes we get a '\r\n'
            // in the buffer first or nor response is given.  If all else fails, try again.
            while (theStream.DataAvailable && !Found_ACK)
            {
                // Read the current character
                currChar = theStream.ReadByte();
                if (currChar == ACK)
                    Found_ACK = true;
            }

            passCount += 1;
        }

        // We've tried a bunch of times and have heard nothing back from the port (nothing's in the buffer).  Let's 
        // bounce outta here
        if (passCount == maxPasses)
            return (null);
        else
        {
            // Allocate a byte array to hold the return data that we care about - up to, but not including the '\n'
            // Size is determined by LOOP data return - this procedure has no way of knowing if it is not passed in.
            byte[] loopString = new byte[returnLength];

            // Wait until the buffer is full - we've received returnLength characters from the command response
            while (thePort.Available <= loopString.Length)
            {
                // Wait a short period to let more data load into the buffer
                System.Threading.Thread.Sleep(200);
            }

            // Read the first 95 bytes of the buffer into the array
            theStream.Read(loopString, 0, returnLength);

            return loopString;
        }
    }
    catch (Exception ex)
    {
        Show_Message(ex.ToString());
        return null;
    }
}
</pre>

<p>That's about all the interesting stuff there is.  There is a basic windows form for triggering the serial or network connection and diplaying the returned 
data as well as various event handlers for button clicks and serial errors.  The code is pretty well documented so should be fairly easy to read through.  I have moved this code (the 
network version) to an ASP.Net web site (not yet visible) and it's working well.  Good luck with your project.
</p>

<h2>Points of Interest</h2>

<p>While I would have loved to treat the processing of the serial and TCP connections with a lot of common code, it turns out that not all of the methods 
dealing with the stream that underlies the TCP connection are implemented in .Net 2.0.  Even the ones that exist are not completely aligned with the methods 
for the streams underyling a serial connection.  As such, I have seperate procedures for dealing with serial connections and TCP sockets.
</p>

<h2>History</h2>

<p>v1.0 8/22/2007


<!-------------------------------    That's it!   --------------------------->
</body>
</html>

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions