Home

Weathercloud.net Posting Arduino

I stumbled upon Weathercloud.net a while back. I was interested in being able to monitor winds in my area so that I could hopefully shutdown Christmas inflatables to prevent high winds from damaging them. In the process I wondered if I could post data to their server and then use it. However the sensors they supported were/are fairly expensive but I had a number of Arduinos and a diy weather station from Sparkfun. They were very receptive to my trying to create Arduino code that could post to their service. So i did. It took a lot of rework but eventually the system was working properly. I only ever posted indoor temperature and humidity because before my code was completed my weather station was destroyed when the pole toppled over... I completed the code as a single piece. Eventually I broke it into a a library section also so it was a touch more modular. Not sure if that was entirely the best idea.
In the process I learned a couple things. I also made the sensor so that it had a simple web server running on it. This was so that I could just call up the sensor quickly and check the status in any web browser at home. There is no security on the webpage but there is really nothing to it either so I was not worried about such.
Below you will find the example program, then the two pieces of the library I made to support it. My example relies on the DHT library also, many versions of which can be found. In case you do not want to view the code immediately all three files are in this weatherc.zip file. Work on this project has been completed as I have moved it over to a Photon-based device. However ask me if you have questions.


WeatherC.ino Example


/*
  Name: WeatherC Example
  Created by: David Snell
  Last Update: 12/4/2015
  Description: An Arduino Uno based project that uses an Ethernet shield and a variety of sensors
  to take readings and send them to Weathercloud.net. There are a number of variables that
  must be identified for an individual network as well as Weathercloud account. This file
  is only meant to be a sample of what could be done and WILL NOT WORK without at least
  the network settings configured as well as a Weathercloud account, ID, and Key associated.

  This sample project is now using a library I made "WeatherC" to create the sensor reading
  variables to store the data in as well as to regularly post it to Weathercloud.
	
  You will need to obtain an ID and Key from Weathercloud when you set up your account.
*/

// Begin Pin Assignments for Uno
/*
  Pin 0 = RX0 - 
  Pin 1 = TX0 - 
  Pin 2 = DIO - 
  Pin 3 = PWM - 
  Pin 4 = DIO - 
  Pin 5 = PWM - 
  Pin 6 = PWM - Input from DHT-22 or equivalent
  Pin 7 = DIO - 
  Pin 8 = DIO - 
  Pin 9 = PWM - 
  Pin 10 = PWM - Ethernet Shield
  Pin 11 = PWM - Ethernet Shield
  Pin 12 = DIO - Ethernet Shield
  Pin 13 = DIO - Ethernet Shield
  Pin A0 = AI - 
  Pin A1 = AI - 
  Pin A2 = AI - 
  Pin A3 = AI - 
  Pin A4 = AI - 
  Pin A5 = AI - 
*/
// End PIN Assignments

// Begin Includes
#include <SPI.h>
#include <Ethernet.h>
#include <EthernetUdp.h>
#include <DHT.h>
#include <WeatherC.h>
// End Includes

// Begin Network Settings
byte mac[] = { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }; // MAC address for ethernet shield goes here, replace the 01s
IPAddress ip( 192, 168, 1, 2 ); // Static IP for ethernet shield. Replace with your IP address.
unsigned int LocalUDP = 8888; // Local UDP port
// End Network Settings

// Begin Weathercloud identifiers
char WeatherID[] = ""; // Enter the ID that Weathercloud provides here. Will not work without it.
char WeatherKey[] = ""; // Enter the Key that Weathercloud provides here. Will not work without it.
// End Weathercloud identifiers

// Begin Sensor Settings
// For my sensors I was using a DHT of one form or another. You could use anything really.
#define DHT_PIN 6 // PIN that the DHT is connected to
#define DHT_TYPE DHT22   // DHT 22 (AM2302) was used in my example
//#define DHT_TYPE DHT11   // DHT 11
unsigned int SensorDelay = 5000; // Set the delay between temperature readings. Default is 5 seconds (5000 milliseconds)
unsigned long SensorTime = 5000; // Set the initial time before a sensor reading occurs
//End Sensor Settings

// Begin Time related
unsigned int UTCTime[ 2 ]; // Array of the segments of time once separated.
unsigned long NTPTime = 1000; // Set the base timing value
unsigned int NTPDelay = 30000; // Delay between readings. Default is 30 seconds (30000 milliseconds)
char timeServer[] = "time.nist.gov"; // time.nist.gov NTP server
const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets
// End Time related section

// Begin Initializing Functions
DHT dht( DHT_PIN, DHT_TYPE ); // Initializer for DHT
WeatherC WC; // Initializer for the WeatherC
EthernetUDP Udp; // Ethernet UDP functions
EthernetServer Server( 80 ); // Setting the web server
//End Initializing Functions

// Begin General
bool Debug = false; // Is debugging enabled? Typically false once coding is settled a bit.
bool Networked = true; // Is networking enabled? Typically true but sometimes disabled while coding.
// End General

// Begin sendNTPpacket
unsigned long sendNTPpacket( char* address ){
  // set all bytes in the buffer to 0
  memset( packetBuffer, 0, NTP_PACKET_SIZE );
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;

  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp:
  Udp.beginPacket(address, 123); //NTP requests are to port 123
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  Udp.endPacket();
}
// End sendNTPpacket

// Begin GetTime
// GetTime breaks the UTC time up into an array for use.
void GetTime() {
  sendNTPpacket( timeServer );
  delay( 1000 );
  if ( Udp.parsePacket() ) {  
    Udp.read( packetBuffer, NTP_PACKET_SIZE );
    unsigned long highWord = word( packetBuffer[ 40 ], packetBuffer[ 41 ] );
    unsigned long lowWord = word( packetBuffer[ 42 ], packetBuffer[ 43 ] );  
    unsigned long secsSince1900 = highWord << 16 | lowWord;  
    unsigned long epoch = secsSince1900 - 2208988800UL;  
    UTCTime[ 0 ] = ( ( epoch % 86400L ) / 3600 );
    UTCTime[ 1 ] = ( ( epoch  % 3600 ) / 60 );
  }
}
// End GetTime

// Begin Webserver
// Webserver is meant to provide a simple interface to check the IP of your sensor and the current readings it has.
// It will refresh every 5 seconds unless stopped.
void Webserver() {
  EthernetClient Web = Server.available();
  int noChar = 0;
  if ( Web ) {
    boolean currentLineIsBlank = true;
    while ( Web.connected() ) {
      if ( Web.available() ) {
        char c = Web.read();
        noChar = 0;
        if (c == '\n' && currentLineIsBlank) {
          Web.println( "HTTP/1.1 200 OK" );
          Web.println( "Content-Type: text/html" );
          Web.println();
          Web.println( "<html>" );
          Web.println( "<head>" );
          Web.println( "<meta http-equiv='refresh' content='5'>" );
          Web.println( "<title>Sensor</title>" );
          Web.println( "</head>" );
          Web.println( "<body>" );
          Web.print( "Time: " );
          Web.print( UTCTime[ 0 ] );
          Web.print( ":" );
          if ( UTCTime[ 1 ] < 10 ) { // If the minute element is less than 10, add a 0 to the output
            Web.print( "0" );
          }
          Web.print( UTCTime[ 1 ] );
          Web.println( "UTC</br>" );
          for( int x = 0; x < WC.MaxSensors; x++ ){ // Run through the maximum number of sensors.
            if( WC.SensorReadings[ x ] != -1000 ) { // If the sensor value is not equal to -1000, display it
              Web.print( WC.SensorNames[ x ] );
              Web.print( "=" );
              Web.print( WC.SensorReadings[ x ] );
              Web.println( "</br>" );
            }
          }
          Web.println( "</body>" );
          Web.println( "</html>" );
          break;
        }
        if (c == '\n') {
          currentLineIsBlank = true;
        } 
        else if (c != '\r') {
          currentLineIsBlank = false;
        }
      }
      noChar++;
      if( noChar > 10000 ){
        if( Debug ){
          Serial.println( "Timed out." );
        }
        Web.stop();
      }
    }
    Web.stop();
  }
}
// End Webserver

// Begin Setup
void setup() {
  if( Debug ){ // If debugging is enabled
    Serial.begin( 115200 );
  }
	
  // Start Sensors
  dht.begin();
  // End Sensors
	
  // Start networking
  if( Networked ){ // If networking is enabled
    if ( Ethernet.begin( mac ) == 0 ) {
      Ethernet.begin( mac, ip );
    }
    delay( 1000 ); // Delay for Ethernet shield
    Server.begin();
    Udp.begin( LocalUDP ); // Start UDP for NTP
		
    // To be honest I am not sure why the next spots are necessary. If the ID and Key were added to the library
    // directly by someone. I could not get it to work nicely with a string or having them copied directly.
    for(int i = 0; i < 16; i++){ // Provide the ID information to the library.
      WC.ID[ i ] = WeatherID[ i ];
    }
    for(int i = 0; i < 32; i++){ // Provide the Key information to the library.
      WC.Key[ i ] = WeatherKey[ i ];
    }
    if( Debug ){ // If debugging is enabled
      Serial.print( "IP = " );
      Serial.println( Ethernet.localIP() );
    }
    WC.Init(); // Initialize the Weathercloud
  }
  // End Networking
}
// End Setup

// Begin Loop
// Main loop of the program
void loop() {
  if( millis() > NTPTime ) { // Is it time to perform an NTP request?
    if( Networked ){ // Is the device networked?
      GetTime(); // Check for the time.
    }
    NTPTime = millis() + NTPDelay; // Set the next time to perform an NTP check
  }
  if( millis() > SensorTime ) { // Is it time to check the sensors?
    // Content within here will need to be HEAVILY modified based on whatever sensors you are including.
    // In this case the program is only checking humidity and temperature.
    float h = dht.readHumidity();
    float t = dht.readTemperature();
    if( Debug ){ // Debugging purposes
      Serial.print( "Temp: " );
      Serial.print( t );
      Serial.print( "c / Humidity: ");
      Serial.print( h );
      Serial.print( "%" );
    }
    if( Networked ){ // Is the device networked?
      WC.SensorReadings[ 0 ] = int( ( t * 10 ) ); // Weathercloud expects an integer but the temperature has a decimal point
      WC.SensorReadings[ 10 ] = int( h );
    }
    SensorTime = millis() + SensorDelay; // Set the next time to perform a Sensor reading
  }
  if( Networked ){ // Is the device networked?
    WC.PostData(); // Post the data to Weathercloud. It makes sure it is no faster than their maximum of 10 minute postings.
    Webserver(); // Provide the webpage for checking sensor status
    Ethernet.maintain(); // Maintain the ethernet
  }
}
// End Loop


WeatherC.cpp

/*
  WeatherC.cpp - Library for providing data to Weathercloud.net
  Created by: David Snell
  Last Update: 12/4/2015
*/

#include "Arduino.h"
#include "WeatherC.h"

void WeatherC::Init( ){
  // Initializing function for WeatherC. Just sets all the sensors to -1000 as an "unset" value.
  for( int Index = 0; Index < MaxSensors; Index++ ){ // Run through the maximum number of sensors.
    SensorReadings[ Index ] = -1000;
  }
}

void WeatherC::PostData() {
  // Function to post data to Weathercloud
  if( millis() > PostTime ){ // If it has been long enough between postings
    WeatherClient.stop(); // Stop any client running in preparation to connect.
    WeatherClient.connect( TargetDNS, TargetPort ); // Connect to the target
    if ( WeatherClient.connected() ) { // If the client connected
      WeatherClient.print( "GET /set/wid/" );
      WeatherClient.print( ID ); // Sending the ID provided
      WeatherClient.print( "/key/" );
      WeatherClient.print( Key ); // Sending the Key provided
      for( int Index = 0; Index < MaxSensors; Index++ ){ // Run through the maximum number of sensors.
        if( SensorReadings[ Index ] != -1000 ) { // If the sensor value is not equal to -1000, post it
          WeatherClient.print( "/" );
          WeatherClient.print( SensorNames[ Index ] ); // Add the SensorName to the string
          WeatherClient.print( "/" );
          WeatherClient.print( SensorReadings[ Index ] ); // Add the SensorReading to the string
        }
      }
      WeatherClient.print( "/ver/" );
      WeatherClient.print( Version ); // Sending the Version required.
      WeatherClient.print( "/type/" );
      WeatherClient.print( Type ); // Sending the Type required.
      WeatherClient.println( " HTTP/1.1" );
      WeatherClient.print( "Host: " );
      WeatherClient.println( TargetDNS ); // Sending the TargetDNS required. Not sure exactly, it is an HTTP thing.
      WeatherClient.println( "User-agent: arduino-ethernet" );
      WeatherClient.println( "Connection: close" );
      WeatherClient.println();
    }
    PostTime = ( millis() + PostDelay ); // Set the next time that a posting can occur
  }
}


WeatherC.h

/*
  WeatherC.h - Library for providing data to Weathercloud.net
  Created by: David Snell
  Last Update: 12/4/2015
	
  Created an array of integers "SensorReadings" that can store all
  of Weathercloud's currently accepted settings (as of their 0.6 API). Every SensorReadings
  value is defaulted to -1000, a value Weathercloud will not accept, and that value is
  used to ignore any sensors that are not being used. If you use a sensor please make
  sure to have its value go to the correct array value and in the correct range that is
  appropriate for that sensor (check the Weathercloud API). I do not plan on having any error
  checking of values being sent to Weathercloud.

  There is also an array of strings "SensorNames" that has the name for each variable
  to be posted to Weathercloud.
	
  In order to reduce the memory needed for this (which is pretty high) it is possible to remove
  SensorNames and SensorReadings from their arrays and reduce the MaxSensors value accordingly.
  You must still make sure you are sending the correct names and readings.
*/

#ifndef WeatherC_h
#define WeatherC_h

#include "Arduino.h"
#include "Ethernet.h"

class WeatherC {
  public:
    void Init( );
    void PostData();
    EthernetClient WeatherClient; // Creating a client using the EthernetClient
    char ID[ 17 ]; // The ID value that Weathercloud is expecting.
    char Key[ 33 ]; // The Key value that Weathercloud is expecting.
    const int MaxSensors = 29; // Max number of sensors that can be used.
    int SensorReadings[ 29 ]; // Array to hold all the sensor readings listed in the below table.
    String SensorNames[ 29 ] = { "tempin", "temp", "tempXX", "tempagroXX", "chill", "dewin", "dew", "heatin", "heat", "thw", "humin",
    "hum", "humXX", "wspd", "wspdXX", "wspdhi", "wspdhiXX", "wspdavg", "wspdavgXX", "wdir", "wdirXX", "wdiravg", "wdiravgXX", "bar",
    "rain", "rainrate", "et", "solarrad", "uvi" };
      // Additional names to add as Weathercloud builds support would be: 
      // "soilmoistXX", "leafwetXX", "bartrend", "time", "forecast", "forecasticon", "battery", "batteryXX"
			
      /* Labeled as SensorReadings Array # = SensorNames name = Description
      0 = tempin = Temperature indoors (tempin) in Celsius
      1 = temp = Temperature in Celsius
      2 = tempXX = Temperature in Celsius
      3 = tempagroXX = Temperature in Celsius
      4 = chill = Temperature in Celsius
      5 = dewin = Temperature in Celsius
      6 = dew = Temperature in Celsius
      7 = heatin = Temperature in Celsius
      8 = heat = Temperature in Celsius
      9 = thw = Temperature in Celsius
      10 = humin = Humidity indoors (humin) in %.
      11 = hum = Humidity (hum) in %.
      12 = humXX = Humidity (humXX) in %.
      13 = wspd = Wind speed (wspd) in m/s.
      14 = wspdXX = Wind speed (wspdXX) in m/s.
      15 = wspdhi = Wind speed (wspdhi) in m/s.
      16 = wspdhiXX = Wind speed (wspdhiXX) in m/s.
      17 = wspdavg = Wind speed (wspdavg) in m/s.
      18 = wspdavgXX = Wind speed (wspdavgXX) in m/s.
      19 = wdir = Wind direction (wdir) in degrees.
      20 = wdirXX = Wind direction (wdirXX) in degrees.
      21 = wdiravg = Wind direction (wdiravg) in degrees.
      22 = wdiravgXX = Wind direction (wdiravgXX) in degrees.
      23 = bar = Atmospheric pressure (bar) in hectopascals.
      24 = rain = Daily total rain (rain) in mm.
      25 = rainrate = Rate of rain (rainrate) in mm per hour.
      26 = et = Daily total ET (et) in mm.
      27 = solarrad = Solar radiation (solarrad) in watts per square meter.
      28 = uvi = UV index (uvi).
      // Types below this point are not yet accepted but are in the API
      29 = soilmoistXX = Soil moisture (soilmoist) in centibars (Cb).
      30 = leafwetXX = Leaf wetness (leafwetXX).
      31 = bartrend = (bartrend)
      32 = time = Time in hh:mm [0, 2400].
      33 = forecast = (forecast)
      34 = forecasticon = (forecasticon)
      35 = battery = Battery voltage (battery) in V.
      36 = batteryXX = Battery state (batteryXX) 0 = low, 1 = OK.
      */
			
  private:
    char TargetDNS[ 21 ] = "api.weathercloud.net"; // The server the data is sent to.
    int TargetPort = 80; // Port that the data is sent to.
    double Version = 1.4; // Version information that Weathercloud is expecting.
    int Type = 921; // Type information that Weathercloud is expecting.
    unsigned long PostTime = 300000; // Initial delay of 5 minutes after boot.
    unsigned long PostDelay = 600000; // Delay between data postings of 10 minutes. Weathercloud will not accept faster than 10 min.
};

#endif