This project combines Atomic Time keeping with local weather station, and additional cool projects using Arduino and Raspberry Pi.
Things used in this project
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