randtekk
Published © LGPL

Rube Goldberg Weather Station with Internet Data Storage

A complete DIY weather station that submits its data to the "THINGSPEAK" website, where it can be viewed from any browser.

AdvancedWork in progress20,345
Rube Goldberg Weather Station with Internet Data Storage

Things used in this project

Hardware components

Arduino Mega 2560
Arduino Mega 2560
×1
RTC Module
×1
Pressure/Altitude/Temperature Sensor
Adafruit Pressure/Altitude/Temperature Sensor
×1
Arduino Nano R3
Arduino Nano R3
×1
DHT22 Temperature Sensor
DHT22 Temperature Sensor
×1
Hall Effect Sensor
Hall Effect Sensor
×2
NodeMCU ESP8266 Weather Station Kit
×1

Software apps and online services

ThingSpeak API
ThingSpeak API
Arduino IDE
Arduino IDE

Story

Read more

Code

MegaWeather.ino

Arduino
Code for Data Receiver (Arduino Mega)
#include <AltSoftSerial.h>

#include <Adafruit_Sensor.h>
#include <VirtualWire.h>

#include <OneWire.h>
#include <DallasTemperature.h>
#include <LiquidCrystal.h>

#define TIMEOUT 5000

//SoftwareSerial dataSerial(39, 38); // RX, TX

const int rs = 2, en = 3, d4 = 4, d5 = 5, d6 = 6, d7 = 7;
const String THINGSPEAK_API_WRITE_KEY = "PWLOULJXD5GO8RWP";
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
int alarmLevel = 7000;
int devCount;
int swPin = 11;
int RlyPin = 8;
const int receive_pin = 9;// Pin for RF receiver for weather data from transmitter in yard
//bool OKtoPost;
bool HeatControl;   //ON status means temperature controlling is active
bool SuspendData;
float t1, t2, t3, t4;
/********************************************************************/
// Data wire is plugged into pin 12 on the Arduino
#define ONE_WIRE_BUS 12
/********************************************************************/
// Setup a oneWire instance to communicate with any OneWire devices
// (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);
/********************************************************************/
// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);
/********************************************************************/
DeviceAddress Probe01 = { 0x28, 0xFF, 0x67, 0x18, 0x23, 0x17, 0x04, 0xBF }; //Outdoor Sensor
DeviceAddress Probe02 = { 0x28, 0xFF, 0xF5, 0x22, 0x23, 0x17, 0x04, 0xD8 }; //Under Stairs Sensor
DeviceAddress Probe03 = { 0x28, 0xFF, 0xA9, 0xE7, 0x22, 0x17, 0x04, 0x7F }; // Utility Room
DeviceAddress Probe04 = { 0x28, 0xFF, 0x7D, 0x18, 0x23, 0x17, 0x04, 0x7E };// Under Kitchen

long delayMS;
long OutdoorTrig = 25; // Trigger Relay if outdoor temp is below
long OutdoorCut = 26; // Cutoff Relay if outdoor temp is above
long SinkTrig = 35;
long SinkCut = 38;

struct package
{
  float temperature = 0;
  float humidity = 0;
  float barometer = 0;
  byte wind = 0;
  byte maxWind = 0;
  byte windPacketPeak = 0;
  float rain = 0;
  float rainRate = 0;
  float randDate = 0;
  float randTime = 0;
  float battery = 0;
  long packetID = 0;
  float CKSum = 1;
};


typedef struct package Package;
Package data;
Package lastData;

struct postingPackage{
  float temperature = 0;
  float barometer = 0;
  float humidity = 0; 
  uint8_t avgWind = 0;
  uint8_t peakWindThisRead = 0;
  float rainThisRead = 0;
  float rainRate = 0;
  float battV = 0;
}; 

typedef struct postingPackage PostingPackage;

PostingPackage dataToPost;

bool wxMode;

void setup() {
  pinMode(13, OUTPUT);
  pinMode(RlyPin, OUTPUT);
  pinMode(swPin, INPUT);
  Serial.begin(115200);
  Serial1.begin(57600);
  HeatControl = false;
  SuspendData = false;
  //OKtoPost = false;// becomes true after first temperature readings
  
  //Serial.println("Dallas Temperature IC Control Library Demo");
  // Start up the library
  sensors.begin();
  lcd.begin(16, 2);
  // Print a message to the LCD.
  lcd.print("Pipe Protector 2");
  lcd.setCursor(0, 1);
  devCount = sensors.getDeviceCount();
  Serial.print("Device Count: ");
  Serial.println(devCount);
  delayMS = 60000;// WIll increase to 60000 ms when finished. One reading per minute is plenty
  randomSeed(analogRead(0));
  vw_set_rx_pin(receive_pin);
  vw_setup(2000);   // Bits per sec
  vw_rx_start();       // Start the receiver PLL running
  wxMode = true;// for now, we'll start in weather mode
  //lastData.

  //sensors.requestTemperatures();
}// End Setup

void stateSwitch(void) {
  /* if(delayMS >=50000){
     delayMS=10000;
    }
    else{
     delayMS=60000;
    }*/
}

bool SuspendToggle(void) {
  SuspendData = !SuspendData;
  return (SuspendData);

}

void sendStatus(void) {
  String myStr;// Variable to hold all values as text string for sending over serial port
  myStr = OutdoorTrig;
  myStr.concat(",");
  myStr.concat(OutdoorCut);
  myStr.concat(",");
  myStr.concat(SinkTrig);
  myStr.concat(",");
  myStr.concat(SinkCut);
  myStr.concat(",");
  myStr.concat(delayMS);
  myStr.concat(",");
  myStr.concat(HeatControl);
  myStr.concat(",");
  myStr.concat(SuspendData);
  Serial.println(myStr);
}




int HighestTemp() {
  int retVal;
  for (int cnt = 0; cnt < devCount; cnt++) {
    //int trVal = GetFTempDec(cnt);
    int trVal = sensors.getTempFByIndex(cnt);
    if (cnt == 0) {

      retVal = trVal;
    }
    //Serial.println(sensors.getTempFByIndex(cnt));

    if (retVal < trVal) {
      retVal = trVal;
    }
  }
  // checkRange(retVal);
  return (retVal);
}

float LowestTemp() {
  float retVal;
  for (int cnt = 0; cnt < devCount; cnt++) {
    //int trVal = GetFTempDec(cnt);
    int trVal = sensors.getTempFByIndex(cnt);
    if (cnt == 0) {

      retVal = trVal;
    }
    //Serial.println(sensors.getTempFByIndex(cnt));

    if (retVal > trVal) {
      retVal = trVal;
    }
  }
  //  checkRange(retVal);
  return (retVal);
}

float printTemperature(DeviceAddress deviceAddress)
{

  float retVal = sensors.getTempF(deviceAddress);

  if (retVal == -196.60)
  {
    Serial.print(F("<Error>"));
  }
  else
  {


    Serial.print(retVal);

    Serial.print(F(" F "));
    return (retVal);
    //Serial.print(DallasTemperature::toFahrenheit(tempC));
  }
}// End printTemperature

void serial1Check(){
String myCmd;
 
  if(Serial1.available() > 0){
   Serial.println("You are there");
       myCmd = Serial1.readString();
    Serial.println(myCmd); 
      
  }
  Serial.println("You ARE LEAVING!!");
}

void serialCheck(){
  if (Serial.available() >> 0) {
    String NewVal = Serial.readString();
    lcd.clear();
    if (NewVal == "+") {
      //lcd.print("HEAT ON");
      // delay( 2000);
      HeatControl = true;

    }
    else if (NewVal == "-") {
      lcd.print(F("HEAT OFF"));
      HeatControl = false;
      //delay (2000);
    }
    else if (NewVal == "S") {
      sendStatus;
      //delay (2000);
    }
    else {
      long secs = NewVal.toInt();
      delayMS = secs * 1000;
      //lcd.clear();
      //lcd.print(delayMS);
      //delay (2000);
    }
    // digitalWrite(13,HeatControl);
  }
}


void pipeProtect(){
  sensors.requestTemperatures(); // Send the command to get temperature readings

    /********************************************************************/
    bool RelayOn;
    RelayOn = digitalRead(RlyPin);
    digitalWrite(13, HeatControl); // Board LED indicates Heat Control Status
    if (HeatControl == true) {

      //if (!(t1> OutdoorTrig && t4>SinkTrig)){

      //if (digitalRead(RlyPin)==false){// Heat status is OFF
      if (t1 > OutdoorCut) {
        RelayOn = false;
      }

      if (t4 > SinkCut) {
        RelayOn = false;
      }
      if (t1 < OutdoorTrig) {
        RelayOn = true;

      }

      if (t4 > 41) { //Absolute cutoff if temp at sink is above 41.  No need to heat more that?
        RelayOn = false;

      }

      if (t4 < SinkTrig) {
        RelayOn = true;

      }
      digitalWrite(RlyPin, RelayOn);
      // }
    }
    else {
      digitalWrite(RlyPin, false);
    }

    if (SuspendData == false) {

      Serial.println(F(" ** Temperatures Read **"));

      Serial.print(F("Outdoor temp. is:   "));
      t1 = printTemperature(Probe01);
      Serial.println();

      Serial.print(F("Near Furnace temp. is:   "));
      t2 = printTemperature(Probe02);

      Serial.println();

      Serial.print(F("Under Utility Room temp. is:   "));
      t3 = printTemperature(Probe03);
      Serial.println();

      Serial.print(F("Under Sink temp. is:   "));
      t4 = printTemperature(Probe04);
      String OutLogic;
      OutLogic = "*&*";
      if (digitalRead(RlyPin) == HIGH) {
        OutLogic.concat("+");
      }
      else {
        OutLogic.concat("-");
      }
      Serial.println();
      Serial.println();
      Serial.println(OutLogic);
      Serial.println();


    }

    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print(F("O="));
    lcd.print(t1);
    lcd.print(F(" F="));
    lcd.print(t2);
    lcd.setCursor(0, 1);
    lcd.print(F("U="));
    lcd.print(t3);
    lcd.print(F(" K="));
    lcd.print(t4);
}



void loop(void)
{
  static bool notFirstIteration;
  static long nextOneWireMillis;
  if (!notFirstIteration) {// Occurs on first run.  
    nextOneWireMillis = millis() + delayMS;
    pipeProtect();
    notFirstIteration = true;
  }

  if (wxMode == true) {// If monitoring weather station

    //Serial.println("WXMODE TRUE");
    rxSubLoop();
    
    delay(100);
    if(Serial1.available()>0){

        Serial.println("Something Returned I think");
        while(Serial1.available()){
          char c = Serial1.read();
          Serial.print(c);
        }
        Serial.println("\nDDDD\n");
  }

  }
  // call sensors.requestTemperatures() to issue a global temperature
  // request to all devices on the bus
  /********************************************************************/
  //Serial.print("**Requesting temperatures...");
  serialCheck();//  Check serial ports for incoming commands
  //serial1Check();
  //Serial.println("Out of Serial1Check");

  
  if (nextOneWireMillis <= millis()) { // This section is for pipe protector portion of program
    nextOneWireMillis = millis() + delayMS;
     pipeProtect();
    // You can have more than one DS18B20 on the same bus.
    // 0 refers to the first IC on the wire
    // delay(delayMS+200);
  }
  

}

RX_Lib.ino

Arduino
Data Receiver Code for Arduino Mega
bool CheckData() {
  float CkVal = data.humidity + data.temperature + data.barometer + data.wind + data.rain + data.maxWind + data.rainRate + data.packetID + data.windPacketPeak  + data.randDate + data.randTime + data.battery;

  bool ckB = ((CkVal == data.CKSum) && (data.CKSum > 30000));
  if (ckB == false) {
    data.temperature = 0;
    data.humidity = 0;
    data.barometer = 0;
    data.wind = 0;
    data.rain = 0;
    data.CKSum = 1;

  }
  return (ckB);

}

float rainFallRate() {


}// END rainFallRate() **********************


float windChill(float mTempF, float mMPH) {// Calculate wind chill if temp <50, wind >3
  float Twc;
  if ((mTempF < 50.0) && (mMPH > 3.0)) {
    Twc = 35.74 + 0.6215 * mTempF - 35.75 * pow(mMPH, 0.16) + 0.4275 * mTempF * pow(mMPH, 0.16);
  }
  else
  {
    Twc = mTempF;// return temp unchanged if no wind chill
  }

  return (Twc);
}// End windChill()---------------------------------------------


float RandianDate(byte dayOfMonth, byte month, byte year) { //  Date to integer format - 01-01-1980 becomes 111180
  String myStr;
  byte myDay = dayOfMonth + 10;// Adding 10 to values to make all 2 digit.  No need to do the years.
  byte myMonth = month + 10;
  byte myYear = year + 10;
  //month += 10;
  myStr.concat(myMonth);

  myStr.concat(myDay);

  myStr.concat(myYear);

  return (myStr.toFloat());
  //return(0);
}


float RandianTime(byte hours, byte minutes, byte seconds) { //  Time to integer format - 14:56:03 becomes 145603
  String myStr;
  hours += 10;   // Adding 10 to these values to ensure all are 2 digits.
  minutes += 10; //  Will subtract 10 again when decoded
  seconds += 10;
  myStr.concat(hours);

  myStr.concat(minutes);

  myStr.concat(seconds);

  return (myStr.toFloat());
  //return(0);
}

String RandianToDate(float RandDate) {
  String msg = "";
  msg.concat(RandDate);
  String token;
  String myDt;
  int trans;

  for ( int cnt = 0; cnt < 6 ; cnt++) {
    token.concat(msg.charAt(cnt));
    if ((cnt == 1) || (cnt == 3)) {
      trans = token.toInt() - 10;
      if (trans < 10) {
        myDt.concat("0");
      }
      myDt.concat(trans);
      myDt.concat("-");
      token = "";
    }

    else if (cnt == 5) {
      trans = token.toInt() - 10;
      myDt.concat("20");
      myDt.concat(trans);
    }

  }
  return (myDt);
}

String RandianToTime(float RandTime) {
  String msg = "";
  msg.concat(RandTime);
  String token;
  String myDt;
  int trans;

  for ( int cnt = 0; cnt < 6 ; cnt++) {
    token.concat(msg.charAt(cnt));
    if ((cnt == 1) || (cnt == 3) || (cnt == 5)) {
      trans = token.toInt() - 10;
      if (trans < 10) {
        myDt.concat("0");
      }
      myDt.concat(trans);
      if (cnt < 5) {
        myDt.concat(":");
      }

      token = "";
    }


  }
  return (myDt);
}

void showReport(void) {
  Serial.print(F("\nTemperature: "));
  Serial.print(data.temperature);
  Serial.print(F(" Degrees F\nHumidity: "));
  Serial.print(data.humidity);
  Serial.print(F("% \nBarometer: "));
  Serial.print(data.barometer);
  Serial.print(F(" Inches Hg \nWind (current): "));
  Serial.print(data.wind);
  Serial.print(F(" Mph\n   Feels like "));
  Serial.print(windChill(data.temperature, data.wind));

  Serial.print(F(" Wind Chill\nPeak Wind this Read: "));
  Serial.print(data.windPacketPeak);
  Serial.print(F(" Mph\nPeak Wind Today: "));
  Serial.print(data.maxWind);
  Serial.print(F(" Mph\nRainfall: "));
  Serial.print(data.rain);
  Serial.print(F(" Inches\nRain Rate: "));
  Serial.print(data.rainRate);
  Serial.print(F(" Inches per Hour\n\nPacket ID: "));

  Serial.print(data.packetID);
  //Serial.print("\nCheck Sum: ");
  //Serial.println(data.CKSum);
  Serial.print(F("\nDate: "));
  Serial.println(RandianToDate(data.randDate));
  Serial.print(F("Time: "));
  Serial.println(RandianToTime(data.randTime));
  Serial.println("");
  Serial.print(F("Battery: "));
  Serial.print(data.battery);
  Serial.println(F(" Volts\n\n****************************************\n"));



}

void dataPrep() { // Process incoming data, prepare dataToPost for transmission to network interface.  Average some values

  //static bool firstSeries;
  static int avgCounter;
  static float tempAvg;
  static int windAvg;
  static int windPeak;
  static int windRing[10];
  static float tempRing[10];// the "Ring arrays keep the last 10 readings for averaging, constatly rotating through
  static float rainVals[10];
  static float barVals[10];
  static float humVals[10];
  float tempTot;
  float batt;
  float rainfallRate;
  static int clearCount;

  if (avgCounter == 0) { //  Reset some vals for next post
    windPeak = 0;
    for (int ct; ct < 10 ; ct++) {
      rainVals[ct] = 0;
      tempRing[ct] = 0;
      tempTot = 0;
    }

  }
  windRing[avgCounter] = data.wind;
  if (data.windPacketPeak > windPeak) {
    windPeak = data.windPacketPeak;
  }
  tempRing[avgCounter] = data.temperature;
  humVals[avgCounter] = data.humidity;
  barVals[avgCounter] = data.barometer;
  rainVals[avgCounter] = data.rain;// - lastData.rain;// Rain measurement from station is a daily total-we need to calculate rain per package
  String trTime = RandianToTime(data.randTime);
  bool saveNOW = (trTime.charAt(4) == '0') && (avgCounter > 0);// will cause update at least every 10 packets, but should keep it at 00 minutes
  if ((avgCounter == 9)|| (saveNOW)) { //Last of data packets. Prepare values, insert into dataToPost, then send to Network Interface
    float rainTot;
    float windTot;
    float humTot;
    float barTot;
    // int tempTot;
    for (int ct = 0; ct < avgCounter+1 ; ct++) {
      rainTot = rainVals[ct];// for rain, just use last reading sent. No need to average, will send as total for day
      windTot += windRing[ct];
      tempTot += tempRing[ct];
      barTot += barVals[ct]-28.00;// Subtracting out lowest likely reading.  Averaging difference to increase accuracy in the event of errors
      humTot += humVals[ct];
      /*    Serial.println();
        Serial.print(tempRing[ct]);
        Serial.print("  ");
        Serial.println(tempTot);
        //Serial.println();
      */
    }
    avgCounter ++;// increment for calculating averages (due to zero based array counter)
    dataToPost.battV = data.battery;
    dataToPost.temperature = tempTot / float(avgCounter);
    dataToPost.barometer = (barTot /  float(avgCounter))+28.00;
    dataToPost.humidity = humTot /  float(avgCounter);
    dataToPost.avgWind = windTot / (avgCounter);
    dataToPost.peakWindThisRead = windPeak;
    dataToPost.rainThisRead = rainTot;
    dataToPost.rainRate = data.rainRate;
    Serial.println(F("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"));
    Serial.print(F("\nData to Post:\n"));
    Serial.println(dataToPost.temperature);
    Serial.println(dataToPost.barometer);
    Serial.println(dataToPost.humidity);
    Serial.println(dataToPost.avgWind);
    Serial.println(dataToPost.peakWindThisRead);
    Serial.println(dataToPost.rainThisRead);
    Serial.println(F("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"));
    delay (2000);
    String url = "@";// add start character
    url += String(dataToPost.temperature);
    url += ",2=";
    url += String(dataToPost.humidity);
    url += ",3=";
    url += String(dataToPost.avgWind);
    url += ",4=";
    url += String(dataToPost.peakWindThisRead);
    url += ",5=";
    url += String(dataToPost.rainThisRead);
    url += ",6=";
    url += String(dataToPost.barometer);
    url += ",7=";
    url += String(dataToPost.rainRate);
    url += ",8=";
    url += String(dataToPost.battV);
    url += "~";// add end character

    String datStr;
    datStr.concat(dataToPost.temperature);
    datStr.concat(",");
    datStr.concat(dataToPost.humidity);
    datStr.concat(",");
    datStr.concat(dataToPost.avgWind);
    datStr.concat(",");
    datStr.concat(dataToPost.peakWindThisRead);
    datStr.concat(",");
    datStr.concat(dataToPost.rainThisRead);
    datStr.concat(",");
    datStr.concat(dataToPost.barometer);
    Serial.println (datStr);
    Serial1.print(url);// This line is where data is submitted to Network Interface (NodeMCU)    
    delay(10000);       // Lets wait a little while then    
    Serial1.print(url);// Send again to minimize data gaps due to errors in transmission    

    avgCounter = -1;
    //  Serial.print("IN the Test");
  }

  avgCounter ++;
  if (avgCounter > 9) {
    avgCounter = 0;
  }


}


void simulateData() {
  static long cnt;

  data.temperature = t1;
  data.humidity = t2;
  data.barometer = t3;
  data.wind = random(t4);
  data.maxWind = t4 + random(0, 21);
  data.windPacketPeak = t4 + random(0, 21);
  data.rain = t1;
  data.rainRate = t2;
  data.randDate = 101218;
  data.randTime = 131313;
  data.packetID = cnt;
  cnt ++;

}



void rxSubLoop(void) {

  // Serial.println("In the rxSubLoop");
  uint8_t buf[sizeof(data)];
  uint8_t buflen = sizeof(data);
  //Serial.println("in Loop");
  //vw_wait_message();
  if (vw_have_message())  // Is there a packet for us?
  {
    vw_get_message(buf, &buflen);
    memcpy(&data, &buf, buflen);
    // delay(500);
    bool myCkV = CheckData();// Check for  valid Packet-return true if valid
    if (myCkV == true) {

      if (data.packetID != lastData.packetID) {
        dataPrep();
        showReport();

        lastData = data;

      }


    }
    else {
      Serial.print(F("Bad Packet"));
      Serial.print(F("\n\n****************************************\n\n"));
    }

  }
  /*else{//A place to add a line for data simlation-for devel purposes
    // This ELSE clause will be commented out after new transmitter comes in

       simulateData();
       dataPrep();
       showReport();

      lastData = data;

     delay(30000);// want data packets simulated every 1 minute
     //Serial.println("Exiting RX_Lib");
    }*/




}

Wx-Tx.ino

Arduino
Code for weather station sensors/transmitter
#include <VirtualWire.h>
#include <Time.h>
#include <TimeLib.h>
#include <dht.h>
#include <Adafruit_MPL3115A2.h>
#include <Wire.h>

#include <avr/power.h>
#include <avr/sleep.h>
#include <avr/wdt.h>


#define DS3231_I2C_ADDRESS 0x68
#define OFF false
#define ON true
#define TOGGLE 2// Use if you want to toggle a value instead of just ON/OFF/TRUE/FALSE-for radio function
dht myDHT; // create instance of DHT sensor
Adafruit_MPL3115A2 baro = Adafruit_MPL3115A2();// Create instance of Barometric sensor


time_t myTime;// Is System Time Object
time_t upTime;// Contains time System Timer was set

const uint8_t ledPin = 13;
const uint8_t transmit_pin = 11;
const uint8_t rainPin = 2;// Wind and rain readings handled by interrupts
const uint8_t windPin = 3; // Wind and rain readings handled by interrupts
const uint8_t dhtPin = 4;
const uint8_t rfPower = 5;// control line for RF Power Relay

const float rainPerTip = .0202;// Specific to dimensions of rain gauge built
const int timeComp = -1516;// Value for periodic time compensation. After this many seconds,
// 1 second adjustment is added or subtracted for on the fly Sys Clock a Correction
// Value was computed after 24 hours running, to determine approximate adjustment value.
// This may be further refined as I use unit for longer periods of time.
float rainInches;
volatile long rainLastMillis; // Millis of previous rain "tip"
volatile long rainMillis;     // Millis of current rain tip
float windAdj = 9.5;  //default value for wind speed (Anemometer) adjustment
volatile long windLastMillis; // last millis reading from anemometer
volatile long revTime; // time in millis of current anemometer revolution
volatile int f_wdt = 1;
volatile long nextSleepMillis;
volatile long wokeUpMillis;

long xmitRate = 60000; // xmitRate is how often packets will be transmitted, in milliseconds.
int tipCount; // Rain gauge bucket "tips", .0202 inch per tip for my gauge.  Resets at midnight
float tempDHT; // Temperature from DHT
float humDHT;  // Humidity from DHT
float curWindMPH;  // Calculated wind speed
float topWind;
bool needsReset;  // Set to true at 11:59 PM, to trigger reset at midnight
bool baroSensorPresent; // Set to true if barometric pressure sensor is detected at startup
long timeCompTrigger;// value of next time compensation in millis
bool serialDump;
bool testMode;
byte testMinutes = 2;

byte ckHour;// ckHour and ckMinute are used to see if time to reset (at midnight)
byte ckMinute;
byte ckSecond;
volatile bool beenAsleep;

struct package
{
  float temperature ;
  float humidity ;
  float barometer ;
  byte wind ;
  byte maxWind;
  byte windPacketPeak;
  float rain ;
  float rainRate;
  float randDate;
  float randTime;
  float battery;
  long packetID;
  float CKSum ;
};


typedef struct package Package;
Package data;

//DHT dht(DHTPIN, DHTTYPE);

void setup()
{
  testMode = true;  // Will start in test mode, this will time out in 5 minutes.   Helps aim antenna
  beenAsleep = false;
  serialDump = testMode;
  // Initialise the IO and ISR
  vw_set_tx_pin(transmit_pin);
  vw_set_ptt_inverted(true); // Required for DR3100
  vw_setup(2000);       // Bits per sec
  Serial.begin(9600);
  Serial.println(F("Working to setup"));

  Wire.begin();
  //serialDump = false;
  //serialDump = true;
  baroSensorPresent = true;
  if (! baro.begin()) {
    Serial.println(F("Couldnt find sensor"));
    baroSensorPresent = false;
    //return;
  }
  else {

    baro.write8(0x2D, 0x23);// calibration for altitude
  }

  pinMode (rainPin, INPUT_PULLUP);
  pinMode (dhtPin, INPUT);
  pinMode(ledPin, OUTPUT);
  pinMode (windPin, INPUT_PULLUP);
  pinMode(rfPower, OUTPUT);
  attachInterrupt(0, rain_Count, FALLING);// Define interrupt pins and processes required
  attachInterrupt(1, wind_Count, FALLING);

  //needsReset = true;// used in testing only
  tipCount = 0;// Initial value 0 "tips", 0 inches of rain
  rainInches = 0;
  revTime = 0;
  readDHT();
  /*** Setup the WDT ***/

  /* Clear the reset flag. */
  MCUSR &= ~(1 << WDRF);

  /* In order to change WDE or the prescaler, we need to
     set WDCE (This will allow updates for 4 clock cycles).
  */
  WDTCSR |= (1 << WDCE) | (1 << WDE);

  /* set new watchdog timeout prescaler value */
  WDTCSR = 1 << WDP0 | 1 << WDP3; /* 8.0 seconds */

  /* Enable the WD interrupt (note no reset). */
  WDTCSR |= _BV(WDIE);


  delay(2000);// Delay to prevent re-read of DHT prematurely after startup
  radio(OFF);

} // END Setup() *******************

//!!!!!!!!!!!!!!!!!! The following 3 functions are used by RTC.  DO NOT CHANGE !!!!!!!!!!!!!!!!!!!
byte decToBcd(byte val)
{
  return ( (val / 10 * 16) + (val % 10) );
}
// Convert binary coded decimal to normal decimal numbers
byte bcdToDec(byte val)
{
  return ( (val / 16 * 10) + (val % 16) );
}


void readRTCTime(byte *second,
                 byte *minute,
                 byte *hour,
                 byte *dayOfWeek,
                 byte *dayOfMonth,
                 byte *month,
                 byte *year)
{
  Wire.beginTransmission(DS3231_I2C_ADDRESS);
  Wire.write(0); // set DS3231 register pointer to 00h
  Wire.endTransmission();
  Wire.requestFrom(DS3231_I2C_ADDRESS, 7);
  // request seven bytes of data from DS3231 starting from register 00h
  *second = bcdToDec(Wire.read() & 0x7f);
  *minute = bcdToDec(Wire.read());
  *hour = bcdToDec(Wire.read() & 0x3f);
  *dayOfWeek = bcdToDec(Wire.read());
  *dayOfMonth = bcdToDec(Wire.read());
  *month = bcdToDec(Wire.read());
  *year = bcdToDec(Wire.read());
}


// !!!!!!!!!!!!!!!!  END OF DO NOT CHANGE SECTION !!!!!!!!!!!!!!!!!!!!!!!!!!!

// **  INTERRUPT PROCESSOR SECTION  **
//*************************************************************

void rain_Count() { // Interrupt Process for Rain Gauge Tip Counting
  rainLastMillis = rainMillis;
  rainMillis = millis();
  tipCount ++; // All we aneed to do here is increment the tip counter
}  // END of Interrupt Processor rain_Count --------------------

// ************************************************************
//*************************************************************

void wind_Count() { // Interrupt Process for Wind measuring
  if (beenAsleep) {
    /*
      nextSleepMillis = millis() + 10000;// wait 10 seconds to see if another wind pulse comes in before sleeping again.
      windLastMillis = nextSleepMillis - 10000;
    */
    //long adj = 15;
    long myVal = millis();
    //long newRevTime = myVal - windLastMillis;
    nextSleepMillis = myVal + 9000;// stay awake for a little while to wait for more wind pulses.
    if (1) { //(((curWindMPH > 8) && (newRevTime < revTime/2)) || ((curWindMPH <8) && (newRevTime < revTime/3))) {// Probably Noise-ignore pulse

    }

    else {

      revTime = myVal - windLastMillis;
      windLastMillis = myVal;
      nextSleepMillis = myVal + 12000;
    }

  }
  else {
    long myVal = millis();
    long newRevTime = myVal - windLastMillis;
    if (((curWindMPH > 8) && (newRevTime < revTime/2)) || ((curWindMPH <8) && (newRevTime < revTime/3))) {// Probably Noise-ignore pulse

    }

    else {

      revTime = myVal - windLastMillis;
      windLastMillis = myVal;
    }

  }

  //Serial.println("TRIGGERED");
  //}

}  // END of Interrupt Processor wind_Count


ISR(WDT_vect)// Watchdog timer interrupt routine
{
  if (f_wdt == 0)
  {
    f_wdt = 1;
  }
  else
  {
    //Serial.println("WDT Overrun!!!");
  }
}


// ************************************************************

void sleepNow()
{
  Serial.println(F("Going to sleep now"));
  // Choose our preferred sleep mode:
  set_sleep_mode(SLEEP_MODE_IDLE);

  // Set sleep enable (SE) bit:
  sleep_enable();
  power_adc_disable();
  power_spi_disable();
  power_timer0_disable();
  power_timer2_disable();
  power_twi_disable();
  // Put the device to sleep:
  sleep_mode();

  // Upon waking up, sketch continues from this point.
  sleep_disable();
  Serial.println(F("GOOD MORNING VIET NAM!"));
  power_all_enable();
}


void radio(uint8_t action) {
  int pwrOnDly = 500;
  if (action < 2) {

    digitalWrite(rfPower, action); // If action = ON or OFF, comply
    if (action == ON) {
      delay(pwrOnDly);// Give radio a moment to power up before using0
    }

  }
  else {
    digitalWrite(rfPower, !digitalRead(rfPower)); // Otherwise Toggle
    delay(pwrOnDly);// see above
  }
}

float readBattery(void) {
  float chipVoltage = 5.096;// Set this value to the measured voltage on 5 v pin on Arduino, USING EXPECTED POWER SUPPLY!!
  int sum = 0;                    // sum of samples taken
  unsigned char sample_count = 0; // current sample number
  // take a number of analog samples and add them up
  while (sample_count < 10) {
    sum += analogRead(A1);
    sample_count++;

    delay(10);
  }
  float voltage = ((float)sum / 10.0 * chipVoltage ) / 1024.0;
  // Serial.println(voltage * 1.922);
  return (voltage * 2);
}

void readDHT() {

  static long nextReadMillis;
  if (millis() >= nextReadMillis) {
    int readData = myDHT.read22(dhtPin);
    tempDHT = myDHT.temperature * 1.8 + 32;
    humDHT = myDHT.humidity;
    nextReadMillis = millis() + 2000;// DHT can only be read every 2 seconds.  This variable prevents premature readings
  }

}// END of readDHT()-------------------------------------------

void rain_Set(float myVal) {// Set rain to a specific value, used if restarting during day with rain

  rainInches = myVal;
  tipCount = rainInches / rainPerTip;
  rainLastMillis = 0;
  Serial.println("");
  Serial.print(F("Rainfall Measurement SET to "));
  Serial.print(myVal);
  Serial.println(F(" Imches\n"));
}// END of rain_Set()-----------------------------------------

void rain_Reset() {

  tipCount = 0;
  rainInches = 0;
  rainLastMillis = 0;
  Serial.println("");
  Serial.println(F("Rainfall Measurement RESET!\n"));
  Serial.println(F(""));


}// END of rain_Reset()-----------------------------------------

void wind_Reset() {

  topWind = 0;
  Serial.println();
  Serial.println(F("Maximum Wind RESET!\n"));
  Serial.println();


}// END of wind_Reset()-----------------------------------------

void all_Reset() {

  wind_Reset();
  rain_Reset();


}// END of all()-----------------------------------------


float RandianDate(byte dayOfMonth, byte month, byte year) { //  Date to integer format - 01-01-1980 becomes 111180
  String myStr;
  byte myDay = dayOfMonth + 10;// Adding 10 to values to make all 2 digit.  No need to do the years.
  byte myMonth = month + 10;
  byte myYear = year + 10;
  //month += 10;
  myStr.concat(myMonth);

  myStr.concat(myDay);

  myStr.concat(myYear);

  return (myStr.toFloat());
  //return(0);
}


float RandianTime(byte hours, byte minutes, byte seconds) { //  Time to integer format - 14:56:03 becomes 145603
  String myStr;
  hours += 10;   // Adding 10 to these values to ensure all are 2 digits.
  minutes += 10; //  Will subtract 10 again when decoded
  seconds += 10;
  myStr.concat(hours);

  myStr.concat(minutes);

  myStr.concat(seconds);

  return (myStr.toFloat());
  //return(0);
}

String RandianToDate(float RandDate) {
  String msg = "";
  msg.concat(RandDate);
  String token;
  String myDt;
  int trans;

  for ( int cnt = 0; cnt < 6 ; cnt++) {
    token.concat(msg.charAt(cnt));
    if ((cnt == 1) || (cnt == 3)) {
      trans = token.toInt() - 10;
      if (trans < 10) {
        myDt.concat("0");
      }
      myDt.concat(trans);
      myDt.concat("-");
      token = "";
    }

    else if (cnt == 5) {
      trans = token.toInt() - 10;
      myDt.concat("20");
      myDt.concat(trans);
    }

  }
  return (myDt);
}

String RandianToTime(float RandTime) {
  String msg = "";
  msg.concat(RandTime);
  String token;
  String myDt;
  int trans;

  for ( int cnt = 0; cnt < 6 ; cnt++) {
    token.concat(msg.charAt(cnt));
    if ((cnt == 1) || (cnt == 3) || (cnt == 5)) {
      trans = token.toInt() - 10;
      if (trans < 10) {
        myDt.concat("0");
      }
      myDt.concat(trans);
      if (cnt < 5) {
        myDt.concat(":");
      }

      token = "";
    }


  }
  return (myDt);
}

void stampTime(void) {
  String dateStr;
  String timeStr;
  byte second;
  byte minute;
  byte hour;

  byte dayOfWeek;
  byte dayOfMonth;
  byte month;
  byte year;
  readRTCTime(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month,
              &year);
  ckMinute = minute;
  ckHour = hour;
  ckSecond = second;
  data.randDate = RandianDate(dayOfMonth, month, year);
  data.randTime = RandianTime(hour, minute, second);
  /*
    Serial.print("Hour: ");
    Serial.print(hour);
    Serial.print(" Minute: ");
    Serial.print(minute);
    Serial.print(" Second ");
    Serial.print(second);
    Serial.print("\n\n");
    Serial.println(data.randTime);

    //Serial.println(data.randTime);
    Serial.println(data.randDate);

  */


}

void serialReport(void) {
  Serial.print("\Temperature: ");
  Serial.print(data.temperature);
  Serial.print(F(" Degrees F\nHumidity: "));
  Serial.print(data.humidity);
  Serial.print(F("% \nBarometer: "));
  Serial.print(data.barometer);
  Serial.print(F(" Inches Hg \nWind (current): "));
  Serial.print(data.wind);
  Serial.print(F(" Mph\nPeak Wind this Read: "));
  Serial.print(data.windPacketPeak);
  Serial.print(F(" Mph\nPeak Wind Today: "));
  Serial.print(data.maxWind);
  Serial.print(F(" Mph\nRainfall: "));
  Serial.print(data.rain);
  Serial.print(F(" Inches\nRain Rate: "));
  Serial.print(data.rainRate);
  Serial.print(F(" Inches per Hour\nPacket ID: "));
  Serial.print(data.packetID);
  Serial.print(F("\nCheck Sum: "));
  Serial.println(data.CKSum);
  Serial.print(F("Date: "));
  Serial.print(RandianToDate(data.randDate));
  Serial.print(F("\nTime: "));
  Serial.println(RandianToTime(data.randTime));
  Serial.print(F("Battery Voltage: "));
  Serial.print(data.battery);
  Serial.print(F(" Vdc\n\n"));

  if (testMode == true) {


    Serial.print(F("**  TEST MODE **  TEST MODE **\n\n"));
  }
}

float CheckSum() { // A Check to ensure valid packet received.  Basic Check Sum (Sum of all fields-all are numeric)
  return (data.temperature + data.humidity + data.barometer + data.wind + data.rain + data.rainRate + data.maxWind + data.packetID + data.windPacketPeak + data.randDate + data.randTime + data.battery);
} // END CheckSum **************************


void setDS3231time(byte second, byte minute, byte hour, byte dayOfWeek, byte
                   dayOfMonth, byte month, byte year)
{
  // sets time and date data to DS3231
  Wire.beginTransmission(DS3231_I2C_ADDRESS);
  Wire.write(0); // set next input to start at the seconds register
  Wire.write(decToBcd(second)); // set seconds
  Wire.write(decToBcd(minute)); // set minutes
  Wire.write(decToBcd(hour)); // set hours
  Wire.write(decToBcd(dayOfWeek)); // set day of week (1=Sunday, 7=Saturday)
  Wire.write(decToBcd(dayOfMonth)); // set date (1 to 31)
  Wire.write(decToBcd(month)); // set month
  Wire.write(decToBcd(year)); // set year (0 to 99)
  Wire.endTransmission();
}

void sendData() { // Send data packet by RF transmitter
  radio(ON);
  digitalWrite(ledPin, HIGH); // Flash a light to show transmitting
  //readSensors(); now doing in loop, not here - prevents differences in "same" packet sends
  vw_send((uint8_t *)&data, sizeof(data));
  vw_wait_tx(); // Wait until the whole message is gone
  digitalWrite(ledPin, LOW);
  // delay(2000);
  radio(OFF);
}// END sendData *******************


void readSensors()// Read all sensors and build data package
{

  readDHT();
  data.humidity = humDHT;
  data.temperature = tempDHT;
  data.barometer = baroReading();
  data.wind = Wind_Speed();
  
  data.maxWind = topWind;

  data.rainRate = rainFallRate();
  data.rain = rainInches;
  data.battery = readBattery();
  data.CKSum = CheckSum();
} // END readSensors() ******************************


float rainFallRate() {
  if (tipCount > 0) {

    float tipDuration = rainMillis - rainLastMillis;
    if (millis() > rainMillis + 600000) {// no new tips in last 10 minutes
      tipDuration = millis() - rainLastMillis;
    }

    if (tipDuration > 120 * 60000) { // tip takes over 2 hour2, effective rate 0 for our purposes
      return (0);
    }
    else {
      float tipSeconds = tipDuration / 1000.00;
      float tipsPerHour = 3600.00 / tipSeconds;

      return ( rainPerTip * tipsPerHour);
    }

  }
  else {
    return (0);
  }


}

float baroReading() {
  if (baroSensorPresent == true) {
    float pascals = baro.getPressure();
    // Our weather page presents pressure in Inches (Hg)
    // Use http://www.onlineconversion.com/pressure.htm for other units
    // Serial.print(pascals/3377); Serial.println(" Inches (Hg)");
    return ((pascals / 3377.00) + 0.87); // 0.87 is barometric adjustment

  }
  else {
    return (99.99); // bogus reading if sensor not detected.

  }


}// End baroReading() ----------------------------------------------

/*
  float rainfallRate() {
  float tipDuration = rainMillis - rainLastMillis;
  float tipSeconds = tipDuration / 1000;
  float tipsPerHour = 3600 / tipSeconds;
  float myRainRate = rainPerTip * tipsPerHour;
  return (myRainRate);
  }// END of RainfallRate() ************************
*/
int Wind_Speed() {

  float retValF = 717.00 * windAdj / (revTime);

  if (millis() >= windLastMillis + 10000) { // No wind pulses in 10 seconds, wind is effectively 0
    retValF = 0.00;
  }
  curWindMPH = retValF;
  if (data.windPacketPeak < retValF){
      data.windPacketPeak = retValF;
    }
  if (topWind<data.wind){
  topWind = data.wind;

  }
  return retValF;

}// END of Wind_Speed() ---------------------------------------------


float baroTempReading() {

  float tempF = baro.getTemperature() * 1.8 + 32;
  //Serial.println(tempF);
  //Serial.println(tempF);
  return (tempF);
  // Serial.print(tempC); Serial.println("*C");

}// End baroTempReading() -----------------------------------------

void showHelp() {

  Serial.println(F("- Valid Serial Commands -"));
  Serial.println(F("W ## - Sets Max Wind to ##"));
  Serial.println(F("I ## - Interval in seconds - Sets Packet Interval"));
  Serial.println(F("R ## - Sets Rainfall"));
  Serial.println(F("+  Wind Adj up 1"));
  Serial.println(F("-  Wind Adj down 1"));
  Serial.println(F("XW - Max Wind Reset"));
  Serial.println(F("XR - Rainfall Reset"));
  Serial.println(F("XX - ALL Reset"));
  Serial.println(F("S - Toggle Serial Dump Mode"));
  Serial.println(F("\n** ? - Shows This Help Screen **"));
}// END showHelp()------------------------------------------------


void checkSerial(void) {
  if (Serial.available() > 0) { // Check for and Begin Processing of incoming Serial commands
    String myCmd = Serial.readString();
    char myChr = myCmd.charAt(0);

    if ((myChr == 'W') || (myChr == 'w')) { // "w" should be followed by a number to set max wind to

      myCmd.replace("w", "");
      myCmd.replace("W", "");
      myCmd.replace(",", "");
      myCmd.replace(" ", "");
      float newWind = myCmd.toFloat();
      data.maxWind = newWind;
      topWind = newWind;

    }
    if ((myChr == 'I') || (myChr == 'i')) { // "I" should be followed by a number to set data transmission interval

      myCmd.replace("i", "");
      myCmd.replace("I", "");
      myCmd.replace(",", "");
      myCmd.replace(" ", "");
      float newInterval = myCmd.toFloat();
      xmitRate = newInterval * 1000;// entry will be  in seconds, converting to millis()

    }
    if ((myChr == 'R') || (myChr == 'r')) { // "R" should be followed by a number to set rainfall
      myCmd.replace("r", "");
      myCmd.replace("R", "");
      myCmd.replace(",", "");
      myCmd.replace(" ", "");
      float rcmd = myCmd.toFloat();
      if (rcmd > 0) { // If number is entered, set rainfall to that, else add 1 tip
        rain_Set(rcmd);
      }
      else {
        rain_Count();

      }

    }
    if ((myChr == 'S') || (myChr == 's')) { // toggles Serial Dump mode

      serialDump = !serialDump;
    }

    if (myChr == '+') {

      windAdj ++;
      Serial.print(F("Wind Adj = "));
      Serial.println(windAdj);
    }
    if (myChr == '-') {
      windAdj --;
      Serial.print(F("Wind Adj = "));
      Serial.println(windAdj);
    }
    if (myChr == '!') { //  Toggle Test Mode

      testMode = !testMode;
      serialDump = testMode;
      Serial.print(F("Toggling Test Mode  - Mode="));
      Serial.println(testMode);
    }
    if (myChr == '?') {
      showHelp();
    }
  }

}// END checkSerial() *****************************************


void enterSleep(void)
{
  set_sleep_mode(SLEEP_MODE_PWR_SAVE);
  wdt_reset();
  sleep_enable();


  /* Disable all of the unused peripherals. This will reduce power
     consumption further and, more importantly, some of these
     peripherals may generate interrupts that will wake our Arduino from
     sleep!
  */
  power_adc_disable();
  power_spi_disable();
  power_timer0_disable();
  power_timer2_disable();
  power_twi_disable();
  beenAsleep = true;
  /* Now enter sleep mode. */
  sleep_mode();

  /* The program will continue from here after the timer timeout*/
  sleep_disable(); /* First thing to do is disable sleep. */
  power_all_enable();
  //wokeUpMillis = millis()-10;
  /* Re-enable the peripherals. */

}


void loop()
{


  static int oldTipCount;//Use to check if a Rain "Tip" has generated an Interrupt response
  static long nextSendMillis;
  static byte nextSendSecond;
  static byte nextSendMinute;
  static long packetCount;
  static float maxWindThisPkt;
  long initTestEnd = long(60000 * testMinutes);
  static bool doneFirstLoop;

  // Serial.println("SofarSOgood");
  //initTestEnd = 0;// Testing line-to be removfed
  stampTime();// called here to check time for reset at midnight. stampTime updates values in ckHour, ckMinute, ckSecond
  if (!doneFirstLoop) { // first iteration. Set up a few things for future iterations
    if (xmitRate >= 60000) {
      nextSendMinute = ckMinute + (xmitRate / 60000);
      nextSendSecond = 00;//ckSecond;

    }
    else if (xmitRate < 60000) {
      nextSendMinute = ckMinute;
      nextSendSecond = ckSecond + xmitRate / 1000;
    }

    doneFirstLoop = true;

  }

  if ((millis() >= initTestEnd)  && (millis() <= initTestEnd + 60000)) {
    testMode = false;

  }

  if ((needsReset == false) && (ckHour == 23) && (ckMinute == 59)) {
    needsReset = true;
    //rain_Reset();
  }

  if ((needsReset == true) && (ckMinute < 5)) {
    all_Reset();// Resets Rain, wind Counter, stores todays values as yesterday at midnight, ready for a brand new day!
    needsReset = false;

    //  *********************************************************************************
  }

  checkSerial();
  //serialReport;
  if (xmitRate < 5000) { // Don't allow less than 5 second interval.
    xmitRate = 5000;
  }

  rainInches = tipCount *  rainPerTip;
  readSensors();
  if (maxWindThisPkt <= data.wind) {
    maxWindThisPkt = data.wind;
    if ((data.maxWind < maxWindThisPkt) && (maxWindThisPkt < 120)) { // if maxWindThisPkt >120, either data is corrupted, *OR*
      data.maxWind = maxWindThisPkt;// Weather Station is being destroyed by tornado
      topWind = data.maxWind;
    }

  }

  /*
    Serial.println("---");
    Serial.println(ckMinute);
    Serial.println(nextSendMinute);
    Serial.println(ckSecond);
    Serial.println(nextSendSecond);
    Serial.println("---");
  */

  if (((ckMinute >= nextSendMinute) && (ckSecond >= nextSendSecond)) || (testMode == true)) { // Time to send data packet.
    if ((ckMinute == 59) && (nextSendMinute == 0)) { //Problem condition-need to skip

    }
    else {


      data.windPacketPeak = maxWindThisPkt;
      maxWindThisPkt = 0;
      packetCount ++;
      nextSendMillis = millis() + xmitRate;
      stampTime();
      data.packetID = packetCount;
      readSensors();
      for (int cnt = 0; cnt < 2 ; cnt++) { // Send each packet 3 times, to hopefully eliminate/minimize lost packets.
        sendData();
        //d+elay(500);// NO longer needed, delays built into SendData for turning radio on.
      }
      if (serialDump == true) {
        serialReport();
      }

      if (xmitRate >= 60000) {
        nextSendMinute = (xmitRate / 60000) + ckMinute;
        //nextSendSecond = ckSecond;
        if (nextSendMinute > 59) {
          nextSendMinute -= 60;
        }

      }
      else if (xmitRate < 60000) {
        //nextSendMinute = ckMinute;
        nextSendSecond = ckSecond + xmitRate / 1000;
        if (nextSendSecond > 59) {
          nextSendSecond -= 60;
        }
      }

    }

  }//*******

  if ((!testMode) && (millis() > nextSleepMillis)) {


    Serial.println("Sleeping");
    delay(300);
    beenAsleep = true;
    enterSleep();
    beenAsleep = false;
    Serial.println("Back to Life");
  }
  //*/
  //
}/// END OF LOOP!!!!

Credits

randtekk

randtekk

5 projects • 20 followers

Comments