The Weather Master - Part 1

212

3

There are many ways in which to master the weather information we can
collect from sensors. This is yet another way to do so. It may not be the simplest, cheapest, or most straight forward. But dam did I learn a lot.

Any basic weather station kit should work as long as it has a anemometer, wind vane, and a rain gauge. If you can get one with Temperture and Humidity then even better, otherwise it's easy to add a DHT22/11. The tricky part is finding a place to put it where it can be exposed to the air but not to the rain. I converted an old radio frequency one which required a bit more hacking and guess work. I got a cheap one from Jaycar in New Zealand, but there are ones on the internet that are a lot easier to use that don't require any modifiying. It may even be cheaper for you getting one from the internet than it is for me as we have to pay more for shipping to the end of the world. Like this one for example

I don't expect anyone to follow this whole instructable all the way through which is why I have broken it down into parts so it's easier to find the information you need. So please have fun picking out and using the elements of this project for your own.

Part 1 - Weather sensor hook up:

  • BMP280 - Using the SPI library
  • DHT11 - Using the DHT library
  • Hall Sensor - For a Rain Gauge, modifying existing self-tipping bucket magnetic switch. Using on an interrupt pin
  • Anemometer - Using an interrupt pin
  • Wind Vane - Reading analog resistor values

Part 2 to follow.

Supplies:

  • Arduino Pro 3.3V 8MHz
  • BMP280 - Pressure and Temperature
  • DHT11 - Temperature and Humidity
  • Hall Sensor - Rain Gauge
  • Anemometer - Wind Speed
  • Wind Vane - Wind Direction
  • 10k Resistors x4
  • 1k Resistor x1
  • FTDI Breakout 3.3V (The one I brought from Sparkfun could be changed from 5V to 3V3 by soldering a connector)

SVG file attached below.

Step 1: BMP280 Using Software SPI

You can use the BMP280 with the I2C interface however I opted for the SPI and used the 'software' SPI instead of the 'hardware' SPI. Which means you can choose which pins are used instead of the default ones which differ for each Arduino. It shouldn't really matter it just depends on what else you want to connect to the Arduino.

I added a BMP280 to the other side of the board that has the Hall sensor on it. It was a very tight fit because the Arduino Pro was sandwiched on top. As well as pressure this little sensor gives you temperature but I never intended on using it especially due to its location deep inside the unit. I added the DHT11 sensor instead which is in the next step.

#include <SPI.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BMP280.h>


#define BMP_SCK 13  // The numbers correspond to the pins on the Arduino<br>#define BMP_MISO 12
#define BMP_MOSI 11
#define BMP_CS 10
// Software SPI
Adafruit_BMP280 bmp(BMP_CS, BMP_MOSI, BMP_MISO,  BMP_SCK);

int pressure; // Pressure data from the BMP280 will be in Pascals, but we will convert to Hectopascals
float tempC; // Temperature data from the BMP280 will be in Celcius. If you are so inclined use the equation: F = C * 9/5 + 32

void setup() {

	Serial.begin(115200);

	if (!bmp.begin()) {
		Serial.println("Could not find a valid BMP280 sensor, check wiring!");
	}
}

void loop() {
	
	float pressureRAW = bmp.readPressure(); // readPressure returns data in pascals
	pressure = round(pressureRAW / 100); // Convert to hPa and round to an int
	tempC = bmp.readTemperature();

	Serial.print("Pressure: ") Serial.print(pressure)Serial.println("hPa")
	Serial.print("Temperature: ") Serial.print(tempC) Serial.println("C")
}

Step 2: DHT11

I used the DHT11 as it is smaller and I could easily put where the old temperature sensor was positioned below the rain gauge. It's out of direct sun light and rain but very open to the air. The main difference between DHT11 and DHT22 is that the latter doesn't need a pullup resistor, DHT11 always does. I used a 10k resistor for this. I ended up putting it inside the heatshrink below the board as I was running out of room. Originally I was going to use the DHT22 and it wouldn't have mattered.

#include <DHT.h>   // Humidity/Temp sensor

#define DHTPIN 8     // Digital Pin
#define DHTTYPE DHT11 //  DHT22
DHT dht(DHTPIN, DHTTYPE);

int dhtHumidity = 0; // returns a percentage
float dhtTemp = 0;  // returns Celcius as the default


void setup()  {  

	Serial.begin(115200);


	dht.begin(); 
}

void loop() {

	dhtHumidity = dht.readHumidity();<br>	dhtTemp = dht.readTemperature();

	// Check if any reads failed and exit early (to try again).
	if (isnan(dhtHumidity) || isnan(dhtTemp)) {  // int   isnan(double __x) // returns 1 if "not a number"
	    Serial.println("Failed to read from DHT sensor!");
	    return;
  	}

	Serial.print("dhtHumidity:  "); Serial.print(dhtHumidity); Serial.println(" %");
	Serial.print("dhtTemp:  "); Serial.print(dhtTemp); Serial.println(" *C");

}

Step 3: Hall Effect Sensor - Rain Gauge

As I was using a weather station that I brought from my local electronics store I had to figure out what the readings from the sensors meant. Of course there was no datasheet that came with it, even the wires didn't tell me anything of relevance. The sensible thing to do would be to get one that came with handy terminals and full documentation. Alas I am not sensible but at least I have a lot of perseverance.

So after pulling out the existing board that was tucked in next to the rain gauge, which I had no idea how to salvage/reuse anyway, I began by trying to make sense of it all. I found this datasheet from Sparkfun which was for a similar weather station. I also copied/learned a lot from this Sparkfun tutorial.

The Rain Gauge mechanism is a self-emptying tipping bucket which has a tiny magnet in the middle. For every 0.2794mm of rain (0.0011" for those so inclined), the gauge tips and the tiny magnet in the middle of the double bucket momentarily triggers the Hall Effect sensor. The sensor is on an interupt pin using the Arduino's internal pullup, this stops the arduino where ever it is in the loop to count every tip of the bucket . The old hall sensor was fixed to the board I discarded so this was something that I had to add. The Hall Effect sensor is able to sense the tiny magnet even though it sits inside the plastic compartment.

Note the use of volatile before some of the variables, this is to do with how they are stored on the Arduino. The link will explain it in further detail.

The other important thing to note are the time variables. The code in the loop keeps track of which second and minute it is and updates the rainHour array so if an interrupt occurs then the rain within that minute is stored in that minute within the array. The rainHour array uses minutes as the index, e.g. while it is the 4th minute every drop of rain is added to that index within the array. When it is a new minute the loop resets the rainHour at that minute to 0, which is essentially pushing out the oldest minute so any new rain isn't added to it. It's a running record of the last 60mins not this current hour. It's the same as dailyrainmm however we are not saving an array of the days, that is easier to do at the other end where the data ends up. For The Weather Master this will be on a Raspberry Pi which will come in a following Instructable.

You may notice the fundamental problem of the dailyrainmm variable is that it doesn't reset on a new day. We could use the loop to count the hours and trigger when a new day happens, but then you would have to start the exactly on midnight and hope it doesn't glitch out over time. In Part 2 of The Weather Master I will show you how to send data via the serial so that we can communicate to an Adafruit Huzzah ESP8266 and that will tell the Arduino when it is a new day.

// Digital Pin

const byte RAIN = 3; // Interrupt Pin. 

// Time Variables
long lastSecond; //The millis counter to see when a second rolls by
byte seconds; //When it hits 60, increase the current minute
byte minutes; //Keeps track of where we are in various arrays of data
byte minutes_10m; //Keeps track of where we are in wind gust/dir over last 10 minutes array of data
byte day;
byte lastDay;


//Rain Variables
float rainmm = 0; // [rain millimeters over the past hour)] -- the accumulated rainfall in the past 60 min
float dailyrainmm = 0; // [rain inches so far today in local time]

// volatiles are subject to modification by IRQs volatile unsigned long raintime, rainlast, raininterval, rain;
volatile float rainHour[60]; //60 floating numbers to keep track of 60 minutes of rain


// Interrupt Request (IRQ) these are called by the hardware interrupts, not by the main code
// Count rain gauge bucket tips as they occur, which are activated by the magnet and hall switch in the rain gauge, attached to input D3
void rainIRQ()  {
	raintime = millis(); // grab current time
	raininterval = raintime - rainlast; // calculate interval between this and last event

	if (raininterval > 10) {   // ignore switch-bounce glitches less than 10mS after initial edge 
		dailyrainmm += 0.2794;  //Use 0.011 for inches. Each dump is of water 0.2794mm
		rainHour[minutes] += 0.2794; //Increase this minute's amount of rain
		rainlast = raintime; // set up for next event 
	}
}

void setup() {

	Serial.begin(115200);


	seconds = 0;
	lastSecond = millis();

	pinMode(RAIN, INPUT_PULLUP); // input from rain gauge sensor using arduino's internal pull up resistor

 	// attach external interrupt pin to Rain IRQ function
	attachInterrupt(digitalPinToInterrupt(3), rainIRQ, FALLING); // Pin 3 on Arduino Leonardo; Pin 2 on Uno.

	interrupts(); // turn on interrupts

}

void loop() {

	//Keep track of which second and minute it is
	if (millis() - lastSecond >= 1000) {
		lastSecond += 1000;
		if (++seconds > 59) {
			seconds = 0;
			if (++minutes > 59) {
				minutes = 0;
			}
			if (++minutes_10m > 9) minutes_10m = 0;

			rainHour[minutes] = 0; //Zero out this minute's rainfall amount because we are cycling through the array
		}
	}

	//Total rainfall for the day is calculated within the interrupt
	//Calculate amount of rainfall for the last 60 minutes
	rainmm = 0;
	for (int i = 0 ; i < 60 ; i++) {
		rainmm += rainHour[i];

	}
	

	Serial.print("Rain fall for the last 60mins: "); Serial.print(rainmm); Serial.println("mm");
	Serial.print("Total Rain fall today: "); Serial.print(dailyrainmm); Serial.println("mm");
		
}


Step 4: Wind - Speed and Direction

The Anemometer is used is a cup-type which triggers a reed/magnet switch and because this needs to be counted all the time it is also on an interrupt. This was pretty straightforward and I could use the existing mechanism. I just had to count the 'clicks'. A wind speed of 2.4km/h (1.492mph) causes the switch to close once per second and a full rotation should give you 2 clicks. You can easily test it once it's connected by spinning it yourself to monitor the clicks. As far as I could tell in my research I had to assume that it also needs 2.4km/h because I don't have a casual wind tunnel lying around.

The Wind Vane I used gets a bit tricky. The Sparkfun datasheet shows one that has 8 switches that are connected to different resistors which allows 16 different position. I opened up the weather station I purchased from JayCar and found that it has 4 switches therefore an acuracy to 8 position instead of 16. If the wind vane is between two switches then both switches are being triggered which is why you get a different reading as to when it is directly on only one of them. Therefore it makes 8 wind positions out of 4 switches.

// digital I/O pins
const byte WSPEED = 2; // Interrupt Pin. 

// analog I/O pins<br>const byte WDIR = A0;

//Time  Variables
long lastSecond; //The millis counter to see when a second rolls by 
byte seconds; //When it hits 60, increase the current minute 
byte seconds_2m; //Keeps track of the "wind speed/dir avg" over last 2 minutes array of data 
byte minutes; //Keeps track of where we are in various arrays of data 
byte minutes_10m; //Keeps track of where we are in wind gust/dir over last 10 minutes array of data
long lastWindCheck = 0;
volatile long lastWindIRQ = 0; 
volatile byte windClicks = 0;
byte windspdavg[120]; //120 bytes to keep track of 2 minute average

#define WIND_DIR_AVG_SIZE 120<br>int winddiravg[WIND_DIR_AVG_SIZE]; //120 ints to keep track of 2 minute average
float windgust_10m[10]; //10 floats to keep track of 10 minute max
int windgustdirection_10m[10]; //10 ints to keep track of 10 minute max
int winddir = 0; // [0-360 instantaneous wind direction]<br>String windHeading = ""; // get_wind_directionStr();
float windspeedkph = 0; // [kph instantaneous wind speed]
float windgustkph = 0; // [mph current wind gust, using software specific time period]
int windgustdir = 0; // [0-360 using software specific time period]
float windspdkph_avg2m = 0; // [mph 2 minute average wind speed mph]
int winddir_avg2m = 0; // [0-360 2 minute average wind direction]
float windgustkph_10m = 0; // [mph past 10 minutes wind gust mph ]
int windgustdir_10m = 0; // [0-360 past 10 minutes wind gust direction]
int AnalogWindDirValue; // I added<br>byte winddirByte = 45;  // 0-7 to represent the 8 posible positions that it can be at on a compass.

void wspeedIRQ()
// Activated by the magnet in the anemometer (2 ticks per rotation), attached to input D3 ( or D2 for Leonardo )
{
  if (millis() - lastWindIRQ > 10) // Ignore switch-bounce glitches less than 10ms (142MPH max reading) after the reed switch closes
  {
    lastWindIRQ = millis(); //Grab the current time
    windClicks++; //There is 1.492MPH for each click per second.
    // Serial.println(" windClicks:  " + windClicks); // For testing
  }
}

void setup() {
	Serial.begin(115200);

	pinMode(WSPEED, INPUT_PULLUP); // input from wind meters windspeed sensor

	// attach external interrupt pin to IRQ functions<br>	attachInterrupt(digitalPinToInterrupt(2), wspeedIRQ, FALLING); 

	// turn on interrupts<br>	interrupts();

	seconds = 0;<br>	lastSecond = millis();

}

void loop()<br>{
	//Keep track of which minute it is
  	if (millis() - lastSecond >= 1000) {
		lastSecond += 1000;

		//Take a speed and direction reading every second for 2 minute average
		if (++seconds_2m > 119) seconds_2m = 0;

	    	//Calc the wind speed and direction every second for 120 second to get 2 minute average
		float currentSpeed = get_wind_speed();
	 	windspeedkph = currentSpeed;
		//float currentSpeed = random(5); //For testing
		int currentDirection = get_wind_direction();
		windspdavg[seconds_2m] = (int)currentSpeed;
		winddiravg[seconds_2m] = currentDirection;
		//if(seconds_2m % 10 == 0) displayArrays(); //For testing

		//Check to see if this is a gust for the minute
		if (currentSpeed > windgust_10m[minutes_10m]) {
	    		windgust_10m[minutes_10m] = currentSpeed;
			windgustdirection_10m[minutes_10m] = currentDirection;
		}

		//Check to see if this is a gust for the day
	  	if (currentSpeed > windgustkph) {
			windgustkph = currentSpeed;
      			windgustdir = get_wind_direction(); //get_wind_directionStr(); // currentDirection;
    		}	
		if (++seconds > 59) {
     			seconds = 0;

			if (++minutes > 59) {
       				minutes = 0;
        			Serial.println("minute");
        		}
		if (++minutes_10m > 9) minutes_10m = 0;

		windgust_10m[minutes_10m] = 0; //Zero out this minute's gust
		}
 	}
	delay(100);<br>}

void calcWind() {

	winddir = get_wind_direction();<br>  	windHeading = get_wind_directionStr();

	//Calc windspdmph_avg2m
	float tempAvg = 0;  
	for (int i = 0 ; i < 120 ; i++) {
		tempAvg += windspdavg[i];
	}
	temp /= 120.0;
	windspdkph_avg2m = tempAvg;

	//Calc winddir_avg2m, Wind Direction
	//You can't just take the average. Google "mean of circular quantities" for more info
  	//We will use the Mitsuta method because it doesn't require trig functions
	//And because it sounds cool.
  	//Based on http://abelian.org/vlf/bearings.html
  	//Based on http://stackoverflow.com/questions/1813483/averaging-angles-again
  	long sum = winddiravg[0];
  	int D = winddiravg[0];
  	for (int i = 1 ; i < WIND_DIR_AVG_SIZE ; i++) {
    		int delta = winddiravg[i] - D;
	if (delta < -180)
		D += delta + 360;
	else if (delta > 180)
		D += delta - 360;
	else
      D += delta;
    sum += D;
  	}
 	 winddir_avg2m = sum / WIND_DIR_AVG_SIZE;
  	if (winddir_avg2m >= 360) winddir_avg2m -= 360;
  	if (winddir_avg2m < 0) winddir_avg2m += 360;

	//Calc windgustmph_10m
	//Calc windgustdir_10m
	//Find the largest windgust in the last 10 minutes
	windgustkph_10m = 0;
	windgustdir_10m = 0;

	//Step through the 10 minutes
	for (int i = 0; i < 10 ; i++) {
		if (windgust_10m[i] > windgustkph_10m) {
		      windgustkph_10m = windgust_10m[i];
		      windgustdir_10m = windgustdirection_10m[i];
    		}
  	}
}

//Returns the instataneous wind speed<br>float get_wind_speed() {
	float deltaTime = millis() - lastWindCheck; //750ms
	deltaTime /= 1000.0; //Covert to seconds
	float windSpeed = (float)windClicks / deltaTime; //3 / 0.750s = 4
	windClicks = 0; //Reset and start watching for new wind
	lastWindCheck = millis();

	// If the switch is closed once per second then the speed is 1.492 MPH or 2.4011412 km/h.  If using MPH: windSpeed *= 1.492;
  	windSpeed *= 2.4011412; //  e.g. 4 * 2.4011412 = 9.604565 km/h

	  // Serial.println(); Serial.print("Wind Speed kph:"); Serial.println(windSpeed);

	return (windSpeed);
}

//Read the wind direction sensor, return heading in degrees
int get_wind_direction() {
	unsigned int adc;
	adc = analogRead(WDIR); // get the current reading from the sensor
	AnalogWindDirValue = adc;  // For testing raw analog value

	// The following table is ADC readings for the wind direction sensor output, sorted from low to high.
	// Each threshold is the midpoint between adjacent headings. The output is degrees for that ADC reading.
	// Note that these are not in compass degree order! See Weather Meters datasheet for more information.

	// The Weather Station I purchased from JayCar has an acuracy to 8 position instead of 16.
	// New analog readings since setting it up with the ArduinoPro. I added 10 to each value to increase its range in just case.
	if (adc < 565)  return (45); // NE   if (adc < 360)
	if (adc < 607)  return (135); // SE  if (adc < 387)
	if (adc < 657)  return (90); // E  if (adc < 420)
	if (adc < 759)  return (315); // NW  if (adc < 487)
	if (adc < 803)  return (0); // N  if (adc < 518)
	if (adc < 843)  return (225); // SW  if (adc < 543)
	if (adc < 900)  return (180); // S  if (adc < 582)
	if (adc < 957)  return (270); // W  if (adc < 620)

	return (-1); // error, disconnected?
}


//Read the wind direction sensor, return heading in degrees
String get_wind_directionStr() {
	unsigned int adc;
	adc = analogRead(WDIR); // get the current reading from the sensor
	AnalogWindDirValue = adc;  // For testing raw analog value

	// The following table is ADC readings for the wind direction sensor output, sorted from low to high.
	// Each threshold is the midpoint between adjacent headings. The output is degrees for that ADC reading.
	// Note that these are not in compass degree order! See Weather Meters datasheet for more information.

	// The Weather Station I purchased from JayCar has an acuracy to 8 position instead of 16.
	// New analog readings since setting it up with the ArduinoPro. I added 10 to each value to increase its range just in case.
	if (adc < 565)  return "045"; // return (045); // NE   if (adc < 360)
	if (adc < 607)  return "135"; //  return (135); // SE  if (adc < 387)
	if (adc < 657)  return "090"; // return (090); // E  if (adc < 420)
	if (adc < 759)  return "315"; // return (315); // NW  if (adc < 487)
	if (adc < 803)  return "000"; //  return (000); // N  if (adc < 518)
	if (adc < 843)  return "225"; //  return (225); // SW  if (adc < 543)
	if (adc < 900)  return "180"; //  return (180); // S  if (adc < 582)
	if (adc < 957)  return "270"; //  return (270); // W  if (adc < 620)

	return ("-1"); // error, disconnected?
}


Snippet from the Original Sparkfun code:

// Snippet from the Original Sparkfun code
// <a href="https://github.com/sparkfun/Wimp_Weather_Station/blob/master/Wimp_Weather_Station.ino" target="_blank">https://github.com/sparkfun/Wimp_Weather_Station/b...</a><br>
unsigned int adc;
adc = averageAnalogRead(WDIR); // get the current reading from the sensor 


if (adc < 380) return (113);      
if (adc < 393) return (68);
if (adc < 414) return (90);
if (adc < 456) return (158);
if (adc < 508) return (135);
if (adc < 551) return (203);
if (adc < 615) return (180);
if (adc < 680) return (23);
if (adc < 746) return (45);
if (adc < 801) return (248);
if (adc < 833) return (225);
if (adc < 878) return (338);
if (adc < 913) return (0);
if (adc < 940) return (293);
if (adc < 967) return (315);
if (adc < 990) return (270);

With my modified version you can see that I had to change the values somewhat, so a bit of trial and error is required. But when you have it plugged into an arduino you can easily test it. My Wind Vane had a marker indicating north, which is then crucial to adhere to when you finally go to install it on your roof or fence.

Step 5: Putting It All Together

Trying to get the pcb board with the Hall Effect sensor on one side and the BMP280 on the other as well as the Arduino sandwiched on top was a bit tricky. However the original weather station used 2 AA batteries that was positioned next to the old board. With them both removed I was able to fit it in, with the added bonus of being able to use the access to the battery slot for the Arduino in case I need to reprogram it with the FTDI breakout board.

const int sketchVersion = 9; // This way for my purpose to know which sketch I had uploaded to it
boolean printStream = true; // For testing Set to true to print transmited weather data to the serial
#include <SoftwareSerial.h>
#include <DHT.h> //Humidity/Temp sensor
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BMP280.h>
          

      
#define DHTPIN 8     // what digital pin we're connected to
#define DHTTYPE DHT11   // DHT22
DHT dht(DHTPIN, DHTTYPE);
      
// hardware SPI
#define BMP_SCK 13
#define BMP_MISO 12
#define BMP_MOSI 11
#define BMP_CS 10
      
Adafruit_BMP280 bmp(BMP_CS, BMP_MOSI, BMP_MISO,  BMP_SCK);
      

// digital I/O pins
const byte WSPEED = 2; // Interrupt Pin. 
const byte RAIN = 3; // Interrupt Pin. <br><br></adafruit_bmp280.h></adafruit_sensor.h></spi.h></dht.h></softwareserial.h>// analog I/O pins
const byte WDIR = A0;

// Time Variables
long lastSecond; //The millis counter to see when a second rolls by
byte seconds; //When it hits 60, increase the current minute
byte seconds_2m; //Keeps track of the "wind speed/dir avg" over last 2 minutes array of data
byte minutes; //Keeps track of where we are in various arrays of data
byte minutes_10m; //Keeps track of where we are in wind gust/dir over last 10 minutes array of data
byte day;
byte lastDay;

long lastWindCheck = 0;
volatile long lastWindIRQ = 0;
volatile byte windClicks = 0;



byte windspdavg[120]; //120 bytes to keep track of 2 minute average

#define WIND_DIR_AVG_SIZE 120
int winddiravg[WIND_DIR_AVG_SIZE]; //120 ints to keep track of 2 minute average, 1 per second
float windgust_10m[10]; //10 floats to keep track of 10 minute max
int windgustdirection_10m[10]; //10 ints to keep track of 10 minute max


//These are all the weather values that we will export:
int winddir = 0; // [0-360 instantaneous wind direction]
String windHeading = ""; // get_wind_directionStr();
float windspeedkph = 0; // [kph instantaneous wind speed]
float windgustkph = 0; // [mph current wind gust, using software specific time period]
int windgustdir = 0; // [0-360 using software specific time period]
float windspdkph_avg2m = 0; // [mph 2 minute average wind speed mph]
int winddir_avg2m = 0; // [0-360 2 minute average wind direction]
float windgustkph_10m = 0; // [mph past 10 minutes wind gust mph ]
int windgustdir_10m = 0; // [0-360 past 10 minutes wind gust direction]
int dhtHumidity = 0; // [%]
float dhtTemp = 0;
float tempC = 0; // [temperature C]
float rainmm = 0; // [rain inches over the past hour)] -- the accumulated rainfall in the past 60 min
float dailyrainmm = 0; // [rain inches so far today in local time]
int pressure;

// volatiles are subject to modification by IRQs (interrupts)
volatile unsigned long raintime, rainlast, raininterval, rain;
volatile float rainHour[60]; //60 floating numbers to keep track of 60 minutes of rain

int AnalogWindDirValue; // For testing
byte winddirByte = 45;  // 0-7 to represent the 8 possible positions that it can be at on a compass.




//Interrupt Requests (these are called by the interrupts, not by the main code)


void wspeedIRQ()
// Activated by the magnet in the anemometer (2 ticks per rotation), attached to input D2
{
  if (millis() - lastWindIRQ > 10) // Ignore switch-bounce glitches less than 10ms (142MPH max reading) after the reed switch closes
  {
    lastWindIRQ = millis(); //Grab the current time
    windClicks++; //There is 1.492MPH for each click per second.
    // Serial.println(" windClicks:  " + windClicks); // For testing
  }
}

void rainIRQ()<br>// Count rain gauge bucket tips as they occur
// Activated by the magnet and reed switch/hall effect sensor in the rain gauge, attached to input D3
{
  raintime = millis(); // grab current time
  raininterval = raintime - rainlast; // calculate interval between this and last event

 if (raininterval > 10) // ignore switch-bounce glitches less than 10mS after initial edge
  {
    dailyrainmm += 0.2794;  // dailyrainin += 0.011; //Each dump is 0.011" of water (or 0.2794mm)
    rainHour[minutes] += 0.2794; //Increase this minute's amount of rain

    rainlast = raintime; // set up for next event
  }
}



void setup()
{
  Serial.begin(115200);



 Serial.println("WeatherPro Initiating..");
  dht.begin(); // Temp & Humidity

  pinMode(WSPEED, INPUT_PULLUP); // input from Anemometer (windspeed) sensor
  pinMode(RAIN, INPUT_PULLUP); // input from Hall Effect (rain gauge) sensor

  if (!bmp.begin()) {
    Serial.println("Could not find a valid BMP280 sensor, check wiring!");
    // while (1);
  }

  seconds = 0;
  lastSecond = millis();

	 // attach external interrupt pins to IRQ functions
	attachInterrupt(digitalPinToInterrupt(2), wspeedIRQ, FALLING); // Pin 2 on Arduino Leonardo; Pin 3 on Uno.
	attachInterrupt(digitalPinToInterrupt(3), rainIRQ, FALLING); // Pin 3 on Arduino Leonardo; Pin 2 on Uno
 

  // turn on interrupts
  interrupts();


  Serial.print("WeatherPro_"); Serial.print(sketchVersion); Serial.println(" active!");

}

void loop()
{

  //Keep track of which minute it is
  if (millis() - lastSecond >= 1000)
  {

    lastSecond += 1000;

   //Take a speed and direction reading every second for 2 minute average
    if (++seconds_2m > 119) seconds_2m = 0;

    //Calc the wind speed and direction every second for 120 seconds to get 2 minute average
    float currentSpeed = get_wind_speed();
    windspeedkph = currentSpeed;
    //float currentSpeed = random(5); //For testing
    int currentDirection = get_wind_direction();
    windspdavg[seconds_2m] = (int)currentSpeed;
    winddiravg[seconds_2m] = currentDirection;
    //if(seconds_2m % 10 == 0) displayArrays(); //For testing

    //Check to see if this is a gust for the minute
    if (currentSpeed > windgust_10m[minutes_10m])
    {
      windgust_10m[minutes_10m] = currentSpeed;
      windgustdirection_10m[minutes_10m] = currentDirection;
    }

    //Check to see if this is a gust for the day
    if (currentSpeed > windgustkph)
    {
      windgustkph = currentSpeed;
      windgustdir = get_wind_direction(); //get_wind_directionStr(); // currentDirection;
    }

    if (++seconds > 59)
    {
      seconds = 0;

    if (++minutes > 59) {
        minutes = 0;
        Serial.println("minute");
        //printWeather(); //Report all readings every minute
      }
      if (++minutes_10m > 9) minutes_10m = 0;

      rainHour[minutes] = 0; //Zero out this minute's rainfall amount
      windgust_10m[minutes_10m] = 0; //Zero out this minute's gust
    }

	//Report all readings every second
	calcWeather();
	printWeather(); 
  }

  delay(100);
}

//Calculates each of the variables that wunderground is expecting
void calcWeather()
{
  //Calc winddir
  winddir = get_wind_direction();
  windHeading = get_wind_directionStr();

 //Calc windspeed
  //windspeedmph = get_wind_speed(); //This is calculated in the main loop

  //Calc windgustmph
  //Calc windgustdir
  //These are calculated in the main loop

  //Calc windspdmph_avg2m
  float temp = 0;
  for (int i = 0 ; i < 120 ; i++)
    temp += windspdavg[i];
  temp /= 120.0;
  windspdkph_avg2m = temp;

 //Calc winddir_avg2m, Wind Direction
  //You can't just take the average. Google "mean of circular quantities" for more info
  //We will use the Mitsuta method because it doesn't require trig functions
  //And because it sounds cool.
  //Based on <a href="http://abelian.org/vlf/bearings.html" target="_blank">http://abelian.org/vlf/bearings.html</a>
  //Based on <a href="http://stackoverflow.com/questions/1813483/averaging-angles-again" target="_blank">http://stackoverflow.com/questions/1813483/averaging-angles-again</a>
  long sum = winddiravg[0];
  int D = winddiravg[0];
  for (int i = 1 ; i < WIND_DIR_AVG_SIZE ; i++)
  {
    int delta = winddiravg[i] - D;

    if (delta < -180)
      D += delta + 360;
    else if (delta > 180)
      D += delta - 360;
    else
      D += delta;

    sum += D;
  }
  winddir_avg2m = sum / WIND_DIR_AVG_SIZE;
  if (winddir_avg2m >= 360) winddir_avg2m -= 360;
  if (winddir_avg2m < 0) winddir_avg2m += 360;

  //Calc windgustmph_10m
  //Calc windgustdir_10m
  //Find the largest windgust in the last 10 minutes
  windgustkph_10m = 0;
  windgustdir_10m = 0;
  //Step through the 10 minutes
  for (int i = 0; i < 10 ; i++)
  {
    if (windgust_10m[i] > windgustkph_10m)
    {
      windgustkph_10m = windgust_10m[i];
      windgustdir_10m = windgustdirection_10m[i];
    }
  }

 // Reading temperature or humidity takes about 250 milliseconds!
  // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
  dhtHumidity = dht.readHumidity();
  // dhtHumidity = 45; // for testing
 // Serial.print("dhtHumidity:  "); Serial.print(dhtHumidity); Serial.println(" %");

  // Read temperature as Celsius (the default)
  dhtTemp = dht.readTemperature(); // testing dhtTemp = 13;
  //Serial.print("dhtTemp:  "); Serial.print(dhtTemp); Serial.println(" *C");

  // Check if any reads failed and exit early (to try again).
  if (isnan(dhtHumidity) || isnan(dhtTemp)) {  // int   isnan(double __x) // returns 1 if "not a number"
    Serial.println("Failed to read from DHT sensor!");
    return;
  }

  //Total rainfall for the day is calculated within the interrupt
  //Calculate amount of rainfall for the last 60 minutes
  rainmm = 0;
  for (int i = 0 ; i < 60 ; i++)
    rainmm += rainHour[i];

  float pressureRAW =  bmp.readPressure();
  pressure = round(pressureRAW / 100); // Convert to hPa and round to an int

  //  Serial.print("Pressure = ");
  // Serial.print(pressure);
  //Serial.println(" hPa"); // eg: Pressure = 101097.39 Pa
}


printWeather() {
    Serial.print("{sensorPro: ");
    Serial.print(sketchVersion);    
    Serial.print("tempC: ");
    Serial.print(tempC);
    Serial.print("pressure: ");
    Serial.print(pressure);
    Serial.print("humidity: ");
    Serial.print(humidity);    
    Serial.print("winddir: ");
    Serial.print(winddir);    
    Serial.print("windspeedkph: ");
    Serial.print(windspeedkph);
    Serial.print("windgustdir: ");
    Serial.print(windgustdir);    
    Serial.print("windgustkph: ");
    Serial.print(windgustkph);
    Serial.print("winddir_avg2m: ");
    Serial.print(winddir_avg2m);    
    Serial.print("windspdkph_avg2m: ");
    Serial.print(windspdkph_avg2m);
    Serial.print("windgustdir_10m: ");
    Serial.print(windgustdir_10m);    
    Serial.print("windgustkph_10m: ");
    Serial.print(windgustkph_10m);
    Serial.print("rainmm: ");
    Serial.print(rainmm);
    Serial.print("dailyrainmm: ");
    Serial.println(dailyrainmm); 
    Serial.println(); 
    Serial.println();
}




//Returns the instataneous wind speed
float get_wind_speed()
{
  float deltaTime = millis() - lastWindCheck; //750ms

  deltaTime /= 1000.0; //Covert to seconds

  float windSpeed = (float)windClicks / deltaTime; //3 / 0.750s = 4

  windClicks = 0; //Reset and start watching for new wind
  lastWindCheck = millis();

  // windSpeed *= 1.492; //4 * 1.492 = 5.968MPH  // If the switch is closed once per second then the speed is 1.492 MPH or 2.4011412 km/h
  windSpeed *= 2.4011412; //  e.g. 4 * 2.4011412 = 9.604565 km/h

  // Serial.println();
  // Serial.print("Wind Speed kph:");
  // Serial.println(windSpeed);

  return (windSpeed);
}

//Read the wind direction sensor, return heading in degrees
int get_wind_direction()
{
  unsigned int adc;

  adc = analogRead(WDIR); // get the current reading from the sensor
  AnalogWindDirValue = adc;  // For testing raw analog value

  // The following table is ADC readings for the wind direction sensor output, sorted from low to high.
  // Each threshold is the midpoint between adjacent headings. The output is degrees for that ADC reading.
  // Note that these are not in compass degree order! See Weather Meters datasheet for more information.

  // The Weather Station I purchased from JayCar has an acuracy to 8 position instead of 16.
  // New analog readings since setting it up with the ArduinoPro. I added 10 to each value to increase its range in just case.
  if (adc < 565)  return (45); // NE   if (adc < 360)
  if (adc < 607)  return (135); // SE  if (adc < 387)
  if (adc < 657)  return (90); // E  if (adc < 420)
  if (adc < 759)  return (315); // NW  if (adc < 487)
  if (adc < 803)  return (0); // N  if (adc < 518)
  if (adc < 843)  return (225); // SW  if (adc < 543)
  if (adc < 900)  return (180); // S  if (adc < 582)
  if (adc < 957)  return (270); // W  if (adc < 620)

  return (-1); // error, disconnected?

}
//Read the wind direction sensor, return heading in degrees
String get_wind_directionStr()
{
  unsigned int adc;

  adc = analogRead(WDIR); // get the current reading from the sensor
  AnalogWindDirValue = adc;  // For testing raw analog value

  // The following table is ADC readings for the wind direction sensor output, sorted from low to high.
  // Each threshold is the midpoint between adjacent headings. The output is degrees for that ADC reading.
  // Note that these are not in compass degree order! See Weather Meters datasheet for more information.

  // The Weather Station I purchased from JayCar has an acuracy to 8 position instead of 16.
  // New analog readings since setting it up with the ArduinoPro. I added 10 to each value to increase its range just in case.
  if (adc < 565)  return "045"; // return (045); // NE   if (adc < 360)
  if (adc < 607)  return "135"; //  return (135); // SE  if (adc < 387)
  if (adc < 657)  return "090"; // return (090); // E  if (adc < 420)
  if (adc < 759)  return "315"; // return (315); // NW  if (adc < 487)
  if (adc < 803)  return "000"; //  return (000); // N  if (adc < 518)
  if (adc < 843)  return "225"; //  return (225); // SW  if (adc < 543)
  if (adc < 900)  return "180"; //  return (180); // S  if (adc < 582)
  if (adc < 957)  return "270"; //  return (270); // W  if (adc < 620)

  return ("-1"); // error, disconnected?
}

Step 6: In Part 2

In the next installment, when I get time to write it, I will show you how I got the Arduino and the Adafruit Huzzah ESP8266 to communicate with software serial. As well as how to post that data over the wifi to a Raspberry Pi server.

Eventually I will also show in an Instructable how I setup a solar panel with the Sunny Buddy and Lithium battery to power the Arduino and Huzzah ESP8266, and also the display I setup for the Raspberry Pi using a 7" Screen and a java program I compiled in Processing.

If you're slightly impatient you can access all the code for the entire project on GitHub here The Weather Master

But be warned I haven't check everything to make sure it is in working order yet.

Sensors Contest

This is an entry in the
Sensors Contest

Share

    Recommendations

    • Backyard Contest

      Backyard Contest
    • Sew Tough Challenge

      Sew Tough Challenge
    • Sensors Contest

      Sensors Contest

    Discussions