This project combines Atomic Time keeping with local weather station, and additional cool projects using Arduino and Raspberry Pi.
Things used in this project
Story
Demo of Smartphone Controlled Atomic Weather Station
Introduction
I was looking for a cool way to use the features of the Adafruit GPS module for a practical purpose, and Tony DiCola’s Arduino GPS Clock inspired me to create a GPS controlled atomic clock, with the addition of a weather station.
What we did
The first step is to wire up the Adafruit GPS Breakout Shield to the Arduino Uno. I thought initially about connecting the shield to the Arduino MKR1000 so I soldered pins onto the shield, but I realized that this small Arduino operates only on 3.3V, but the shield uses 5V. If you solder stacking headers onto the shield, it will fit right on top of the Uno.
Connect the RGB 16×2 Character LCD Display to the Uno according to the diagram below, and you can display information from the GPS, such as time, date, longitude, latitude, and altitude.
The temp/humidity sensor is soldered onto the PCB: it simply requires a couple of jumper wires for 5V and Ground, and one wire to connect to Arduino’s Digital I/O pin.
I wanted to add a wireless component to the station, so instead of physical buttons to switch modes I could use my smartphone to display different information. I first tried this using a Bluetooth module but had no success in getting Arduino to receive the commands I sent. Then I decided to connect a Raspberry Pi 2 to the Arduino and use serial communication to send a simple command from my phone to the Uno. I set up the Raspberry Pi with WiFi capability though the WiPi USB dongle. Although Bluetooth was unsuccessful, I was glad to have the Raspberry Pi. The Pi is easily controlled with a smartphone app, its WiFi capability allows me to access the Internet, and the addition of Python programs allows me to expand this project.
On the IPhone I downloaded the SSH Control App, which allows you to connect wirelessly to the Raspberry Pi and send terminal commands through buttons and switches. With just a swipe of a slider switch you can start a Python program, then tap a button to send a character through the Pi to the Arduino, which will receive the command and change the mode on the LCD display.
Now that I had connection to the Internet, I realized I had the opportunity to get the outdoor weather from online, so I downloaded the forecastiopy library and added the outdoor conditions to the weather station.
For additional fun, I wired up a White LED Backlight to a potentiometer, and placed it outside the box to brighten up its surroundings. I connected a speaker to digital pin 11 for a chime every hour.
Useful tutorials:
- Adafruit 16×2 Character LCD Display: Refer to page 19 for connecting an RGB Display, but instead of digital pins 7, 8, 9, 10, 11, 12 use 2, 3, 4, 5, 6, 9. I have also wired the last 3 pins (for each color) to ground because the I/O pins are full.
Schematics
Code
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)
/* 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)); }
Source : Smartphone Controlled Atomic Weather Station