/**************************************************************************
# File: /home/john/house/weather/recieving-data/weather-to-netcat-1.ino
# Date: Sun 24 May 2015 16:28:03 EDT
# Author: John F. Moore <john@lions-wing.net>
# Last Revised: Time-stamp: <2015-05-25 13:25:00 john> maintained by emacs
# Description:
# Revision: $Header: $
# =======================================================================
***********************************************************************/
/*
This is a combination of the Weather Shield Example. An NTP program to read and format
a Date Time stamp. And a NetCat write module which can place the information into a file
on another computer over the network.
The NPT is talking to Merlin so there is no problem with over loading the public
NTP servers. The data is written to a File which I will then read into the Weewx program
to create weather displays, and eventually send to the Weather Underground to be part
of their program.
*/
/*
Weather Shield Example
By: Nathan Seidle
SparkFun Electronics
Date: November 16th, 2013
License: This code is public domain but you buy me a beer if you use this and we meet someday (Beerware license).
Much of this is based on Mike Grusin's USB Weather Board code: https://www.sparkfun.com/products/10586
This code reads all the various sensors (wind speed, direction, rain gauge, humidty, pressure, light, batt_lvl)
and reports it over the serial comm port. This can be easily routed to an datalogger (such as OpenLog) or
a wireless transmitter (such as Electric Imp).
Measurements are reported once a second but windspeed and rain gauge are tied to interrupts that are
calcualted at each report.
This example code assumes the GPS module is not used.
*/
#include <Wire.h> //I2C needed for sensors
#include "MPL3115A2.h" //Pressure sensor
#include "HTU21D.h" //Humidity sensor
#include <Ethernet.h> // Used for Ethernet
#include <SPI.h>
#include <Time.h>
#include <EthernetUdp.h>
MPL3115A2 myPressure; //Create an instance of the pressure sensor
HTU21D myHumidity; //Create an instance of the humidity sensor
//Hardware pin definitions
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// digital I/O pins
const byte WSPEED = 3;
const byte RAIN = 2;
const byte STAT1 = 7;
const byte STAT2 = 8;
// analog I/O pins
const byte REFERENCE_3V3 = A3;
const byte LIGHT = A1;
const byte BATT = A2;
const byte WDIR = A0;
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//Global 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;
//We need to keep track of the following variables:
//Wind speed/dir each update (no storage)
//Wind gust/dir over the day (no storage)
//Wind speed/dir, avg over 2 minutes (store 1 per second)
//Wind gust/dir over last 10 minutes (store 1 per minute)
//Rain over the past hour (store 1 per minute)
//Total rain over date (store one per day)
byte windspdavg[120]; //120 bytes to keep track of 2 minute average
int winddiravg[120]; //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
volatile float rainHour[60]; //60 floating numbers to keep track of 60 minutes of rain
//These are all the weather values that wunderground expects:
int winddir = 0; // [0-360 instantaneous wind direction]
float windspeedmph = 0; // [mph instantaneous wind speed]
float windgustmph = 0; // [mph current wind gust, using software specific time period]
int windgustdir = 0; // [0-360 using software specific time period]
float windspdmph_avg2m = 0; // [mph 2 minute average wind speed mph]
int winddir_avg2m = 0; // [0-360 2 minute average wind direction]
float windgustmph_10m = 0; // [mph past 10 minutes wind gust mph ]
int windgustdir_10m = 0; // [0-360 past 10 minutes wind gust direction]
float humidity = 0; // [%]
float tempf = 0; // [temperature F]
float rainin = 0; // [rain inches over the past hour)] -- the accumulated rainfall in the past 60 min
volatile float dailyrainin = 0; // [rain inches so far today in local time]
//float baromin = 30.03;// [barom in] - It's hard to calculate baromin locally, do this in the agent
float pressure = 0;
//float dewptf; // [dewpoint F] - It's hard to calculate dewpoint locally, do this in the agent
float batt_lvl = 11.8; //[analog value from 0 to 1023]
float light_lvl = 455; //[analog value from 0 to 1023]
// volatiles are subject to modification by IRQs
volatile unsigned long raintime, rainlast, raininterval, rain;
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//Interrupt routines (these are called by the hardware interrupts, not by the main code)
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void rainIRQ()
// Count rain gauge bucket tips as they occur
// Activated by the magnet and reed switch in the rain gauge, attached to input D2
{
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
{
dailyrainin += 0.011; //Each dump is 0.011" of water
rainHour[minutes] += 0.011; //Increase this minute's amount of rain
rainlast = raintime; // set up for next event
}
}
void wspeedIRQ()
// Activated by the magnet in the anemometer (2 ticks per rotation), attached to input D3
{
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.
}
}
// **** ETHERNET SETTING ****
// Arduino Uno pins: 10 = CS, 11 = MOSI, 12 = MISO, 13 = SCK
// Ethernet MAC address - must be unique on your network - MAC Reads T4A001 in hex (unique in your network)
byte mac[] = { 0x90, 0xA2, 0xDA, 0x0D, 0x55, 0xF3 };
// the dns server ip
IPAddress dnServer(192, 168, 1, 10);
// the router's gateway address:
IPAddress gateway(192, 168, 1, 50);
// the subnet:
IPAddress subnet(255, 255, 255, 0);
//the IP address is dependent on your network
IPAddress ip(192, 168, 1, 134);
// NTP Servers:
IPAddress timeServer(192, 168, 1, 10); // Local time server
// IPAddress timeServer(132, 163, 4, 101); // time-a.timefreq.bldrdoc.gov
// IPAddress timeServer(132, 163, 4, 102); // time-b.timefreq.bldrdoc.gov
// IPAddress timeServer(132, 163, 4, 103); // time-c.timefreq.bldrdoc.gov
// const int timeZone = 1; // Central European Time
//const int timeZone = -5; // Eastern Standard Time (USA)
const int timeZone = -4; // Eastern Daylight Time (USA)
//const int timeZone = -8; // Pacific Standard Time (USA)
//const int timeZone = -7; // Pacific Daylight Time (USA)
EthernetUDP Udp;
unsigned int localPort = 8888; // local port to listen for UDP packets
EthernetClient client;
char server[] = "192.168.1.20"; // IP Adres (or name) of server to dump data to
int interval = 5000; // Wait between dumps
void setup()
{
Serial.begin(9600);
Serial.println("Weather Shield Example");
pinMode(STAT1, OUTPUT); //Status LED Blue
pinMode(STAT2, OUTPUT); //Status LED Green
pinMode(WSPEED, INPUT_PULLUP); // input from wind meters windspeed sensor
pinMode(RAIN, INPUT_PULLUP); // input from wind meters rain gauge sensor
pinMode(REFERENCE_3V3, INPUT);
pinMode(LIGHT, INPUT);
//Configure the pressure sensor
myPressure.begin(); // Get sensor online
myPressure.setModeBarometer(); // Measure pressure in Pascals from 20 to 110 kPa
myPressure.setOversampleRate(7); // Set Oversample to the recommended 128
myPressure.enableEventFlags(); // Enable all three pressure and temp event flags
//Configure the humidity sensor
myHumidity.begin();
seconds = 0;
lastSecond = millis();
// attach external interrupt pins to IRQ functions
attachInterrupt(0, rainIRQ, FALLING);
attachInterrupt(1, wspeedIRQ, FALLING);
// turn on interrupts
interrupts();
Serial.println("Weather Shield online!");
Serial.println("TimeNTP Example");
Ethernet.begin(mac, ip, dnServer, gateway, subnet);
//if (Ethernet.begin(mac) == 0) {
// no point in carrying on, so do nothing forevermore:
//while (1) {
//Serial.println("Failed to configure Ethernet using DHCP");
//delay(10000);
//}
//}
Serial.print("IP number assigned by DHCP is ");
Serial.println(Ethernet.localIP());
Udp.begin(localPort);
Serial.println("waiting for sync");
setSyncProvider(getNtpTime);
}
time_t prevDisplay = 0; // when the digital clock was displayed
void loop()
{
getNtpTime();
//Keep track of which minute it is
if(millis() - lastSecond >= 1000)
{
digitalWrite(STAT1, HIGH); //Blink stat LED
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();
//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 > windgustmph)
{
windgustmph = currentSpeed;
windgustdir = currentDirection;
}
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
windgust_10m[minutes_10m] = 0; //Zero out this minute's gust
}
// if you get a connection, report back via serial:
if (client.connect(server, 1000)) {
Serial.println("-> Connected");
// Make a HTTP request:
client.print( "Date = ");
client.println( now() );
// clientClockDisplay();
printWeather();
client.println();
client.stop();
}
else {
// you didn't get a connection to the server:
Serial.println("--> connection failed/n");
}
digitalWrite(STAT1, LOW); //Turn off stat LED
}
delay(10000);
}
//Calculates each of the variables that wunderground is expecting
void calcWeather()
{
//Calc winddir
winddir = get_wind_direction();
//Calc windspeed
windspeedmph = get_wind_speed();
//Calc windgustmph
//Calc windgustdir
//Report the largest windgust today
windgustmph = 0;
windgustdir = 0;
//Calc windspdmph_avg2m
float temp = 0;
for(int i = 0 ; i < 120 ; i++)
temp += windspdavg[i];
temp /= 120.0;
windspdmph_avg2m = temp;
//Calc winddir_avg2m
temp = 0; //Can't use winddir_avg2m because it's an int
for(int i = 0 ; i < 120 ; i++)
temp += winddiravg[i];
temp /= 120;
winddir_avg2m = temp;
//Calc windgustmph_10m
//Calc windgustdir_10m
//Find the largest windgust in the last 10 minutes
windgustmph_10m = 0;
windgustdir_10m = 0;
//Step through the 10 minutes
for(int i = 0; i < 10 ; i++)
{
if(windgust_10m[i] > windgustmph_10m)
{
windgustmph_10m = windgust_10m[i];
windgustdir_10m = windgustdirection_10m[i];
}
}
//Calc humidity
humidity = myHumidity.readHumidity();
//float temp_h = myHumidity.readTemperature();
//Serial.print(" TempH:");
//Serial.print(temp_h, 2);
//Calc tempf from pressure sensor
tempf = myPressure.readTempF();
//Serial.print(" TempP:");
//Serial.print(tempf, 2);
//Total rainfall for the day is calculated within the interrupt
//Calculate amount of rainfall for the last 60 minutes
rainin = 0;
for(int i = 0 ; i < 60 ; i++)
rainin += rainHour[i];
//Calc pressure
pressure = ( myPressure.readPressure() * 0.0002953337 );
//Calc dewptf
//Calc light level
light_lvl = get_light_level();
//Calc battery level
batt_lvl = get_battery_level();
}
//Returns the voltage of the light sensor based on the 3.3V rail
//This allows us to ignore what VCC might be (an Arduino plugged into USB has VCC of 4.5 to 5.2V)
float get_light_level()
{
float operatingVoltage = analogRead(REFERENCE_3V3);
float lightSensor = analogRead(LIGHT);
operatingVoltage = 3.3 / operatingVoltage; //The reference voltage is 3.3V
lightSensor = operatingVoltage * lightSensor;
return(lightSensor);
}
//Returns the voltage of the raw pin based on the 3.3V rail
//This allows us to ignore what VCC might be (an Arduino plugged into USB has VCC of 4.5 to 5.2V)
//Battery level is connected to the RAW pin on Arduino and is fed through two 5% resistors:
//3.9K on the high side (R1), and 1K on the low side (R2)
float get_battery_level()
{
float operatingVoltage = analogRead(REFERENCE_3V3);
float rawVoltage = analogRead(BATT);
operatingVoltage = 3.30 / operatingVoltage; //The reference voltage is 3.3V
rawVoltage = operatingVoltage * rawVoltage; //Convert the 0 to 1023 int to actual voltage on BATT pin
rawVoltage *= 4.90; //(3.9k+1k)/1k - multiple BATT voltage by the voltage divider to get actual system voltage
return(rawVoltage);
}
//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
/* Serial.println();
Serial.print("Windspeed:");
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
// 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.
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);
return (-1); // error, disconnected?
}
//Prints the various variables directly to the port
//I don't like the way this function is written but Arduino doesn't support floats under sprintf
void printWeather()
{
calcWeather(); //Go calc all the various sensors
client.print("windDir = ");
client.println(winddir);
client.print("windSpeed = ");
client.println(windspeedmph, 1);
client.print("windGust = ");
client.println(windgustmph, 1);
client.print("windGustDir = ");
client.println(windgustdir);
client.print("outHumidity = ");
client.println(humidity, 1);
client.print("outTemp = ");
client.println(tempf, 1);
client.print("rain = ");
client.println(rainin, 2);
client.print("barometer = ");
client.println(pressure, 2);
client.print("radiation = ");
client.println(light_lvl, 2);
client.println("");
}
void digitalClockDisplay(){
// digital clock display of the time
Serial.print(year());
printDays(month());
printDays(day());
Serial.print(" ");
Serial.print(hour());
printDigits(minute());
printDigits(second());
Serial.println();
}
void printDigits(int digits){
// utility for digital clock display: prints preceding colon and leading 0
Serial.print(":");
if(digits < 10)
Serial.print('0');
Serial.print(digits);
}
void printDays(int digits){
// utility for digital clock display: prints preceding colon and leading 0
Serial.print("-");
if(digits < 10)
Serial.print('0');
Serial.print(digits);
}
void clientClockDisplay(){
// digital clock display of the time
client.print(year());
clientprintDays(month());
clientprintDays(day());
client.print(" ");
client.print(hour());
clientprintDigits(minute());
clientprintDigits(second());
client.println();
}
void clientprintDigits(int digits){
// utility for digital clock display: prints preceding colon and leading 0
client.print(":");
if(digits < 10)
client.print('0');
client.print(digits);
}
void clientprintDays(int digits){
// utility for digital clock display: prints preceding colon and leading 0
client.print("-");
if(digits < 10)
client.print('0');
client.print(digits);
}
/*-------- NTP code ----------*/
const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets
time_t getNtpTime()
{
while (Udp.parsePacket() > 0) ; // discard any previously received packets
Serial.println("Transmit NTP Request");
sendNTPpacket(timeServer);
uint32_t beginWait = millis();
while (millis() - beginWait < 1500) {
int size = Udp.parsePacket();
if (size >= NTP_PACKET_SIZE) {
Serial.println("Receive NTP Response");
Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer
unsigned long secsSince1900;
// convert four bytes starting at location 40 to a long integer
secsSince1900 = (unsigned long)packetBuffer[40] << 24;
secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
secsSince1900 |= (unsigned long)packetBuffer[43];
return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
}
}
Serial.println("No NTP Response :-(");
return 0; // return 0 if unable to get the time
}
// send an NTP request to the time server at the given address
void sendNTPpacket(IPAddress &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();
}
Written by John F. Moore
Last Revised: Wed Oct 18 11:01:23 EDT 2017