The Weather Master - Part 1
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.
Downloads
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.
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") }
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"); }
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"); }
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.
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? }
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.