Daniel Martin
Published © GPL3+

Smartphone Controlled Atomic Weather Station

This project combines Atomic Time keeping with local weather station, and additional cool projects using Arduino and Raspberry Pi.

BeginnerFull instructions provided6 hours1,662
Smartphone Controlled Atomic Weather Station

Things used in this project

Hardware components

Arduino UNO & Genuino UNO
Arduino UNO & Genuino UNO
×1
Raspberry Pi 2 Model B
Raspberry Pi 2 Model B
×1
Adafruit Ultimate GPS Breakout
Adafruit Ultimate GPS Breakout
×1
DHT22 Temperature Sensor
DHT22 Temperature Sensor
×1
RGB Backlight LCD - 16x2
Adafruit RGB Backlight LCD - 16x2
×1
Adafruit Speaker - 3" Diameter - 8 Ohm 1 Watt
×1
Adafruit White LED Backlight Module - Large 45mm x 86mm
×1
Adafruit GPS Antenna - External Active Antenna
×1
Jumper wires (generic)
Jumper wires (generic)
×1
PCB
×1
Rotary potentiometer (generic)
Rotary potentiometer (generic)
×1
USB-A to B Cable
USB-A to B Cable
×1

Software apps and online services

SSH Control

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Schematics

Breadboard Diagram

Connect all the jumper wires as shown. An RGB LCD Display has two more pins at the right end; both must be connected to Ground. The GPS Shield is not shown.

Code

Tones

Arduino
A full list of tones with their corresponding frequencies
/*************************************************
 * Public Constants
 *************************************************/

#define NOTE_B0  31
#define NOTE_C1  33
#define NOTE_CS1 35
#define NOTE_D1  37
#define NOTE_DS1 39
#define NOTE_E1  41
#define NOTE_F1  44
#define NOTE_FS1 46
#define NOTE_G1  49
#define NOTE_GS1 52
#define NOTE_A1  55
#define NOTE_AS1 58
#define NOTE_B1  62
#define NOTE_C2  65
#define NOTE_CS2 69
#define NOTE_D2  73
#define NOTE_DS2 78
#define NOTE_E2  82
#define NOTE_F2  87
#define NOTE_FS2 93
#define NOTE_G2  98
#define NOTE_GS2 104
#define NOTE_A2  110
#define NOTE_AS2 117
#define NOTE_B2  123
#define NOTE_C3  131
#define NOTE_CS3 139
#define NOTE_D3  147
#define NOTE_DS3 156
#define NOTE_E3  165
#define NOTE_F3  175
#define NOTE_FS3 185
#define NOTE_G3  196
#define NOTE_GS3 208
#define NOTE_A3  220
#define NOTE_AS3 233
#define NOTE_B3  247
#define NOTE_C4  262
#define NOTE_CS4 277
#define NOTE_D4  294
#define NOTE_DS4 311
#define NOTE_E4  330
#define NOTE_F4  349
#define NOTE_FS4 370
#define NOTE_G4  392
#define NOTE_GS4 415
#define NOTE_A4  440
#define NOTE_AS4 466
#define NOTE_B4  494
#define NOTE_C5  523
#define NOTE_CS5 554
#define NOTE_D5  587
#define NOTE_DS5 622
#define NOTE_E5  659
#define NOTE_F5  698
#define NOTE_FS5 740
#define NOTE_G5  784
#define NOTE_GS5 831
#define NOTE_A5  880
#define NOTE_AS5 932
#define NOTE_B5  988
#define NOTE_C6  1047
#define NOTE_CS6 1109
#define NOTE_D6  1175
#define NOTE_DS6 1245
#define NOTE_E6  1319
#define NOTE_F6  1397
#define NOTE_FS6 1480
#define NOTE_G6  1568
#define NOTE_GS6 1661
#define NOTE_A6  1760
#define NOTE_AS6 1865
#define NOTE_B6  1976
#define NOTE_C7  2093
#define NOTE_CS7 2217
#define NOTE_D7  2349
#define NOTE_DS7 2489
#define NOTE_E7  2637
#define NOTE_F7  2794
#define NOTE_FS7 2960
#define NOTE_G7  3136
#define NOTE_GS7 3322
#define NOTE_A7  3520
#define NOTE_AS7 3729
#define NOTE_B7  3951
#define NOTE_C8  4186
#define NOTE_CS8 4435
#define NOTE_D8  4699
#define NOTE_DS8 4978

Raspberry Pi Code Atomic Weather Station

Python
Raspberry Pi sends command from smartphone to Arduino
import serial
import time
from forecastiopy import *

ser = serial.Serial('/dev/ttyACM0', 9600)

api_key = "YOUR_API_KEY" 
lat = Your_Latitude 
lng = Your_Longitude 


while 1:
    c = input('Press button to switch mode: ')
    if len(c) == 1:
	
        if c == 'W':
            fio = ForecastIO.ForecastIO(api_key, latitude=lat, longitude=lng)
            current = FIOCurrently.FIOCurrently(fio)
            daily = FIODaily.FIODaily(fio)
            
            weather = str(int(current.temperature))
            we = list(weather)
            for day in range(0, 1):
                for item in daily.get_day(day).keys():
                    if item == 'temperatureMax':
                        #print(str(daily.get_day(day)[item]))
                        highTemp = str(int(daily.get_day(day)[item]))
                    if item == 'temperatureMin':
                        #print(str(daily.get_day(day)[item]))
                        lowTemp = str(int(daily.get_day(day)[item]))

            lT = list(lowTemp)
            hT = list(highTemp)
    
            ser.write("W")
            time.sleep(0.25);
            ser.write(we[0])
            time.sleep(0.25)
            ser.write(we[1])
            time.sleep(0.25)
            ser.write(hT[0])
            time.sleep(0.25)
            ser.write(hT[1])
            time.sleep(0.25)
            ser.write(lT[0])
            time.sleep(0.25)
            ser.write(lT[1])
            time.sleep(0.25)


            
        else:
            ser.write(c.encode())

Arduino Atomic Weather Station Code

Arduino
Arduino Uno code that displays information from a GPS module and a temperature sensor.
/* This Arduino code receives a single character from the Raspberry Pi
   and performs a varety of tasks: displaying time/indoor temperature,
   location, outdoor temperature, greeting, and birthday, as well as
   playing a tone.
   Some parts of this code are taken from open-source examples.

   Libraries to install:
   Adafruit_GPS-master - https://github.com/adafruit/Adafruit_GPS
   DHT-sensor-library-master - https://github.com/adafruit/DHT-sensor-library

   Created by Daniel Martin, 2016
 * */

#include <Adafruit_GPS.h>
#include <SoftwareSerial.h>

#include "DHT.h"

#define DHTPIN 10     // what digital pin we're connected to

// Uncomment whatever type you're using!
//#define DHTTYPE DHT11   // DHT 11
#define DHTTYPE DHT22   // DHT 22  (AM2302), AM2321

DHT dht(DHTPIN, DHTTYPE);


// If you're using a GPS module:
// Connect the GPS Power pin to 5V
// Connect the GPS Ground pin to ground
// If using software serial (sketch example default):
//   Connect the GPS TX (transmit) pin to Digital 3
//   Connect the GPS RX (receive) pin to Digital 2
// If using hardware serial (e.g. Arduino Mega):
//   Connect the GPS TX (transmit) pin to Arduino RX1, RX2 or RX3
//   Connect the GPS RX (receive) pin to matching TX1, TX2 or TX3

// If you're using the Adafruit GPS shield, change
// SoftwareSerial mySerial(3, 2); -> SoftwareSerial mySerial(8, 7);
// and make sure the switch is set to SoftSerial

// If using software serial, keep this line enabled
// (you can change the pin numbers to match your wiring):
SoftwareSerial mySerial(8, 7);

// If using hardware serial (e.g. Arduino Mega), comment out the
// above SoftwareSerial line, and enable this line instead
// (you can change the Serial number to match your wiring):

//HardwareSerial mySerial = Serial1;


Adafruit_GPS GPS(&mySerial);

#include <Time.h>             // Time Library
#include "Tones11.h"          // The code containing the frequencies of notes as variables
#include <LiquidCrystal.h>    // LCD Library
#include <Wire.h>


LiquidCrystal lcd(2, 3, 4, 5, 6, 9);



// Set GPSECHO to 'false' to turn off echoing the GPS data to the Serial console
// Set to 'true' if you want to debug and listen to the raw GPS sentences.
#define GPSECHO  true

const int UTC_offset = -7;   // Pacific Time

// this keeps track of whether we're using the interrupt
// off by default!
boolean usingInterrupt = false;
void useInterrupt(boolean); // Func prototype keeps Arduino 0023 happy

char state;         //initializes the variable that will receive a char from Pi
int mode = 1;       //the mode of the display (default time, date, temp, humidity

// Used for converting the date, month, and year to the desired time zone
const int TimeZone = -8;  //Pacific Time
int DSTbegin[] = { //DST 2013 - 2025 in Canada and US
  310, 309, 308, 313, 312, 311, 310, 308, 314, 313, 312, 310, 309
};
int DSTend[] = { //DST 2013 - 2025 in Canada and US
  1103, 1102, 1101, 1106, 1105, 1104, 1103, 1101, 1107, 1106, 1105, 1103, 1102
};
int DaysAMonth[] = { //number of days a month
  31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};

//initialization of variables for the time/date display
int Year;
int Month;
int Day = 0;
int Hour;
byte Minute;
byte Second;
char ap = 'A';
boolean suspendSec = false;

//initializes variables to refresh the display every few seconds
int timerNum = 200;
int refresh = 0;

// The Pi sends six characters (3 numbers for out temp) which will be filled into this array
char wea[6] = {'a', 'a', 'a', 'a', 'a', 'a'};

// Creates the degree symbol
byte degree[8] = {
  B01110,
  B10001,
  B10001,
  B01110,
  B00000,
  B00000,
  B00000,
  B00000
};

// Creates the bell symbol
byte bell[8] = {
  B00100,
  B01110,
  B01110,
  B11111,
  B11111,
  B00100,
  B00000,
  B00000
};


void setup()
{
  dht.begin();
  // set up the LCD's number of rows and columns:
  lcd.begin(16, 2);
  lcd.clear();
  // Print a message to the LCD.
  lcd.print("RGB 16x2 Display ");
  lcd.setCursor(0, 1);
  lcd.print(" Multicolor LCD ");

  pinMode(13, OUTPUT);
  lcd.createChar(0, degree);
  lcd.createChar(1, bell);

  Serial.begin(9600);

  // 9600 NMEA is the default baud rate for Adafruit MTK GPS's- some use 4800
  GPS.begin(9600);

  // uncomment this line to turn on RMC (recommended minimum) and GGA (fix data) including altitude
  GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
  // uncomment this line to turn on only the "minimum recommended" data
  //GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCONLY);
  // For parsing data, we don't suggest using anything but either RMC only or RMC+GGA since
  // the parser doesn't care about other sentences at this time

  // Set the update rate
  GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);   // 1 Hz update rate
  // For the parsing code to work nicely and have time to sort thru the data, and
  // print it out we don't suggest using anything higher than 1 Hz

  // Request updates on antenna status, comment out to keep quiet
  //GPS.sendCommand(PGCMD_ANTENNA);

  // the nice thing about this code is you can have a timer0 interrupt go off
  // every 1 millisecond, and read data from the GPS for you. that makes the
  // loop code a heck of a lot easier!
  useInterrupt(true);

  delay(2000);
  lcd.clear();
  // Ask for firmware version
  mySerial.println(PMTK_Q_RELEASE);
}


// Interrupt is called once a millisecond, looks for any new GPS data, and stores it
SIGNAL(TIMER0_COMPA_vect) {
  char c = GPS.read();
  // if you want to debug, this is a good time to do it!
#ifdef UDR0
  // if (GPSECHO)
  //if (c) UDR0 = c;
  // writing direct to UDR0 is much much faster than Serial.print
  // but only one character can be written at a time.
#endif
}

// By default we are not using the interrupt
void useInterrupt(boolean v) {
  if (v) {
    // Timer0 is already used for millis() - we'll just interrupt somewhere
    // in the middle and call the "Compare A" function above
    OCR0A = 0xAF;
    TIMSK0 |= _BV(OCIE0A);
    usingInterrupt = true;
  } else {
    // do not call the interrupt function COMPA anymore
    TIMSK0 &= ~_BV(OCIE0A);
    usingInterrupt = false;
  }
}

uint32_t timer = millis();
void loop()                     // run over and over again
{
  if (GPS.newNMEAreceived()) {


    if (!GPS.parse(GPS.lastNMEA()))   // this also sets the newNMEAreceived() flag to false
      return;  // we can fail to parse a sentence in which case we should just wait for another
  }


  if (Serial.available() > 0) {
    delay(3000);
    state = Serial.read();  //read what character was sent from the Pi

    switch (state)
    {
      case 'M':       //change the mode of the display
        mode = -mode;
        lcd.clear();

        break;
      case 'A':     //beep at 2500Hz for 1 second
        tone(11, 2500, 1000);
        break;
      case 'B':     //show the next birthday
        nextBirthday();
        delay(5000);
        lcd.clear();
        break;
      case 'G':    //display a time-based greeting
        greeting();
        delay(5000);
        lcd.clear();
        break;
      case 'W':   //show outdoor temperature
        showWeather();
        delay(5000);
        lcd.clear();
        break;

    }
  }

  if (mode == 1) {      //set to time, date, indoor temp, humidity mode
    mode1();
  }
  else if (mode == -1) {      //set to longitude, latitude, altitude, heat index mode
    mode2();
  }
}

//mode 1 displays the time, date, indoor temp, and humidity
void mode1() {

  if (timer > millis())  timer = millis();

  if (millis() - timer > timerNum) {        //every 0.2 seconds
    timer = millis(); // reset the timer

    getTime();
    conversion();
    displayTime();
    if (refresh % (1000 / timerNum * 5) == 0) {       //check temperature every 5 seconds
      checkTemp();
    }
    displayDate();
    if (refresh % (1000 / timerNum * 5) == 0) {       //check date every 5 seconds
      checkHum();
    }
    refresh++;
    if (refresh > (1000 / timerNum * 60)) {         //refresh display every minute
      lcd.clear();
      refresh = 0;
    }


  }
}

//mode 2 displays latitude, longitude, altitude, and heat index
void mode2() {
  displayLocation();
  heatIndex();
  if ((Minute == 0) && (Second == 0)) {         //at every hour sound the chime
    fullHour();
  }
  delay(200);
}

//displays the hour, minute, second, and am/pm
//takes a 24-hour clock time already converted to the correct time zone
void displayTime() {

  lcd.setCursor(0, 0);

  if (Hour > 12) {  //13:00 to 23:59 is changed to 1:00p-11:59p

    Hour -= 12;
    ap = 'P';
    if (Hour <= 9) {
      lcd.print(' ');
    }
    lcd.print(Hour, DEC);
  }
  else if (Hour == 0) {   //0:00 to 0:59 is 12:00a-12:59a
    Hour = 12;
    ap = 'A';
    lcd.print(Hour, DEC);
  }
  else if (Hour == 12) {  //at 12, am becomes pm
    ap = 'P';
    lcd.print(Hour, DEC);
  }
  else {                //displays all other times (1:00a-11:59a)
    ap = 'A';
    if (Hour <= 9) {
      lcd.print(' ');
    }
    lcd.print(Hour, DEC);
  }
  lcd.print(':');

  if ((Minute >= 0) && (Minute <= 9)) {
    lcd.print('0');
  }
  lcd.print(Minute, DEC);     //displays minute
  lcd.print(':');
  if ((Second >= 0) && (Second <= 9)) {
    lcd.print('0');
  }
  lcd.print(Second, DEC);     //displays second

  lcd.print(ap);              //displays 'A' for am and 'P' for pm
  if ((Minute == 0) && (Second == 0)) {   //at every hour sound the chime
    fullHour();
  }
}

//displays the month, date, and two digit year, after time-zone conversion
void displayDate()
{
  lcd.setCursor(0, 1);
  if (Month < 10) {
    lcd.print(' ');
  }
  lcd.print(Month, DEC); lcd.print('/');
  lcd.print(Day, DEC); lcd.print("/");
  lcd.print(Year, DEC);           // the year 2001 is displayed as "01"

}

//get the raw year, month, day, hour, minute, second from GPS (GMT)
void getTime() {
  Year = GPS.year;
  Month = GPS.month;
  Day = GPS.day;
  Hour = GPS.hour;
  Minute = GPS.minute;
  Second = GPS.seconds;
}

//convert to your time zone
//this code was created by JeonLab, some variables have been changed
//http://www.instructables.com/id/GPS-time-UTC-to-local-time-conversion-using-Arduin/?ALLSTEPS
void conversion() {

  if (Year%4 == 0) DaysAMonth[1] = 29; //leap year check
  Hour += TimeZone;

  if (Month * 100 + Day >= DSTbegin[Year - 13] &&
      Month * 100 + Day < DSTend[Year - 13]) Hour += 1;
  if (Hour < 0)
  {
    Hour += 24;
    Day -= 1;
    if (Day < 1)
    {
      if (Month == 1)
      {
        Month = 12;
        Year -= 1;
      }
      else
      {
        Month -= 1;
      }
      Day = DaysAMonth[Month - 1];
    }
  }
  if (Hour >= 24)
  {
    Hour -= 24;
    Day += 1;
    if (Day > DaysAMonth[Month - 1])
    {
      Day = 1;
      Month += 1;
      if (Month > 12) Year += 1;
    }
  }

}

//reads the indoor temperature from the sensor and displays it
void checkTemp() {

  float t = dht.readTemperature();

  float f = dht.readTemperature(true);


  lcd.print(' ');
  lcd.print(int(f));
  lcd.write(byte(0));
  lcd.print('F');
}

//reads the relative humidity from the sensor and displays it
void checkHum() {
  float h = dht.readHumidity();
  lcd.print(" RH ");
  lcd.print(int(h));
  lcd.print("%");
}

//sounds a chime every hour
//the display shows the hour, a/p, and a bell during the chime
void fullHour() {
  suspendSec = true;
  lcd.clear();
  lcd.print(Hour);
  lcd.print(ap);
  lcd.print(' ');
  lcd.write(byte(1));

  //number of times chime plays is equal to the hour
  for (int chimenum = 1; chimenum <= Hour; chimenum++) {

    tone(11, NOTE_E5, 1000);
    delay(1000);
    noTone(11);
    delay(2000);

  }
  suspendSec = false;
  lcd.clear();
}

//displays the integer longitude and latitude in degrees, and the altitude in meters
void displayLocation() {
  // Serial.print("Location: ");
  lcd.setCursor(0, 0);
  lcd.print(int(GPS.latitudeDegrees));
  lcd.write(byte(0));
  lcd.print(" ");
  lcd.print(int(GPS.longitudeDegrees));
  lcd.write(byte(0));
  lcd.print(' ');


  lcd.print(int(GPS.altitude));
  lcd.print('m');

}

//calculates and displays the heat index from the temp sensor, using temp and humidity
void heatIndex() {
  lcd.setCursor(0, 0);
  float f = dht.readTemperature(true);
  float h = dht.readHumidity();
  float hif = dht.computeHeatIndex(f, h);
  lcd.setCursor(0, 1);
  lcd.print("HIndex ");
  lcd.print(hif);
}

//calculates the day of year a date is (ex. Jan 31 is 31st day, Dec 31 is 365th or 366th day)
// This code is from: https://gist.github.com/jrleeman/3b7c10712112e49d8607
//params: day, month, year
//returns day of year
int calculateDayOfYear(int dy, int mnth, int yr) {

  // Given a day, month, and year (4 digit), returns
  // the day of year. Errors return 999.

  int daysInMonth[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

  // Verify we got a 4-digit year
  if (yr < 1000) {
    return 999;
  }

  // Check if it is a leap year, this is confusing business
  // See: https://support.microsoft.com/en-us/kb/214019
  if (yr % 4  == 0) {
    if (yr % 100 != 0) {
      daysInMonth[1] = 29;
    }
    else {
      if (yr % 400 == 0) {
        daysInMonth[1] = 29;
      }
    }
  }

  // Make sure we are on a valid day of the month
  if (dy < 1)
  {
    return 999;
  } else if (dy > daysInMonth[mnth - 1]) {
    return 999;
  }

  int doy = 0;
  for (int i = 0; i < mnth - 1; i++) {
    doy += daysInMonth[i];
  }

  doy += dy;
  return doy;
}

//displays the closest birthday in the future
//the dates must be modified for your personal birthday(s)
//the default is four birthdays
void nextBirthday() {
  getTime();
  conversion();
  int nextBday;
  int fullYear = 2000 + Year;
  int Jbday = calculateDayOfYear(1, 1, fullYear);   //January 1, John's birthday
  int Rbday = calculateDayOfYear(1, 2, fullYear);   //February 1, Robert's birthday   
  int Sbday = calculateDayOfYear(1, 3, fullYear);   //March 1, Sarah's birthday
  int Mbday = calculateDayOfYear(1, 4, fullYear);   //April 1, Mary's birthday
  int today = calculateDayOfYear(Day, Month, fullYear);

  if (today > Jbday) Jbday += 365;
  if (today > Rbday) Rbday += 365;
  if (today > Sbday) Sbday += 365;
  if (today > Mbday) Mbday += 365;

  //find out the number of days to next birthday
  int bdayarray[4] = {Jbday - today, Rbday - today, Sbday - today, Mbday - today};
  for (int i = 1; i < sizeof(bdayarray); i++) {
    if (bdayarray[i] < bdayarray[0]) {
      nextBday = bdayarray[i];
    }
    else {
      nextBday = bdayarray[0];
    }
  }
  lcd.clear();
  lcd.setCursor(0, 0);
  if (nextBday == bdayarray[0]) lcd.print("Johns ");
  else if (nextBday == bdayarray[1]) lcd.print("Roberts ");
  else if (nextBday == bdayarray[2]) lcd.print("Sarahs ");
  else if (nextBday == bdayarray[3]) lcd.print("Marys ");
  lcd.print("Birthday");
  lcd.setCursor(0, 1);
  lcd.print("In ");
  lcd.print(nextBday);
  lcd.print(" days");
  delay(1000);

}

//displays a time-based greeting
void greeting() {
  getTime();
  conversion();
  lcd.clear();
  lcd.setCursor(0, 0);
  if ((Hour >= 5) && (Hour < 12)) lcd.print("Good morning!");          //5am-11:59am
  else if ((Hour >= 12) && (Hour < 17)) lcd.print("Good afternoon!");  //12pm-4:59pm
  else if ((Hour >= 17) && (Hour < 21)) lcd.print("Good evening!");    //5pm-8:59pm
  else if ((Hour >= 21) && (Hour < 24)) lcd.print("Good night!");      //9pm-11:59pm
  else if ((Hour >= 0) && (Hour < 5)) lcd.print("Good night!");        //12am-4:59am

}

//display the outdoor temp, and today's high and low from Raspberry Pi
void showWeather() {
  for (int i = 0; i < sizeof(wea); i++) {
    wea[i] = Serial.read();
  }
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Out Temp ");
  lcd.print(wea[0]);
  lcd.print(wea[1]);
  lcd.write(byte(0));
  lcd.print("F");
  lcd.setCursor(0, 1);
  lcd.print("Today ");
  lcd.print(wea[2]);
  lcd.print(wea[3]);
  lcd.write(byte(0));
  lcd.print("/");
  lcd.print(wea[4]);
  lcd.print(wea[5]);
  lcd.write(byte(0));
}

Credits

Daniel Martin

Daniel Martin

0 projects • 1 follower
I am studying EE in UC Davis, and interested in tinkering with Arduino and Pi. Some of my Maker projects are at piduino.weebly.com
Thanks to Tony DiCola.

Comments