/* 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));
}
Comments