sunsetClock/sunsetClock.ino

307 lines
9.6 KiB
C++

#include <Arduino.h>
#include <SPI.h> //for screen
#include <U8g2lib.h> //for screen
#include <stdlib.h>
#include <Wire.h> //for RTC
#include <DS3231.h> //for RTC
#include "SparkFun_Ublox_Arduino_Library_Series_6_7.h" //for gps
#include <TimeLib.h> //https://github.com/PaulStoffregen/Time
//#include <Chronos.h> //for easy date calc https://inductive-kickback.com/projects/chronos/
#include <SolarCalculator.h> //https://github.com/jpb10/SolarCalculator
#include "I2C_eeprom.h" //for EEPROM https://github.com/RobTillaart/I2C_EEPROM
#include "helperFunctions.h"
#include "images.h"
#include "pico/stdlib.h"
#define GPS_POWER_PIN 2
#define INTERRUPT_PIN 3
unsigned long timer = 0UL;
enum sunriseORSunset {
SHOW_SUNRISE,
SHOW_SUNSET
};
enum sunriseORSunset showSunriseOrSunset = SHOW_SUNSET;
//setup screen
U8G2_SH1106_128X64_NONAME_F_4W_HW_SPI u8g2(U8G2_R2, 17, 21, 20);
uint8_t *buf;
char outString[99];
//RTC setup
DS3231 rtc;
bool h12;
bool pmt;
bool setRtcDateAndTime = true;
bool setDate = true;
bool RTCsetTime = true;
//GPS setup
SFE_UBLOX_GPS myGPS;
double lat = 0;
double lng = 0;
bool isGPSOn = true;
bool doSunsetCalcs = true;
bool locationDataSet = false;
//sunset setup
int tzOff = 13; //local TZ offset
bool centuryBit; //needed to get month from RTC
double az, elev; //for sun location in the sky
double transit, sunrise, sunset; //sun related times
double civilTransit, civilRiise, civilSet; //astro related times
double nextDayTransit, nextDaySunrise, nextDaySunset; // vars to be used for the following day cals
double nextDayCivilTransit, nextDayCivilRise, nextDayCivilSet;
char str[6]; //needed to convert hours to string
//setup EEPROM
//I2C_eeprom store(0x50, I2C_DEVICESIZE_24LC256);
struct locationData {
double lat = 0;
double lng = 0;
} location;
void gpsOff() {
digitalWrite(GPS_POWER_PIN, LOW);
isGPSOn = false;
}
void gpsOn() {
digitalWrite(GPS_POWER_PIN, HIGH);
isGPSOn = true;
}
void setLowCPUSpeed() {
set_sys_clock_pll(900000000, 6, 6); //set cpu to 15 MHZ
}
void setHighCPUSpeed() {
set_sys_clock_pll(1596000000, 6, 2); //set cpu to 133MHZ
}
/*
void wakeGPS() {
setHighCPUSpeed();
gpsOn();
connectGPS();
doSunsetCalcs = true;
}*/
void connectGPS() { //need to do this everytime the GPS turns back on
do { //set gps to 115200 baudrate code comes from sparkfun examples
Serial.println("GNSS: trying 115200 baud");
Serial1.begin(115200);
if (myGPS.begin(Serial1) == true) break;
delay(100);
Serial.println("GNSS: trying 9600 baud");
Serial1.begin(9600);
if (myGPS.begin(Serial1) == true) {
Serial.println("GNSS: connected at 9600 baud, switching to 115200");
myGPS.setSerialRate(115200);
delay(100);
} else {
delay(2000); //Wait a bit before trying again to limit the Serial output
}
} while (1);
myGPS.setUART1Output(COM_TYPE_UBX); //Set the UART1 port to output UBX only (turn off NMEA noise)
myGPS.setNavigationFrequency(1);
myGPS.setDynamicModel(DYN_MODEL_PORTABLE);
}
void setup() {
//Screen setup
u8g2.begin();
u8g2.clearDisplay();
u8g2.setPowerSave(0);
u8g2.setFont(u8g2_font_ncenB14_tr);
u8g2.clearBuffer();
sprintf(outString, "SUNCLOCK"); //show start up text
u8g2.drawStr(7, 35, outString);
u8g2.sendBuffer();
//interrupt pin
// pinMode(INTERRUPT_PIN, INPUT_PULLUP);
//attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), wakeGPS, FALLING);
//RTC setup
Wire.begin();
rtc.setClockMode(false);
//GPS setup
Serial1.setRX(1);
Serial1.setTX(0);
pinMode(GPS_POWER_PIN, OUTPUT_2MA);
gpsOn();
connectGPS();
//RTC time set
if (isGPSOn && myGPS.getPVT()) { //myGPS.getPVT() will return true if data is available
rtc.setYear(myGPS.getYear());
rtc.setMonth(myGPS.getMonth());
rtc.setDate(myGPS.getDay());
rtc.setHour(myGPS.getHour());
rtc.setMinute(myGPS.getMinute());
rtc.setSecond(myGPS.getSecond());
}
if (isGPSOn && myGPS.getPVT()) { //gps has to be on to set lat and long, once gps chords are set the gps is turned off so no need to set gps chords.
lat = myGPS.getLatitude();
lng = myGPS.getLongitude();
lat = lat / 10000000;
lng = lng / 10000000;
locationDataSet = true;
gpsOff();
}
timer = millis(); //get time so we only set the clock once per second
}
void loop() {
if (isGPSOn && myGPS.getPVT()) { //myGPS.getPVT() will return true if data is available
rtc.setYear(myGPS.getYear());
rtc.setMonth(myGPS.getMonth());
rtc.setDate(myGPS.getDay());
rtc.setHour(myGPS.getHour());
rtc.setMinute(myGPS.getMinute());
rtc.setSecond(myGPS.getSecond());
}
if (isGPSOn && myGPS.getPVT()) { //gps has to be on to set lat and long, once gps chords are set the gps is turned off so no need to set gps chords.
lat = myGPS.getLatitude();
lng = myGPS.getLongitude();
lat = lat / 10000000;
lng = lng / 10000000;
locationDataSet = true;
}
if (isGPSOn && lat != 0 && lng != 0) {
gpsOff();
}
if (millis() > timer + 1000) {
DateTime tm(rtc.getYear(), (int)rtc.getMonth(centuryBit), (int)rtc.getDate(), (int)rtc.getHour(h12, pmt), rtc.getMinute(), (int)rtc.getSecond()); //set date and time to use for the rest of the loop
if (doSunsetCalcs) {
//get sunset time
calcSunriseSunset(tm.year(), tm.month(), tm.day(), lat, lng, transit, sunrise, sunset);
//get civilSet time
calcCivilDawnDusk(tm.year(), tm.month(), tm.day(), lat, lng, civilTransit, civilRiise, civilSet);
//calcs for sunrise times
uint16_t year = tm.year();
uint16_t month = tm.month();
uint16_t day = tm.day();
if (((tm.hour() + tzOff) % 24) > 6) { //only increment day if it is before midnight
//detect when day is at the end of the month and increment month and set day to 1 otherwise increment day by 1
if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) {
if (day == 31) {
if (month == 12) {
year++;
month = 1;
day = 1;
} else {
month++;
day = 1;
}
} else {
day++;
}
} else if (month == 4 || month == 6 || month == 9 || month == 11) {
if (day == 30) {
month++;
day = 1;
} else {
day++;
}
} else {
if (month == 2) {
if (year % 400 == 0 || year % 4 == 0) { //special case for leap year
if (day == 29) {
month++;
day = 1;
} else {
if (day == 28) {
month++;
day = 1;
}
}
} else {
day++;
}
}
}
}
//calc sunrise times
//calcSunriseSunset(tm.year(), tm.month(), tm.day(), lat, lng, nextDaySunrise, nextDaySunrise, nextDaySunset);
calcSunriseSunset(year, month, day, lat, lng, nextDayTransit, nextDaySunrise, nextDaySunset);
calcCivilDawnDusk(year, month, day, lat, lng, nextDayCivilTransit, nextDayCivilRise, nextDayCivilSet);
}
//get current time
double dhour = tm.hour();
double dmin = tm.minute();
double dsec = tm.second();
//calc time to sunset time
double currentTimeInHours = dhour + (dmin / 60) + (dsec / 60 / 60);
double TimeToSunset = sunset - currentTimeInHours;
double timeTocivilSet = civilSet - currentTimeInHours;
//calc time to sunrise time
double timeToSunrise = nextDaySunrise + tzOff - currentTimeInHours + tzOff; //+tzoff is a work around as library gives incorrect time
double timeToCivilRise = nextDayCivilRise + tzOff - currentTimeInHours + tzOff;
if (TimeToSunset < 0) {
TimeToSunset = 0;
}
if (timeTocivilSet < 0) {
timeTocivilSet = 0;
showSunriseOrSunset = SHOW_SUNRISE;
}
if (timeToSunrise < 0) {
timeToSunrise = 0;
showSunriseOrSunset = SHOW_SUNSET;
}
if (timeToCivilRise < 0) {
timeToCivilRise = 0;
}
u8g2.clearBuffer();
sprintf(outString, "%.2f:%.2f", lat, lng); //show location chords
u8g2.drawStr(0, 35, outString);
sprintf(outString, "%s", hoursToString(currentTimeInHours + tzOff, str)); //show current time
u8g2.drawStr(0, 55, outString);
if (showSunriseOrSunset == SHOW_SUNSET) {
sprintf(outString, "%s", hoursToString(TimeToSunset, str)); //show time to sunset
u8g2.drawStr(0, 15, outString);
sprintf(outString, "%s", hoursToString(timeTocivilSet, str)); //show civilSet time
u8g2.drawStr(75, 15, outString);
}
if (showSunriseOrSunset == SHOW_SUNRISE) {
sprintf(outString, "%s", hoursToString(timeToSunrise, str)); //show time to sunrise
// sprintf(outString, "%f", timeToSunrise);
u8g2.drawStr(0, 15, outString);
sprintf(outString, "%s", hoursToString(timeToCivilRise, str)); //show civilRise time
u8g2.drawStr(75, 15, outString);
}
u8g2.sendBuffer();
if (locationDataSet == true) { //set to low power mode
doSunsetCalcs = false; //stop doing sunset calcs once we have the gps data.
locationDataSet = false; //this this to false because location data will need to be reset once GPS turns back on
setLowCPUSpeed();
}
timer = millis();
}
if (BOOTSEL) {
rp2040.rebootToBootloader();
}
}