Heaven’s Above – Lunar & Solar Clock

Introducing “Heaven’s Above – Lunar and Solar Position Clock”, a real-time display of solar  position and lunar phase and position. In real-time, the display shows both the elevation and azimuth of the sun during the daytime, and the elevation, azimuth and phase of the moon at night. You need never feel alone again!

Heaven's Above

Heaven’s Above – Lunar and Solar Position Clock: showing the moon’s position – an elevation of 10 degrees at 125 degrees (SE) – taken at half-past midnight on 5 June 2015, and the the waning gibous moon phase. Moon rise, when the moon rises above the horizon, was at 11:52PM.

Heaven's Above - Lunar and Solar Position Clock

Heaven’s Above – Lunar and Solar Position Clock: showing the sun’s position – an elevation of 67 degrees in the cardinal direction of 145 degrees (just East of SSE, or ESSE) – just after noon on 5 June 2015.

Some of my most recent projects (including the two mechanical builds of the SolarTracer and the display of the Graphics LCD Analog Clock) have involved displaying in real-time the actual position of the sun in the sky – both its azimuth (cardinal position) and elevation above the horizon. I have now completed the more complex calculations for determining the same information for the moon and combined them into this project.

Similar to the Two-Four Timepiece – 24 hour clock, I built this project using an Arduino UNO (made of discrete parts), a real-time clock, and a radially arranged array of 105 addressable LEDs. These LEDs are arranged in radial lines at the cardinal (N, E, S and W) points and three levels of intermediate cardinal points (NE, SE, SW and NW, NNE; ENE, ESE, SSE, SSW, WSW, WNW and NNW; and points in between e.g. NNNE…). The 32 radial lines are therefore positioned 11.25º apart. Solar and lunar azimuths are displayed compass-wise relative to the North LED, that is coloured separately. The solar and lunar elevations are shown relative to an east-west “horizon”.

The LEDs are interconnected to create a continuous serial data path that is controlled by a single Arduino pin. The data path starts at the “North” pole  (LED0) and meanders clockwise around the array. In this image, the solar azimuth is shown as ESSE. The solar elevation is seen at 67º.

Heaven's Above - Lunar and Solar Position Clock

Heaven’s Above – Lunar and Solar Position Clock: showing arrangement of the 105 addressable LEDs.

 

The Arduino code for Heaven’s Above – Lunar and Solar Position Clock is shown below.

[codesyntax lang=”php” title=”Heaven’s Avove – Build 1 – Arduino code” blockstate=”collapsed”]

//**************************************************************************************************
//                           HEAVEN'S ABOVE: SUN & MOON POSITION CLOCK
//                                   Adrian Jones, June 2015
//
//**************************************************************************************************

// Build 1
//   r1 150601  initial build with moon phase
//   r2 150602  added lunar elevation and azimuth
//   r3 150603  added lunar illumination, solar elevation and azimuth
//   r4 150604  
//********************************************************************************************
#define build 1
#define revision 3
//********************************************************************************************

#include <avr/pgmspace.h>         // for memory storage in program space
#include <math.h>                 // math for moon position calculations 

// LED strip
#include "FastLED.h"
#define DATA_PIN 5                // Data transfer pin
#define LED_COUNT 105             // total number of LEDs
CRGB leds[LED_COUNT];             // create LED array

// RTC
#include <Wire.h>
#include "RTClib.h"              
RTC_DS1307 RTC;                   // create instance of RTC
int hr, mn, se, osec=-1, omin=-1, ohr=-1, dy, ody=0, mo, yr, dw;
int TZ;

// eeprom storage
#include <EEPROM.h>               // for alarm and mode data storage
int dst;                          // dst setting                      
int *set[] = { &dst };            // values to set/recover from EEPROM

#define d 0xFF                    // delimeter
#define centre 5                  // centre LED

// all concentric circles (0 is outer)
const char circles[][32] PROGMEM = {          // all circles from outside in
    {0,11,15,23,27,37,41,49,62,66,74,78,88,92,100,104,10,14,22,26,36,40,48,52,53,63,67,75,79,89,93,101},  // circle 0 - outer
    {1,12,16,24,28,38,42,50,61,65,73,77,87,91, 99,103, 9,13,21,25,35,39,47,51,54,64,68,76,80,90,94,102},  // circle 1
    {2,17,29,43,60,72,86,98, 8,20,34,46,55,69, 81, 95, d},                                                // circle 2
    {3,18,30,44,59,71,85,97, 7,19,33,45,56,70, 82, 96, d},                                                // circle 3
    {4,31,58,84, 6,32,57,83, d},                                                                          // circle 4
    {5, d}                                                                                                // circle 5 - inner
};

// all radial lines (0 is North)
const char lines[][6] PROGMEM = {            // all lines starting from 12 o'clock...
    {0, 1, 2,  3, 4, 5},      // line 0
    {11,12, d},               // line 1 
    {15,16,17,18, d},         // line 2 
    {23,24, d},               // line 3 
    {27,28,29,30,31, 5},      // line 4 
    {37,38, d},               // line 5 
    {41,42,43,44, d},         // line 6 
    {49,50, d},               // line 7 
    {62,61,60,59,58, 5},      // line 8
    {66,65, d},               // line 9
    {74,73,72,71, d},         // line 10
    {78,77, d},               // line 11
    {88,87,86,85,84, 5},      // line 12
    {92,91, d},               // line 13
    {100,99,98,97, d},        // line 14
    {104,103, d},             // line 15
    {10, 9, 8, 7, 6, 5},      // line 16
    {14,13, d},               // line 17
    {22,21,20,19, d},         // line 18
    {26,25, d},               // line 19
    {36,35,34,33,32, 5},      // line 20
    {40,39, d},               // line 21
    {48,47,46,45, d},         // line 22
    {52,51, d},               // line 23
    {53,54,55,56,57, 5},      // line 24
    {63,64, d},               // line 25
    {67,68,69,70, d},         // line 26
    {75,76, d},               // line 27
    {79,80,81,82,83, 5},      // line 28
    {89,90, d},               // line 29
    {93,94,95,96, d},         // line 30
    {101,102, d}              // line 31
};

// letters of "Moon"
const char letters[][32] PROGMEM = {
  {88,78,74,66,62,49,41,37,27,28,29,30,31, 5, 83, 82,81,80,79,75,67,63,53,52,48,40,36, d},               // "M"
  { 0,11,15,23,27,37,41,49,62,66,74,78,88,92,100,104,10,14,22,26,36,40,48,52,53,63,67,75,79,89,93,101},  // "O"
  { 2,17,29,43,60,72,86,98, 8,20,34,46,55,69, 81, 95, d},                                                // "o"
  {27,37,41,49,62,66,74,78,88,87,86,85,84, 5, 83, 83,82,81,80,36,40,48,52,53,63,67,75,79, d}             // "N" 
};

// phases of the moon (0 is new moon, p1-waxing crescent, p5-first quarter, p10-full moon, etc.)
const char phases[][11] PROGMEM = {
  {24,28,38,42,50,61,65,73,77,87,91},     // p1
  {16,29,43,60,72,86,99,d},               // p2
  {12,17,30,44,59,71,85,98,103,d},        // p3
  {1,18,31,58,84,97,9,d},                 // p4
  {2,3,4,6,7,8,d},                        // p5 first / last quarter
  {96,83,57,32,19,d},                     // p6
  {102,95,82,33,20,13,d},                 // p7
  {70,56,45,20,d},                        // p8
  {94,81,69,55,46,34,21,d},               // p9
  {90,80,76,68,64,54,51,47,39,35,25}      // p10
};

#define num_phases 20.0           // number of moon phases (0-20)
#define msat 140                  // min saturation
#define fsat 200                  // full saturation
float phase;

// cardinal directions
const char ca0[]  PROGMEM = "N  ";
const char ca1[]  PROGMEM = "NNE";
const char ca2[]  PROGMEM = "NE ";
const char ca3[]  PROGMEM = "ENE";
const char ca4[]  PROGMEM = "E  ";
const char ca5[]  PROGMEM = "ESE";
const char ca6[]  PROGMEM = "SE ";
const char ca7[]  PROGMEM = "SSE";
const char ca8[]  PROGMEM = "S  ";
const char ca9[]  PROGMEM = "SSW";
const char caA[]  PROGMEM = "SW ";
const char caB[]  PROGMEM = "WSW";
const char caC[]  PROGMEM = "W  ";
const char caD[]  PROGMEM = "WNW";
const char caE[]  PROGMEM = "NW  ";
const char caF[]  PROGMEM = "NNW";
const char * const cas[] PROGMEM = { ca0,ca1,ca2,ca3,ca4,ca5,ca6,ca7,ca8,ca9,caA,caB,caC,caD,caE,caF };
char cb[4];                       // character buffer for character strings. Set dimension to length of longest string

// second hand timer
long interval = 20;               // time between changing intensity
long previousMillis = 0;          // old count
unsigned long currentMillis;      // current counter

// hue and intensity settings
#define shue   212                // seconds hue
#define hinc     3                // hue increment
#define sval   100                // seconds max intensity
byte start_col=50;                // main starting colour
int sv=0, bright = 40;            // general brightness
boolean brighten=true;            // seconds intensity direction

// sun and moon positions
#define LONGITUDE -75.64316       // current location longitude
#define LATITUDE  45.66167        // current position latitude
#define rad  PI/180               // degrees / rad 
#define irad 180/PI               // rad / degree  
#define ob rad*23.4397            // obliquity of the earth (23.4397 deg.)
 
double mdist;                     // distance to moon
double mfract, mphase, mangle;    // moon refraction, phase
double mra,mdec,maz,mad,mal;      // moon info. (right ascension,declination, azimuth, azimuth_degrees, altitude)
double sra,sdec,saz,sad,sal;      // solar information

// sun times configuration (angle, morning name, evening name)

float times[] = {-0.833, -0.3, -6.0, -12.0, -18, 6};

float dpl=360/32;                 // degrees per line (11.25 deg.)
float adj=dpl/2;                  // 1/2 step (5.625 deg.) in LED outer ring
int nmaz,omaz=-1,nmel,omel=-1;    // moon azimuth and elevation
int nsaz,osaz=-1,nsel,osel=-1;    // sun azimuth and elevation

// Sun Position library
#include <sundata.h>              // sun position library
sundata vdm(0,0,0);               // create instance... see later
float sunup, sundn;               // sunrise and sunset times (as decimal)
int sunup_h,sunup_m,sundn_h,sundn_m;
boolean dtim;                     // daytime? (true/false)


//***************************
#define SERIAL_BAUDRATE 57600
#define serEn  true              // serial output
#define animEn true              // initial animation
#define sp Serial.print   
#define spln Serial.println
//***************************


//*****************************************************************************************//
//                                      Initial Setup
//*****************************************************************************************//
void setup() {
  
#if(serEn) 
  Serial.begin(57600);
  spln(F("HEAVEN'S ABOVE: SUN & MOON POSITION CLOCK by Adrian Jones"));
  sp(F("Build ")); sp(build); sp(F(".")); spln(revision);
  sp(F("Free RAM: "));  sp(freeRam()); spln(F("B"));
  spln(F("*********************************************************"));
#endif
  
  FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, LED_COUNT);   // LED Strip
  
  Wire.begin();
  RTC.begin();                                         // start RTC
  getTime();                                           // get current time

  if (! RTC.isrunning() || (String(__DATE__).lastIndexOf(String(yr)) == -1)) {
    RTC.adjust(DateTime(__DATE__, __TIME__));          // adjust if year is off or RTC not running
    #if(serEn) 
      sp(F("RTC reset to ")); sp(__DATE__); sp(F(" ")); spln(__TIME__);
    #endif
  } 
  // RTC.adjust(DateTime(__DATE__, __TIME__));         // reset to system time
  // RTC.adjust(DateTime(2015, 1, 18, 1, 59, 50 ));    // test time

  restoreSettings();                                   // restore EEPROM settings

  getTime();
  TZ = (IsDST)?-4:-5;                                // determine time offset (hours) from GMT
  vdm = sundata(LATITUDE, LONGITUDE, TZ);            // create sundata instance for current location

// start-up animation
#if(animEn) 
  doSpiral(0, 80, 3, 5);                             // do spiral
  delay(300);
  doAllLines(start_col, bright, hinc, 10, 0);        // colour all radial lines
  delay(300);
  doLetters(start_col, bright, 1, 500);              // moon text
  doAllPhases(60, 43, 100);                          // all moon phases
#endif

  doBackground(true);                                // write out background
}
 
//*****************************************************************************************//
//                                      MAIN LOOP
//*****************************************************************************************//
void loop() {
  getTime();
  doDSTAdjust();   
  doSecTimer();                                      // pulse seconds timer
  
  if(se != osec) {                                   // every second
    getSunTimes();

    getSunPosition();
    getMoonPosition();
    getMoonIllumination();                           // preceeding two are prerequisites
    phase = mphase*num_phases;

    doSerialTime();                                  // write out time

    if(!dtim && se%2) doPhase(phase, 50, 43, mfract);  // phase of the moon if in the sky 
    if(!dtim) doMoonLines();                         // draw lunar azimuth and elevation pips
    if(dtim)  doSunLines();                          // draw solar azimuth and elevation pips
    osec = se;
  }
}



// ********************************************************************************** //
//                                 SERIAL OUTPUT
// ********************************************************************************** //

void doSerialTime() {
#if(serEn) 
    sp(F("DATETIME: ")); sp(yr-2000); sp(F("/"));
    if(mo<10) sp(F("0")); sp(mo); sp(F("/"));
    if(dy<10) sp(F("0")); sp(dy); sp(F(" "));
    if(hr<10) sp(F("0")); sp(hr); sp(F(":"));
    if(mn<10) sp(F("0")); sp(mn); sp(F(":"));
    if(se<10) sp(F("0")); sp(se);
    sp(F("\tSUN rise: ")); if(sunup_h<10) sp(F("0")); sp(sunup_h); sp(F(":")); if(sunup_m<10) sp(F("0")); sp(sunup_m);
    sp(F(" set: "));  if(sundn_h<10) sp(F("0")); sp(sundn_h); sp(F(":")); if(sundn_m<10) sp(F("0")); sp(sundn_m);
    sp(F(" elev: ")); sp(sal*irad,1); 
    sp(F(" azim: ")); sp(sad,1); sp(F(" [")); sp(getCardinal(sad)); sp(F("]"));
    sp(F(" day?: ")); sp(dtim?"Yes":"No");
    sp(F("\tMOON phase: ")); sp(phase); sp(F(" [")); sp(moonphase()); sp(F("]")); 
    sp(F(" elev: ")); sp(mal*irad,1);
    sp(F(" azim: ")); sp(mad,1); sp(F(" [")); sp(getCardinal(mad)); sp(F("]"));
    sp(F(" illum: ")); sp(mfract*100.0,1); sp(F("%"));
    spln(); 
#endif
}

// ********************************************************************************** //
//                                  MOON POSITION CALCULATIONS
// ********************************************************************************** //
// moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas

void getMoonPosition() {
  double lw = rad * -LONGITUDE; 
  double phi = rad * LATITUDE;
  double nd =  nDays();                         // number of days since Jan 1, 2000 12:00 UTC 
  
  double L = rad * (218.316 + 13.176396 * nd);  // ecliptic longitude
  double M = rad * (134.963 + 13.064993 * nd);  // mean anomoly
  double F = rad * (93.272  + 13.229350 * nd);  // mean distance
  
  double mlo = L + (rad * 6.289 * sin(M));      // lunar longitude
  double mla = rad * 5.128 * sin(F);            // lunar lattitude
  // geocentric ecliptic coordinates of the moon
  mdist = 385001L - (20905L * cos(M));          // lunar distance (km)
  mra   = rightAscension(mlo, mla);             // right ascension
  mdec   = declination(mlo, mla);               // declination

  double H = siderealTime(nd,lw) - mra;
  maz = azimuth(H, phi, mdec);                   // lunar azimuth
  mad = 180.0+maz*irad;
  
  mal = altitude(H, phi, mdec);                  // lunar elevation 
  mal = mal + rad * 0.017 / tan(mal + rad * 10.26 / (mal + rad * 5.10));    // altitude correction for refraction
}


// ********************************************************************************** //
//                             MOON ILLUMINATION CALCULATIONS
// ********************************************************************************** //
// calculations for illumination parameters of the moon,
// based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas and
// Chapter 48 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.

void getMoonIllumination() {
   long sdist = 149598000L;        // distance from Earth to Sun in km

   double phi   = acos(sin(sdec)*sin(mdec) + cos(sdec)*cos(mdec)*cos(sra - mra));
   double inc   = atan2(sdist*sin(phi), mdist - sdist*cos(phi));
   double angle = atan2(cos(sdec)*sin(sra - mra), sin(sdec)*cos(mdec)-cos(sdec)*sin(mdec)*cos(sra-mra));
   mfract = (1 + cos(inc)) / 2;
   mphase = (0.5 + 0.5*inc*(angle < 0 ? -1 : 1) / PI);
   mangle = angle;
}


double nDays() {
  DateTime now = RTC.now();
  DateTime ntim (now.unixtime() - (TZ*3600));  // current time UTC
  DateTime stim (2000, 1, 1, 12, 0, 0);        // Jan 1, 2000 12:00 UTC
  return (((double) ntim.unixtime() - (double) stim.unixtime())/86400L);
}

double siderealTime(double ds, double ls)      { return rad * (280.16 + 360.9856235 * ds) - ls;  }
double rightAscension(double lr, double br)    { return atan2((sin(lr)*cos(ob) - tan(br)*sin(ob)), cos(lr) ); }
double declination(double ld, double bd)       { return asin(sin(bd)*cos(ob) + cos(bd)*sin(ob)*sin(ld)); }
double azimuth(double ha,double pa,double da)  { return atan2(sin(ha), (cos(ha)*sin(pa) - tan(da)*cos(pa))); }
double altitude(double ha,double pa,double da) { return asin(sin(pa)*sin(da) + cos(pa)*cos(da)*cos(ha)); }

// ********************************************************************************** //
//                                  SOLAR CALCULATIONS
// ********************************************************************************** //

void getSunTimes() {
    vdm.time(yr, mo, dy, hr, mn, se);                // update with current year, month, day, hour, minutes and seconds
    vdm.calculations();                              // update calculations using current time
    sunup  = vdm.sunrise_time();                     // store sunrise time in decimal form
    sunup_h = int(sunup);
    sunup_m = int(60.0 * (float)(sunup-sunup_h));
    sundn  = vdm.sunset_time();                      // store sunset time in decimal form
    sundn_h = int(sundn);
    sundn_m = int(60.0 * (float)(sundn-sundn_h));
    dtim = isdtim(sunup,sundn);                      // is the sun in the sky ?
}


// // sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas
void getSunPosition() {
  double lw  = rad * -LONGITUDE;
  double phi = rad * LATITUDE;
  double nd = nDays();                         // number of days since Jan 1, 2000 12:00 UTC 

  double M = solarMeanAnomaly(nd);
  double L = eclipticLongitude(M);
  sdec = declination(L, 0);
  sra  = rightAscension(L, 0);
  
  double H  = siderealTime(nd,lw) - sra;
  saz = azimuth(H, phi, sdec);
  sad = 180.0+saz*irad;
  sal = altitude(H, phi, sdec);
}

double solarMeanAnomaly(double ds) { return rad * (357.5291 + 0.98560028 * ds); }

double eclipticLongitude(double M) {
    double C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)); // equation of center
    double P = rad * 102.9372;                                                    // perihelion of the Earth
    return (M + C + P + PI);
}


// ********************************************************************************** //
//                                      OPERATION ROUTINES
// ********************************************************************************** //
// FREERAM: Returns the number of bytes currently free in RAM  
int freeRam(void) {
  extern int  __bss_end, *__brkval; 
  int free_memory; 
  if((int)__brkval == 0) {
    free_memory = ((int)&free_memory) - ((int)&__bss_end); 
  } 
  else {
    free_memory = ((int)&free_memory) - ((int)__brkval); 
  }
  return free_memory; 
}

// ********************************************************************************** //
//                                    DISPLAY ROUTINES
// ********************************************************************************** //

void doBackground(boolean allclear) {
  if(allclear)   doAllClear();                                   // clear the board 
  doCircle(0, (dtim?60:170), 20);                                // background 
  doCircleDot(0,0,212,255,50);                                   // North pip
  FastLED.show();
}

// markings for moon al;titude and elevation
void doMoonLines() {
  nmaz = int( (mad + adj) / dpl);                                // calculate position of moon azimuth dot
  nmel = int( (90.0-mal*irad + adj) / dpl);                      // calculate position of moon elevation dot
  if(nmel == omel && nmaz == omaz) return;                       // return if no change

  doBackground(false);                                           // if necessary redraw background 
  doLine(nmel,170,80, 2);                                        // insert elevation line and 
  doCircleDot(1,nmaz,106,255,80);                                // insert azimuth dot 
  
  omaz = nmaz;                                                   // update old azimuth dot
  omel = nmel;                                                   // update old elevation dot
  FastLED.show();
}

// markings for sun al;titude and elevation
void doSunLines()   {
  nsaz = int( (sad + adj) / dpl);                                // calculate position of sun azimuth dot
  nsel = int( (90 - sal*irad + adj) / dpl);                      // calculate position of sun elevation dot
  if(nsaz == osaz && nsel == osel) return;
  
  doBackground(true);                                            // if necessary redraw background 

  doLine(nsel,42,80,centre);                                     // insert azimuth line and
  doLine(   8,42,80,centre);                                     // azimuth reference
  doLine(nsaz,106, 80, 2);                                       // insert elevation line 
 
  osaz = nsaz;                                                   // update old azimuth dot
  osel = nsel;                                                   // update old elevation dot
  FastLED.show();
}


// pulsing seconds marker
void doSecTimer() {
  currentMillis = millis();                                      // get current timer
  if((currentMillis - previousMillis > interval)) {              // if duration expired
    sv = (brighten)? min(sval,(sv+2)): max(0,(sv-2));            // increment (decrement) intensity
    doCircle(centre, shue, sv);                                  // set new second marker...
    FastLED.show();                                              // ...and show it
    if(sv >= sval || sv <= 0) brighten = !brighten;              // reverse intensity direction  
    previousMillis = currentMillis;                              // reset timer
  }
}

void doAllClear() {
  for (byte j=0; j<LED_COUNT;j++) leds[j] =CHSV(0,0,0);
}

void doLetters(byte col, byte val, byte col_inc, int pause) {
  doAllClear();
  for (int i = 0; i < (sizeof(letters)/sizeof(letters[0])); i++) {
    for (byte j=0; j < (sizeof(letters[i])/sizeof(letters[0][0])); j++) {
      if(pgm_read_byte( &letters[i][j] ) == d) break;
      leds[pgm_read_byte( &letters[i][j] )] =  CHSV( col, 255, val );
       col = (col+col_inc)%256;
    }
   FastLED.show(); 
   delay(pause);
   doAllClear();
  }
}

void doAllPhases(byte val, byte col, int del) {
 for(byte x=0;x<num_phases; x++) {
   doAllClear();
   doPhase(x,val,col,1.0);
   delay(del);
 }
}
 
void doPhase(int num, byte val, byte col, float illum) {
  val = int( (float)val*illum);
  if(num == 0) return;
  byte i,j,s,e;
  s = (num <=10)?   0 : num-10;
  e = (num <=10)? num : 10;
  
  for(i=s; i < e; i++) {
    for (j=0; j < (sizeof(phases[i])/sizeof(phases[i][0])); j++) {
      if(pgm_read_byte( &phases[i][j] ) == d) break;
      leds[pgm_read_byte( &phases[i][j] )] =  CHSV( col, random(msat,fsat), val );
    }
  }
  FastLED.show();
}


void doAllLines(byte col, byte val, byte col_inc, byte step_delay, byte from) {
  for (int i = 0; i < (sizeof(lines)/sizeof(lines[0])); i++) {
    for (byte j=from; j < (sizeof(lines[i])/sizeof(lines[0][0])); j++) {
      if(pgm_read_byte( &lines[i][j] ) == d || pgm_read_byte( &lines[i][j] ) == centre) break;
      leds[pgm_read_byte( &lines[i][j] )] =  CHSV( col, 255, val );
      FastLED.show(); 
      col = (col+col_inc)%256;
      delay(step_delay);
    }
  }
}


void doLine(byte line, byte col, byte val, byte num) {
  for (byte j=0; j < (sizeof(lines[line])/sizeof(lines[line][0])); j++) {
    if(pgm_read_byte( &lines[line][j] ) == d ||  (j >= num)) break;
    leds[pgm_read_byte( &lines[line][j] )] =  CHSV( col, 255, val );
  }
  FastLED.show(); 
}


void doSpiral(byte col, byte val, byte col_inc, byte step_delay) {
  for (byte i = 0; i < (sizeof(circles)/sizeof(circles[0])); i++) {
    for (byte j=0; j < (sizeof(circles[i])/sizeof(circles[i][0])); j++) {
      if(pgm_read_byte( &circles[i][j] ) == d) break;
      leds[pgm_read_byte( &circles[i][j] )] =  CHSV( col, 255, val );
      FastLED.show(); 
      delay(step_delay);
      col += col_inc;
    }
  }
}


void doCircle(byte num, byte col, byte val) {
  for (byte j=0; j < (sizeof(circles[num])/sizeof(circles[num][0])); j++) {
    if(pgm_read_byte( &circles[num][j] ) == d) break;
    leds[pgm_read_byte( &circles[num][j] )] =  CHSV( col, 255, val );
  }
  FastLED.show(); 
}

void doCircleDot(byte num, byte pos, byte col, byte sat, byte val) {
  pos = constrain(pos, 0, sizeof(circles[num])/sizeof(circles[num][0]));
  if(pgm_read_byte( &circles[num][pos] ) == d) return;
  leds[pgm_read_byte( &circles[num][pos] )] =  CHSV( col, sat, val );
  FastLED.show(); 
}

/* ********************************************************************************** //
//                                       TIME ROUTINES
// ********************************************************************************** //
   In most of Canada Daylight Saving Time begins at 2:00 a.m. local time on the second Sunday in March. 
   On the first Sunday in November areas on DST return to Standard Time at 2:00 a.m. local time. 
*/

// IsDST returns true if DST, false otherwise
boolean IsDST() {
  if (mo < 3 || mo > 11) { return false; }                // January, February, and December are out.
  if (mo > 3 && mo < 11) { return true;  }                // April to October are in
  int previousSunday = dy - dw;                               
  if (mo == 3) { return previousSunday >= 8; }            // In March, we are DST if our previous Sunday was on or after the 8th.
  return previousSunday <= 0;                             // In November we must be before the first Sunday to be DST. That means the previous Sunday must be before the 1st.
}

// getTime obtains current time from RTC
void getTime() {
  DateTime now = RTC.now();
  hr = now.hour(); if(hr==0) hr=24;                      // adjust to 1-24
  mn = now.minute();
  se = now.second();
  yr = now.year();
  mo = now.month();
  dy = now.day();
  dw = now.dayOfWeek();
}

// doDSTAdjust increments (or decrements) by one hour when entering (or leaving) DST
void doDSTAdjust() {
  if(dst == IsDST()) return;                    // if prior setting is same as DST setting, do nothing
  if(hr != 2) return;                           // do nothing until 2pm

  DateTime now = RTC.now();                     // get time
  if(IsDST() && !dst) {
    DateTime newTime (now.unixtime() + 3600);   // add one hour
    RTC.adjust(newTime);                        
  }
  if(!IsDST() && dst) {
    DateTime newTime (now.unixtime() - 3600);   // subtract one hour
    RTC.adjust(newTime);
  }
  dst = IsDST(); 
  saveSettings();                // save change to DST
}

boolean isdtim(float up, float down) {
  float ftime = hr+(mn/60.0);
  return (ftime >= up && ftime < down);
}

// ********************************************************************************** //
//                                MOON PHASE ROUTINES
// ********************************************************************************** //

// moonphase:  the phase day (0 to 29, where 0=new moon, 15=full etc.): see http://www.ben-daglish.net/moon.shtml
int moonphase() {
  long lp = 2551443;                                      // 2551223 secs / lunar cycle
  DateTime now = RTC.now();
  DateTime new_moon (1970, 1, 7, 20, 35, 0);
  long ph = (now.unixtime() - new_moon.unixtime()) % lp;  // scales to 0-29 days
  ph = floor (ph/3600L);                                    
  ph = map(ph, 0.0, 29*24.0, 0, num_phases-1);            // map to number of phases
  return ph;  
}

// getCardinal: converts the headsing (in degrees) to a text-based cardinal heading ("ENE", for example)
String getCardinal( float h ) { 
  h += 11.25; if (h > 360.0) h = h-360.0;
  strcpy_P(cb, (char*) pgm_read_word(&(cas[(int) (h / 22.5)]) ) );
  return cb;  
}


// ********************************************************************************** //
//                                       EEPROM ROUTINES
// ********************************************************************************** //
// EEPROM save, restore and set/save defaults
void saveSettings()    { for(int addr = 0; addr < sizeof(set)/2; addr++) { EEPROM.write(addr, *set[addr]); } }    // save
void restoreSettings() { for(int addr = 0; addr < sizeof(set)/2; addr++) { *set[addr] = EEPROM.read(addr); } }   // recover EEPROM value

[/codesyntax]

 

Now to incorporate a GPS system….

Come on... leave a comment...